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");