Skip to content

Fix class sorting in embedded code blocks when cwd differs from project dir#452

Open
benface wants to merge 1 commit intotailwindlabs:mainfrom
benface:fix/embedded-codeblock-config-resolution
Open

Fix class sorting in embedded code blocks when cwd differs from project dir#452
benface wants to merge 1 commit intotailwindlabs:mainfrom
benface:fix/embedded-codeblock-config-resolution

Conversation

@benface
Copy link
Copy Markdown

@benface benface commented Mar 24, 2026

Problem

Tailwind class sorting silently fails in embedded code blocks (e.g. ```tsx inside markdown) when process.cwd() does not point to the project directory. This commonly happens in the VS Code Prettier extension, where the extension host process has a different working directory than the project.

Symptoms

  • Classes in ```tsx / ```jsx code blocks inside markdown are not sorted when formatting via VS Code
  • Classes in ```html code blocks are sorted (because Prettier does not set a synthetic filepath for html embeds)
  • Both work correctly from the CLI (npx prettier --write)

Root cause

When Prettier formats an embedded tsx code block inside markdown, it calls textToDoc with { parser: "typescript", filepath: "dummy.tsx" }. This synthetic relative filepath causes resolvePrettierConfigDir to:

  1. Compute inputDir = path.dirname("dummy.tsx")"."
  2. Call prettier.resolveConfigFile("dummy.tsx") which resolves relative to process.cwd()
  3. Fail to find the config (cwd is wrong) → fall back to process.cwd()
  4. Resolve tailwindStylesheet (a relative path like "packages/react/styles/tailwind.css") against the wrong base directory
  5. Silently fail to load the design system → no class sorting

For html code blocks, Prettier does not set a synthetic filepath, so options.filepath is inherited from the parent markdown file — which is an absolute path that resolves correctly.

Reproduction

// Run from a directory that is NOT the project root (e.g. /)
cd /
node --input-type=module -e "
import prettier from \"/path/to/project/node_modules/prettier/index.cjs\";

const md = \"\\\`\\\`\\\`tsx\\n<div className=\\\"bg-black w-2\\\">hello</div>\\n\\\`\\\`\\\`\\n\";

const result = await prettier.format(md, {
  filepath: \"/path/to/project/src/example.md\",
  parser: \"markdown\",
  plugins: [\"prettier-plugin-tailwindcss\"],
  tailwindStylesheet: \"src/styles.css\",  // relative path
});

// Expected: w-2 bg-black (sorted)
// Actual:   bg-black w-2 (unsorted)
console.log(result);
"

Solution

Store the last successfully resolved config directory in a module-level variable and use it as a fallback when config resolution fails for embedded code blocks, instead of falling back to process.cwd().

This is safe because within a single prettier.format() call, the parent file (e.g. the markdown file) is always parsed first — resolving the config correctly — before any embedded code blocks are formatted. The stored config directory is then available for the embedded parsers.

Changes

3-line change in src/config.ts:

  1. Add lastResolvedConfigDir variable
  2. Store it when config is successfully resolved
  3. Use lastResolvedConfigDir ?? process.cwd() instead of process.cwd() in both fallback paths

This preserves the existing behavior when cwd is correct, and fixes the embedded code block case when it is not.

…ct dir

When Prettier formats embedded code blocks (e.g. tsx inside markdown),
it sets a synthetic filepath like 'dummy.tsx'. The config resolution
falls back to process.cwd() which may not be the project directory
(e.g. in the VS Code extension host), causing the tailwindStylesheet
to resolve to the wrong path and class sorting to silently fail.

This stores the last successfully resolved config directory and uses
it as a fallback instead of process.cwd().
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