diff --git a/benchmarks/trim-indentation.js b/benchmarks/trim-indentation.js index 4795ee6b5..f5595d6a1 100644 --- a/benchmarks/trim-indentation.js +++ b/benchmarks/trim-indentation.js @@ -18,7 +18,6 @@ for (const size of [1, 1e1, 1e2]) { (regexp) => () => text.replace(regexp, "") )(/[ \t]*$/gu), - // eslint-disable-next-line require-unicode-regexp "RegExp (no `u` flag)": () => text.replace(/[ \t]*$/, ""), }, ); diff --git a/eslint.config.js b/eslint.config.js index 8b3f29049..ef6336078 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -128,7 +128,10 @@ const configs = [ "prefer-rest-params": "error", "prefer-spread": "error", "require-await": "error", - "require-unicode-regexp": "error", + // The `u` or `v` flag cause problems when dealing with non-ascii characters + // https://github.com/prettier/prettier/pull/18453 + // https://github.com/prettier/prettier/pull/18455 + "require-unicode-regexp": "off", "symbol-description": "error", yoda: [ "error", @@ -492,12 +495,6 @@ const configs = [ "prefer-arrow-callback": "off", }, }, - // ESBuild doesn't support regular expressions with `u` flag - // https://github.com/evanw/esbuild/issues/4128 - { - files: ["scripts/build/esbuild-plugins/**/*"], - rules: { "require-unicode-regexp": "off" }, - }, { files: ["src/document/printer/printer.js"], rules: { diff --git a/src/language-css/parser-postcss.js b/src/language-css/parser-postcss.js index ca8d0a25a..eeb97da8c 100644 --- a/src/language-css/parser-postcss.js +++ b/src/language-css/parser-postcss.js @@ -4,6 +4,7 @@ import postcssScssParse from "postcss-scss/lib/scss-parse"; import createError from "../common/parser-create-error.js"; import { parseFrontMatter } from "../main/front-matter/index.js"; import isObject from "../utilities/is-object.js"; +import replaceNonLineBreaksWithSpace from "../utilities/replace-non-line-breaks-with-space.js"; import { calculateLoc, locEnd, @@ -56,7 +57,8 @@ function parseNestedCSS(node, options) { node.source.start.offset + node.prop.length, node.source.end.offset, ); - const fakeContent = textBefore.replaceAll(/[^\n]/gu, " ") + nodeText; + const fakeContent = + replaceNonLineBreaksWithSpace(textBefore) + nodeText; let parse; if (options.parser === "scss") { parse = parseScss; diff --git a/src/language-html/parse/parse.js b/src/language-html/parse/parse.js index e1c6d6b35..80548eab1 100644 --- a/src/language-html/parse/parse.js +++ b/src/language-html/parse/parse.js @@ -6,6 +6,7 @@ import { } from "angular-html-parser"; import createError from "../../common/parser-create-error.js"; import { parseFrontMatter } from "../../main/front-matter/index.js"; +import replaceNonLineBreaksWithSpace from "../../utilities/replace-non-line-breaks-with-space.js"; import { normalizeParseOptions, toAngularHtmlParserParseOptions, @@ -143,7 +144,7 @@ function parseSubHtml( ) { const { offset } = startSpan; const textToParse = - text.slice(0, offset).replaceAll(/[^\n]/gu, " ") + subContent; + replaceNonLineBreaksWithSpace(text.slice(0, offset)) + subContent; const subAst = parse( textToParse, parser, diff --git a/src/language-js/utilities/get-text-without-comments.js b/src/language-js/utilities/get-text-without-comments.js index bbff70c46..b79ba97e7 100644 --- a/src/language-js/utilities/get-text-without-comments.js +++ b/src/language-js/utilities/get-text-without-comments.js @@ -1,4 +1,4 @@ -import * as assert from "#universal/assert"; +import replaceNonLineBreaksWithSpace from "../../utilities/replace-non-line-breaks-with-space.js"; import { locEnd, locStart } from "../loc.js"; function getTextWithoutComments(options, start, end) { @@ -21,14 +21,10 @@ function getTextWithoutComments(options, start, end) { text = text.slice(0, startIndex) + - text.slice(startIndex, endIndex).replaceAll(/[^\n]/gu, " ") + + replaceNonLineBreaksWithSpace(text.slice(startIndex, endIndex)) + text.slice(endIndex); } - if (process.env.NODE_ENV !== "production") { - assert.equal(text.length, end - start); - } - return text; } diff --git a/src/main/front-matter/parse.js b/src/main/front-matter/parse.js index 43203b01d..24a277d10 100644 --- a/src/main/front-matter/parse.js +++ b/src/main/front-matter/parse.js @@ -1,3 +1,4 @@ +import replaceNonLineBreaksWithSpace from "../../utilities/replace-non-line-breaks-with-space.js"; import { FRONT_MATTER_MARK } from "./constants.js"; const DELIMITER_LENGTH = 3; @@ -106,10 +107,7 @@ function parse(text) { frontMatter, get content() { const { raw } = frontMatter; - return ( - // eslint-disable-next-line require-unicode-regexp -- string length matters so intentionally not using unicode flag - raw.replaceAll(/[^\n]/g, " ") + text.slice(raw.length) - ); + return replaceNonLineBreaksWithSpace(raw) + text.slice(raw.length); }, }; } diff --git a/src/utilities/replace-non-line-breaks-with-space.js b/src/utilities/replace-non-line-breaks-with-space.js new file mode 100644 index 000000000..d88609beb --- /dev/null +++ b/src/utilities/replace-non-line-breaks-with-space.js @@ -0,0 +1,19 @@ +import * as assert from "#universal/assert"; + +/** +Replaces all characters in the input string except line breaks `\n` with a space. + +@param {string} string - The input string to process. +@returns {string} +*/ +function replaceNonLineBreaksWithSpace(string) { + const replaced = string.replaceAll(/[^\n]/g, " "); + + if (process.env.NODE_ENV !== "production") { + assert.equal(replaced.length, string.length); + } + + return replaced; +} + +export default replaceNonLineBreaksWithSpace; diff --git a/tests/format/css/custom-properties/__snapshots__/format.test.js.snap b/tests/format/css/custom-properties/__snapshots__/format.test.js.snap index 1096697a9..4b4778334 100644 --- a/tests/format/css/custom-properties/__snapshots__/format.test.js.snap +++ b/tests/format/css/custom-properties/__snapshots__/format.test.js.snap @@ -17,3 +17,27 @@ printWidth: 80 ================================================================================ `; + +exports[`emoji.css format 1`] = ` +====================================options===================================== +parsers: ["css", "scss", "less"] +printWidth: 80 + | printWidth +=====================================input====================================== +/* 😄😄😄😄😄😄😄😄😄😄😄😄😄😄😄😄😄😄😄 */ + +--foo: { + color: red; + /* 😄😄😄😄😄😄😄😄😄😄😄😄😄😄😄😄😄😄😄 */ +} + +=====================================output===================================== +/* 😄😄😄😄😄😄😄😄😄😄😄😄😄😄😄😄😄😄😄 */ + +--foo: { + color: red; + /* 😄😄😄😄😄😄😄😄😄😄😄😄😄😄😄😄😄😄😄 */ +}; + +================================================================================ +`; diff --git a/tests/format/css/custom-properties/emoji.css b/tests/format/css/custom-properties/emoji.css new file mode 100644 index 000000000..2001a1169 --- /dev/null +++ b/tests/format/css/custom-properties/emoji.css @@ -0,0 +1,6 @@ +/* 😄😄😄😄😄😄😄😄😄😄😄😄😄😄😄😄😄😄😄 */ + +--foo: { + color: red; + /* 😄😄😄😄😄😄😄😄😄😄😄😄😄😄😄😄😄😄😄 */ +} diff --git a/tests/format/js/import/__snapshots__/format.test.js.snap b/tests/format/js/import/__snapshots__/format.test.js.snap index 0efcf715f..c71edd7d8 100644 --- a/tests/format/js/import/__snapshots__/format.test.js.snap +++ b/tests/format/js/import/__snapshots__/format.test.js.snap @@ -278,201 +278,6 @@ import x, { ================================================================================ `; -exports[`empty-import.js - {"bracketSpacing":false} format 1`] = ` -====================================options===================================== -bracketSpacing: false -parsers: ["babel", "flow", "typescript"] -printWidth: 80 - | printWidth -=====================================input====================================== -import { } from '@types/googlemaps'; -import 'a'; -import /* comment */ 'a'; -import // comment -'a'; -import { /* comment */ } from 'a'; -import /* comment */ {} from 'a'; -import {} /* comment */ from 'a'; -import {} from /* comment */ 'a'; -import /* comment */ {/* comment */} /* comment */ from /* comment */ 'a'; -import { - // comment -} from 'a'; -import // comment -{} from 'a'; -import {} -// comment -from 'a'; -import {} from -// comment -'a'; -import -// comment -{ -// comment -} -// comment -from -// comment -'a'; - -import // {} from -'a'; -import {} from // comment ends with from -'a'; -import {} from /* comment ends with from */ 'a'; -import {} from // comment not ends with from ___ -'a'; -import {} from /* comment not ends with from ___ */ 'a'; - -import // comment ends with from -'a'; -import /* comment ends with from */ 'a'; -import // comment not ends with from ___ -'a'; -import /* comment not ends with from ___ */ 'a'; - -=====================================output===================================== -import {} from "@types/googlemaps"; -import "a"; -import /* comment */ "a"; -import // comment -"a"; -import {} from /* comment */ "a"; -import {} from /* comment */ "a"; -import {} from /* comment */ "a"; -import {} from /* comment */ "a"; -import {} from /* comment */ /* comment */ /* comment */ /* comment */ "a"; -import {} from // comment -"a"; -import {} from // comment -"a"; -import {} from // comment -"a"; -import {} from // comment -"a"; -import {} from // comment -// comment -// comment -// comment -"a"; - -import // {} from -"a"; -import {} from // comment ends with from -"a"; -import {} from /* comment ends with from */ "a"; -import {} from // comment not ends with from ___ -"a"; -import {} from /* comment not ends with from ___ */ "a"; - -import // comment ends with from -"a"; -import /* comment ends with from */ "a"; -import // comment not ends with from ___ -"a"; -import /* comment not ends with from ___ */ "a"; - -================================================================================ -`; - -exports[`empty-import.js format 1`] = ` -====================================options===================================== -parsers: ["babel", "flow", "typescript"] -printWidth: 80 - | printWidth -=====================================input====================================== -import { } from '@types/googlemaps'; -import 'a'; -import /* comment */ 'a'; -import // comment -'a'; -import { /* comment */ } from 'a'; -import /* comment */ {} from 'a'; -import {} /* comment */ from 'a'; -import {} from /* comment */ 'a'; -import /* comment */ {/* comment */} /* comment */ from /* comment */ 'a'; -import { - // comment -} from 'a'; -import // comment -{} from 'a'; -import {} -// comment -from 'a'; -import {} from -// comment -'a'; -import -// comment -{ -// comment -} -// comment -from -// comment -'a'; - -import // {} from -'a'; -import {} from // comment ends with from -'a'; -import {} from /* comment ends with from */ 'a'; -import {} from // comment not ends with from ___ -'a'; -import {} from /* comment not ends with from ___ */ 'a'; - -import // comment ends with from -'a'; -import /* comment ends with from */ 'a'; -import // comment not ends with from ___ -'a'; -import /* comment not ends with from ___ */ 'a'; - -=====================================output===================================== -import {} from "@types/googlemaps"; -import "a"; -import /* comment */ "a"; -import // comment -"a"; -import {} from /* comment */ "a"; -import {} from /* comment */ "a"; -import {} from /* comment */ "a"; -import {} from /* comment */ "a"; -import {} from /* comment */ /* comment */ /* comment */ /* comment */ "a"; -import {} from // comment -"a"; -import {} from // comment -"a"; -import {} from // comment -"a"; -import {} from // comment -"a"; -import {} from // comment -// comment -// comment -// comment -"a"; - -import // {} from -"a"; -import {} from // comment ends with from -"a"; -import {} from /* comment ends with from */ "a"; -import {} from // comment not ends with from ___ -"a"; -import {} from /* comment not ends with from ___ */ "a"; - -import // comment ends with from -"a"; -import /* comment ends with from */ "a"; -import // comment not ends with from ___ -"a"; -import /* comment not ends with from ___ */ "a"; - -================================================================================ -`; - exports[`inline.js - {"bracketSpacing":false} format 1`] = ` ====================================options===================================== bracketSpacing: false diff --git a/tests/format/js/import/empty-import/__snapshots__/format.test.js.snap b/tests/format/js/import/empty-import/__snapshots__/format.test.js.snap new file mode 100644 index 000000000..4fce1dcd1 --- /dev/null +++ b/tests/format/js/import/empty-import/__snapshots__/format.test.js.snap @@ -0,0 +1,118 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`empty-import.js format 1`] = ` +====================================options===================================== +parsers: ["babel", "flow", "typescript"] +printWidth: 80 + | printWidth +=====================================input====================================== +import { } from '@types/googlemaps'; +import 'a'; +import /* comment */ 'a'; +import // comment +'a'; +import { /* comment */ } from 'a'; +import /* comment */ {} from 'a'; +import {} /* comment */ from 'a'; +import {} from /* comment */ 'a'; +import /* comment */ {/* comment */} /* comment */ from /* comment */ 'a'; +import { + // comment +} from 'a'; +import // comment +{} from 'a'; +import {} +// comment +from 'a'; +import {} from +// comment +'a'; +import +// comment +{ +// comment +} +// comment +from +// comment +'a'; + +import // {} from +'a'; +import {} from // comment ends with from +'a'; +import {} from /* comment ends with from */ 'a'; +import {} from // comment not ends with from ___ +'a'; +import {} from /* comment not ends with from ___ */ 'a'; + +import // comment ends with from +'a'; +import /* comment ends with from */ 'a'; +import // comment not ends with from ___ +'a'; +import /* comment not ends with from ___ */ 'a'; + +=====================================output===================================== +import {} from "@types/googlemaps"; +import "a"; +import /* comment */ "a"; +import // comment +"a"; +import {} from /* comment */ "a"; +import {} from /* comment */ "a"; +import {} from /* comment */ "a"; +import {} from /* comment */ "a"; +import {} from /* comment */ /* comment */ /* comment */ /* comment */ "a"; +import {} from // comment +"a"; +import {} from // comment +"a"; +import {} from // comment +"a"; +import {} from // comment +"a"; +import {} from // comment +// comment +// comment +// comment +"a"; + +import // {} from +"a"; +import {} from // comment ends with from +"a"; +import {} from /* comment ends with from */ "a"; +import {} from // comment not ends with from ___ +"a"; +import {} from /* comment not ends with from ___ */ "a"; + +import // comment ends with from +"a"; +import /* comment ends with from */ "a"; +import // comment not ends with from ___ +"a"; +import /* comment not ends with from ___ */ "a"; + +================================================================================ +`; + +exports[`empty-import-2.js format 1`] = ` +====================================options===================================== +parsers: ["babel", "flow", "typescript"] +printWidth: 80 + | printWidth +=====================================input====================================== +import {} from /* 😄😄😄😄 */ /* 😄😄😄😄 */'a'; +// ^^^^^^^ SHOLUL print \`from\` +import /* 😄😄😄😄😄😄😄___ */ /* from */ "a"; +// ^ Should NOT print \`from\` + +=====================================output===================================== +import {} from /* 😄😄😄😄 */ /* 😄😄😄😄 */ "a"; +// ^^^^^^^ SHOLUL print \`from\` +import /* 😄😄😄😄😄😄😄___ */ /* from */ "a"; +// ^ Should NOT print \`from\` + +================================================================================ +`; diff --git a/tests/format/js/import/empty-import/empty-import-2.js b/tests/format/js/import/empty-import/empty-import-2.js new file mode 100644 index 000000000..8654a8458 --- /dev/null +++ b/tests/format/js/import/empty-import/empty-import-2.js @@ -0,0 +1,4 @@ +import {} from /* 😄😄😄😄 */ /* 😄😄😄😄 */'a'; +// ^^^^^^^ SHOLUL print `from` +import /* 😄😄😄😄😄😄😄___ */ /* from */ "a"; +// ^ Should NOT print `from` diff --git a/tests/format/js/import/empty-import.js b/tests/format/js/import/empty-import/empty-import.js similarity index 100% rename from tests/format/js/import/empty-import.js rename to tests/format/js/import/empty-import/empty-import.js diff --git a/tests/format/js/import/empty-import/format.test.js b/tests/format/js/import/empty-import/format.test.js new file mode 100644 index 000000000..67c251cce --- /dev/null +++ b/tests/format/js/import/empty-import/format.test.js @@ -0,0 +1 @@ +runFormatTest(import.meta, ["babel", "flow", "typescript"]);