From 0d6db6e98b9177af5c055405edea6dd0d9313812 Mon Sep 17 00:00:00 2001 From: Klaus Hott Vidal Date: Mon, 4 Dec 2023 23:47:43 -0300 Subject: [PATCH 01/18] documenting the `hardline` within an `ifBreak` odd behaviour (#15746) Co-authored-by: Georgii Dolzhykov --- commands.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/commands.md b/commands.md index f7ca26626..8543ee094 100644 --- a/commands.md +++ b/commands.md @@ -97,6 +97,10 @@ ifBreak(";", " "); `groupId` can be used to check another _already printed_ group instead of the current group. +If a [`hardline`](#hardline) or [`breakParent`](#breakParent) is present within the possible contents, the parent groups will be broken regardless of said content being printed, which might not be desireable. This behaviour is a design limitation. Usually the desired result can be achieved in a different way. + +In the rare case that `hardline` is definitely needed, consider using [`hardlineWithoutBreakParent`](#hardlinewithoutbreakparent-and-literallinewithoutbreakparent) instead to avoid an unwanted group break propagation. + ### `breakParent` ```ts From 96aa09b8cf79860a0c8ad910c509990980de1c7a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 5 Dec 2023 11:06:18 +0800 Subject: [PATCH 02/18] chore(deps): update dependency postcss to v8.4.32 (#15752) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index 463c77ed7..d09b57d58 100644 --- a/package.json +++ b/package.json @@ -86,7 +86,7 @@ "outdent": "0.8.0", "parse-json": "8.1.0", "please-upgrade-node": "3.2.0", - "postcss": "8.4.31", + "postcss": "8.4.32", "postcss-less": "6.0.0", "postcss-media-query-parser": "0.2.3", "postcss-scss": "4.0.9", diff --git a/yarn.lock b/yarn.lock index 7c03398ab..e9d96d490 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6662,7 +6662,7 @@ __metadata: languageName: node linkType: hard -"nanoid@npm:^3.3.6": +"nanoid@npm:^3.3.7": version: 3.3.7 resolution: "nanoid@npm:3.3.7" bin: @@ -7294,14 +7294,14 @@ __metadata: languageName: node linkType: hard -"postcss@npm:8.4.31": - version: 8.4.31 - resolution: "postcss@npm:8.4.31" +"postcss@npm:8.4.32": + version: 8.4.32 + resolution: "postcss@npm:8.4.32" dependencies: - nanoid: ^3.3.6 + nanoid: ^3.3.7 picocolors: ^1.0.0 source-map-js: ^1.0.2 - checksum: 1d8611341b073143ad90486fcdfeab49edd243377b1f51834dc4f6d028e82ce5190e4f11bb2633276864503654fb7cab28e67abdc0fbf9d1f88cad4a0ff0beea + checksum: 220d9d0bf5d65be7ed31006c523bfb11619461d296245c1231831f90150aeb4a31eab9983ac9c5c89759a3ca8b60b3e0d098574964e1691673c3ce5c494305ae languageName: node linkType: hard @@ -7417,7 +7417,7 @@ __metadata: outdent: 0.8.0 parse-json: 8.1.0 please-upgrade-node: 3.2.0 - postcss: 8.4.31 + postcss: 8.4.32 postcss-less: 6.0.0 postcss-media-query-parser: 0.2.3 postcss-scss: 4.0.9 From 69f550ff7f781a1bbf82f97c9bc915cf4dd709fc Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 5 Dec 2023 11:06:56 +0800 Subject: [PATCH 03/18] chore(deps): update dependency @babel/parser to v7.23.5 (#15753) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index d09b57d58..723c068ab 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "dependencies": { "@angular/compiler": "17.0.2", "@babel/code-frame": "7.22.13", - "@babel/parser": "7.23.4", + "@babel/parser": "7.23.5", "@babel/types": "7.23.3", "@glimmer/syntax": "0.85.13", "@iarna/toml": "2.2.5", diff --git a/yarn.lock b/yarn.lock index e9d96d490..8e17a57b0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -250,12 +250,12 @@ __metadata: languageName: node linkType: hard -"@babel/parser@npm:7.23.4, @babel/parser@npm:^7.1.0, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.22.15, @babel/parser@npm:^7.23.3, @babel/parser@npm:^7.23.4": - version: 7.23.4 - resolution: "@babel/parser@npm:7.23.4" +"@babel/parser@npm:7.23.5, @babel/parser@npm:^7.1.0, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.22.15, @babel/parser@npm:^7.23.3, @babel/parser@npm:^7.23.4": + version: 7.23.5 + resolution: "@babel/parser@npm:7.23.5" bin: parser: ./bin/babel-parser.js - checksum: 1d90e17d966085b8ea12f357ffcc76568969364481254f0ae3e7ed579e9421d31c7fd3876ccb3b215a5b2ada48251b0c2d0f21ba225ee194f0e18295b49085f2 + checksum: ea763629310f71580c4a3ea9d3705195b7ba994ada2cc98f9a584ebfdacf54e92b2735d351672824c2c2b03c7f19206899f4d95650d85ce514a822b19a8734c7 languageName: node linkType: hard @@ -7328,7 +7328,7 @@ __metadata: "@angular/compiler": 17.0.2 "@babel/code-frame": 7.22.13 "@babel/generator": 7.23.3 - "@babel/parser": 7.23.4 + "@babel/parser": 7.23.5 "@babel/types": 7.23.3 "@glimmer/reference": 0.85.13 "@glimmer/syntax": 0.85.13 From c453fc925f01f8cc4611443017bd66f2f112b74e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 5 Dec 2023 11:23:18 +0800 Subject: [PATCH 04/18] chore(deps): update babel to v7.23.5 (#15755) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 6 ++--- yarn.lock | 69 ++++++++++++++-------------------------------------- 2 files changed, 21 insertions(+), 54 deletions(-) diff --git a/package.json b/package.json index 723c068ab..f177cce2a 100644 --- a/package.json +++ b/package.json @@ -31,9 +31,9 @@ ], "dependencies": { "@angular/compiler": "17.0.2", - "@babel/code-frame": "7.22.13", + "@babel/code-frame": "7.23.5", "@babel/parser": "7.23.5", - "@babel/types": "7.23.3", + "@babel/types": "7.23.5", "@glimmer/syntax": "0.85.13", "@iarna/toml": "2.2.5", "@prettier/is-es5-identifier-name": "0.2.0", @@ -108,7 +108,7 @@ "yaml-unist-parser": "2.0.1" }, "devDependencies": { - "@babel/generator": "7.23.3", + "@babel/generator": "7.23.5", "@glimmer/reference": "0.85.13", "@types/estree": "1.0.5", "@types/file-entry-cache": "5.0.4", diff --git a/yarn.lock b/yarn.lock index 8e17a57b0..dd24611dd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -45,23 +45,13 @@ __metadata: languageName: node linkType: hard -"@babel/code-frame@npm:7.22.13": - version: 7.22.13 - resolution: "@babel/code-frame@npm:7.22.13" - dependencies: - "@babel/highlight": ^7.22.13 - chalk: ^2.4.2 - checksum: 22e342c8077c8b77eeb11f554ecca2ba14153f707b85294fcf6070b6f6150aae88a7b7436dd88d8c9289970585f3fe5b9b941c5aa3aa26a6d5a8ef3f292da058 - languageName: node - linkType: hard - -"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.12.13, @babel/code-frame@npm:^7.21.4, @babel/code-frame@npm:^7.22.13, @babel/code-frame@npm:^7.23.4": - version: 7.23.4 - resolution: "@babel/code-frame@npm:7.23.4" +"@babel/code-frame@npm:7.23.5, @babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.12.13, @babel/code-frame@npm:^7.21.4, @babel/code-frame@npm:^7.22.13, @babel/code-frame@npm:^7.23.4": + version: 7.23.5 + resolution: "@babel/code-frame@npm:7.23.5" dependencies: "@babel/highlight": ^7.23.4 chalk: ^2.4.2 - checksum: 29999d08c3dbd803f3c296dae7f4f40af1f9e381d6bbc76e5a75327c4b8b023bcb2e209843d292f5d71c3b5c845df1da959d415ed862d6a68e0ad6c5c9622d37 + checksum: d90981fdf56a2824a9b14d19a4c0e8db93633fd488c772624b4e83e0ceac6039a27cd298a247c3214faa952bf803ba23696172ae7e7235f3b97f43ba278c569a languageName: node linkType: hard @@ -95,27 +85,15 @@ __metadata: languageName: node linkType: hard -"@babel/generator@npm:7.23.3": - version: 7.23.3 - resolution: "@babel/generator@npm:7.23.3" +"@babel/generator@npm:7.23.5, @babel/generator@npm:^7.23.3, @babel/generator@npm:^7.23.4, @babel/generator@npm:^7.7.2": + version: 7.23.5 + resolution: "@babel/generator@npm:7.23.5" dependencies: - "@babel/types": ^7.23.3 + "@babel/types": ^7.23.5 "@jridgewell/gen-mapping": ^0.3.2 "@jridgewell/trace-mapping": ^0.3.17 jsesc: ^2.5.1 - checksum: b6e71cca852d4e1aa01a28a30b8c74ffc3b8d56ccb7ae3ee783028ee015f63ad861a2e386c3eb490a9a8634db485a503a33521680f4af510151e90346c46da17 - languageName: node - linkType: hard - -"@babel/generator@npm:^7.23.3, @babel/generator@npm:^7.23.4, @babel/generator@npm:^7.7.2": - version: 7.23.4 - resolution: "@babel/generator@npm:7.23.4" - dependencies: - "@babel/types": ^7.23.4 - "@jridgewell/gen-mapping": ^0.3.2 - "@jridgewell/trace-mapping": ^0.3.17 - jsesc: ^2.5.1 - checksum: 7403717002584eaeb58559f4d0de19b79e924ef2735711278f7cb5206d081428bf3960578566d6fa4102b7b30800d44f70acffea5ecef83f0cb62361c2a23062 + checksum: 845ddda7cf38a3edf4be221cc8a439dee9ea6031355146a1a74047aa8007bc030305b27d8c68ec9e311722c910610bde38c0e13a9ce55225251e7cb7e7f3edc8 languageName: node linkType: hard @@ -207,7 +185,7 @@ __metadata: languageName: node linkType: hard -"@babel/helper-string-parser@npm:^7.22.5, @babel/helper-string-parser@npm:^7.23.4": +"@babel/helper-string-parser@npm:^7.23.4": version: 7.23.4 resolution: "@babel/helper-string-parser@npm:7.23.4" checksum: c0641144cf1a7e7dc93f3d5f16d5327465b6cf5d036b48be61ecba41e1eece161b48f46b7f960951b67f8c3533ce506b16dece576baef4d8b3b49f8c65410f90 @@ -239,7 +217,7 @@ __metadata: languageName: node linkType: hard -"@babel/highlight@npm:^7.0.0, @babel/highlight@npm:^7.22.13, @babel/highlight@npm:^7.23.4": +"@babel/highlight@npm:^7.0.0, @babel/highlight@npm:^7.23.4": version: 7.23.4 resolution: "@babel/highlight@npm:7.23.4" dependencies: @@ -442,25 +420,14 @@ __metadata: languageName: node linkType: hard -"@babel/types@npm:7.23.3": - version: 7.23.3 - resolution: "@babel/types@npm:7.23.3" - dependencies: - "@babel/helper-string-parser": ^7.22.5 - "@babel/helper-validator-identifier": ^7.22.20 - to-fast-properties: ^2.0.0 - checksum: b96f1ec495351aeb2a5f98dd494aafa17df02a351548ae96999460f35c933261c839002a34c1e83552ff0d9f5e94d0b5b8e105d38131c7c9b0f5a6588676f35d - languageName: node - linkType: hard - -"@babel/types@npm:^7.0.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.22.15, @babel/types@npm:^7.22.5, @babel/types@npm:^7.23.0, @babel/types@npm:^7.23.3, @babel/types@npm:^7.23.4, @babel/types@npm:^7.3.3, @babel/types@npm:^7.8.3": - version: 7.23.4 - resolution: "@babel/types@npm:7.23.4" +"@babel/types@npm:7.23.5, @babel/types@npm:^7.0.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.22.15, @babel/types@npm:^7.22.5, @babel/types@npm:^7.23.0, @babel/types@npm:^7.23.3, @babel/types@npm:^7.23.4, @babel/types@npm:^7.23.5, @babel/types@npm:^7.3.3, @babel/types@npm:^7.8.3": + version: 7.23.5 + resolution: "@babel/types@npm:7.23.5" dependencies: "@babel/helper-string-parser": ^7.23.4 "@babel/helper-validator-identifier": ^7.22.20 to-fast-properties: ^2.0.0 - checksum: 8a1ab20da663d202b1c090fdef4b157d3c7d8cb1cf60ea548f887d7b674935371409804d6cba52f870c22ced7685fcb41b0578d3edde720990de00cbb328da54 + checksum: 3d21774480a459ef13b41c2e32700d927af649e04b70c5d164814d8e04ab584af66a93330602c2925e1a6925c2b829cc153418a613a4e7d79d011be1f29ad4b2 languageName: node linkType: hard @@ -7326,10 +7293,10 @@ __metadata: resolution: "prettier@workspace:." dependencies: "@angular/compiler": 17.0.2 - "@babel/code-frame": 7.22.13 - "@babel/generator": 7.23.3 + "@babel/code-frame": 7.23.5 + "@babel/generator": 7.23.5 "@babel/parser": 7.23.5 - "@babel/types": 7.23.3 + "@babel/types": 7.23.5 "@glimmer/reference": 0.85.13 "@glimmer/syntax": 0.85.13 "@iarna/toml": 2.2.5 From fc440b4b66c82aa9af32c3e617f264062de2ec46 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 5 Dec 2023 11:24:03 +0800 Subject: [PATCH 05/18] chore(deps): update dependency eslint-config-prettier to v9.1.0 (#15754) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index f177cce2a..ac919c108 100644 --- a/package.json +++ b/package.json @@ -125,7 +125,7 @@ "esbuild-plugins-node-modules-polyfill": "1.6.1", "esbuild-visualizer": "0.4.1", "eslint": "8.53.0", - "eslint-config-prettier": "9.0.0", + "eslint-config-prettier": "9.1.0", "eslint-formatter-friendly": "7.0.0", "eslint-plugin-compat": "4.2.0", "eslint-plugin-import": "2.29.0", diff --git a/yarn.lock b/yarn.lock index dd24611dd..f719621cc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3694,14 +3694,14 @@ __metadata: languageName: node linkType: hard -"eslint-config-prettier@npm:9.0.0": - version: 9.0.0 - resolution: "eslint-config-prettier@npm:9.0.0" +"eslint-config-prettier@npm:9.1.0": + version: 9.1.0 + resolution: "eslint-config-prettier@npm:9.1.0" peerDependencies: eslint: ">=7.0.0" bin: eslint-config-prettier: bin/cli.js - checksum: 362e991b6cb343f79362bada2d97c202e5303e6865888918a7445c555fb75e4c078b01278e90be98aa98ae22f8597d8e93d48314bec6824f540f7efcab3ce451 + checksum: 9229b768c879f500ee54ca05925f31b0c0bafff3d9f5521f98ff05127356de78c81deb9365c86a5ec4efa990cb72b74df8612ae15965b14136044c73e1f6a907 languageName: node linkType: hard @@ -7334,7 +7334,7 @@ __metadata: esbuild-visualizer: 0.4.1 escape-string-regexp: 5.0.0 eslint: 8.53.0 - eslint-config-prettier: 9.0.0 + eslint-config-prettier: 9.1.0 eslint-formatter-friendly: 7.0.0 eslint-plugin-compat: 4.2.0 eslint-plugin-import: 2.29.0 From 117c04fcc834801834d822272bf2a1db6f1f78de Mon Sep 17 00:00:00 2001 From: fisker Cheung Date: Tue, 5 Dec 2023 14:39:11 +0800 Subject: [PATCH 06/18] Simplify mockable (#15729) --- .eslintrc.cjs | 1 - scripts/build/config.js | 16 +++++----------- src/cli/format.js | 7 +++++-- src/cli/is-tty.js | 2 +- src/cli/prettier-internal.js | 1 + src/config/get-prettier-config-explorer.js | 4 +--- src/index.js | 3 +++ tests/integration/__tests__/bundle.js | 4 +++- tests/integration/__tests__/mockable.js | 18 +++++------------- tests/integration/cli-worker.js | 7 +++---- tests/integration/env.js | 8 ++++---- 11 files changed, 31 insertions(+), 40 deletions(-) diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 24af63a4d..d7f520562 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -325,7 +325,6 @@ module.exports = { path.resolve(__dirname, "src/**"), `!${path.resolve(__dirname, "src/cli/**")}`, `!${path.resolve(__dirname, "src/index.js")}`, - `!${path.resolve(__dirname, "src/common/mockable.js")}`, ], message: "Don't use code from other directory.", }, diff --git a/scripts/build/config.js b/scripts/build/config.js index a2198222b..505201621 100644 --- a/scripts/build/config.js +++ b/scripts/build/config.js @@ -610,6 +610,11 @@ const nodejsFiles = [ }), path: require.resolve("@babel/code-frame"), }, + { + module: require.resolve("lilconfig"), + find: "exports.lilconfigSync = lilconfigSync;", + replacement: "", + }, ], addDefaultExport: true, }, @@ -634,17 +639,6 @@ const nodejsFiles = [ external: ["benchmark"], replaceModule: [replaceDiffPackageEntry("lib/patch/create.js")], }, - { - input: "src/common/mockable.js", - outputBaseName: "internal/internal", - replaceModule: [ - { - module: require.resolve("lilconfig"), - find: "exports.lilconfigSync = lilconfigSync;", - replacement: "", - }, - ], - }, ].flatMap((file) => { let { input, output, outputBaseName, ...buildOptions } = file; diff --git a/src/cli/format.js b/src/cli/format.js index 69eb0ef59..6f313f3b6 100644 --- a/src/cli/format.js +++ b/src/cli/format.js @@ -3,8 +3,11 @@ import path from "node:path"; import chalk from "chalk"; import { createTwoFilesPatch } from "diff"; import * as prettier from "../index.js"; -import mockable from "../common/mockable.js"; -import { createIsIgnoredFunction, errors } from "./prettier-internal.js"; +import { + createIsIgnoredFunction, + errors, + mockable, +} from "./prettier-internal.js"; import { expandPatterns } from "./expand-patterns.js"; import getOptionsForFile from "./options/get-options-for-file.js"; import isTTY from "./is-tty.js"; diff --git a/src/cli/is-tty.js b/src/cli/is-tty.js index 947543825..fc8e9bca0 100644 --- a/src/cli/is-tty.js +++ b/src/cli/is-tty.js @@ -1,4 +1,4 @@ -import mockable from "../common/mockable.js"; +import { mockable } from "./prettier-internal.js"; // Some CI pipelines incorrectly report process.stdout.isTTY status, // which causes unwanted lines in the output. An additional check for isCI() helps. diff --git a/src/cli/prettier-internal.js b/src/cli/prettier-internal.js index 3599a89d6..45493090b 100644 --- a/src/cli/prettier-internal.js +++ b/src/cli/prettier-internal.js @@ -11,4 +11,5 @@ export const { normalizeOptionSettings, vnopts, fastGlob, + mockable, } = sharedWithCli; diff --git a/src/config/get-prettier-config-explorer.js b/src/config/get-prettier-config-explorer.js index a2ceeef5a..5d4bf25eb 100644 --- a/src/config/get-prettier-config-explorer.js +++ b/src/config/get-prettier-config-explorer.js @@ -6,8 +6,6 @@ import parseJson from "parse-json"; import mockable from "../common/mockable.js"; import loadExternalConfig from "./load-external-config.js"; -const { lilconfig } = mockable; - const searchPlaces = [ "package.json", ".prettierrc", @@ -106,7 +104,7 @@ async function transform(result) { * @return {ReturnType} */ function getExplorer() { - return lilconfig("prettier", { searchPlaces, loaders, transform }); + return mockable.lilconfig("prettier", { searchPlaces, loaders, transform }); } export default getExplorer; diff --git a/src/index.js b/src/index.js index d64500e69..7ac46cc4e 100644 --- a/src/index.js +++ b/src/index.js @@ -25,6 +25,7 @@ import normalizeOptions from "./main/normalize-options.js"; import partition from "./utils/partition.js"; import isNonEmptyArray from "./utils/is-non-empty-array.js"; import omit from "./utils/object-omit.js"; +import mockable from "./common/mockable.js"; /** * @param {*} fn @@ -99,6 +100,7 @@ const sharedWithCli = { partition, omit, }, + mockable, }; const debugApis = { @@ -107,6 +109,7 @@ const debugApis = { formatDoc: withPlugins(core.formatDoc), printToDoc: withPlugins(core.printToDoc), printDocToString: withPlugins(core.printDocToString), + mockable, }; export { diff --git a/tests/integration/__tests__/bundle.js b/tests/integration/__tests__/bundle.js index b1f20632e..d74c2dcb6 100644 --- a/tests/integration/__tests__/bundle.js +++ b/tests/integration/__tests__/bundle.js @@ -118,7 +118,9 @@ test("Commonjs version", () => { expect(typeof prettierCommonjsVersion.util.getStringWidth).toBe("function"); expect(Object.keys(prettierCommonjsVersion.__debug).sort()).toEqual( - Object.keys(prettier.__debug).sort(), + Object.keys(prettier.__debug) + .filter((key) => key !== "mockable") + .sort(), ); expect(typeof prettierCommonjsVersion.__debug.parse).toBe("function"); diff --git a/tests/integration/__tests__/mockable.js b/tests/integration/__tests__/mockable.js index bde8dcb97..d3eaf48e2 100644 --- a/tests/integration/__tests__/mockable.js +++ b/tests/integration/__tests__/mockable.js @@ -1,19 +1,14 @@ import path from "node:path"; -import { pathToFileURL } from "node:url"; import createEsmUtils from "esm-utils"; -import { mockable } from "../env.js"; +import prettier from "../../config/prettier-entry.js"; const { __dirname } = createEsmUtils(import.meta); +const { mockable } = prettier.__debug; // This don't has to be the same result as `prettier.resolveConfig`, // Because we are testing with default `lilconfigOptions` describe("lilconfig", () => { - let lilconfig; - beforeAll(async () => { - ({ - default: { lilconfig }, - } = await import(pathToFileURL(mockable))); - }); + const { lilconfig } = mockable; const configs = [ { @@ -52,9 +47,6 @@ describe("lilconfig", () => { } }); -test("isCI", async () => { - const { - default: { isCI }, - } = await import(pathToFileURL(mockable)); - expect(typeof isCI()).toBe("boolean"); +test("isCI", () => { + expect(typeof mockable.isCI()).toBe("boolean"); }); diff --git a/tests/integration/cli-worker.js b/tests/integration/cli-worker.js index fd760126f..c4b9405fb 100644 --- a/tests/integration/cli-worker.js +++ b/tests/integration/cli-worker.js @@ -4,7 +4,7 @@ import path from "node:path"; import url from "node:url"; import readline from "node:readline"; import { lilconfig } from "lilconfig"; -import { prettierCli, mockable as mockableModuleFile } from "./env.js"; +import { prettierCli, prettierMainEntry } from "./env.js"; const normalizeToPosix = path.sep === "\\" @@ -56,9 +56,8 @@ async function run() { process.stdin.isTTY = Boolean(options.isTTY); process.stdout.isTTY = Boolean(options.stdoutIsTTY); - const { default: mockable } = await import( - url.pathToFileURL(mockableModuleFile) - ); + const prettier = await import(url.pathToFileURL(prettierMainEntry)); + const { mockable } = prettier.__debug; // We cannot use `jest.setMock("get-stream", impl)` here, because in the // production build everything is bundled into one file so there is no diff --git a/tests/integration/env.js b/tests/integration/env.js index 5effb781d..a5cac1b69 100644 --- a/tests/integration/env.js +++ b/tests/integration/env.js @@ -11,10 +11,10 @@ const prettierCli = path.join( typeof bin === "object" ? bin.prettier : bin, ); -const mockable = isProduction - ? path.join(PRETTIER_DIR, "./internal/internal.mjs") - : path.join(PRETTIER_DIR, "./src/common/mockable.js"); +const prettierMainEntry = isProduction + ? path.join(PRETTIER_DIR, "./index.mjs") + : path.join(PRETTIER_DIR, "./src/index.js"); const projectRoot = path.join(__dirname, "../.."); -export { isProduction, mockable, prettierCli, projectRoot }; +export { isProduction, prettierMainEntry, prettierCli, projectRoot }; From 5f0956bc30ea88ca7faf2f9f38ba60a86cd61605 Mon Sep 17 00:00:00 2001 From: fisker Cheung Date: Tue, 5 Dec 2023 15:00:43 +0800 Subject: [PATCH 07/18] Implement our own config explorer (#15663) --- changelog_unreleased/api/15663.md | 3 + docs/api.md | 2 +- docs/configuration.md | 2 +- package.json | 1 - scripts/build/config.js | 5 - src/common/mockable.js | 4 +- src/config/get-prettier-config-explorer.js | 110 ---------------- .../config-searcher.js | 119 ++++++++++++++++++ src/config/prettier-config-explorer/index.js | 64 ++++++++++ .../prettier-config-explorer/load-config.js | 48 +++++++ .../load-external-config.js | 10 +- .../prettier-config-explorer/loaders.js | 68 ++++++++++ src/config/resolve-config.js | 48 +++---- src/utils/is-file.js | 22 ++++ .../__snapshots__/config-invalid.js.snap | 2 +- tests/integration/__tests__/config-invalid.js | 29 +++++ tests/integration/__tests__/mockable.js | 45 ------- tests/integration/cli-worker.js | 8 +- .../broken-package-json/.prettierignore | 2 + .../invalid/broken-package-json/.prettierrc | 1 + yarn.lock | 8 -- 21 files changed, 393 insertions(+), 208 deletions(-) create mode 100644 changelog_unreleased/api/15663.md delete mode 100644 src/config/get-prettier-config-explorer.js create mode 100644 src/config/prettier-config-explorer/config-searcher.js create mode 100644 src/config/prettier-config-explorer/index.js create mode 100644 src/config/prettier-config-explorer/load-config.js rename src/config/{ => prettier-config-explorer}/load-external-config.js (65%) create mode 100644 src/config/prettier-config-explorer/loaders.js create mode 100644 src/utils/is-file.js create mode 100644 tests/integration/cli/config/invalid/broken-package-json/.prettierignore create mode 100644 tests/integration/cli/config/invalid/broken-package-json/.prettierrc diff --git a/changelog_unreleased/api/15663.md b/changelog_unreleased/api/15663.md new file mode 100644 index 000000000..29aac10bf --- /dev/null +++ b/changelog_unreleased/api/15663.md @@ -0,0 +1,3 @@ +#### Improve config file search (#15663 by @fisker) + +The Prettier config file search performance has been improved by more effective cache strategy. diff --git a/docs/api.md b/docs/api.md index af40bde7e..d0fbf560c 100644 --- a/docs/api.md +++ b/docs/api.md @@ -66,7 +66,7 @@ If `options.editorconfig` is `true` and an [`.editorconfig` file](https://editor The promise will be rejected if there was an error parsing the configuration file. -The search starts at `process.cwd()`, or at `fileUrlOrPath` if provided. Please see the [lilconfig docs](https://github.com/antonk52/lilconfig) for details on how the resolving works. +The search starts at `process.cwd()`, or at the directory of `fileUrlOrPath` if provided. ```js const configFile = await prettier.resolveConfigFile(filePath); diff --git a/docs/configuration.md b/docs/configuration.md index eee65eb8e..5929585c9 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -3,7 +3,7 @@ id: configuration title: Configuration File --- -Prettier uses [lilconfig](https://github.com/antonk52/lilconfig) for configuration file support. This means you can configure Prettier via (in order of precedence): +You can configure Prettier via (in order of precedence): - A `"prettier"` key in your `package.json` file. - A `.prettierrc` file written in JSON or YAML. diff --git a/package.json b/package.json index ac919c108..2f9a4edc9 100644 --- a/package.json +++ b/package.json @@ -75,7 +75,6 @@ "js-yaml": "4.1.0", "json5": "2.2.3", "leven": "4.0.0", - "lilconfig": "2.1.0", "lines-and-columns": "2.0.4", "linguist-languages": "7.27.0", "mem": "9.0.2", diff --git a/scripts/build/config.js b/scripts/build/config.js index 505201621..8e67bba58 100644 --- a/scripts/build/config.js +++ b/scripts/build/config.js @@ -610,11 +610,6 @@ const nodejsFiles = [ }), path: require.resolve("@babel/code-frame"), }, - { - module: require.resolve("lilconfig"), - find: "exports.lilconfigSync = lilconfigSync;", - replacement: "", - }, ], addDefaultExport: true, }, diff --git a/src/common/mockable.js b/src/common/mockable.js index 57583c2f2..dc6d38692 100644 --- a/src/common/mockable.js +++ b/src/common/mockable.js @@ -1,5 +1,4 @@ import fs from "node:fs/promises"; -import { lilconfig } from "lilconfig"; import getStdin from "get-stdin"; import { isCI } from "ci-info"; @@ -8,7 +7,8 @@ function writeFormattedFile(file, data) { } const mockable = { - lilconfig, + // eslint-disable-next-line unicorn/no-useless-undefined + getPrettierConfigSearchStopDirectory: () => undefined, getStdin, isCI: () => isCI, writeFormattedFile, diff --git a/src/config/get-prettier-config-explorer.js b/src/config/get-prettier-config-explorer.js deleted file mode 100644 index 5d4bf25eb..000000000 --- a/src/config/get-prettier-config-explorer.js +++ /dev/null @@ -1,110 +0,0 @@ -import { pathToFileURL } from "node:url"; -import parseToml from "@iarna/toml/parse-async.js"; -import parseJson5 from "json5/lib/parse.js"; -import yaml from "js-yaml"; -import parseJson from "parse-json"; -import mockable from "../common/mockable.js"; -import loadExternalConfig from "./load-external-config.js"; - -const searchPlaces = [ - "package.json", - ".prettierrc", - ".prettierrc.json", - ".prettierrc.yaml", - ".prettierrc.yml", - ".prettierrc.json5", - ".prettierrc.js", - ".prettierrc.mjs", - ".prettierrc.cjs", - "prettier.config.js", - "prettier.config.mjs", - "prettier.config.cjs", - ".prettierrc.toml", -]; - -async function loadJs(filepath /*, content*/) { - const module = await import(pathToFileURL(filepath).href); - return module.default; -} - -function loadYaml(filepath, content) { - try { - return yaml.load(content); - } catch (/** @type {any} */ error) { - error.message = `YAML Error in ${filepath}:\n${error.message}`; - throw error; - } -} - -const loaders = { - async ".toml"(filePath, content) { - try { - return await parseToml(content); - } catch (/** @type {any} */ error) { - error.message = `TOML Error in ${filePath}:\n${error.message}`; - throw error; - } - }, - ".json5"(filePath, content) { - try { - return parseJson5(content); - } catch (/** @type {any} */ error) { - error.message = `JSON5 Error in ${filePath}:\n${error.message}`; - throw error; - } - }, - ".json"(filePath, content) { - try { - return parseJson(content); - } catch (/** @type {any} */ error) { - error.message = `JSON Error in ${filePath}:\n${error.message}`; - throw error; - } - }, - ".js": loadJs, - ".mjs": loadJs, - ".yaml": loadYaml, - ".yml": loadYaml, - noExt: loadYaml, -}; - -async function transform(result) { - if (!result?.config) { - return result; - } - - let { config, filepath } = result; - - /* - We support external config - - ```json - { - "prettier": "my-prettier-config-package-or-file" - } - ``` - */ - if (typeof config === "string") { - config = await loadExternalConfig(config, filepath); - result.config = config; - } - - if (typeof config !== "object") { - throw new TypeError( - "Config is only allowed to be an object, " + - `but received ${typeof config} in "${filepath}"`, - ); - } - - delete config.$schema; - return result; -} - -/** - * @return {ReturnType} - */ -function getExplorer() { - return mockable.lilconfig("prettier", { searchPlaces, loaders, transform }); -} - -export default getExplorer; diff --git a/src/config/prettier-config-explorer/config-searcher.js b/src/config/prettier-config-explorer/config-searcher.js new file mode 100644 index 000000000..a73a015b8 --- /dev/null +++ b/src/config/prettier-config-explorer/config-searcher.js @@ -0,0 +1,119 @@ +import path from "node:path"; +import iterateDirectoryUp from "iterate-directory-up"; +import isFile from "../../utils/is-file.js"; +import { loadConfigFromPackageJson } from "./loaders.js"; + +const CONFIG_FILE_NAMES = [ + "package.json", + ".prettierrc", + ".prettierrc.json", + ".prettierrc.yaml", + ".prettierrc.yml", + ".prettierrc.json5", + ".prettierrc.js", + ".prettierrc.mjs", + ".prettierrc.cjs", + "prettier.config.js", + "prettier.config.mjs", + "prettier.config.cjs", + ".prettierrc.toml", +]; + +async function isPackageJsonFileWithPrettierConfig(file) { + try { + return Boolean(await loadConfigFromPackageJson(file)); + } catch { + return false; + } +} + +async function searchConfigInDirectory(directory) { + for (const fileName of CONFIG_FILE_NAMES) { + const file = path.join(directory, fileName); + + if (!(await isFile(file))) { + continue; + } + + if ( + fileName !== "package.json" || + (await isPackageJsonFileWithPrettierConfig(file)) + ) { + return file; + } + } +} + +function createCachedSearchConfigInDirectory() { + const cache = new Map(); + + return async function (fileOrDirectory) { + if (cache.has(fileOrDirectory)) { + return cache.get(fileOrDirectory); + } + + const promise = searchConfigInDirectory(fileOrDirectory); + cache.set(fileOrDirectory, promise); + const result = await promise; + cache.set(fileOrDirectory, result); + return result; + }; +} + +/** @type {Map} */ // @ts-expect-error -- intentionally not add the `get` method +const noopMap = { has: () => false, set() {} }; +class ConfigSearcher { + #shouldCache; + #stopDirectory; + #searchedFilesStore; + #searchConfigInDirectory; + constructor({ stopDirectory, shouldCache }) { + this.#shouldCache = shouldCache; + this.#stopDirectory = stopDirectory; + this.#searchConfigInDirectory = shouldCache + ? createCachedSearchConfigInDirectory() + : searchConfigInDirectory; + this.#searchedFilesStore = shouldCache ? new Map() : noopMap; + } + + async #searchConfigInDirectories(startDirectory) { + const store = this.#searchedFilesStore; + for (const directory of iterateDirectoryUp( + startDirectory, + this.#stopDirectory, + )) { + // The parent directory may already searched + if (store.has(directory)) { + return store.get(directory); + } + + const file = await this.#searchConfigInDirectory(directory); + if (file) { + return file; + } + } + } + + async search(startDirectory) { + const store = this.#searchedFilesStore; + if (store.has(startDirectory)) { + return store.get(startDirectory); + } + + const configFile = await this.#searchConfigInDirectories(startDirectory); + + // Directories between startDirectory and configFile directory should has the same result + if (this.#shouldCache) { + for (const directory of iterateDirectoryUp( + startDirectory, + configFile ? path.dirname(configFile) : startDirectory, + )) { + store.set(directory, configFile); + } + } + + return configFile; + } +} + +export default ConfigSearcher; diff --git a/src/config/prettier-config-explorer/index.js b/src/config/prettier-config-explorer/index.js new file mode 100644 index 000000000..d8f1fa3f6 --- /dev/null +++ b/src/config/prettier-config-explorer/index.js @@ -0,0 +1,64 @@ +import path from "node:path"; +import mockable from "../../common/mockable.js"; +import loadConfigWithoutCache from "./load-config.js"; +import ConfigSearcher from "./config-searcher.js"; + +const loadCache = new Map(); +const searchCache = new Map(); +function clearCache() { + loadCache.clear(); + searchCache.clear(); +} + +/** + * @param {string} configFile + * @param {{shouldCache?: boolean}} param1 + * @returns {Promise>} + */ +function loadConfig(configFile, { shouldCache }) { + configFile = path.resolve(configFile); + if (!shouldCache || !loadCache.has(configFile)) { + const promise = loadConfigWithoutCache(configFile); + // Even if `shouldCache` is false, we still cache it, so we can use it later + loadCache.set(configFile, promise); + return promise; + } + + return loadCache.get(configFile); +} + +/** + * @param {{shouldCache?: boolean, stopDirectory?: string}} param0 + */ +function getSearchFunction({ shouldCache, stopDirectory }) { + stopDirectory = stopDirectory ? path.resolve(stopDirectory) : undefined; + const searchCacheKey = JSON.stringify({ shouldCache, stopDirectory }); + + if (!searchCache.has(searchCacheKey)) { + const searcher = new ConfigSearcher({ shouldCache, stopDirectory }); + const searchFunction = searcher.search.bind(searcher); + + searchCache.set(searchCacheKey, searchFunction); + } + + return searchCache.get(searchCacheKey); +} + +/** + * @param {string} startDirectory + * @param {{shouldCache?: boolean, stopDirectory?: string}} options + * @returns {Promise} + */ +function searchConfig(startDirectory, options = {}) { + startDirectory = startDirectory + ? path.resolve(startDirectory) + : process.cwd(); + + options.stopDirectory = mockable.getPrettierConfigSearchStopDirectory(); + + const search = getSearchFunction(options); + + return search(startDirectory); +} + +export { searchConfig, loadConfig, clearCache }; diff --git a/src/config/prettier-config-explorer/load-config.js b/src/config/prettier-config-explorer/load-config.js new file mode 100644 index 000000000..50658e62c --- /dev/null +++ b/src/config/prettier-config-explorer/load-config.js @@ -0,0 +1,48 @@ +import path from "node:path"; +import loadExternalConfig from "./load-external-config.js"; +import loaders, { loadConfigFromPackageJson } from "./loaders.js"; + +async function loadConfig(configFile) { + const { base: fileName, ext: extension } = path.parse(configFile); + const load = + fileName === "package.json" + ? loadConfigFromPackageJson + : loaders[extension]; + + if (!load) { + throw new Error( + `No loader specified for extension "${extension || "noExt"}"`, + ); + } + + let config = await load(configFile); + + if (!config) { + return; + } + + /* + We support external config + + ```json + { + "prettier": "my-prettier-config-package-or-file" + } + ``` + */ + if (typeof config === "string") { + config = await loadExternalConfig(config, configFile); + } + + if (typeof config !== "object") { + throw new TypeError( + "Config is only allowed to be an object, " + + `but received ${typeof config} in "${configFile}"`, + ); + } + + delete config.$schema; + return config; +} + +export default loadConfig; diff --git a/src/config/load-external-config.js b/src/config/prettier-config-explorer/load-external-config.js similarity index 65% rename from src/config/load-external-config.js rename to src/config/prettier-config-explorer/load-external-config.js index 96988a08c..2b40eddd4 100644 --- a/src/config/load-external-config.js +++ b/src/config/prettier-config-explorer/load-external-config.js @@ -1,12 +1,12 @@ -import requireFromFile from "../utils/require-from-file.js"; -import importFromFile from "../utils/import-from-file.js"; +import requireFromFile from "../../utils/require-from-file.js"; +import importFromFile from "../../utils/import-from-file.js"; const requireErrorCodesShouldBeIgnored = new Set([ "MODULE_NOT_FOUND", "ERR_REQUIRE_ESM", "ERR_PACKAGE_PATH_NOT_EXPORTED", ]); -async function loadExternalConfig(config, filepath) { +async function loadExternalConfig(externalConfig, configFile) { /* Try `require()` first, this is how it works in Prettier v2. Kept this because the external config path or package may can't load with `import()`: @@ -15,14 +15,14 @@ async function loadExternalConfig(config, filepath) { 3. is a dirname with index.js inside */ try { - return requireFromFile(config, filepath); + return requireFromFile(externalConfig, configFile); } catch (/** @type {any} */ error) { if (!requireErrorCodesShouldBeIgnored.has(error?.code)) { throw error; } } - const module = await importFromFile(config, filepath); + const module = await importFromFile(externalConfig, configFile); return module.default; } diff --git a/src/config/prettier-config-explorer/loaders.js b/src/config/prettier-config-explorer/loaders.js new file mode 100644 index 000000000..2f663e333 --- /dev/null +++ b/src/config/prettier-config-explorer/loaders.js @@ -0,0 +1,68 @@ +import { pathToFileURL } from "node:url"; +import parseToml from "@iarna/toml/parse-async.js"; +import parseJson5 from "json5/lib/parse.js"; +import jsYaml from "js-yaml"; +import parseJson from "parse-json"; +import readFile from "../../utils/read-file.js"; + +async function readJson(file) { + const content = await readFile(file); + try { + return parseJson(content); + } catch (/** @type {any} */ error) { + error.message = `JSON Error in ${file}:\n${error.message}`; + throw error; + } +} + +async function loadJs(file) { + const module = await import(pathToFileURL(file).href); + return module.default; +} + +async function loadConfigFromPackageJson(file) { + const { prettier } = await readJson(file); + return prettier; +} + +async function loadYaml(file) { + const content = await readFile(file); + try { + return jsYaml.load(content); + } catch (/** @type {any} */ error) { + error.message = `YAML Error in ${file}:\n${error.message}`; + throw error; + } +} + +const loaders = { + async ".toml"(file) { + const content = await readFile(file); + try { + return await parseToml(content); + } catch (/** @type {any} */ error) { + error.message = `TOML Error in ${file}:\n${error.message}`; + throw error; + } + }, + async ".json5"(file) { + const content = await readFile(file); + try { + return parseJson5(content); + } catch (/** @type {any} */ error) { + error.message = `JSON5 Error in ${file}:\n${error.message}`; + throw error; + } + }, + ".json": readJson, + ".js": loadJs, + ".mjs": loadJs, + ".cjs": loadJs, + ".yaml": loadYaml, + ".yml": loadYaml, + // No extension + "": loadYaml, +}; + +export default loaders; +export { readJson, loadConfigFromPackageJson }; diff --git a/src/config/resolve-config.js b/src/config/resolve-config.js index 4c15de5f9..707007d89 100644 --- a/src/config/resolve-config.js +++ b/src/config/resolve-config.js @@ -3,15 +3,16 @@ import micromatch from "micromatch"; import mem, { memClear } from "mem"; import { toPath } from "url-or-path"; import partition from "../utils/partition.js"; +import { + searchConfig as searchPrettierConfig, + loadConfig as loadPrettierConfigFile, + clearCache as clearPrettierConfigCache, +} from "./prettier-config-explorer/index.js"; import loadEditorConfigWithoutCache from "./resolve-editorconfig.js"; -import getPrettierConfigExplorerWithoutCache from "./get-prettier-config-explorer.js"; -const getPrettierConfigExplorer = mem(getPrettierConfigExplorerWithoutCache, { - cacheKey: ([options]) => options.cache, -}); const memoizedLoadEditorConfig = mem(loadEditorConfigWithoutCache); function clearCache() { - memClear(getPrettierConfigExplorer); + clearPrettierConfigCache(); memClear(memoizedLoadEditorConfig); } @@ -25,22 +26,24 @@ function loadEditorConfig(filePath, options) { )(filePath); } -function loadPrettierConfig(filePath, options) { - const { useCache, config: configPath } = options; - const { load, search } = getPrettierConfigExplorer({ - cache: Boolean(useCache), - }); +async function loadPrettierConfig(filePath, options) { + const shouldCache = options.useCache; + let configFile = options.config; - if (configPath) { - return load(configPath); + if (!configFile) { + const directory = filePath + ? path.dirname(path.resolve(filePath)) + : undefined; + configFile = await searchPrettierConfig(directory, { shouldCache }); } - if (!filePath) { - return search(); + if (!configFile) { + return; } - const dirname = path.dirname(path.resolve(filePath)); - return search(dirname); + const config = await loadPrettierConfigFile(configFile, { shouldCache }); + + return { config, configFile }; } async function resolveConfig(fileUrlOrPath, options) { @@ -64,7 +67,7 @@ async function resolveConfig(fileUrlOrPath, options) { if (Array.isArray(merged.plugins)) { merged.plugins = merged.plugins.map((value) => typeof value === "string" && value.startsWith(".") // relative path - ? path.resolve(path.dirname(result.filepath), value) + ? path.resolve(path.dirname(result.configFile), value) : value, ); } @@ -73,19 +76,18 @@ async function resolveConfig(fileUrlOrPath, options) { } async function resolveConfigFile(fileUrlOrPath) { - const { search } = getPrettierConfigExplorerWithoutCache(); - const dirname = fileUrlOrPath + const directory = fileUrlOrPath ? path.dirname(path.resolve(toPath(fileUrlOrPath))) : undefined; - const result = await search(dirname); - return result?.filepath ?? null; + const result = await searchPrettierConfig(directory, { shouldCache: false }); + return result ?? null; } function mergeOverrides(configResult, filePath) { - const { config, filepath: configPath } = configResult || {}; + const { config, configFile } = configResult || {}; const { overrides, ...options } = config || {}; if (filePath && overrides) { - const relativeFilePath = path.relative(path.dirname(configPath), filePath); + const relativeFilePath = path.relative(path.dirname(configFile), filePath); for (const override of overrides) { if ( pathMatchesGlobs( diff --git a/src/utils/is-file.js b/src/utils/is-file.js new file mode 100644 index 000000000..d9526826a --- /dev/null +++ b/src/utils/is-file.js @@ -0,0 +1,22 @@ +import fs from "node:fs/promises"; +import { toPath } from "url-or-path"; + +/** + * @param {string | URL} file + * @param {{allowSymlinks: boolean}} [options] + * @returns {Promise} + */ +async function isFile(file, options) { + const allowSymlinks = options?.allowSymlinks ?? true; + + let stats; + try { + stats = await (allowSymlinks ? fs.stat : fs.lstat)(toPath(file)); + } catch { + return false; + } + + return stats.isFile(); +} + +export default isFile; diff --git a/tests/integration/__tests__/__snapshots__/config-invalid.js.snap b/tests/integration/__tests__/__snapshots__/config-invalid.js.snap index 9b78f11b4..105c5d0cf 100644 --- a/tests/integration/__tests__/__snapshots__/config-invalid.js.snap +++ b/tests/integration/__tests__/__snapshots__/config-invalid.js.snap @@ -63,7 +63,7 @@ exports[`throw error with invalid config precedence option (configPrecedence) (w exports[`throw error with invalid config target (directory) (stderr) 1`] = ` "[error] Invalid configuration: -[error] EISDIR: illegal operation on a directory, read" +[error] Unable to read '/config/invalid/folder/.prettierrc': EISDIR: illegal operation on a directory, read" `; exports[`throw error with invalid config target (directory) (stdout) 1`] = `""`; diff --git a/tests/integration/__tests__/config-invalid.js b/tests/integration/__tests__/config-invalid.js index 7cf19705a..4becfe576 100644 --- a/tests/integration/__tests__/config-invalid.js +++ b/tests/integration/__tests__/config-invalid.js @@ -1,3 +1,4 @@ +import fs from "node:fs/promises"; import outdent from "outdent"; import jestPathSerializer from "../path-serializer.js"; @@ -178,3 +179,31 @@ describe("Invalid config value", () => { stderr: "", }); }); + +// Can't put a invalid `package.json` file in the test dir +test("Invalid package.json", async () => { + const packageJsonFile = new URL( + "../cli/config/invalid/broken-package-json/package.json", + import.meta.url, + ); + + try { + await fs.writeFile(packageJsonFile, '{"prettier":{}}'); + const { stdout: configFileForValidPackageJson } = await runCli( + "cli/config/invalid/broken-package-json", + ["--find-config-path", "foo.js"], + ); + + expect(configFileForValidPackageJson).toBe("package.json"); + + await fs.writeFile(packageJsonFile, '{"prettier":{'); + const { stdout: configFileForInvalidPackageJson } = await runCli( + "cli/config/invalid/broken-package-json", + ["--find-config-path", "foo.js"], + ); + + expect(configFileForInvalidPackageJson).toBe(".prettierrc"); + } finally { + await fs.rm(packageJsonFile, { force: true }); + } +}); diff --git a/tests/integration/__tests__/mockable.js b/tests/integration/__tests__/mockable.js index d3eaf48e2..68b5f53ea 100644 --- a/tests/integration/__tests__/mockable.js +++ b/tests/integration/__tests__/mockable.js @@ -1,52 +1,7 @@ -import path from "node:path"; -import createEsmUtils from "esm-utils"; import prettier from "../../config/prettier-entry.js"; -const { __dirname } = createEsmUtils(import.meta); const { mockable } = prettier.__debug; -// This don't has to be the same result as `prettier.resolveConfig`, -// Because we are testing with default `lilconfigOptions` -describe("lilconfig", () => { - const { lilconfig } = mockable; - - const configs = [ - { - title: "prettier.config.cjs", - dirname: path.join(__dirname, "../cli/config/js/"), - file: path.join(__dirname, "../cli/config/js/prettier.config.cjs"), - value: { - endOfLine: "auto", - tabWidth: 8, - }, - }, - { - title: "package.json", - dirname: path.join(__dirname, "../cli/config/package/"), - file: path.join(__dirname, "../cli/config/package/package.json"), - value: { - tabWidth: 3, - overrides: [ - { - files: "*.ts", - options: { - tabWidth: 5, - }, - }, - ], - }, - }, - ]; - - for (const { title, dirname, file, value } of configs) { - test(`async version ${title}`, async () => { - const { config, filepath } = await lilconfig("prettier").search(dirname); - expect(config).toEqual(value); - expect(filepath).toBe(file); - }); - } -}); - test("isCI", () => { expect(typeof mockable.isCI()).toBe("boolean"); }); diff --git a/tests/integration/cli-worker.js b/tests/integration/cli-worker.js index c4b9405fb..583569fab 100644 --- a/tests/integration/cli-worker.js +++ b/tests/integration/cli-worker.js @@ -3,7 +3,6 @@ import fs from "node:fs"; import path from "node:path"; import url from "node:url"; import readline from "node:readline"; -import { lilconfig } from "lilconfig"; import { prettierCli, prettierMainEntry } from "./env.js"; const normalizeToPosix = @@ -65,11 +64,8 @@ async function run() { // eslint-disable-next-line require-await mockable.getStdin = async () => options.input || ""; mockable.isCI = () => Boolean(options.ci); - mockable.lilconfig = (moduleName, options) => - lilconfig(moduleName, { - ...options, - stopDir: url.fileURLToPath(new URL("./cli", import.meta.url)), - }); + mockable.getPrettierConfigSearchStopDirectory = () => + url.fileURLToPath(new URL("./cli", import.meta.url)); // eslint-disable-next-line require-await mockable.writeFormattedFile = async (filename, content) => { filename = normalizeToPosix(path.relative(process.cwd(), filename)); diff --git a/tests/integration/cli/config/invalid/broken-package-json/.prettierignore b/tests/integration/cli/config/invalid/broken-package-json/.prettierignore new file mode 100644 index 000000000..396a8fc23 --- /dev/null +++ b/tests/integration/cli/config/invalid/broken-package-json/.prettierignore @@ -0,0 +1,2 @@ +# will be created by test +package.json diff --git a/tests/integration/cli/config/invalid/broken-package-json/.prettierrc b/tests/integration/cli/config/invalid/broken-package-json/.prettierrc new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/tests/integration/cli/config/invalid/broken-package-json/.prettierrc @@ -0,0 +1 @@ +{} diff --git a/yarn.lock b/yarn.lock index f719621cc..6258c6fdd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6214,13 +6214,6 @@ __metadata: languageName: node linkType: hard -"lilconfig@npm:2.1.0": - version: 2.1.0 - resolution: "lilconfig@npm:2.1.0" - checksum: 8549bb352b8192375fed4a74694cd61ad293904eee33f9d4866c2192865c44c4eb35d10782966242634e0cbc1e91fe62b1247f148dc5514918e3a966da7ea117 - languageName: node - linkType: hard - "lines-and-columns@npm:2.0.4, lines-and-columns@npm:^2.0.3": version: 2.0.4 resolution: "lines-and-columns@npm:2.0.4" @@ -7371,7 +7364,6 @@ __metadata: js-yaml: 4.1.0 json5: 2.2.3 leven: 4.0.0 - lilconfig: 2.1.0 lines-and-columns: 2.0.4 linguist-languages: 7.27.0 magic-string: 0.30.5 From 4c3cfa7a813e072ae4f581ecf6c5b27ebb063f15 Mon Sep 17 00:00:00 2001 From: fisker Cheung Date: Tue, 5 Dec 2023 15:39:24 +0800 Subject: [PATCH 08/18] Support TypeScript 5.3 (#15718) --- changelog_unreleased/typescript/15718.md | 7 ++ package.json | 8 +- scripts/build/modify-typescript-module.js | 10 ++ src/language-js/parse/postprocess/index.js | 10 +- src/language-js/parse/typescript.js | 1 + src/language-js/print/module.js | 59 ++++++++- .../__snapshots__/jsfmt.spec.js.snap | 11 -- .../jsfmt.spec.js | 3 - .../__snapshots__/jsfmt.spec.js.snap | 115 ++++++++++-------- .../format/js/import-attributes/jsfmt.spec.js | 11 +- .../js/import-attributes/keyword-detect.js | 10 ++ .../__snapshots__/jsfmt.spec.js.snap | 6 +- yarn.lock | 108 ++++++++-------- 13 files changed, 213 insertions(+), 146 deletions(-) create mode 100644 changelog_unreleased/typescript/15718.md create mode 100644 tests/format/js/import-attributes/keyword-detect.js diff --git a/changelog_unreleased/typescript/15718.md b/changelog_unreleased/typescript/15718.md new file mode 100644 index 000000000..008780346 --- /dev/null +++ b/changelog_unreleased/typescript/15718.md @@ -0,0 +1,7 @@ +#### Support "Import Attributes" (#15718 by @fisker) + +[TypeScript 5.3](https://devblogs.microsoft.com/typescript/announcing-typescript-5-3/#import-attributes) supports the latest updates to the [import attributes](https://github.com/tc39/proposal-import-attributes) proposal. + +```tsx +import something from "./something.json" with { type: "json" }; +``` diff --git a/package.json b/package.json index 2f9a4edc9..fcf9a6ed3 100644 --- a/package.json +++ b/package.json @@ -38,8 +38,8 @@ "@iarna/toml": "2.2.5", "@prettier/is-es5-identifier-name": "0.2.0", "@prettier/parse-srcset": "3.1.0", - "@typescript-eslint/typescript-estree": "6.10.0", - "@typescript-eslint/visitor-keys": "6.10.0", + "@typescript-eslint/typescript-estree": "6.13.2", + "@typescript-eslint/visitor-keys": "6.13.2", "acorn": "8.11.2", "acorn-jsx": "5.3.2", "angular-estree-parser": "9.0.0", @@ -98,7 +98,7 @@ "sdbm": "2.0.0", "strip-ansi": "7.1.0", "to-fast-properties": "4.0.0", - "typescript": "5.2.2", + "typescript": "5.3.2", "unicode-regex": "4.0.0", "unified": "9.2.2", "url-or-path": "2.3.0", @@ -113,7 +113,7 @@ "@types/file-entry-cache": "5.0.4", "@types/find-cache-dir": "3.2.1", "@types/jest": "29.5.8", - "@typescript-eslint/eslint-plugin": "6.10.0", + "@typescript-eslint/eslint-plugin": "6.13.2", "benchmark": "2.1.4", "browserslist-to-esbuild": "1.2.0", "c8": "8.0.1", diff --git a/scripts/build/modify-typescript-module.js b/scripts/build/modify-typescript-module.js index 5fe145562..043a09a7d 100644 --- a/scripts/build/modify-typescript-module.js +++ b/scripts/build/modify-typescript-module.js @@ -198,6 +198,13 @@ function modifyTypescriptModule(text) { } } + // server + for (const module of source.modules) { + if (module.path.startsWith("src/server/")) { + source.removeModule(module); + } + } + // `transformers` source.removeModule("src/compiler/transformer.ts"); for (const module of source.modules) { @@ -268,6 +275,9 @@ function modifyTypescriptModule(text) { `, ); + // `pnp` + source.removeModule("src/compiler/pnp.ts"); + /* spell-checker: disable */ // `ts.createParenthesizerRules` source.replaceAlignedCode({ diff --git a/src/language-js/parse/postprocess/index.js b/src/language-js/parse/postprocess/index.js index 80cb5bf95..fc52dc4b8 100644 --- a/src/language-js/parse/postprocess/index.js +++ b/src/language-js/parse/postprocess/index.js @@ -97,10 +97,10 @@ function postprocess(ast, options) { ast.extra = { ...ast.extra, __isUsingHackPipeline: true }; break; - // TODO: Remove this when https://github.com/meriyah/meriyah/issues/200 get fixed - case "ExportAllDeclaration": { - const { exported } = node; - if (parser === "meriyah" && exported?.type === "Identifier") { + case "ExportAllDeclaration": + // TODO: Remove this when https://github.com/meriyah/meriyah/issues/200 get fixed + if (parser === "meriyah" && node.exported?.type === "Identifier") { + const { exported } = node; const raw = text.slice(locStart(exported), locEnd(exported)); if (raw.startsWith('"') || raw.startsWith("'")) { node.exported = { @@ -112,7 +112,7 @@ function postprocess(ast, options) { } } break; - } + // In Flow parser, it doesn't generate union/intersection types for single type case "TSUnionType": case "TSIntersectionType": diff --git a/src/language-js/parse/typescript.js b/src/language-js/parse/typescript.js index c0773a3c8..d751bce6c 100644 --- a/src/language-js/parse/typescript.js +++ b/src/language-js/parse/typescript.js @@ -17,6 +17,7 @@ const baseParseOptions = { tokens: true, loggerFn: false, project: [], + jsDocParsingMode: "none", // TODO: Use new properties when update printer suppressDeprecatedPropertyWarnings: true, }; diff --git a/src/language-js/print/module.js b/src/language-js/print/module.js index 4b83d8be4..99cb85e19 100644 --- a/src/language-js/print/module.js +++ b/src/language-js/print/module.js @@ -1,3 +1,4 @@ +import assert from "node:assert"; import isNonEmptyArray from "../../utils/is-non-empty-array.js"; import UnexpectedNodeError from "../../utils/unexpected-node-error.js"; import { @@ -20,7 +21,7 @@ import { rawText, createTypeCheckFunction, } from "../utils/index.js"; -import { locStart, hasSameLoc } from "../loc.js"; +import { locStart, hasSameLoc, locEnd } from "../loc.js"; import { printDecoratorsBeforeExport } from "./decorators.js"; import { printDeclareToken } from "./misc.js"; @@ -247,6 +248,57 @@ function shouldNotPrintSpecifiers(node, options) { ); } +function getTextWithoutComments(options, start, end) { + let text = options.originalText.slice(start, end); + + for (const comment of options[Symbol.for("comments")]) { + const commentStart = locStart(comment); + // Comments are sorted, we can escape if the comment is after the range + if (commentStart > end) { + break; + } + + const commentEnd = locEnd(comment); + if (commentEnd < start) { + continue; + } + + const commentLength = commentEnd - commentStart; + text = + text.slice(0, commentStart - start) + + " ".repeat(commentLength) + + text.slice(commentEnd - start); + } + + if (process.env.NODE_ENV !== "production") { + assert(text.length === end - start); + } + + return text; +} + +function getImportAttributesOrAssertionsKeyword(node, options) { + if ( + // Babel parser add this property to indicate the keyword is `assert` + node.extra?.deprecatedAssertSyntax || + !isNonEmptyArray(node.attributes) + ) { + return "assert"; + } + + const textBetweenSourceAndAttributes = getTextWithoutComments( + options, + node.source.range[1], + node.attributes[0].range[0], + ); + + if (textBetweenSourceAndAttributes.trimStart().startsWith("assert")) { + return "assert"; + } + + return "with"; +} + /** * Print Import Attributes syntax. * If old ImportAssertions syntax is used, print them here. @@ -264,10 +316,7 @@ function printImportAttributes(path, options, print) { return ""; } - const keyword = - property === "assertions" || node.extra?.deprecatedAssertSyntax - ? "assert" - : "with"; + const keyword = getImportAttributesOrAssertionsKeyword(node, options); return [ ` ${keyword} {`, options.bracketSpacing ? " " : "", diff --git a/tests/format/js/explicit-resource-management/__snapshots__/jsfmt.spec.js.snap b/tests/format/js/explicit-resource-management/__snapshots__/jsfmt.spec.js.snap index b68238451..5af60a31d 100644 --- a/tests/format/js/explicit-resource-management/__snapshots__/jsfmt.spec.js.snap +++ b/tests/format/js/explicit-resource-management/__snapshots__/jsfmt.spec.js.snap @@ -742,17 +742,6 @@ exports[`valid-for-using-binding-of-of.js [meriyah] format 1`] = ` Cause: [2:21]: Invalid left-hand side in for-of" `; -exports[`valid-for-using-binding-of-of.js [typescript] format 1`] = ` -"')' expected. (2:26) - 1 | async function f() { -> 2 | for (await using of of of); - | ^ - 3 | for await (await using of of of); - 4 | } - 5 | -Cause: ')' expected." -`; - exports[`valid-for-using-binding-of-of.js format 1`] = ` ====================================options===================================== parsers: ["babel", "flow", "typescript"] diff --git a/tests/format/js/explicit-resource-management/jsfmt.spec.js b/tests/format/js/explicit-resource-management/jsfmt.spec.js index e97824449..1a962d686 100644 --- a/tests/format/js/explicit-resource-management/jsfmt.spec.js +++ b/tests/format/js/explicit-resource-management/jsfmt.spec.js @@ -32,9 +32,6 @@ run_spec(import.meta, ["babel", "flow", "typescript"], { "valid-await-using-comments.js", "using-declarations.js", - // https://github.com/microsoft/TypeScript/issues/55555 - "valid-for-using-binding-of-of.js", - // TypeScript does not support module expr "valid-module-block-top-level-using-binding.js", "valid-module-block-top-level-await-using-binding.js", diff --git a/tests/format/js/import-attributes/__snapshots__/jsfmt.spec.js.snap b/tests/format/js/import-attributes/__snapshots__/jsfmt.spec.js.snap index 9024e0269..f08aa1276 100644 --- a/tests/format/js/import-attributes/__snapshots__/jsfmt.spec.js.snap +++ b/tests/format/js/import-attributes/__snapshots__/jsfmt.spec.js.snap @@ -71,17 +71,6 @@ exports[`empty.js [meriyah] format 1`] = ` Cause: [2:36]: Unexpected token: 'with'" `; -exports[`empty.js [typescript] format 1`] = ` -"';' expected. (2:33) - 1 | export * as foo from "foo.json" -> 2 | export * as bar from "bar.json" with { } - | ^ - 3 | export * as baz from "baz.json" with { /* comment */ } - 4 | - 5 | import * as foo from "foo.json" -Cause: ';' expected." -`; - exports[`empty.js format 1`] = ` ====================================options===================================== parsers: ["babel", "typescript"] @@ -108,6 +97,68 @@ import * as baz from "baz.json" /* comment */; ================================================================================ `; +exports[`keyword-detect.js [acorn] format 1`] = ` +"Unexpected token (1:35) +> 1 | import "./test.json" /* assert */ with /* assert */ { type: "json" } + | ^ + 2 | import {} from "./test.json" /* assert */ with /* assert */ { type: "json" } + 3 | import "./test.json" /* with */ assert /* with */ { type: "json" } + 4 | import {} from "./test.json" /* with */ assert /* with */ { type: "json" } +Cause: Unexpected token (1:34)" +`; + +exports[`keyword-detect.js [espree] format 1`] = ` +"Unexpected token with (1:35) +> 1 | import "./test.json" /* assert */ with /* assert */ { type: "json" } + | ^ + 2 | import {} from "./test.json" /* assert */ with /* assert */ { type: "json" } + 3 | import "./test.json" /* with */ assert /* with */ { type: "json" } + 4 | import {} from "./test.json" /* with */ assert /* with */ { type: "json" } +Cause: Unexpected token with" +`; + +exports[`keyword-detect.js [meriyah] format 1`] = ` +"Unexpected token: 'with' (1:38) +> 1 | import "./test.json" /* assert */ with /* assert */ { type: "json" } + | ^ + 2 | import {} from "./test.json" /* assert */ with /* assert */ { type: "json" } + 3 | import "./test.json" /* with */ assert /* with */ { type: "json" } + 4 | import {} from "./test.json" /* with */ assert /* with */ { type: "json" } +Cause: [1:38]: Unexpected token: 'with'" +`; + +exports[`keyword-detect.js format 1`] = ` +====================================options===================================== +parsers: ["babel", "typescript"] +printWidth: 80 + | printWidth +=====================================input====================================== +import "./test.json" /* assert */ with /* assert */ { type: "json" } +import {} from "./test.json" /* assert */ with /* assert */ { type: "json" } +import "./test.json" /* with */ assert /* with */ { type: "json" } +import {} from "./test.json" /* with */ assert /* with */ { type: "json" } + +export {} from "./test.json" /* assert */ with /* assert */ { type: "json" } +export {} from "./test.json" /* with */ assert /* with */ { type: "json" } + +export * from "./test.json" /* assert */ with /* assert */ { type: "json" } +export * from "./test.json" /* with */ assert /* with */ { type: "json" } + +=====================================output===================================== +import "./test.json" /* assert */ /* assert */ with { type: "json" }; +import {} from "./test.json" /* assert */ /* assert */ with { type: "json" }; +import "./test.json" /* with */ /* with */ assert { type: "json" }; +import {} from "./test.json" /* with */ /* with */ assert { type: "json" }; + +export {} from "./test.json" /* assert */ /* assert */ with { type: "json" }; +export {} from "./test.json" /* with */ /* with */ assert { type: "json" }; + +export * from "./test.json" /* assert */ /* assert */ with { type: "json" }; +export * from "./test.json" /* with */ /* with */ assert { type: "json" }; + +================================================================================ +`; + exports[`multi-types.js [acorn] format 1`] = ` "Unexpected token (1:31) > 1 | import json from "./foo.json" with { type: "json", type: "bar" }; @@ -132,14 +183,6 @@ exports[`multi-types.js [meriyah] format 1`] = ` Cause: [1:34]: Unexpected token: 'with'" `; -exports[`multi-types.js [typescript] format 1`] = ` -"';' expected. (1:31) -> 1 | import json from "./foo.json" with { type: "json", type: "bar" }; - | ^ - 2 | -Cause: ';' expected." -`; - exports[`multi-types.js format 1`] = ` ====================================options===================================== parsers: ["babel", "typescript"] @@ -178,14 +221,6 @@ exports[`non-type.js [meriyah] format 1`] = ` Cause: [1:31]: Unexpected token: 'with'" `; -exports[`non-type.js [typescript] format 1`] = ` -"';' expected. (1:28) -> 1 | import foo from "foo.json" with { lazy: "true" }; - | ^ - 2 | -Cause: ';' expected." -`; - exports[`non-type.js format 1`] = ` ====================================options===================================== parsers: ["babel", "typescript"] @@ -230,16 +265,6 @@ exports[`re-export.js [meriyah] format 1`] = ` Cause: [1:47]: Unexpected token: 'with'" `; -exports[`re-export.js [typescript] format 1`] = ` -"';' expected. (1:44) -> 1 | export { default as foo2 } from "foo.json" with { type: "json" }; - | ^ - 2 | export * from "foo.json" with { type: "json" }; - 3 | export * as foo3 from "foo.json" with { type: "json" }; - 4 | -Cause: ';' expected." -`; - exports[`re-export.js format 1`] = ` ====================================options===================================== parsers: ["babel", "typescript"] @@ -282,14 +307,6 @@ exports[`static-import.js [meriyah] format 1`] = ` Cause: [1:34]: Unexpected token: 'with'" `; -exports[`static-import.js [typescript] format 1`] = ` -"';' expected. (1:31) -> 1 | import json from "./foo.json" with { type: "json" }; - | ^ - 2 | -Cause: ';' expected." -`; - exports[`static-import.js format 1`] = ` ====================================options===================================== parsers: ["babel", "typescript"] @@ -328,14 +345,6 @@ exports[`without-from.js [meriyah] format 1`] = ` Cause: [1:17]: Unexpected token: 'with'" `; -exports[`without-from.js [typescript] format 1`] = ` -"';' expected. (1:14) -> 1 | import "foo" with { type: "json" } - | ^ - 2 | -Cause: ';' expected." -`; - exports[`without-from.js format 1`] = ` ====================================options===================================== parsers: ["babel", "typescript"] diff --git a/tests/format/js/import-attributes/jsfmt.spec.js b/tests/format/js/import-attributes/jsfmt.spec.js index 168477a66..7e4f7ab95 100644 --- a/tests/format/js/import-attributes/jsfmt.spec.js +++ b/tests/format/js/import-attributes/jsfmt.spec.js @@ -8,6 +8,7 @@ run_spec(import.meta, ["babel", "typescript"], { "re-export.js", "without-from.js", "non-type.js", + "keyword-detect.js", ], espree: [ "dynamic-import.js", @@ -17,6 +18,7 @@ run_spec(import.meta, ["babel", "typescript"], { "re-export.js", "without-from.js", "non-type.js", + "keyword-detect.js", ], meriyah: [ "dynamic-import.js", @@ -26,14 +28,7 @@ run_spec(import.meta, ["babel", "typescript"], { "re-export.js", "without-from.js", "non-type.js", - ], - typescript: [ - "empty.js", - "multi-types.js", - "static-import.js", - "re-export.js", - "without-from.js", - "non-type.js", + "keyword-detect.js", ], }, }); diff --git a/tests/format/js/import-attributes/keyword-detect.js b/tests/format/js/import-attributes/keyword-detect.js new file mode 100644 index 000000000..4d0ed0657 --- /dev/null +++ b/tests/format/js/import-attributes/keyword-detect.js @@ -0,0 +1,10 @@ +import "./test.json" /* assert */ with /* assert */ { type: "json" } +import {} from "./test.json" /* assert */ with /* assert */ { type: "json" } +import "./test.json" /* with */ assert /* with */ { type: "json" } +import {} from "./test.json" /* with */ assert /* with */ { type: "json" } + +export {} from "./test.json" /* assert */ with /* assert */ { type: "json" } +export {} from "./test.json" /* with */ assert /* with */ { type: "json" } + +export * from "./test.json" /* assert */ with /* assert */ { type: "json" } +export * from "./test.json" /* with */ assert /* with */ { type: "json" } diff --git a/tests/format/misc/errors/typescript/__snapshots__/jsfmt.spec.js.snap b/tests/format/misc/errors/typescript/__snapshots__/jsfmt.spec.js.snap index fb4a8a9f7..8bbbfec42 100644 --- a/tests/format/misc/errors/typescript/__snapshots__/jsfmt.spec.js.snap +++ b/tests/format/misc/errors/typescript/__snapshots__/jsfmt.spec.js.snap @@ -47,11 +47,11 @@ Cause: Unexpected token. Did you mean \`{'>'}\` or \`>\`?" `; exports[`module-attributes-static.ts [typescript] format 1`] = ` -"';' expected. (1:28) +"'{' expected. (1:33) > 1 | import foo from "foo.json" with type: "json"; - | ^ + | ^ 2 | -Cause: ';' expected." +Cause: '{' expected." `; exports[`newline-before-arrow.ts [typescript] format 1`] = ` diff --git a/yarn.lock b/yarn.lock index 6258c6fdd..1f7632064 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1909,15 +1909,15 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/eslint-plugin@npm:6.10.0": - version: 6.10.0 - resolution: "@typescript-eslint/eslint-plugin@npm:6.10.0" +"@typescript-eslint/eslint-plugin@npm:6.13.2": + version: 6.13.2 + resolution: "@typescript-eslint/eslint-plugin@npm:6.13.2" dependencies: "@eslint-community/regexpp": ^4.5.1 - "@typescript-eslint/scope-manager": 6.10.0 - "@typescript-eslint/type-utils": 6.10.0 - "@typescript-eslint/utils": 6.10.0 - "@typescript-eslint/visitor-keys": 6.10.0 + "@typescript-eslint/scope-manager": 6.13.2 + "@typescript-eslint/type-utils": 6.13.2 + "@typescript-eslint/utils": 6.13.2 + "@typescript-eslint/visitor-keys": 6.13.2 debug: ^4.3.4 graphemer: ^1.4.0 ignore: ^5.2.4 @@ -1930,7 +1930,7 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: eaf1f66ae1915426dad8d229c8cb80d2b320572a30c3fbc57d560d40edc2d17d004101a2fcbe331bc458df19a00f8b705f2442ee02e028bb595f4e9f9152e99d + checksum: e50cbbe7104eecef59faf3355ab981d9f353b19327f0b4607dfd829b4726f9e694b536fe43ab55f50bb00fbfdd2e4268a7e2a568b28d5fcd0d2a32a8d2466218 languageName: node linkType: hard @@ -1944,22 +1944,22 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/scope-manager@npm:6.10.0": - version: 6.10.0 - resolution: "@typescript-eslint/scope-manager@npm:6.10.0" +"@typescript-eslint/scope-manager@npm:6.13.2": + version: 6.13.2 + resolution: "@typescript-eslint/scope-manager@npm:6.13.2" dependencies: - "@typescript-eslint/types": 6.10.0 - "@typescript-eslint/visitor-keys": 6.10.0 - checksum: c9b9483082ae853f10b888cf04d4a14f666ac55e749bfdb7b7f726fc51127a6340b5e2f50d93f134a8854ddcc41f7b116b214753251a8b033d0d84c600439c54 + "@typescript-eslint/types": 6.13.2 + "@typescript-eslint/visitor-keys": 6.13.2 + checksum: ff8fd64ddf324e296e2e0e34a8f73149c9a5f14d1761ea8e8665fc5998faa2b0bbbd1a5d416aa10d725f13c804032d532f68e39a0ca6cc36d1c9b9c0aea94311 languageName: node linkType: hard -"@typescript-eslint/type-utils@npm:6.10.0": - version: 6.10.0 - resolution: "@typescript-eslint/type-utils@npm:6.10.0" +"@typescript-eslint/type-utils@npm:6.13.2": + version: 6.13.2 + resolution: "@typescript-eslint/type-utils@npm:6.13.2" dependencies: - "@typescript-eslint/typescript-estree": 6.10.0 - "@typescript-eslint/utils": 6.10.0 + "@typescript-eslint/typescript-estree": 6.13.2 + "@typescript-eslint/utils": 6.13.2 debug: ^4.3.4 ts-api-utils: ^1.0.1 peerDependencies: @@ -1967,7 +1967,7 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: cfe9520cf0c0f50b115d2591acb2abf99ffe5789b3536268ca65b624c8498812d91f187e80c41bea7cf2cebad9c38f69ef27440f872a20fb53c59856d8f5df38 + checksum: ba54e5746139f778c35e4058e523ec8c20b68cf6472b3a7784170328e48c228f0761d2fc7e43dab053ca7d85ac4378b6965567774e6afedf551e600638404215 languageName: node linkType: hard @@ -1978,10 +1978,10 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/types@npm:6.10.0": - version: 6.10.0 - resolution: "@typescript-eslint/types@npm:6.10.0" - checksum: e63a9e05eb3d736d02a09131627d5cb89394bf0d9d6b46fb4b620be902d89d73554720be65acbc194787bff9ffcd518c9a6cf88fd63e418232b4181e8d8438df +"@typescript-eslint/types@npm:6.13.2": + version: 6.13.2 + resolution: "@typescript-eslint/types@npm:6.13.2" + checksum: 4493ff06fa07c68c5adbcbd842f6dd6f5c88f14d160b53c3379b6b703e6f62808fab7fdebcc06ff06a56f20ab432b6ceeb0afb8931dc97d4061cb417e787f2c1 languageName: node linkType: hard @@ -2003,12 +2003,12 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/typescript-estree@npm:6.10.0": - version: 6.10.0 - resolution: "@typescript-eslint/typescript-estree@npm:6.10.0" +"@typescript-eslint/typescript-estree@npm:6.13.2": + version: 6.13.2 + resolution: "@typescript-eslint/typescript-estree@npm:6.13.2" dependencies: - "@typescript-eslint/types": 6.10.0 - "@typescript-eslint/visitor-keys": 6.10.0 + "@typescript-eslint/types": 6.13.2 + "@typescript-eslint/visitor-keys": 6.13.2 debug: ^4.3.4 globby: ^11.1.0 is-glob: ^4.0.3 @@ -2017,24 +2017,24 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: 15bd8d9239a557071d6b03e7aa854b769fcc2dbdff587ed94be7ee8060dabdb05bcae4251df22432f625f82087e7f6986e9aab04f7eea35af694d4edd76a21af + checksum: 0c18ee5ef594a2411a788fe9d7bc6d51a03bce38d9d764bcb24ab557e5bc1942c2ddf9bd6fb4877eb102b0ae488974fb7b7fe72daa70a2054bf04d3cc6803546 languageName: node linkType: hard -"@typescript-eslint/utils@npm:6.10.0": - version: 6.10.0 - resolution: "@typescript-eslint/utils@npm:6.10.0" +"@typescript-eslint/utils@npm:6.13.2": + version: 6.13.2 + resolution: "@typescript-eslint/utils@npm:6.13.2" dependencies: "@eslint-community/eslint-utils": ^4.4.0 "@types/json-schema": ^7.0.12 "@types/semver": ^7.5.0 - "@typescript-eslint/scope-manager": 6.10.0 - "@typescript-eslint/types": 6.10.0 - "@typescript-eslint/typescript-estree": 6.10.0 + "@typescript-eslint/scope-manager": 6.13.2 + "@typescript-eslint/types": 6.13.2 + "@typescript-eslint/typescript-estree": 6.13.2 semver: ^7.5.4 peerDependencies: eslint: ^7.0.0 || ^8.0.0 - checksum: b6bd4d68623fb8d616ae63a88f2954258411a0cc113029fba801d1e74b4c0319fdfbcac0070527afe5cc38c012c8718e4faecd1603000924d7b89e8fefc3f24d + checksum: b66bcf2a945e9c55f3dccb48af49565863d974837ee23b2f01ce7f3fb2462eb8a5871784d4a2fcc80dac7d5cd4ed90c8d01431cd177c0249de89a448f6663fc8 languageName: node linkType: hard @@ -2066,13 +2066,13 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/visitor-keys@npm:6.10.0": - version: 6.10.0 - resolution: "@typescript-eslint/visitor-keys@npm:6.10.0" +"@typescript-eslint/visitor-keys@npm:6.13.2": + version: 6.13.2 + resolution: "@typescript-eslint/visitor-keys@npm:6.13.2" dependencies: - "@typescript-eslint/types": 6.10.0 + "@typescript-eslint/types": 6.13.2 eslint-visitor-keys: ^3.4.1 - checksum: 9640bfae41e6109ffba31e68b1720382de0538d021261e2fc9e514c83c703084393c0818ca77ed26b950273e45e593371120281e8d4bbd09cb8c2d46c9fe4f03 + checksum: 4b4def7acd7451e6a18dab3ee13f06504b3d23e51f195fced7c544f2203ee8a83426c82fa57ab6b58725c70fdedaf7a3eccb69793180be35756eed0f2c69fe04 languageName: node linkType: hard @@ -7299,9 +7299,9 @@ __metadata: "@types/file-entry-cache": 5.0.4 "@types/find-cache-dir": 3.2.1 "@types/jest": 29.5.8 - "@typescript-eslint/eslint-plugin": 6.10.0 - "@typescript-eslint/typescript-estree": 6.10.0 - "@typescript-eslint/visitor-keys": 6.10.0 + "@typescript-eslint/eslint-plugin": 6.13.2 + "@typescript-eslint/typescript-estree": 6.13.2 + "@typescript-eslint/visitor-keys": 6.13.2 acorn: 8.11.2 acorn-jsx: 5.3.2 angular-estree-parser: 9.0.0 @@ -7397,7 +7397,7 @@ __metadata: tempy: 3.1.0 to-fast-properties: 4.0.0 ts-expect: 1.3.0 - typescript: 5.2.2 + typescript: 5.3.2 unicode-regex: 4.0.0 unified: 9.2.2 url-or-path: 2.3.0 @@ -8707,23 +8707,23 @@ __metadata: languageName: node linkType: hard -"typescript@npm:5.2.2": - version: 5.2.2 - resolution: "typescript@npm:5.2.2" +"typescript@npm:5.3.2": + version: 5.3.2 + resolution: "typescript@npm:5.3.2" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: 7912821dac4d962d315c36800fe387cdc0a6298dba7ec171b350b4a6e988b51d7b8f051317786db1094bd7431d526b648aba7da8236607febb26cf5b871d2d3c + checksum: d92534dda639eb825db013203404c1fabca8ac630564283c9e7dc9e64fd9c9346c2de95ecebdf3e6e8c1c32941bca1cfe0da37877611feb9daf8feeaea58d230 languageName: node linkType: hard -"typescript@patch:typescript@5.2.2#~builtin": - version: 5.2.2 - resolution: "typescript@patch:typescript@npm%3A5.2.2#~builtin::version=5.2.2&hash=f3b441" +"typescript@patch:typescript@5.3.2#~builtin": + version: 5.3.2 + resolution: "typescript@patch:typescript@npm%3A5.3.2#~builtin::version=5.3.2&hash=e012d7" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: 0f4da2f15e6f1245e49db15801dbee52f2bbfb267e1c39225afdab5afee1a72839cd86000e65ee9d7e4dfaff12239d28beaf5ee431357fcced15fb08583d72ca + checksum: 47b2e63418ea7ef7dd0bf359ee41c6fca852c680826089269c5d03673ead471e052b172561f377f9e51212054e5f0fe19dd58085ff7db7b168449a6af704c784 languageName: node linkType: hard From 7015f10d8e2763b8fd84ddf2c160917f783d480f Mon Sep 17 00:00:00 2001 From: fisker Cheung Date: Tue, 5 Dec 2023 16:13:31 +0800 Subject: [PATCH 09/18] Keep curly braces and `from` keyword in empty `import` statements (#15756) --- changelog_unreleased/javascript/15756.md | 16 ++ src/language-js/print/module.js | 29 +-- .../import/__snapshots__/jsfmt.spec.js.snap | 166 ++++++++++++++++++ tests/format/js/import/empty-import.js | 45 +++++ .../__snapshots__/jsfmt.spec.js.snap | 97 ++++++++++ .../typescript/import-export/empty-import.ts | 46 +++++ 6 files changed, 385 insertions(+), 14 deletions(-) create mode 100644 changelog_unreleased/javascript/15756.md create mode 100644 tests/format/typescript/import-export/empty-import.ts diff --git a/changelog_unreleased/javascript/15756.md b/changelog_unreleased/javascript/15756.md new file mode 100644 index 000000000..7c78449b7 --- /dev/null +++ b/changelog_unreleased/javascript/15756.md @@ -0,0 +1,16 @@ +#### Keep curly braces and `from` keyword in empty `import` statements (#15756 by @fisker) + + +```js +// Input +import { } from 'foo'; +import { /* comment */ } from 'bar'; + +// Prettier stable +import {} from "foo"; +import /* comment */ "bar"; + +// Prettier main +import {} from "foo"; +import {} from /* comment */ "bar"; +``` diff --git a/src/language-js/print/module.js b/src/language-js/print/module.js index 99cb85e19..2b3de0432 100644 --- a/src/language-js/print/module.js +++ b/src/language-js/print/module.js @@ -148,7 +148,7 @@ function printModuleSource(path, options, print) { /** @type{Doc[]} */ const parts = []; - if (!shouldNotPrintSpecifiers(node, options)) { + if (shouldPrintSpecifiers(node, options)) { parts.push(" from"); } parts.push(" ", print("source")); @@ -159,7 +159,7 @@ function printModuleSource(path, options, print) { function printModuleSpecifiers(path, options, print) { const { node } = path; - if (shouldNotPrintSpecifiers(node, options)) { + if (!shouldPrintSpecifiers(node, options)) { return ""; } @@ -231,21 +231,22 @@ function printModuleSpecifiers(path, options, print) { return parts; } -function shouldNotPrintSpecifiers(node, options) { - const { type, importKind, source, specifiers } = node; - +function shouldPrintSpecifiers(node, options) { if ( - type !== "ImportDeclaration" || - isNonEmptyArray(specifiers) || - importKind === "type" + node.type !== "ImportDeclaration" || + isNonEmptyArray(node.specifiers) || + node.importKind === "type" ) { - return false; + return true; } - // TODO: check tokens - return !/{\s*}/.test( - options.originalText.slice(locStart(node), locStart(source)), + const text = getTextWithoutComments( + options, + locStart(node), + locStart(node.source), ); + + return text.trimEnd().endsWith("from"); } function getTextWithoutComments(options, start, end) { @@ -288,8 +289,8 @@ function getImportAttributesOrAssertionsKeyword(node, options) { const textBetweenSourceAndAttributes = getTextWithoutComments( options, - node.source.range[1], - node.attributes[0].range[0], + locEnd(node.source), + locStart(node.attributes[0]), ); if (textBetweenSourceAndAttributes.trimStart().startsWith("assert")) { diff --git a/tests/format/js/import/__snapshots__/jsfmt.spec.js.snap b/tests/format/js/import/__snapshots__/jsfmt.spec.js.snap index c61aafb15..debf429eb 100644 --- a/tests/format/js/import/__snapshots__/jsfmt.spec.js.snap +++ b/tests/format/js/import/__snapshots__/jsfmt.spec.js.snap @@ -286,9 +286,92 @@ 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"; ================================================================================ `; @@ -300,9 +383,92 @@ 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"; ================================================================================ `; diff --git a/tests/format/js/import/empty-import.js b/tests/format/js/import/empty-import.js index f08bd7860..aa75b16d1 100644 --- a/tests/format/js/import/empty-import.js +++ b/tests/format/js/import/empty-import.js @@ -1 +1,46 @@ 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'; diff --git a/tests/format/typescript/import-export/__snapshots__/jsfmt.spec.js.snap b/tests/format/typescript/import-export/__snapshots__/jsfmt.spec.js.snap index ce63210d0..8fbcc19bd 100644 --- a/tests/format/typescript/import-export/__snapshots__/jsfmt.spec.js.snap +++ b/tests/format/typescript/import-export/__snapshots__/jsfmt.spec.js.snap @@ -1,5 +1,102 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`empty-import.ts format 1`] = ` +====================================options===================================== +parsers: ["typescript"] +printWidth: 80 + | printWidth +=====================================input====================================== +import type { } from '@types/googlemaps'; +import 'a'; +import /* comment */ 'a'; +import // comment +'a'; +import type { /* comment */ } from 'a'; +import /* comment */ type {} from 'a'; +import type {} /* comment */ from 'a'; +import type {} from /* comment */ 'a'; +import /* comment */ type {/* comment */} /* comment */ from /* comment */ 'a'; +import type { + // comment +} from 'a'; +import // comment +type {} from 'a'; +import type {} +// comment +from 'a'; +import type {} from +// comment +'a'; +import type +// comment +{ +// comment +} +// comment +from +// comment +'a'; + +import // {} from +'a'; +import type {} from // comment ends with from +'a'; +import type {} from /* comment ends with from */ 'a'; +import type {} from // comment not ends with from ___ +'a'; +import type {} 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 type {} from "@types/googlemaps"; +import "a"; +import /* comment */ "a"; +import // comment +"a"; +import type {} from /* comment */ "a"; +import type {} from /* comment */ "a"; +import type {} from /* comment */ "a"; +import type {} from /* comment */ "a"; +import type {} from /* comment */ /* comment */ /* comment */ /* comment */ "a"; +import type {} from // comment +"a"; +import type {} from // comment +"a"; +import type {} from // comment +"a"; +import type {} from // comment +"a"; +import type {} from // comment +// comment +// comment +// comment +"a"; + +import // {} from +"a"; +import type {} from // comment ends with from +"a"; +import type {} from /* comment ends with from */ "a"; +import type {} from // comment not ends with from ___ +"a"; +import type {} 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[`type-modifier.ts format 1`] = ` ====================================options===================================== parsers: ["typescript"] diff --git a/tests/format/typescript/import-export/empty-import.ts b/tests/format/typescript/import-export/empty-import.ts new file mode 100644 index 000000000..185385b71 --- /dev/null +++ b/tests/format/typescript/import-export/empty-import.ts @@ -0,0 +1,46 @@ +import type { } from '@types/googlemaps'; +import 'a'; +import /* comment */ 'a'; +import // comment +'a'; +import type { /* comment */ } from 'a'; +import /* comment */ type {} from 'a'; +import type {} /* comment */ from 'a'; +import type {} from /* comment */ 'a'; +import /* comment */ type {/* comment */} /* comment */ from /* comment */ 'a'; +import type { + // comment +} from 'a'; +import // comment +type {} from 'a'; +import type {} +// comment +from 'a'; +import type {} from +// comment +'a'; +import type +// comment +{ +// comment +} +// comment +from +// comment +'a'; + +import // {} from +'a'; +import type {} from // comment ends with from +'a'; +import type {} from /* comment ends with from */ 'a'; +import type {} from // comment not ends with from ___ +'a'; +import type {} 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'; From 47ca2c15525b8fab31c087278d6bb84096ceb66e Mon Sep 17 00:00:00 2001 From: fisker Cheung Date: Tue, 5 Dec 2023 17:55:18 +0800 Subject: [PATCH 10/18] Keep empty import attributes and assertions (#15757) --- changelog_unreleased/javascript/15757.md | 16 ++++++ src/language-js/print/module.js | 56 +++++++++++++++---- .../__snapshots__/jsfmt.spec.js.snap | 8 +-- .../__snapshots__/jsfmt.spec.js.snap | 2 +- .../__snapshots__/jsfmt.spec.js.snap | 8 +-- .../__snapshots__/jsfmt.spec.js.snap | 2 +- 6 files changed, 70 insertions(+), 22 deletions(-) create mode 100644 changelog_unreleased/javascript/15757.md diff --git a/changelog_unreleased/javascript/15757.md b/changelog_unreleased/javascript/15757.md new file mode 100644 index 000000000..f7bab6bb1 --- /dev/null +++ b/changelog_unreleased/javascript/15757.md @@ -0,0 +1,16 @@ +#### Keep empty import attributes and assertions (#15757 by @fisker) + + +```js +// Input +import foo from "foo" with {}; +import bar from "bar" assert {}; + +// Prettier stable +import foo from "foo"; +import bar from "bar"; + +// Prettier main +import foo from "foo" with {}; +import bar from "bar" assert {}; +``` diff --git a/src/language-js/print/module.js b/src/language-js/print/module.js index 2b3de0432..65d37cf4e 100644 --- a/src/language-js/print/module.js +++ b/src/language-js/print/module.js @@ -249,6 +249,24 @@ function shouldPrintSpecifiers(node, options) { return text.trimEnd().endsWith("from"); } +function shouldPrintAttributes(node, options) { + if (!node.source) { + return false; + } + + if (isNonEmptyArray(node.attributes) || isNonEmptyArray(node.assertions)) { + return true; + } + + const text = getTextWithoutComments( + options, + locEnd(node.source), + locEnd(node), + ).trimStart(); + + return text.startsWith("with") || text.startsWith("assert"); +} + function getTextWithoutComments(options, start, end) { let text = options.originalText.slice(start, end); @@ -282,15 +300,20 @@ function getImportAttributesOrAssertionsKeyword(node, options) { if ( // Babel parser add this property to indicate the keyword is `assert` node.extra?.deprecatedAssertSyntax || - !isNonEmptyArray(node.attributes) + (isNonEmptyArray(node.assertions) && !isNonEmptyArray(node.attributes)) ) { return "assert"; } + if (!isNonEmptyArray(node.assertions) && isNonEmptyArray(node.attributes)) { + return "with"; + } + + const firstAttribute = node.attributes?.[0] ?? node.assertions?.[0]; const textBetweenSourceAndAttributes = getTextWithoutComments( options, locEnd(node.source), - locStart(node.attributes[0]), + firstAttribute ? locStart(firstAttribute) : locEnd(node), ); if (textBetweenSourceAndAttributes.trimStart().startsWith("assert")) { @@ -307,24 +330,33 @@ function getImportAttributesOrAssertionsKeyword(node, options) { function printImportAttributes(path, options, print) { const { node } = path; + if (!shouldPrintAttributes(node, options)) { + return ""; + } + + const keyword = getImportAttributesOrAssertionsKeyword(node, options); + /** @type{Doc[]} */ + const parts = [` ${keyword} {`]; + const property = isNonEmptyArray(node.attributes) ? "attributes" : isNonEmptyArray(node.assertions) ? "assertions" : undefined; + if (property) { + if (options.bracketSpacing) { + parts.push(" "); + } - if (!property) { - return ""; + parts.push(join(", ", path.map(print, property))); + + if (options.bracketSpacing) { + parts.push(" "); + } } + parts.push("}"); - const keyword = getImportAttributesOrAssertionsKeyword(node, options); - return [ - ` ${keyword} {`, - options.bracketSpacing ? " " : "", - join(", ", path.map(print, property)), - options.bracketSpacing ? " " : "", - "}", - ]; + return parts; } function printModuleSpecifier(path, options, print) { diff --git a/tests/format/js/import-assertions/__snapshots__/jsfmt.spec.js.snap b/tests/format/js/import-assertions/__snapshots__/jsfmt.spec.js.snap index cd510baf0..d966610d2 100644 --- a/tests/format/js/import-assertions/__snapshots__/jsfmt.spec.js.snap +++ b/tests/format/js/import-assertions/__snapshots__/jsfmt.spec.js.snap @@ -86,12 +86,12 @@ import * as bar from "bar.json" assert { } import * as baz from "baz.json" assert { /* comment */ } =====================================output===================================== export * as foo from "foo.json"; -export * as bar from "bar.json"; -export * as baz from "baz.json" /* comment */; +export * as bar from "bar.json" assert {}; +export * as baz from "baz.json" /* comment */ assert {}; import * as foo from "foo.json"; -import * as bar from "bar.json"; -import * as baz from "baz.json" /* comment */; +import * as bar from "bar.json" assert {}; +import * as baz from "baz.json" /* comment */ assert {}; ================================================================================ `; diff --git a/tests/format/js/import-assertions/bracket-spacing/__snapshots__/jsfmt.spec.js.snap b/tests/format/js/import-assertions/bracket-spacing/__snapshots__/jsfmt.spec.js.snap index 884bb803b..1908fac6b 100644 --- a/tests/format/js/import-assertions/bracket-spacing/__snapshots__/jsfmt.spec.js.snap +++ b/tests/format/js/import-assertions/bracket-spacing/__snapshots__/jsfmt.spec.js.snap @@ -69,7 +69,7 @@ printWidth: 80 =====================================input====================================== export * as bar from "bar.json" assert { } =====================================output===================================== -export * as bar from "bar.json"; +export * as bar from "bar.json" assert {}; ================================================================================ `; diff --git a/tests/format/js/import-attributes/__snapshots__/jsfmt.spec.js.snap b/tests/format/js/import-attributes/__snapshots__/jsfmt.spec.js.snap index f08aa1276..db49f25ec 100644 --- a/tests/format/js/import-attributes/__snapshots__/jsfmt.spec.js.snap +++ b/tests/format/js/import-attributes/__snapshots__/jsfmt.spec.js.snap @@ -87,12 +87,12 @@ import * as baz from "baz.json" with { /* comment */ } =====================================output===================================== export * as foo from "foo.json"; -export * as bar from "bar.json"; -export * as baz from "baz.json" /* comment */; +export * as bar from "bar.json" with {}; +export * as baz from "baz.json" /* comment */ with {}; import * as foo from "foo.json"; -import * as bar from "bar.json"; -import * as baz from "baz.json" /* comment */; +import * as bar from "bar.json" with {}; +import * as baz from "baz.json" /* comment */ with {}; ================================================================================ `; diff --git a/tests/format/js/import-attributes/bracket-spacing/__snapshots__/jsfmt.spec.js.snap b/tests/format/js/import-attributes/bracket-spacing/__snapshots__/jsfmt.spec.js.snap index 18d759c64..67936c32d 100644 --- a/tests/format/js/import-attributes/bracket-spacing/__snapshots__/jsfmt.spec.js.snap +++ b/tests/format/js/import-attributes/bracket-spacing/__snapshots__/jsfmt.spec.js.snap @@ -69,7 +69,7 @@ printWidth: 80 =====================================input====================================== export * as bar from "bar.json" with { } =====================================output===================================== -export * as bar from "bar.json"; +export * as bar from "bar.json" with {}; ================================================================================ `; From 389225527c84a5c0c69dfa7c22d78dcb30edecd3 Mon Sep 17 00:00:00 2001 From: fisker Cheung Date: Tue, 5 Dec 2023 18:18:56 +0800 Subject: [PATCH 11/18] Remove outdated code (#15759) --- src/language-js/traverse/visitor-keys.evaluate.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/language-js/traverse/visitor-keys.evaluate.js b/src/language-js/traverse/visitor-keys.evaluate.js index d0e8121d1..7e28c32ae 100644 --- a/src/language-js/traverse/visitor-keys.evaluate.js +++ b/src/language-js/traverse/visitor-keys.evaluate.js @@ -61,10 +61,6 @@ const excludeKeys = { // Flow parser changed `.types` to `.elementTypes` https://github.com/facebook/flow/commit/5b60e6a81dc277dfab2e88fa3737a4dc9aafdcab // TupleTypeAnnotation: ["types"], PropertyDefinition: ["tsModifiers"], - - // From `babelVisitorKeys` - DeclareInterface: ["mixins", "implements"], - InterfaceDeclaration: ["mixins", "implements"], }; const visitorKeys = Object.fromEntries( From d3b3d4f5e3ba794f00cfa63db55a22e8b1faf30e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 5 Dec 2023 18:27:18 +0800 Subject: [PATCH 12/18] chore(deps): update dependency hermes-parser to v0.18.0 (#15761) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index fcf9a6ed3..9add0e286 100644 --- a/package.json +++ b/package.json @@ -64,7 +64,7 @@ "get-east-asian-width": "1.2.0", "get-stdin": "9.0.0", "graphql": "16.8.1", - "hermes-parser": "0.17.1", + "hermes-parser": "0.18.0", "html-element-attributes": "3.3.0", "html-tag-names": "2.1.0", "html-ua-styles": "0.0.3", diff --git a/yarn.lock b/yarn.lock index 1f7632064..460e30bdd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4683,19 +4683,19 @@ __metadata: languageName: node linkType: hard -"hermes-estree@npm:0.17.1": - version: 0.17.1 - resolution: "hermes-estree@npm:0.17.1" - checksum: ffa6f997ad0b2a0dfe8ec782fc8cc5224a0d943b330944e62fe58c93c1f590afb67ba064d5a308c51a824b0db6e1bdc1654bfaf6bdfb4b0de4fb8f9cd8cf1050 +"hermes-estree@npm:0.18.0": + version: 0.18.0 + resolution: "hermes-estree@npm:0.18.0" + checksum: c99ef2daf0e838bd738aa53e037e66b04c6e2bdc5426c460743a63fa55113d16a91045f2706e3ea99977e2291dd1a23746aeb0c9fe1bfe8d2fbced1a513e2740 languageName: node linkType: hard -"hermes-parser@npm:0.17.1": - version: 0.17.1 - resolution: "hermes-parser@npm:0.17.1" +"hermes-parser@npm:0.18.0": + version: 0.18.0 + resolution: "hermes-parser@npm:0.18.0" dependencies: - hermes-estree: 0.17.1 - checksum: 0a8fd611c708ec076654e9b7aed4b8c5b4302bdef87402a66e9c09ec3f925aa3c12718c80ebc52154ec5712a348ad375a69838f243c9bc2189ec6459415d6760 + hermes-estree: 0.18.0 + checksum: 163202f1916f1df34a560148d626eb9a82eb2b9738882cff350853051f4ebf5c56472c64029f17408ce1b1ea88309108970687dd586630febb57fea9af7e1b56 languageName: node linkType: hard @@ -7348,7 +7348,7 @@ __metadata: get-east-asian-width: 1.2.0 get-stdin: 9.0.0 graphql: 16.8.1 - hermes-parser: 0.17.1 + hermes-parser: 0.18.0 html-element-attributes: 3.3.0 html-tag-names: 2.1.0 html-ua-styles: 0.0.3 From 15c742844f3a942a855e3837a4175c2d5f9614b7 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 5 Dec 2023 19:36:55 +0800 Subject: [PATCH 13/18] chore(deps): update dependency flow-parser to v0.223.3 (#15760) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 9add0e286..7e985eb15 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,7 @@ "fast-json-stable-stringify": "2.1.0", "file-entry-cache": "7.0.2", "find-cache-dir": "5.0.0", - "flow-parser": "0.222.0", + "flow-parser": "0.223.3", "get-east-asian-width": "1.2.0", "get-stdin": "9.0.0", "graphql": "16.8.1", diff --git a/yarn.lock b/yarn.lock index 460e30bdd..717a96bb5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4294,10 +4294,10 @@ __metadata: languageName: node linkType: hard -"flow-parser@npm:0.222.0": - version: 0.222.0 - resolution: "flow-parser@npm:0.222.0" - checksum: 286fbd397e86eca7c2aaf7d280e2385670d261f986b24122471b153d8c646d924b3679807103aacb6e60282ad7ed8fb0f4112ecff89f6d0995b59021f73c0c40 +"flow-parser@npm:0.223.3": + version: 0.223.3 + resolution: "flow-parser@npm:0.223.3" + checksum: 4c0df178ff2e9140f57b5e7321b11e1cb827e00fbad6f342742c5834bd313f1bb0fe84d919d36e0c593e916c4ae5c8a93fcdb42c4328d6c5d77d75c8eed12090 languageName: node linkType: hard @@ -7344,7 +7344,7 @@ __metadata: fast-json-stable-stringify: 2.1.0 file-entry-cache: 7.0.2 find-cache-dir: 5.0.0 - flow-parser: 0.222.0 + flow-parser: 0.223.3 get-east-asian-width: 1.2.0 get-stdin: 9.0.0 graphql: 16.8.1 From 2ca5d757b746d661636bc91795fa7d98db10021f Mon Sep 17 00:00:00 2001 From: Georgii Dolzhykov Date: Wed, 6 Dec 2023 00:10:45 +0200 Subject: [PATCH 14/18] Fix expect call in dts test (#15766) --- tests/dts/unit/run.js | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/tests/dts/unit/run.js b/tests/dts/unit/run.js index 94431b3a6..5e8039335 100644 --- a/tests/dts/unit/run.js +++ b/tests/dts/unit/run.js @@ -29,6 +29,18 @@ describe("Unit tests for dts files", () => { /** @type {import("typescript").Diagnostic[]} */ const diagnostics = ts.getPreEmitDiagnostics(program); - expect(diagnostics).toEqual([]); + expect(diagnostics.map(formatDiagnostic)).toEqual([]); }); }); + +/** + * @param {import("typescript").Diagnostic} diagnostic + */ +function formatDiagnostic({ file, start, code, messageText }) { + let location = ""; + if (file) { + const { line, character } = ts.getLineAndCharacterOfPosition(file, start); + location = `${file.fileName}:${line + 1}:${character + 1} `; + } + return `${location}TS${code}: ${messageText}`; +} From 8e816ade2ebd8b9e3d6bc464916747899535f7de Mon Sep 17 00:00:00 2001 From: Sander Maijers <3374183+sanmai-NL@users.noreply.github.com> Date: Thu, 7 Dec 2023 17:54:18 +0100 Subject: [PATCH 15/18] Allow skipping symlink patterns, to avoid raising a fault (#15533) Co-authored-by: fisker --- changelog_unreleased/cli/15533.md | 5 +++++ src/cli/expand-patterns.js | 12 +++++++++--- tests/integration/__tests__/patterns-dirs.js | 19 +++++++++++++++++++ 3 files changed, 33 insertions(+), 3 deletions(-) create mode 100644 changelog_unreleased/cli/15533.md diff --git a/changelog_unreleased/cli/15533.md b/changelog_unreleased/cli/15533.md new file mode 100644 index 000000000..4af08f62b --- /dev/null +++ b/changelog_unreleased/cli/15533.md @@ -0,0 +1,5 @@ +#### Skip explicitly passed symbolic links with `--no-error-on-unmatched-pattern` (#15533 by @sanmai-NL) + +Since Prettier v3, we stopped following symbolic links, however in some use cases, the symbolic link patterns can't be filtered out, and there is no way to prevent Prettier from throwing errors. + +In Prettier main, you can use `--no-error-on-unmatched-pattern` to simply skip symbolic links. diff --git a/src/cli/expand-patterns.js b/src/cli/expand-patterns.js index 2b7085ddb..d0b786181 100644 --- a/src/cli/expand-patterns.js +++ b/src/cli/expand-patterns.js @@ -69,9 +69,15 @@ async function* expandPatternsInternal(context) { const stat = await lstatSafe(absolutePath); if (stat) { if (stat.isSymbolicLink()) { - yield { - error: `Explicitly specified pattern "${pattern}" is a symbolic link.`, - }; + if (context.argv.errorOnUnmatchedPattern !== false) { + yield { + error: `Explicitly specified pattern "${pattern}" is a symbolic link.`, + }; + } else { + context.logger.debug( + `Skipping pattern "${pattern}", as it is a symbolic link.`, + ); + } } else if (stat.isFile()) { entries.push({ type: "file", diff --git a/tests/integration/__tests__/patterns-dirs.js b/tests/integration/__tests__/patterns-dirs.js index 7963780f9..1ce748d19 100644 --- a/tests/integration/__tests__/patterns-dirs.js +++ b/tests/integration/__tests__/patterns-dirs.js @@ -321,6 +321,25 @@ function isSymlinkSupported() { { stdout: "test-a/symlink-to-directory-b/b.js" }, base, ); + + testPatterns( + "", + [ + "test-a/symlink-to-file-b", + "--no-error-on-unmatched-pattern", + "--log-level", + "debug", + ], + { + status: 0, + stdout: "", + stderr: + '[debug] normalized argv: {"":["test-a/symlink-to-file-b"],"cache":false,"color":true,"editorconfig":true,"errorOnUnmatchedPattern":false,"logLevel":"debug","ignorePath":[".prettierignore"],"configPrecedence":"cli-override","debugRepeat":0,"plugins":[],"listDifferent":true,"_":["test-a/symlink-to-file-b"],"__raw":{"_":["test-a/symlink-to-file-b"],"cache":false,"color":true,"editorconfig":true,"error-on-unmatched-pattern":false,"l":true,"log-level":"debug","ignore-path":".prettierignore","config-precedence":"cli-override","debug-repeat":0,"plugin":[]}}' + + "\n" + + '[debug] Skipping pattern "test-a/symlink-to-file-b", as it is a symbolic link.', + }, + base, + ); }); function testPatterns( From 39e4e7bf409d7ecc864a493d8ffa5f4f12403651 Mon Sep 17 00:00:00 2001 From: Georgii Dolzhykov Date: Fri, 8 Dec 2023 16:34:26 +0200 Subject: [PATCH 16/18] Add cursorOffset to Playground (#15751) --- website/playground/Playground.js | 65 +++++++++++++++++++++++++++++++- website/playground/panels.js | 8 ++-- 2 files changed, 67 insertions(+), 6 deletions(-) diff --git a/website/playground/Playground.js b/website/playground/Playground.js index c7fa74552..a24e04e81 100644 --- a/website/playground/Playground.js +++ b/website/playground/Playground.js @@ -104,7 +104,15 @@ class Playground extends React.Component { this.setContent = (content) => this.setState({ content }); this.clearContent = this.setContent.bind(this, ""); this.resetOptions = () => this.setState({ options: defaultOptions }); - this.setSelection = (selection) => this.setState({ selection }); + this.setSelection = (selection) => { + this.setState({ selection }); + if (this.state.trackCursorOffset) { + this.handleOptionValueChange( + this.cursorOffsetOption, + util.convertSelectionToRange(selection, this.state.content)[0], + ); + } + }; this.setSelectionAsRange = () => { const { selection, content, options } = this.state; const [rangeStart, rangeEnd] = util.convertSelectionToRange( @@ -126,6 +134,9 @@ class Playground extends React.Component { this.rangeEndOption = props.availableOptions.find( (opt) => opt.name === "rangeEnd", ); + this.cursorOffsetOption = props.availableOptions.find( + (opt) => opt.name === "cursorOffset", + ); this.formatInput = this.formatInput.bind(this); this.insertDummyId = this.insertDummyId.bind(this); @@ -172,6 +183,7 @@ class Playground extends React.Component { ...ENABLED_OPTIONS, "rangeStart", "rangeEnd", + "cursorOffset", ]); const cliOptions = util.buildCliArgs(orderedOptions, options); @@ -247,7 +259,7 @@ class Playground extends React.Component { reformat={editorState.showSecondFormat} rethrowEmbedErrors={editorState.rethrowEmbedErrors} > - {({ formatted, debug }) => { + {({ formatted, debug, cursorOffset }) => { const fullReport = this.getMarkdown({ formatted, reformatted: debug.reformatted, @@ -294,6 +306,49 @@ class Playground extends React.Component { Set selected text as range + + ) ) : null} diff --git a/website/playground/panels.js b/website/playground/panels.js index 02117e843..7b39e6b75 100644 --- a/website/playground/panels.js +++ b/website/playground/panels.js @@ -97,10 +97,10 @@ class CodeMirrorPanel extends React.Component { } updateOverlay() { - if (!this.props.readOnly) { - if (this._overlay) { - this._codeMirror.removeOverlay(this._overlay); - } + if (this._overlay) { + this._codeMirror.removeOverlay(this._overlay); + } + if (this.props.overlayStart !== undefined) { const [start, end] = getIndexPosition(this.props.value, [ this.props.overlayStart, this.props.overlayEnd, From 1e30f66f3979ea09adca75dc169cfe0229343a48 Mon Sep 17 00:00:00 2001 From: Mark Amery Date: Fri, 8 Dec 2023 14:35:02 +0000 Subject: [PATCH 17/18] Remove claim, untrue since over 5 years ago, that cursorOffset is incompatible with rangeStart/rangeEnd (#15750) --- changelog_unreleased/misc/15750.md | 3 +++ docs/api.md | 2 +- docs/options.md | 2 -- src/index.d.ts | 4 +--- src/main/core-options.evaluate.js | 8 ++------ src/standalone.d.ts | 2 +- tests/dts/unit/cases/standalone.ts | 2 -- .../markdown/__snapshots__/jsfmt.spec.js.snap | 14 ++------------ tests/format/markdown/markdown/real-world-case.md | 4 +--- .../__tests__/__snapshots__/early-exit.js.snap | 6 ------ .../__tests__/__snapshots__/help-options.js.snap | 3 --- .../__tests__/__snapshots__/schema.js.snap | 9 +++------ .../__tests__/__snapshots__/support-info.js.snap | 6 +++--- website/versioned_docs/version-stable/api.md | 2 +- website/versioned_docs/version-stable/options.md | 2 -- 15 files changed, 18 insertions(+), 51 deletions(-) create mode 100644 changelog_unreleased/misc/15750.md diff --git a/changelog_unreleased/misc/15750.md b/changelog_unreleased/misc/15750.md new file mode 100644 index 000000000..ebbf8223c --- /dev/null +++ b/changelog_unreleased/misc/15750.md @@ -0,0 +1,3 @@ +#### Fix false claim in docs that cursorOffset is incompatible with rangeStart/rangeEnd (#15750 by @ExplodingCabbage) + +The cursorOffset option has in fact been compatible with rangeStart/rangeEnd for over 5 years, thanks to work by @ds300. However, Prettier's documentation (including the CLI `--help` text) continued to claim otherwise, falsely. The documentation is now fixed. diff --git a/docs/api.md b/docs/api.md index d0fbf560c..5d49e8e32 100644 --- a/docs/api.md +++ b/docs/api.md @@ -26,7 +26,7 @@ await prettier.format("foo ( );", { semi: false, parser: "babel" }); `formatWithCursor` both formats the code, and translates a cursor position from unformatted code to formatted code. This is useful for editor integrations, to prevent the cursor from moving when code is formatted. -The `cursorOffset` option should be provided, to specify where the cursor is. This option cannot be used with `rangeStart` and `rangeEnd`. +The `cursorOffset` option should be provided, to specify where the cursor is. ```js await prettier.formatWithCursor(" 1", { cursorOffset: 2, parser: "babel" }); diff --git a/docs/options.md b/docs/options.md index c2df48f24..c79643c81 100644 --- a/docs/options.md +++ b/docs/options.md @@ -254,8 +254,6 @@ These two options can be used to format code starting and ending at a given char - Backwards to the start of the first line containing the selected statement. - Forwards to the end of the selected statement. -These options cannot be used with `cursorOffset`. - | Default | CLI Override | API Override | | ---------- | --------------------- | ------------------- | | `0` | `--range-start ` | `rangeStart: ` | diff --git a/src/index.d.ts b/src/index.d.ts index 5a7d30776..e5100597e 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -548,8 +548,6 @@ export interface CursorOptions extends Options { * Specify where the cursor is. */ cursorOffset: number; - rangeStart?: never; - rangeEnd?: never; } export interface CursorResult { @@ -572,7 +570,7 @@ export function check(source: string, options?: Options): Promise; * `formatWithCursor` both formats the code, and translates a cursor position from unformatted code to formatted code. * This is useful for editor integrations, to prevent the cursor from moving when code is formatted. * - * The `cursorOffset` option should be provided, to specify where the cursor is. This option cannot be used with `rangeStart` and `rangeEnd`. + * The `cursorOffset` option should be provided, to specify where the cursor is. */ export function formatWithCursor( source: string, diff --git a/src/main/core-options.evaluate.js b/src/main/core-options.evaluate.js index 36e2a6af7..5c68cc734 100644 --- a/src/main/core-options.evaluate.js +++ b/src/main/core-options.evaluate.js @@ -49,10 +49,8 @@ const options = { type: "int", default: -1, range: { start: -1, end: Number.POSITIVE_INFINITY, step: 1 }, - description: outdent` - Print (to stderr) where a cursor at the given position would move to after formatting. - This option cannot be used with --range-start and --range-end. - `, + description: + "Print (to stderr) where a cursor at the given position would move to after formatting.", cliCategory: CATEGORY_EDITOR, }, endOfLine: { @@ -160,7 +158,6 @@ const options = { description: outdent` Format code ending at a given character offset (exclusive). The range will extend forwards to the end of the selected statement. - This option cannot be used with --cursor-offset. `, cliCategory: CATEGORY_EDITOR, }, @@ -172,7 +169,6 @@ const options = { description: outdent` Format code starting at a given character offset. The range will extend backwards to the start of the first line containing the selected statement. - This option cannot be used with --cursor-offset. `, cliCategory: CATEGORY_EDITOR, }, diff --git a/src/standalone.d.ts b/src/standalone.d.ts index b77367bb7..953ccf49c 100644 --- a/src/standalone.d.ts +++ b/src/standalone.d.ts @@ -4,7 +4,7 @@ import { CursorOptions, CursorResult, Options, SupportInfo } from "./index.js"; * formatWithCursor both formats the code, and translates a cursor position from unformatted code to formatted code. * This is useful for editor integrations, to prevent the cursor from moving when code is formatted * - * The cursorOffset option should be provided, to specify where the cursor is. This option cannot be used with rangeStart and rangeEnd. + * The cursorOffset option should be provided, to specify where the cursor is. * * ```js * await prettier.formatWithCursor(" 1", { cursorOffset: 2, parser: "babel" }); diff --git a/tests/dts/unit/cases/standalone.ts b/tests/dts/unit/cases/standalone.ts index 2a23e24d0..863900241 100644 --- a/tests/dts/unit/cases/standalone.ts +++ b/tests/dts/unit/cases/standalone.ts @@ -4,13 +4,11 @@ prettierStandalone.formatWithCursor(" 1", { cursorOffset: 2, parser: "babel" }); prettierStandalone.formatWithCursor(" 1", { cursorOffset: 2, parser: "babel", - // @ts-expect-error rangeStart: 2, }); prettierStandalone.formatWithCursor(" 1", { cursorOffset: 2, parser: "babel", - // @ts-expect-error rangeEnd: 2, }); prettierStandalone.format(" 1", { parser: "babel" }); diff --git a/tests/format/markdown/markdown/__snapshots__/jsfmt.spec.js.snap b/tests/format/markdown/markdown/__snapshots__/jsfmt.spec.js.snap index a273f370b..86e3747c1 100644 --- a/tests/format/markdown/markdown/__snapshots__/jsfmt.spec.js.snap +++ b/tests/format/markdown/markdown/__snapshots__/jsfmt.spec.js.snap @@ -465,7 +465,7 @@ This is similar to the \`--list-different\` parameter in the CLI and is useful f \`formatWithCursor\` both formats the code, and translates a cursor position from unformatted code to formatted code. This is useful for editor integrations, to prevent the cursor from moving when code is formatted. -The \`cursorOffset\` option should be provided, to specify where the cursor is. This option cannot be used with \`rangeStart\` and \`rangeEnd\`. +The \`cursorOffset\` option should be provided, to specify where the cursor is. \`\`\`js prettier.formatWithCursor(" 1", { cursorOffset: 2 }); @@ -646,8 +646,6 @@ These two options can be used to format code starting and ending at a given char * Backwards to the start of the first line containing the selected statement. * Forwards to the end of the selected statement. -These options cannot be used with \`cursorOffset\`. - Default | CLI Override | API Override --------|--------------|------------- \`0\` | \`--range-start \`| \`rangeStart: \` @@ -1454,7 +1452,6 @@ unformatted code to formatted code. This is useful for editor integrations, to prevent the cursor from moving when code is formatted. The \`cursorOffset\` option should be provided, to specify where the cursor is. -This option cannot be used with \`rangeStart\` and \`rangeEnd\`. \`\`\`js prettier.formatWithCursor(' 1', { cursorOffset: 2 }); @@ -1670,8 +1667,6 @@ character offset (inclusive and exclusive, respectively). The range will extend: - Backwards to the start of the first line containing the selected statement. - Forwards to the end of the selected statement. -These options cannot be used with \`cursorOffset\`. - | Default | CLI Override | API Override | | ---------- | --------------------- | ------------------- | | \`0\` | \`--range-start \` | \`rangeStart: \` | @@ -2442,7 +2437,7 @@ This is similar to the \`--list-different\` parameter in the CLI and is useful f \`formatWithCursor\` both formats the code, and translates a cursor position from unformatted code to formatted code. This is useful for editor integrations, to prevent the cursor from moving when code is formatted. -The \`cursorOffset\` option should be provided, to specify where the cursor is. This option cannot be used with \`rangeStart\` and \`rangeEnd\`. +The \`cursorOffset\` option should be provided, to specify where the cursor is. \`\`\`js prettier.formatWithCursor(" 1", { cursorOffset: 2 }); @@ -2623,8 +2618,6 @@ These two options can be used to format code starting and ending at a given char * Backwards to the start of the first line containing the selected statement. * Forwards to the end of the selected statement. -These options cannot be used with \`cursorOffset\`. - Default | CLI Override | API Override --------|--------------|------------- \`0\` | \`--range-start \`| \`rangeStart: \` @@ -3431,7 +3424,6 @@ unformatted code to formatted code. This is useful for editor integrations, to prevent the cursor from moving when code is formatted. The \`cursorOffset\` option should be provided, to specify where the cursor is. -This option cannot be used with \`rangeStart\` and \`rangeEnd\`. \`\`\`js prettier.formatWithCursor(" 1", { cursorOffset: 2 }); @@ -3647,8 +3639,6 @@ character offset (inclusive and exclusive, respectively). The range will extend: - Backwards to the start of the first line containing the selected statement. - Forwards to the end of the selected statement. -These options cannot be used with \`cursorOffset\`. - | Default | CLI Override | API Override | | ---------- | --------------------- | ------------------- | | \`0\` | \`--range-start \` | \`rangeStart: \` | diff --git a/tests/format/markdown/markdown/real-world-case.md b/tests/format/markdown/markdown/real-world-case.md index ff18ed16d..1f6ff7ab7 100644 --- a/tests/format/markdown/markdown/real-world-case.md +++ b/tests/format/markdown/markdown/real-world-case.md @@ -455,7 +455,7 @@ This is similar to the `--list-different` parameter in the CLI and is useful for `formatWithCursor` both formats the code, and translates a cursor position from unformatted code to formatted code. This is useful for editor integrations, to prevent the cursor from moving when code is formatted. -The `cursorOffset` option should be provided, to specify where the cursor is. This option cannot be used with `rangeStart` and `rangeEnd`. +The `cursorOffset` option should be provided, to specify where the cursor is. ```js prettier.formatWithCursor(" 1", { cursorOffset: 2 }); @@ -636,8 +636,6 @@ These two options can be used to format code starting and ending at a given char * Backwards to the start of the first line containing the selected statement. * Forwards to the end of the selected statement. -These options cannot be used with `cursorOffset`. - Default | CLI Override | API Override --------|--------------|------------- `0` | `--range-start `| `rangeStart: ` diff --git a/tests/integration/__tests__/__snapshots__/early-exit.js.snap b/tests/integration/__tests__/__snapshots__/early-exit.js.snap index c75519ac4..25bc0e206 100644 --- a/tests/integration/__tests__/__snapshots__/early-exit.js.snap +++ b/tests/integration/__tests__/__snapshots__/early-exit.js.snap @@ -107,15 +107,12 @@ Config options: Editor options: --cursor-offset Print (to stderr) where a cursor at the given position would move to after formatting. - This option cannot be used with --range-start and --range-end. Defaults to -1. --range-end Format code ending at a given character offset (exclusive). The range will extend forwards to the end of the selected statement. - This option cannot be used with --cursor-offset. Defaults to Infinity. --range-start Format code starting at a given character offset. The range will extend backwards to the start of the first line containing the selected statement. - This option cannot be used with --cursor-offset. Defaults to 0. Other options: @@ -274,15 +271,12 @@ Config options: Editor options: --cursor-offset Print (to stderr) where a cursor at the given position would move to after formatting. - This option cannot be used with --range-start and --range-end. Defaults to -1. --range-end Format code ending at a given character offset (exclusive). The range will extend forwards to the end of the selected statement. - This option cannot be used with --cursor-offset. Defaults to Infinity. --range-start Format code starting at a given character offset. The range will extend backwards to the start of the first line containing the selected statement. - This option cannot be used with --cursor-offset. Defaults to 0. Other options: diff --git a/tests/integration/__tests__/__snapshots__/help-options.js.snap b/tests/integration/__tests__/__snapshots__/help-options.js.snap index 960199f40..554a07d2c 100644 --- a/tests/integration/__tests__/__snapshots__/help-options.js.snap +++ b/tests/integration/__tests__/__snapshots__/help-options.js.snap @@ -136,7 +136,6 @@ exports[`show detailed usage with --help cursor-offset (stdout) 1`] = ` "--cursor-offset Print (to stderr) where a cursor at the given position would move to after formatting. - This option cannot be used with --range-start and --range-end. Default: -1" `; @@ -505,7 +504,6 @@ exports[`show detailed usage with --help range-end (stdout) 1`] = ` Format code ending at a given character offset (exclusive). The range will extend forwards to the end of the selected statement. - This option cannot be used with --cursor-offset. Default: Infinity" `; @@ -519,7 +517,6 @@ exports[`show detailed usage with --help range-start (stdout) 1`] = ` Format code starting at a given character offset. The range will extend backwards to the start of the first line containing the selected statement. - This option cannot be used with --cursor-offset. Default: 0" `; diff --git a/tests/integration/__tests__/__snapshots__/schema.js.snap b/tests/integration/__tests__/__snapshots__/schema.js.snap index 971261fa8..a9a3e05a7 100644 --- a/tests/integration/__tests__/__snapshots__/schema.js.snap +++ b/tests/integration/__tests__/__snapshots__/schema.js.snap @@ -36,8 +36,7 @@ exports[`schema 1`] = ` }, "cursorOffset": { "default": -1, - "description": "Print (to stderr) where a cursor at the given position would move to after formatting. -This option cannot be used with --range-start and --range-end.", + "description": "Print (to stderr) where a cursor at the given position would move to after formatting.", "type": "integer", }, "embeddedLanguageFormatting": { @@ -345,15 +344,13 @@ This option cannot be used with --range-start and --range-end.", "rangeEnd": { "default": Infinity, "description": "Format code ending at a given character offset (exclusive). -The range will extend forwards to the end of the selected statement. -This option cannot be used with --cursor-offset.", +The range will extend forwards to the end of the selected statement.", "type": "integer", }, "rangeStart": { "default": 0, "description": "Format code starting at a given character offset. -The range will extend backwards to the start of the first line containing the selected statement. -This option cannot be used with --cursor-offset.", +The range will extend backwards to the start of the first line containing the selected statement.", "type": "integer", }, "requirePragma": { diff --git a/tests/integration/__tests__/__snapshots__/support-info.js.snap b/tests/integration/__tests__/__snapshots__/support-info.js.snap index ee5d880c8..a74818f21 100644 --- a/tests/integration/__tests__/__snapshots__/support-info.js.snap +++ b/tests/integration/__tests__/__snapshots__/support-info.js.snap @@ -806,7 +806,7 @@ exports[`CLI --support-info (stdout) 1`] = ` { "category": "Special", "default": -1, - "description": "Print (to stderr) where a cursor at the given position would move to after formatting.\\nThis option cannot be used with --range-start and --range-end.", + "description": "Print (to stderr) where a cursor at the given position would move to after formatting.", "name": "cursorOffset", "pluginDefaults": {}, "range": { "end": null, "start": -1, "step": 1 }, @@ -1001,7 +1001,7 @@ exports[`CLI --support-info (stdout) 1`] = ` { "category": "Special", "default": null, - "description": "Format code ending at a given character offset (exclusive).\\nThe range will extend forwards to the end of the selected statement.\\nThis option cannot be used with --cursor-offset.", + "description": "Format code ending at a given character offset (exclusive).\\nThe range will extend forwards to the end of the selected statement.", "name": "rangeEnd", "pluginDefaults": {}, "range": { "end": null, "start": 0, "step": 1 }, @@ -1010,7 +1010,7 @@ exports[`CLI --support-info (stdout) 1`] = ` { "category": "Special", "default": 0, - "description": "Format code starting at a given character offset.\\nThe range will extend backwards to the start of the first line containing the selected statement.\\nThis option cannot be used with --cursor-offset.", + "description": "Format code starting at a given character offset.\\nThe range will extend backwards to the start of the first line containing the selected statement.", "name": "rangeStart", "pluginDefaults": {}, "range": { "end": null, "start": 0, "step": 1 }, diff --git a/website/versioned_docs/version-stable/api.md b/website/versioned_docs/version-stable/api.md index 139a9fa4d..25193d66a 100644 --- a/website/versioned_docs/version-stable/api.md +++ b/website/versioned_docs/version-stable/api.md @@ -27,7 +27,7 @@ await prettier.format("foo ( );", { semi: false, parser: "babel" }); `formatWithCursor` both formats the code, and translates a cursor position from unformatted code to formatted code. This is useful for editor integrations, to prevent the cursor from moving when code is formatted. -The `cursorOffset` option should be provided, to specify where the cursor is. This option cannot be used with `rangeStart` and `rangeEnd`. +The `cursorOffset` option should be provided, to specify where the cursor is. ```js await prettier.formatWithCursor(" 1", { cursorOffset: 2, parser: "babel" }); diff --git a/website/versioned_docs/version-stable/options.md b/website/versioned_docs/version-stable/options.md index b2ee7712b..2a4fb1577 100644 --- a/website/versioned_docs/version-stable/options.md +++ b/website/versioned_docs/version-stable/options.md @@ -255,8 +255,6 @@ These two options can be used to format code starting and ending at a given char - Backwards to the start of the first line containing the selected statement. - Forwards to the end of the selected statement. -These options cannot be used with `cursorOffset`. - | Default | CLI Override | API Override | | ---------- | --------------------- | ------------------- | | `0` | `--range-start ` | `rangeStart: ` | From 5f7aedc1cf0a1b3b4ec2c5a5ca0c09d1e0d00660 Mon Sep 17 00:00:00 2001 From: Klaus Hott Vidal Date: Fri, 8 Dec 2023 11:35:35 -0300 Subject: [PATCH 18/18] fix example to fit the actual experimentalTernaries behaviour (#15747) --- website/blog/2023-11-13-curious-ternaries.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/website/blog/2023-11-13-curious-ternaries.md b/website/blog/2023-11-13-curious-ternaries.md index def3a01d6..25ff8e16d 100644 --- a/website/blog/2023-11-13-curious-ternaries.md +++ b/website/blog/2023-11-13-curious-ternaries.md @@ -80,8 +80,7 @@ const animalName = pet.isScary() ? 'wolf' : 'dog' - : pet.canMeow() ? - 'cat' + : pet.canMeow() ? 'cat' : 'probably a bunny'; ```