Skip to content

Fix embedded Ruby in markdown corrupting formatting for all .rb files#1

Open
coreyja wants to merge 2 commits intomainfrom
fix/lazy-server-per-config
Open

Fix embedded Ruby in markdown corrupting formatting for all .rb files#1
coreyja wants to merge 2 commits intomainfrom
fix/lazy-server-per-config

Conversation

@coreyja
Copy link
Member

@coreyja coreyja commented Mar 16, 2026

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 .rb files.

  • Bug Fixes

    • Spawn a server per unique plugin set using a Map keyed by resolved plugin config to prevent cross-parser option leakage.
    • Generate unique temp connection files and handle missing config files; ensure errors reject without falling through to resolve.
  • Refactors

    • Replace the PRETTIER_RUBY_HOST test shortcut with node:test integration tests that run prettier as a subprocess to verify server isolation.
    • Update package.json scripts: add test:jest with --forceExit, add test:integration, and remove Jest globalTeardown.

Written for commit 947fa4e. Summary will update on new commits.

coreyja added 2 commits March 16, 2026 15:11
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.
@coreyja coreyja marked this pull request as ready for review March 16, 2026 20:47
Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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'",
Copy link

@cubic-dev-ai cubic-dev-ai bot Mar 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
Suggested change
"test:integration": "node --test 'test/integration/*.test.mjs'",
"test:integration": "node --test test/integration",
Fix with Cubic

// (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(",");
Copy link

@cubic-dev-ai cubic-dev-ai bot Mar 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
Suggested change
return getPlugins(opts).sort().join(",");
return JSON.stringify({
plugins: getPlugins(opts).sort(),
rubyExecutablePath: opts.rubyExecutablePath || "ruby",
filepath: opts.filepath || ""
});
Fix with Cubic

return dir;
}

function runPrettierStatus(args, opts = {}) {
Copy link

@cubic-dev-ai cubic-dev-ai bot Mar 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
Fix with Cubic

if (!servers.has(key)) {
servers.set(
key,
spawnServer(opts).then((s) => s.connectionOptions)
Copy link

@cubic-dev-ai cubic-dev-ai bot Mar 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
Suggested change
spawnServer(opts).then((s) => s.connectionOptions)
spawnServer(opts)
.then((s) => s.connectionOptions)
.catch((error) => {
servers.delete(key);
throw error;
})
Fix with Cubic

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant