diff --git a/.eslintrc.yml b/.eslintrc.yml index 4573377ef..f5ad72553 100644 --- a/.eslintrc.yml +++ b/.eslintrc.yml @@ -111,6 +111,7 @@ overrides: - files: src/**/*.js rules: prettier-internal-rules/no-doc-builder-concat: off + prettier-internal-rules/no-single-doc-concat: error prettier-internal-rules/prefer-fast-path-each: error prettier-internal-rules/prefer-is-non-empty-array: error - files: src/document/*.js diff --git a/scripts/tools/eslint-plugin-prettier-internal-rules/index.js b/scripts/tools/eslint-plugin-prettier-internal-rules/index.js index 3bec6a3e2..02ad369e9 100644 --- a/scripts/tools/eslint-plugin-prettier-internal-rules/index.js +++ b/scripts/tools/eslint-plugin-prettier-internal-rules/index.js @@ -7,6 +7,7 @@ module.exports = { "jsx-identifier-case": require("./jsx-identifier-case"), "no-doc-builder-concat": require("./no-doc-builder-concat"), "no-node-comments": require("./no-node-comments"), + "no-single-doc-concat": require("./no-single-doc-concat"), "prefer-fast-path-each": require("./prefer-fast-path-each"), "prefer-is-non-empty-array": require("./prefer-is-non-empty-array"), "require-json-extensions": require("./require-json-extensions"), diff --git a/scripts/tools/eslint-plugin-prettier-internal-rules/no-single-doc-concat.js b/scripts/tools/eslint-plugin-prettier-internal-rules/no-single-doc-concat.js new file mode 100644 index 000000000..25b4b4930 --- /dev/null +++ b/scripts/tools/eslint-plugin-prettier-internal-rules/no-single-doc-concat.js @@ -0,0 +1,44 @@ +"use strict"; + +// TODO: Remove this rule when deprecated `concat` is not using in codebase, #9993. + +const selector = [ + "CallExpression", + '[callee.type="Identifier"]', + '[callee.name="concat"]', + "[arguments.length=1]", + '[arguments.0.type="ArrayExpression"]', + "[arguments.0.elements.length=1]", +].join(""); + +const messageId = "no-single-doc-concat"; + +module.exports = { + meta: { + type: "suggestion", + docs: { + url: + "https://github.com/prettier/prettier/blob/master/scripts/eslint-plugin-prettier-internal-rules/no-single-doc-concat.js", + }, + messages: { + [messageId]: "Do not use `concat()` to concat single `Doc`.", + }, + fixable: "code", + }, + create(context) { + const sourceCode = context.getSourceCode(); + return { + [selector](node) { + context.report({ + node, + messageId, + fix: (fixer) => + fixer.replaceText( + node, + sourceCode.getText(node.arguments[0].elements[0]) + ), + }); + }, + }; + }, +}; diff --git a/scripts/tools/eslint-plugin-prettier-internal-rules/test.js b/scripts/tools/eslint-plugin-prettier-internal-rules/test.js index e02c8008e..81cb65e21 100644 --- a/scripts/tools/eslint-plugin-prettier-internal-rules/test.js +++ b/scripts/tools/eslint-plugin-prettier-internal-rules/test.js @@ -144,6 +144,17 @@ test("no-node-comments", { ], }); +test("no-single-doc-concat", { + valid: [], + invalid: [ + { + code: 'concat([path.call(print, "params")])', + output: 'path.call(print, "params")', + errors: 1, + }, + ], +}); + test("prefer-fast-path-each", { valid: ["const foo = path.map()"], invalid: [ diff --git a/src/language-css/printer-postcss.js b/src/language-css/printer-postcss.js index 771dd54da..97b27b791 100644 --- a/src/language-css/printer-postcss.js +++ b/src/language-css/printer-postcss.js @@ -213,7 +213,7 @@ function genericPrint(path, options, print) { if (node.function) { return concat([ node.name, - concat([path.call(print, "params")]), + path.call(print, "params"), isTemplatePlaceholderNodeWithoutSemiColon ? "" : ";", ]); } @@ -223,7 +223,7 @@ function genericPrint(path, options, print) { "@", node.name, ": ", - node.value ? concat([path.call(print, "value")]) : "", + node.value ? path.call(print, "value") : "", node.raws.between.trim() ? node.raws.between.trim() + " " : "", node.nodes ? concat([ diff --git a/src/language-graphql/printer-graphql.js b/src/language-graphql/printer-graphql.js index 161493c63..fe475df15 100644 --- a/src/language-graphql/printer-graphql.js +++ b/src/language-graphql/printer-graphql.js @@ -21,7 +21,7 @@ function genericPrint(path, options, print) { case "Document": { const parts = []; path.each((pathChild, index, definitions) => { - parts.push(concat([pathChild.call(print)])); + parts.push(pathChild.call(print)); if (index !== definitions.length - 1) { parts.push(hardline); if ( diff --git a/src/language-handlebars/printer-glimmer.js b/src/language-handlebars/printer-glimmer.js index 8047d3041..a465561e4 100644 --- a/src/language-handlebars/printer-glimmer.js +++ b/src/language-handlebars/printer-glimmer.js @@ -138,11 +138,14 @@ function print(path, options, print) { // same, there is no value for this AttrNode and it should be printed // without the `=""`. Example: `` -> `` if (isEmptyText && locStart(n.value) === locEnd(n.value)) { - return concat([n.name]); + return n.name; } const value = path.call(print, "value"); const quotedValue = isText - ? printStringLiteral(getDocParts(value).join(), options) + ? printStringLiteral( + typeof value === "string" ? value : getDocParts(value).join(), + options + ) : value; return concat([n.name, "=", quotedValue]); } @@ -157,7 +160,7 @@ function print(path, options, print) { } case "Hash": { - return concat([join(line, path.map(print, "pairs"))]); + return join(line, path.map(print, "pairs")); } case "HashPair": { return concat([n.key, "=", path.call(print, "value")]); @@ -201,7 +204,7 @@ function print(path, options, print) { // TODO: format style and srcset attributes // and cleanup concat that is not necessary if (!isInAttributeOfName(path, "class")) { - return concat([n.chars]); + return n.chars; } let leadingSpace = ""; diff --git a/src/language-js/print/call-arguments.js b/src/language-js/print/call-arguments.js index f30610c72..c7b43e52d 100644 --- a/src/language-js/print/call-arguments.js +++ b/src/language-js/print/call-arguments.js @@ -82,7 +82,7 @@ function printCallArguments(path, options, print) { let shouldBreak = false; iterateFunctionParametersPath(argPath, (parameterPath) => { - shouldBreak = shouldBreak || willBreak(concat([print(parameterPath)])); + shouldBreak = shouldBreak || willBreak(print(parameterPath)); }); return shouldBreak; diff --git a/src/language-js/print/jsx.js b/src/language-js/print/jsx.js index 5d9e38f80..3fb1db705 100644 --- a/src/language-js/print/jsx.js +++ b/src/language-js/print/jsx.js @@ -118,7 +118,7 @@ function printJsxElementInternal(path, options, print) { const rawJsxWhitespace = options.singleQuote ? "{' '}" : '{" "}'; const jsxWhitespace = isMdxBlock - ? concat([" "]) + ? " " : ifBreak(concat([rawJsxWhitespace, softline]), " "); const isFacebookTranslationTag = diff --git a/src/language-js/printer-estree.js b/src/language-js/printer-estree.js index e6b5d5b2d..619d8289b 100644 --- a/src/language-js/printer-estree.js +++ b/src/language-js/printer-estree.js @@ -488,9 +488,7 @@ function printPathNoParens(path, options, print, args) { }, "expressions"); return group(concat(parts)); } - return group( - concat([join(concat([",", line]), path.map(print, "expressions"))]) - ); + return group(join(concat([",", line]), path.map(print, "expressions"))); } case "ThisExpression": return "this"; diff --git a/src/language-yaml/print/mapping-item.js b/src/language-yaml/print/mapping-item.js index 90aa6c6b1..8d7c686c6 100644 --- a/src/language-yaml/print/mapping-item.js +++ b/src/language-yaml/print/mapping-item.js @@ -23,7 +23,7 @@ function printMappingItem(node, parentNode, path, print, options) { const isEmptyMappingValue = isEmptyNode(value); if (isEmptyMappingKey && isEmptyMappingValue) { - return concat([": "]); + return ": "; } const printedKey = path.call(print, "key");