diff --git a/.gitignore b/.gitignore index bd07d4e..4bed9a7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ node_modules /dist +plugin-dist diff --git a/CHANGELOG.md b/CHANGELOG.md index 62920a2..442ecd0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -- Nothing yet! +### Fixed + +- Fix "Missing visitor keys for 'undefined'" error when multiple copies of Prettier exist in a dependency tree ([#469](https://github.com/tailwindlabs/prettier-plugin-tailwindcss/issues/469)) ## [0.8.1] - 2026-07-15 diff --git a/src/index.ts b/src/index.ts index 57beef6..c769f48 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1065,6 +1065,15 @@ let html = defineTransform({ vue: { dynamicAttrs: [':class', 'v-bind:class'] }, }, + // Use the printer from the same `prettier/plugins/html` module the parsers + // come from. When multiple copies of Prettier exist in a dependency tree the + // AST produced by our copy may not match what the running Prettier's builtin + // printer expects (e.g. Prettier v3.7 renamed the HTML AST `type` field to + // `kind`) causing a "Missing visitor keys for 'undefined'" error. + printers: { + html: {}, + }, + transform: transformHtml, }) diff --git a/tests/fixtures.test.ts b/tests/fixtures.test.ts index 5c8c9a6..401abdf 100644 --- a/tests/fixtures.test.ts +++ b/tests/fixtures.test.ts @@ -160,4 +160,24 @@ describe('fixtures', () => { expect(formatted.trim()).toEqual(expected.trim()) }) } + + // https://github.com/tailwindlabs/prettier-plugin-tailwindcss/issues/469 + test.concurrent('multiple copies of Prettier in a dependency tree', async ({ expect }) => { + let fixturePath = path.resolve(__dirname, 'fixtures/prettier-mismatch') + let pluginCopyPath = path.resolve(fixturePath, 'plugin-dist') + + // Copy the built plugin into the fixture so it resolves the fixture's + // copy of Prettier while the CLI runs the repo's copy + await fs.cp(path.resolve(__dirname, '../dist'), pluginCopyPath, { recursive: true }) + + let inputPath = path.resolve(fixturePath, 'index.html') + let outputPath = path.resolve(fixturePath, 'output.html') + let cmd = `${binPath} ${inputPath} --plugin ${pluginCopyPath}/index.mjs` + + let results = await execAsync(cmd) + let formatted = results.stdout.replace(/\r\n/g, '\n') + let expected = await fs.readFile(outputPath, 'utf-8').then((c) => c.replace(/\r\n/g, '\n')) + + expect(formatted.trim()).toEqual(expected.trim()) + }) }) diff --git a/tests/fixtures/prettier-mismatch/index.html b/tests/fixtures/prettier-mismatch/index.html new file mode 100644 index 0000000..7593a7e --- /dev/null +++ b/tests/fixtures/prettier-mismatch/index.html @@ -0,0 +1 @@ +
diff --git a/tests/fixtures/prettier-mismatch/output.html b/tests/fixtures/prettier-mismatch/output.html new file mode 100644 index 0000000..04a62c8 --- /dev/null +++ b/tests/fixtures/prettier-mismatch/output.html @@ -0,0 +1 @@ +
diff --git a/tests/fixtures/prettier-mismatch/package-lock.json b/tests/fixtures/prettier-mismatch/package-lock.json new file mode 100644 index 0000000..8528cb8 --- /dev/null +++ b/tests/fixtures/prettier-mismatch/package-lock.json @@ -0,0 +1,27 @@ +{ + "name": "prettier-mismatch", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "prettier": "3.6.2" + } + }, + "node_modules/prettier": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", + "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + } + } +} diff --git a/tests/fixtures/prettier-mismatch/package.json b/tests/fixtures/prettier-mismatch/package.json new file mode 100644 index 0000000..76e5035 --- /dev/null +++ b/tests/fixtures/prettier-mismatch/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "prettier": "3.6.2" + } +}