Fix embedded Ruby in markdown corrupting formatting for all .rb files#1
Fix embedded Ruby in markdown corrupting formatting for all .rb files#1
Conversation
The plugin spawned a single Ruby server process whose syntax_tree plugins were determined by whichever file triggered the first parse() call. When a markdown file with a ```ruby fenced block was processed first, Prettier passed the markdown parser's resolved options (e.g. trailingComma: "all") instead of the ruby plugin's defaults (trailingComma: "none"). This caused the trailing_comma plugin to load into the server, adding trailing commas to every subsequent .rb file. Replace the singleton connectionOptions with a Map keyed by the effective plugin configuration. Each unique set of syntax_tree plugins gets its own server process, so options from one parser context cannot leak into another. Also fix a minor bug where reject() fell through to resolve() in the socket end handler.
…ceExit Remove the PRETTIER_RUBY_HOST env var bypass that let the test suite skip the real server lifecycle. The jest tests now spawn servers lazily through the plugin's own Map, with --forceExit so jest doesn't hang on open socket handles. Remove globalTeardown (server cleanup happens via process exit handlers). Add node:test integration tests that run prettier as a subprocess to verify .rb formatting is stable regardless of whether markdown files with embedded Ruby are processed in the same invocation.
There was a problem hiding this comment.
4 issues found across 5 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="package.json">
<violation number="1" location="package.json:11">
P1: This `node --test` glob is incompatible with the repo's current Node 20 CI, and the single-quoted pattern is not portable to the Windows job. Use a directory/path that Node 20 can execute directly instead of a quoted glob.</violation>
</file>
<file name="test/integration/server_isolation.test.mjs">
<violation number="1" location="test/integration/server_isolation.test.mjs:68">
P2: Don't swallow prettier subprocess failures here; these integration tests can pass even when the formatter crashes.</violation>
</file>
<file name="src/plugin.js">
<violation number="1" location="src/plugin.js:165">
P1: Include other spawn-time environment inputs in the server cache key; keying only by plugins can reuse a Ruby server from the wrong project or executable.</violation>
<violation number="2" location="src/plugin.js:175">
P2: Remove failed startups from the cache so later parses can retry spawning the server.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
| "lint": "eslint --cache .", | ||
| "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js" | ||
| "test:jest": "node --experimental-vm-modules node_modules/jest/bin/jest.js --forceExit", | ||
| "test:integration": "node --test 'test/integration/*.test.mjs'", |
There was a problem hiding this comment.
P1: This node --test glob is incompatible with the repo's current Node 20 CI, and the single-quoted pattern is not portable to the Windows job. Use a directory/path that Node 20 can execute directly instead of a quoted glob.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At package.json, line 11:
<comment>This `node --test` glob is incompatible with the repo's current Node 20 CI, and the single-quoted pattern is not portable to the Windows job. Use a directory/path that Node 20 can execute directly instead of a quoted glob.</comment>
<file context>
@@ -7,7 +7,9 @@
"lint": "eslint --cache .",
- "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js"
+ "test:jest": "node --experimental-vm-modules node_modules/jest/bin/jest.js --forceExit",
+ "test:integration": "node --test 'test/integration/*.test.mjs'",
+ "test": "npm run test:jest && npm run test:integration"
},
</file context>
| "test:integration": "node --test 'test/integration/*.test.mjs'", | |
| "test:integration": "node --test test/integration", |
| // (i.e. which syntax_tree plugins are loaded). Options like printWidth and | ||
| // tabWidth are sent per-request and do not need to be part of the key. | ||
| function serverKey(opts) { | ||
| return getPlugins(opts).sort().join(","); |
There was a problem hiding this comment.
P1: Include other spawn-time environment inputs in the server cache key; keying only by plugins can reuse a Ruby server from the wrong project or executable.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/plugin.js, line 165:
<comment>Include other spawn-time environment inputs in the server cache key; keying only by plugins can reuse a Ruby server from the wrong project or executable.</comment>
<file context>
@@ -143,18 +153,31 @@ export async function spawnServer(opts, killOnExit = true) {
+// (i.e. which syntax_tree plugins are loaded). Options like printWidth and
+// tabWidth are sent per-request and do not need to be part of the key.
+function serverKey(opts) {
+ return getPlugins(opts).sort().join(",");
}
</file context>
| return getPlugins(opts).sort().join(","); | |
| return JSON.stringify({ | |
| plugins: getPlugins(opts).sort(), | |
| rubyExecutablePath: opts.rubyExecutablePath || "ruby", | |
| filepath: opts.filepath || "" | |
| }); |
| return dir; | ||
| } | ||
|
|
||
| function runPrettierStatus(args, opts = {}) { |
There was a problem hiding this comment.
P2: Don't swallow prettier subprocess failures here; these integration tests can pass even when the formatter crashes.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At test/integration/server_isolation.test.mjs, line 68:
<comment>Don't swallow prettier subprocess failures here; these integration tests can pass even when the formatter crashes.</comment>
<file context>
@@ -0,0 +1,146 @@
+ return dir;
+}
+
+function runPrettierStatus(args, opts = {}) {
+ try {
+ const stdout = execFileSync(
</file context>
| if (!servers.has(key)) { | ||
| servers.set( | ||
| key, | ||
| spawnServer(opts).then((s) => s.connectionOptions) |
There was a problem hiding this comment.
P2: Remove failed startups from the cache so later parses can retry spawning the server.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/plugin.js, line 175:
<comment>Remove failed startups from the cache so later parses can retry spawning the server.</comment>
<file context>
@@ -143,18 +153,31 @@ export async function spawnServer(opts, killOnExit = true) {
+ if (!servers.has(key)) {
+ servers.set(
+ key,
+ spawnServer(opts).then((s) => s.connectionOptions)
+ );
}
</file context>
| spawnServer(opts).then((s) => s.connectionOptions) | |
| spawnServer(opts) | |
| .then((s) => s.connectionOptions) | |
| .catch((error) => { | |
| servers.delete(key); | |
| throw error; | |
| }) |
The plugin spawned a single Ruby server process whose syntax_tree plugins
were determined by whichever file triggered the first parse() call. When
a markdown file with a ```ruby fenced block was processed first, Prettier
passed the markdown parser's resolved options (e.g. trailingComma: "all")
instead of the ruby plugin's defaults (trailingComma: "none"). This caused
the trailing_comma plugin to load into the server, adding trailing commas
to every subsequent .rb file.
Replace the singleton connectionOptions with a Map keyed by the effective
plugin configuration. Each unique set of syntax_tree plugins gets its own
server process, so options from one parser context cannot leak into another.
Also fix a minor bug where reject() fell through to resolve() in the
socket end handler.
Summary by cubic
Fixes option leakage from embedded Ruby in markdown by isolating the Ruby parser server per plugin configuration. Embedded markdown can no longer cause trailing commas or other settings to affect
.rbfiles.Bug Fixes
rejectwithout falling through toresolve.Refactors
PRETTIER_RUBY_HOSTtest shortcut withnode:testintegration tests that runprettieras a subprocess to verify server isolation.package.jsonscripts: addtest:jestwith--forceExit, addtest:integration, and remove JestglobalTeardown.Written for commit 947fa4e. Summary will update on new commits.