From 626c09be2af6cb1786998fc5a20cd3ebc110bc0c Mon Sep 17 00:00:00 2001 From: Julian Scheid Date: Mon, 27 May 2024 21:18:40 +0200 Subject: [PATCH 1/8] Avoid extra local variables in bootstrap Wrap the majority of the bootstrap code in a function to scope variables and prevent them from polluting the global namespace, where they might conflict with variables used by the core JS code. --- bootstrap.js | 61 +++++++++++++++++++++++++++++----------------------- 1 file changed, 34 insertions(+), 27 deletions(-) diff --git a/bootstrap.js b/bootstrap.js index 184e89b..081584e 100644 --- a/bootstrap.js +++ b/bootstrap.js @@ -17,37 +17,44 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -const nodeVersion = process["version"]; -if (Number(nodeVersion["split"](/[v.]/)[1]) < 6) { - process.exit(3); -} - -const externalRequire = require; -const gunzipSync = externalRequire("zlib")["gunzipSync"]; -const vm = externalRequire("vm"); -const fs = externalRequire("fs"); +function loadScript() { + const nodeVersion = process["version"]; + if (Number(nodeVersion["split"](/[v.]/)[1]) < 6) { + process.exit(3); + } -function readFully(fd, size) { - const buf = Buffer["alloc"](size); - let offset = 0; - while (offset < size) { - const numRead = fs["readSync"](fd, buf, offset, size - offset, null); - if (numRead <= 0) { - throw new Error("EOF"); + const externalRequire = require; + + const gunzipSync = externalRequire("zlib")["gunzipSync"]; + const vm = externalRequire("vm"); + const fs = externalRequire("fs"); + + function readFully(fd, size) { + const buf = Buffer["alloc"](size); + let offset = 0; + while (offset < size) { + const numRead = fs["readSync"](fd, buf, offset, size - offset, null); + if (numRead <= 0) { + throw new Error("EOF"); + } + offset += numRead; } - offset += numRead; + return buf; } - return buf; -} -const script = new vm["Script"]( - gunzipSync( - Buffer.from( - readFully(0, Number(process["argv"][1])).toString("ascii"), - "base64" - ) - ).toString("utf-8") -); + const script = new vm["Script"]( + gunzipSync( + Buffer.from( + readFully(0, Number(process["argv"][1])).toString("ascii"), + "base64" + ) + ).toString("utf-8") + ); + + return script; +} +const script = loadScript(); +global.env = process.env; script["runInThisContext"]()(script); From c03ea158ccc966e6b81cfb91f896ec26716d48c2 Mon Sep 17 00:00:00 2001 From: Julian Scheid Date: Mon, 27 May 2024 21:21:46 +0200 Subject: [PATCH 2/8] Support for Prettier 3 plugins - Translate plugin module names to absolute paths relative to the Prettier installation found. - Pass process environment through to main JS code. --- prettier-el.js | 82 ++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 62 insertions(+), 20 deletions(-) diff --git a/prettier-el.js b/prettier-el.js index 3bdf351..2e70f77 100644 --- a/prettier-el.js +++ b/prettier-el.js @@ -21,6 +21,8 @@ function noop() {} global["console"]["warn"] = noop; +process.env = global.env; + // Hack to circumvent Closure Compiler CommonJS resolution const externalRequire = require; @@ -48,7 +50,15 @@ const ignoreParser = "ignored"; const syncBeacon = Buffer.from("#prettier.el-sync#\n"); -/** @type{PrettierAPI} */ +/** + * @typedef {{ + * api: !(PrettierAPI|V3CompatAPI), + * require: !function(string) + * }} + */ +var PrettierAPIAndRequire; + +/** @type{PrettierAPIAndRequire} */ let globalPrettier; /** @@ -123,7 +133,7 @@ function makeU32(val) { * Find a globally installed Prettier or error if not found. Memoize results for * future lookups. * - * @return {!PrettierAPI | !Error} + * @return {PrettierAPIAndRequire | !Error} */ function getGlobalPrettier() { if (globalPrettier) { @@ -164,7 +174,7 @@ function getGlobalPrettier() { for (let i = 0; i < pathOptions.length; ++i) { if (pathOptions[i]) { try { - return globalRequire(pathOptions[i]); + return { api: globalRequire(pathOptions[i]), require: globalRequire }; } catch (e) { if ( !(e instanceof Error) || @@ -225,7 +235,7 @@ function findFileInAncestry(directory, fileNames) { /** * Try requiring the Prettier package using the given require function. * - * @param {!Function} targetRequire The require function to use. + * @param {!function(string)} targetRequire The require function to use. * @return {PrettierAPI} The Prettier package if found, or null if not found. */ function tryRequirePrettier(targetRequire) { @@ -252,7 +262,7 @@ function tryRequirePrettier(targetRequire) { * Find locally installed Prettier, or null if not found. * * @param {!string} directory The directory for which to find a local Prettier installation. - * @return {PrettierAPI} The Prettier package if found, or null if not found. + * @return {PrettierAPIAndRequire|null} The Prettier package if found, or null if not found. */ function getLocalPrettier(directory) { const targetRequire = createRequire(path["join"](directory, "package.json")); @@ -260,7 +270,7 @@ function getLocalPrettier(directory) { // Try loading prettier for non-PnP packages and return it if found. const prettier = tryRequirePrettier(targetRequire); if (prettier) { - return prettier; + return { api: prettier, require: targetRequire }; } // Try finding .pnp.[c]js and bail out if we can't find it. @@ -271,7 +281,12 @@ function getLocalPrettier(directory) { // Setup PnP API and retry loading prettier. targetRequire(pnpJs)["setup"](); - return tryRequirePrettier(targetRequire); + const prettierFromPnp = tryRequirePrettier(targetRequire); + if (!prettierFromPnp) { + return null; + } + + return { api: prettierFromPnp, require: targetRequire }; } /** @@ -282,7 +297,7 @@ function getLocalPrettier(directory) { * @param {!string} directory The directory for which to find the Prettier * package. * - * @return {!PrettierAPI | !Error} + * @return {PrettierAPIAndRequire | !Error} */ function getPrettierForDirectory(directory) { if (prettierCache.has(directory)) { @@ -318,7 +333,7 @@ function getPrettierForDirectory(directory) { * * @param {!string} filepath * - * @return {!PrettierAPI | !V3CompatAPI} The Prettier package found. + * @return {PrettierAPIAndRequire} The Prettier package found. */ function getPrettierForPath(filepath) { const result = path["isAbsolute"](filepath) @@ -328,8 +343,9 @@ function getPrettierForPath(filepath) { throw result; } - if (isV3Later(result)) return result; - return new V3CompatAPI(result); + const { api } = result; + if (isV3Later(api)) return result; + return { api: new V3CompatAPI(api), require: result.require }; } function parseParsers(parsersString) { @@ -359,13 +375,21 @@ function compatParsers(parsers) { ); } -async function bestParser(prettier, parsers, options, filepath, inferParser) { +async function bestParser( + prettier, + parsers, + options, + filepath, + inferParser, + req +) { let fileInfo = null; if (filepath) { fileInfo = await prettier.getFileInfo(filepath, { ["ignorePath"]: findFileInAncestry(path["dirname"](filepath), [ ".prettierignore", ]), + ["plugins"]: options["plugins"], }); } @@ -433,7 +457,7 @@ global["m"] = function m(baseScript, cacheFilename, inp) { const editorconfig = packet[1] === "E".charCodeAt(0); const filepath = packet.toString("utf-8", 2, newlineIndex1); - const prettier = getPrettierForPath(filepath); + const { api: prettier } = getPrettierForPath(filepath); if (filepath.length > 0) { await prettier.resolveConfig(filepath, { editorconfig, @@ -482,6 +506,20 @@ global["m"] = function m(baseScript, cacheFilename, inp) { return result; } + /** + * Make plugin module paths absolute by resolving with the given + * require function, + * + * @param options the Prettier options, will be modified in place. + * @param {function(string)} req the require function to use for resolving modules. + */ + function postProcessOptions(options, req) { + const plugins = options["plugins"]; + if (plugins) { + options["plugins"] = plugins.map((plugin) => req["resolve"](plugin)); + } + } + /** * Handle a request for formatting a file. * @@ -495,7 +533,7 @@ global["m"] = function m(baseScript, cacheFilename, inp) { const inferParser = config["infer-parser"]; try { - const prettier = getPrettierForPath(filepath); + const { api: prettier, require: req } = getPrettierForPath(filepath); const timeBeforeFormat = Date.now(); @@ -507,13 +545,15 @@ global["m"] = function m(baseScript, cacheFilename, inp) { }) .then((x) => x || {}); } + postProcessOptions(options, req); const parser = await bestParser( prettier, compatParsers(config["parsers"]), options, filepath, - inferParser + inferParser, + req ); const out = [syncBeacon]; @@ -632,11 +672,12 @@ global["m"] = function m(baseScript, cacheFilename, inp) { ); const parsers = parseParsers(parsersString); - const prettier = getPrettierForPath(filepath); + const { api: prettier, require: req } = getPrettierForPath(filepath); const options = await prettier .resolveConfig(filepath, { editorconfig }) .then((x) => x || {}); + postProcessOptions(options, req); let optionsFromParser; if (isV3Later(prettier)) { @@ -672,7 +713,8 @@ global["m"] = function m(baseScript, cacheFilename, inp) { parsers, optionsFromParser, filepath, - inferParser + inferParser, + req ); const optionsBuf = createBase64Buffer( JSON.stringify({ @@ -763,7 +805,7 @@ global["m"] = function m(baseScript, cacheFilename, inp) { * * @param {!string} filename Filename to be used to construct the require function. * - * @return {!Function} the require function. + * @return {!function(string)} the require function. */ function createRequire(filename) { // Added in Node v12.2.0 @@ -785,7 +827,7 @@ function createRequire(filename) { * * @param {!string} filename Filename to be used to construct the require function. * - * @return {!Function} the require function. + * @return {!function(string)} the require function. */ function _createRequire(filename) { const mod = new nativeModule["Module"](filename, null); @@ -800,7 +842,7 @@ function _createRequire(filename) { // The following is for Prettier version 2 and below. class V3CompatAPI { - /** @param {!PrettierAPI} prettier */ + /** @param {!(PrettierAPI|V3CompatAPI)} prettier */ constructor(prettier) { this.prettier = prettier; } From 71939bee94fe2de04f1d01200e6c3227493f94e5 Mon Sep 17 00:00:00 2001 From: Julian Scheid Date: Mon, 27 May 2024 21:34:11 +0200 Subject: [PATCH 3/8] Explicit trailingComma configuration This is for Prettier 3 compat, as they've changed the default apparently. --- package.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/package.json b/package.json index b6fdd30..d0d6afd 100644 --- a/package.json +++ b/package.json @@ -12,5 +12,8 @@ "dependencies": { "google-closure-compiler": "20220405.0.0", "node-zopfli-es": "1.0.7" + }, + "prettier": { + "trailingComma": "es5" } } From d7b18360d7967ffb4885a7c3eee3d77d9efca720 Mon Sep 17 00:00:00 2001 From: Julian Scheid Date: Mon, 27 May 2024 22:03:13 +0200 Subject: [PATCH 4/8] Drop packageManager setting from package.json --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index d0d6afd..0aefa1a 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,6 @@ "version": "0.0.0", "private": true, "license": "GPLv3+", - "packageManager": "yarn@1", "devDependencies": { "closurecompiler-externs": "1.0.4", "diff-match-patch": "1.0.5" From 75fffc9d3b09baaba3d98351e589250abd8594d8 Mon Sep 17 00:00:00 2001 From: Julian Scheid Date: Mon, 27 May 2024 22:14:38 +0200 Subject: [PATCH 5/8] Drop packageManager setting from test packages --- test-stable/package.json | 1 - test-v2/package.json | 1 - 2 files changed, 2 deletions(-) diff --git a/test-stable/package.json b/test-stable/package.json index d48c7a1..4954cbb 100644 --- a/test-stable/package.json +++ b/test-stable/package.json @@ -7,7 +7,6 @@ "workspaces": [ "*" ], - "packageManager": "yarn@1", "devDependencies": { "prettier": "3.0.0" } diff --git a/test-v2/package.json b/test-v2/package.json index dab62f9..e49a4f8 100644 --- a/test-v2/package.json +++ b/test-v2/package.json @@ -7,7 +7,6 @@ "workspaces": [ "*" ], - "packageManager": "yarn@1", "devDependencies": { "prettier": "2.6.2" } From 173edcb276d2c7795289780be686cba4164c279b Mon Sep 17 00:00:00 2001 From: Julian Scheid Date: Mon, 27 May 2024 22:37:11 +0200 Subject: [PATCH 6/8] Hack around top-level Prettier config leak --- test-v2/ruby-mode/package.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test-v2/ruby-mode/package.json b/test-v2/ruby-mode/package.json index b30c0a8..67bdf54 100644 --- a/test-v2/ruby-mode/package.json +++ b/test-v2/ruby-mode/package.json @@ -4,5 +4,8 @@ "private": true, "devDependencies": { "@prettier/plugin-ruby": "2.1.0" + }, + "prettier": { + "trailingComma": "none" } } From 4b429dab0e04b7f10424b2b3f006f740f39756ac Mon Sep 17 00:00:00 2001 From: Julian Scheid Date: Sat, 1 Jun 2024 14:37:43 +0200 Subject: [PATCH 7/8] Address review feedback --- prettier-el.js | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/prettier-el.js b/prettier-el.js index 2e70f77..18092d6 100644 --- a/prettier-el.js +++ b/prettier-el.js @@ -375,14 +375,7 @@ function compatParsers(parsers) { ); } -async function bestParser( - prettier, - parsers, - options, - filepath, - inferParser, - req -) { +async function bestParser(prettier, parsers, options, filepath, inferParser) { let fileInfo = null; if (filepath) { fileInfo = await prettier.getFileInfo(filepath, { @@ -552,8 +545,7 @@ global["m"] = function m(baseScript, cacheFilename, inp) { compatParsers(config["parsers"]), options, filepath, - inferParser, - req + inferParser ); const out = [syncBeacon]; @@ -713,8 +705,7 @@ global["m"] = function m(baseScript, cacheFilename, inp) { parsers, optionsFromParser, filepath, - inferParser, - req + inferParser ); const optionsBuf = createBase64Buffer( JSON.stringify({ @@ -867,7 +858,7 @@ class V3CompatAPI { } /** - * @param {!PrettierAPI | V3CompatAPI} prettier + * @param {!(PrettierAPI|V3CompatAPI)} prettier * @return {!boolean} */ function isV3Later(prettier) { From dac98f4591186c5e591b80bf283f3d01eadb6579 Mon Sep 17 00:00:00 2001 From: Julian Scheid Date: Sat, 1 Jun 2024 14:57:25 +0200 Subject: [PATCH 8/8] Update Changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c93aec..e54ab59 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to - `prettier-diff-timeout-seconds` customization option - `prettier-diff-edit-cost` customization option +- Support for Prettier 3 ## Changed