diff --git a/changelog_unreleased/javascript/16563.md b/changelog_unreleased/javascript/16563.md new file mode 100644 index 000000000..349ca3b6f --- /dev/null +++ b/changelog_unreleased/javascript/16563.md @@ -0,0 +1,23 @@ +#### Don't remove useless `\` in string literals (#16563 by @sosukesuzuki) + +Previously, Prettier would remove useless escape characters (`\`) from string literals. However, this behavior was inconsistent as it did not apply to template literals, which was reported in [issue #16542](https://github.com/prettier/prettier/issues/16542). + +This issue is a feature request to extend this behavior to template literals as well. + +After discussing this internally, the Prettier team concluded that removing useless escape characters, whether in string literals or template literals, is the responsibility of a linter, not a formatter. + +This change disables the removal of useless escape characters in string literals across all languages supported by Prettier. To keep the previous behavior, we recommend using ESLint rules like [`no-useless-escape`](https://eslint.org/docs/latest/rules/no-useless-escape). + +Escaping quotes within string literals (e.g. `"\"\'"`) works the same as before. This is explained on the [Rationale](https://prettier.io/docs/en/rationale.html#strings) page of the official documentation. + + +```jsx +// Input +const str = "\a"; + +// Prettier stable +const str = "a"; + +// Prettier main +const str = "\a"; +``` diff --git a/src/utils/print-string.js b/src/utils/print-string.js index 8f4cf6efa..d29ec1c7d 100644 --- a/src/utils/print-string.js +++ b/src/utils/print-string.js @@ -33,12 +33,10 @@ function printString(raw, options) { return makeString( rawContent, enclosingQuote, - !( - options.parser === "css" || - options.parser === "less" || - options.parser === "scss" || - options.__embeddedInHtml - ), + // Until Prettier 3.3.3, this option was set to true for most parsers, with some exceptions like CSS. + // Since Prettier 3.3.4, it is set to false for all parsers. + // For more details, please see https://github.com/prettier/prettier/issues/16542#issuecomment-2282249280. + false, ); } diff --git a/tests/format/flow-repo/malformed_code/__snapshots__/format.test.js.snap b/tests/format/flow-repo/malformed_code/__snapshots__/format.test.js.snap index cdae89784..31db12a29 100644 --- a/tests/format/flow-repo/malformed_code/__snapshots__/format.test.js.snap +++ b/tests/format/flow-repo/malformed_code/__snapshots__/format.test.js.snap @@ -17,7 +17,7 @@ const error: string = 123; // Should be an error, but can't lex this file // @flow // Example found at // https://github.com/sebmarkbage/art/blob/51ffce8164a555d652843241c2fdda52e186cbbd/parsers/svg/text.js#L137 -const evil_regex_as_a_string = "/[s�]*$/"; +const evil_regex_as_a_string = "/[\\s�]*$/"; const error: string = 123; // Should be an error, but can't lex this file diff --git a/tests/format/js/quote-props/__snapshots__/format.test.js.snap b/tests/format/js/quote-props/__snapshots__/format.test.js.snap index 2c612aa6d..41c1af74a 100644 --- a/tests/format/js/quote-props/__snapshots__/format.test.js.snap +++ b/tests/format/js/quote-props/__snapshots__/format.test.js.snap @@ -496,7 +496,7 @@ Object.entries({ }; a = { - a: 1, + "\\a": 1, b: 2, }; @@ -714,8 +714,8 @@ Object.entries({ }; a = { - a: 1, - b: 2, + '\\a': 1, + 'b': 2, }; ================================================================================ @@ -931,8 +931,8 @@ Object.entries({ }; a = { - a: 1, - b: 2, + "\\a": 1, + "b": 2, }; ================================================================================ @@ -1148,7 +1148,7 @@ Object.entries({ }; a = { - "a": 1, + "\\a": 1, "b": 2, }; diff --git a/tests/format/js/quotes/__snapshots__/format.test.js.snap b/tests/format/js/quotes/__snapshots__/format.test.js.snap index b09a54787..c7e491779 100644 --- a/tests/format/js/quotes/__snapshots__/format.test.js.snap +++ b/tests/format/js/quotes/__snapshots__/format.test.js.snap @@ -236,21 +236,21 @@ singleQuote: true // Unnecessary escapes. ("'"); ('"'); -('a'); -('a'); -('hola'); -('hola'); +('\\a'); +('\\a'); +('hol\\a'); +('hol\\a'); ('hol\\\\a (the a is not escaped)'); ('hol\\\\a (the a is not escaped)'); -('multiple a unnecessary a escapes'); -('multiple a unnecessary a escapes'); -('unnecessarily escaped character preceded by escaped backslash \\\\a'); -('unnecessarily escaped character preceded by escaped backslash \\\\a'); +('multiple \\a unnecessary \\a escapes'); +('multiple \\a unnecessary \\a escapes'); +('unnecessarily escaped character preceded by escaped backslash \\\\\\a'); +('unnecessarily escaped character preceded by escaped backslash \\\\\\a'); ('unescaped character preceded by two escaped backslashes \\\\\\\\a'); ('unescaped character preceded by two escaped backslashes \\\\\\\\a'); -('aa'); // consecutive unnecessarily escaped characters -('aa'); // consecutive unnecessarily escaped characters -('escaped \\u2030 ‰ (should not stay escaped)'); +('\\a\\a'); // consecutive unnecessarily escaped characters +('\\a\\a'); // consecutive unnecessarily escaped characters +('escaped \\u2030 \\‰ (should not stay escaped)'); // Meaningful escapes ('octal escapes \\0 \\1 \\2 \\3 \\4 \\5 \\6 \\7'); @@ -448,21 +448,21 @@ printWidth: 80 // Unnecessary escapes. ("'"); ('"'); -("a"); -("a"); -("hola"); -("hola"); +("\\a"); +("\\a"); +("hol\\a"); +("hol\\a"); ("hol\\\\a (the a is not escaped)"); ("hol\\\\a (the a is not escaped)"); -("multiple a unnecessary a escapes"); -("multiple a unnecessary a escapes"); -("unnecessarily escaped character preceded by escaped backslash \\\\a"); -("unnecessarily escaped character preceded by escaped backslash \\\\a"); +("multiple \\a unnecessary \\a escapes"); +("multiple \\a unnecessary \\a escapes"); +("unnecessarily escaped character preceded by escaped backslash \\\\\\a"); +("unnecessarily escaped character preceded by escaped backslash \\\\\\a"); ("unescaped character preceded by two escaped backslashes \\\\\\\\a"); ("unescaped character preceded by two escaped backslashes \\\\\\\\a"); -("aa"); // consecutive unnecessarily escaped characters -("aa"); // consecutive unnecessarily escaped characters -("escaped \\u2030 ‰ (should not stay escaped)"); +("\\a\\a"); // consecutive unnecessarily escaped characters +("\\a\\a"); // consecutive unnecessarily escaped characters +("escaped \\u2030 \\‰ (should not stay escaped)"); // Meaningful escapes ("octal escapes \\0 \\1 \\2 \\3 \\4 \\5 \\6 \\7"); diff --git a/tests/format/js/strings/__snapshots__/format.test.js.snap b/tests/format/js/strings/__snapshots__/format.test.js.snap index b21ee067e..7b3b62e09 100644 --- a/tests/format/js/strings/__snapshots__/format.test.js.snap +++ b/tests/format/js/strings/__snapshots__/format.test.js.snap @@ -154,10 +154,10 @@ trailingComma: "all" =====================================output===================================== // https://github.com/babel/babel/pull/11852 -"8", "9"; +"\\8", "\\9"; () => { "use strict"; - "8", "9"; + "\\8", "\\9"; }; ================================================================================ @@ -205,10 +205,10 @@ trailingComma: "es5" =====================================output===================================== // https://github.com/babel/babel/pull/11852 -"8", "9"; +"\\8", "\\9"; () => { "use strict"; - "8", "9"; + "\\8", "\\9"; }; ================================================================================ diff --git a/tests/format/json/json-superset/format.test.js b/tests/format/json/json-superset/format.test.js index 8bf030711..d53638b23 100644 --- a/tests/format/json/json-superset/format.test.js +++ b/tests/format/json/json-superset/format.test.js @@ -25,7 +25,7 @@ runFormatTest( code: SINGLE_QUOTE + BACKSLASH + character + SINGLE_QUOTE, output: character === UNDERSCORE || character === SPACE - ? DOUBLE_QUOTE + character + DOUBLE_QUOTE + LINE_FEED + ? DOUBLE_QUOTE + BACKSLASH + character + DOUBLE_QUOTE + LINE_FEED : DOUBLE_QUOTE + BACKSLASH + character + DOUBLE_QUOTE + LINE_FEED, })), ...characters.map((character) => ({ diff --git a/tests/format/json/json/__snapshots__/format.test.js.snap b/tests/format/json/json/__snapshots__/format.test.js.snap index 1ec777060..7e6d7a9ff 100644 --- a/tests/format/json/json/__snapshots__/format.test.js.snap +++ b/tests/format/json/json/__snapshots__/format.test.js.snap @@ -1536,7 +1536,7 @@ trailingComma: "all" "quote": "\\"", "backslash": "\\\\", "controls": "\\b\\f\\n\\r\\t", - "slash": "/ & /", + "slash": "/ & \\/", "alpha": "abcdefghijklmnopqrstuvwyz", "ALPHA": "ABCDEFGHIJKLMNOPQRSTUVWYZ", "digit": "0123456789", @@ -1560,7 +1560,7 @@ trailingComma: "all" "compact": [1, 2, 3, 4, 5, 6, 7], "jsontext": "{\\"object with 1 member\\":[\\"array with 1 element\\"]}", "quotes": "" \\u0022 %22 0x22 034 "", - "/\\\\\\"\\uCAFE\\uBABE\\uAB98\\uFCDE\\ubcda\\uef4A\\b\\f\\n\\r\\t\`1~!@#$%^&*()_+-=[]{}|;:',./<>?": "A key can be any string" + "\\/\\\\\\"\\uCAFE\\uBABE\\uAB98\\uFCDE\\ubcda\\uef4A\\b\\f\\n\\r\\t\`1~!@#$%^&*()_+-=[]{}|;:',./<>?": "A key can be any string" }, 0.5, 98.6, @@ -1667,7 +1667,7 @@ trailingComma: "all" quote: '"', backslash: "\\\\", controls: "\\b\\f\\n\\r\\t", - slash: "/ & /", + slash: "/ & \\/", alpha: "abcdefghijklmnopqrstuvwyz", ALPHA: "ABCDEFGHIJKLMNOPQRSTUVWYZ", digit: "0123456789", @@ -1691,7 +1691,7 @@ trailingComma: "all" compact: [1, 2, 3, 4, 5, 6, 7], jsontext: '{"object with 1 member":["array with 1 element"]}', quotes: "" \\u0022 %22 0x22 034 "", - "/\\\\\\"\\uCAFE\\uBABE\\uAB98\\uFCDE\\ubcda\\uef4A\\b\\f\\n\\r\\t\`1~!@#$%^&*()_+-=[]{}|;:',./<>?": "A key can be any string", + "\\/\\\\\\"\\uCAFE\\uBABE\\uAB98\\uFCDE\\ubcda\\uef4A\\b\\f\\n\\r\\t\`1~!@#$%^&*()_+-=[]{}|;:',./<>?": "A key can be any string", }, 0.5, 98.6, @@ -1798,7 +1798,7 @@ trailingComma: "es5" "quote": "\\"", "backslash": "\\\\", "controls": "\\b\\f\\n\\r\\t", - "slash": "/ & /", + "slash": "/ & \\/", "alpha": "abcdefghijklmnopqrstuvwyz", "ALPHA": "ABCDEFGHIJKLMNOPQRSTUVWYZ", "digit": "0123456789", @@ -1822,7 +1822,7 @@ trailingComma: "es5" "compact": [1, 2, 3, 4, 5, 6, 7], "jsontext": "{\\"object with 1 member\\":[\\"array with 1 element\\"]}", "quotes": "" \\u0022 %22 0x22 034 "", - "/\\\\\\"\\uCAFE\\uBABE\\uAB98\\uFCDE\\ubcda\\uef4A\\b\\f\\n\\r\\t\`1~!@#$%^&*()_+-=[]{}|;:',./<>?": "A key can be any string" + "\\/\\\\\\"\\uCAFE\\uBABE\\uAB98\\uFCDE\\ubcda\\uef4A\\b\\f\\n\\r\\t\`1~!@#$%^&*()_+-=[]{}|;:',./<>?": "A key can be any string" }, 0.5, 98.6, @@ -1929,7 +1929,7 @@ trailingComma: "es5" quote: '"', backslash: "\\\\", controls: "\\b\\f\\n\\r\\t", - slash: "/ & /", + slash: "/ & \\/", alpha: "abcdefghijklmnopqrstuvwyz", ALPHA: "ABCDEFGHIJKLMNOPQRSTUVWYZ", digit: "0123456789", @@ -1953,7 +1953,7 @@ trailingComma: "es5" compact: [1, 2, 3, 4, 5, 6, 7], jsontext: '{"object with 1 member":["array with 1 element"]}', quotes: "" \\u0022 %22 0x22 034 "", - "/\\\\\\"\\uCAFE\\uBABE\\uAB98\\uFCDE\\ubcda\\uef4A\\b\\f\\n\\r\\t\`1~!@#$%^&*()_+-=[]{}|;:',./<>?": "A key can be any string", + "\\/\\\\\\"\\uCAFE\\uBABE\\uAB98\\uFCDE\\ubcda\\uef4A\\b\\f\\n\\r\\t\`1~!@#$%^&*()_+-=[]{}|;:',./<>?": "A key can be any string", }, 0.5, 98.6,