diff --git a/CHANGELOG.md b/CHANGELOG.md index 61b481f..38dbec3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add support for OXC + Hermes Prettier plugins ([#376](https://github.com/tailwindlabs/prettier-plugin-tailwindcss/pull/376), [#380](https://github.com/tailwindlabs/prettier-plugin-tailwindcss/pull/380)) - Sort template literals in Angular expressions ([#377](https://github.com/tailwindlabs/prettier-plugin-tailwindcss/pull/377)) +- Don't repeatedly add backslashes to escape sequences when formatting ([#381](https://github.com/tailwindlabs/prettier-plugin-tailwindcss/pull/381)) ## [0.6.13] - 2025-06-19 diff --git a/src/index.ts b/src/index.ts index 5cf1519..3be204b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -28,6 +28,8 @@ import { spliceChangesIntoString, visit } from './utils.js' let base = await loadPlugins() +const ESCAPE_SEQUENCE_PATTERN = /\\(['"\\nrtbfv0-7xuU])/g + function createParser( parserFormat: string, transform: (ast: any, context: TransformerContext) => void, @@ -505,41 +507,41 @@ function sortStringLiteral( removeDuplicates, collapseWhitespace, }) + let didChange = result !== node.value - node.value = result - // A string literal was escaped if: - // - There are backslashes in the raw value; AND - // - The raw value is not the same as the value (excluding the surrounding quotes) - let wasEscaped = false + if (!didChange) return false - if (node.extra) { - // JavaScript (StringLiteral) - wasEscaped = - node.extra?.rawValue.includes('\\') && - node.extra?.raw.slice(1, -1) !== node.value - } else { - // TypeScript (Literal) - wasEscaped = - node.value.includes('\\') && node.raw.slice(1, -1) !== node.value - } + node.value = result - let escaped = wasEscaped ? result.replace(/\\/g, '\\\\') : result + // Preserve the original escaping level for the new content + let raw = node.extra?.raw ?? node.raw + let quote = raw[0] + let originalRawContent = raw.slice(1, -1) + let originalValue = node.extra?.rawValue ?? node.value if (node.extra) { + // The original list has ecapes so we ensure that the sorted list also + // maintains those by replacing backslashes from escape sequences. + // + // It seems that TypeScript-based ASTs don't need this special handling + // which is why this is guarded inside the `node.extra` check + if (originalRawContent !== originalValue && originalValue.includes('\\')) { + result = result.replace(ESCAPE_SEQUENCE_PATTERN, '\\\\$1') + } + // JavaScript (StringLiteral) - let raw = node.extra.raw node.extra = { ...node.extra, rawValue: result, - raw: raw[0] + escaped + raw.slice(-1), + raw: quote + result + quote, } } else { // TypeScript (Literal) - let raw = node.raw - node.raw = raw[0] + escaped + raw.slice(-1) + node.raw = quote + result + quote } - return didChange + + return true } function isStringLiteral(node: any) { diff --git a/tests/tests.ts b/tests/tests.ts index ad7a60b..ab11ca9 100644 --- a/tests/tests.ts +++ b/tests/tests.ts @@ -111,6 +111,10 @@ export let javascript: TestEntry[] = [ `;
`, `;
`, ], + [ + `;
`, + `;
`, + ], ] javascript = javascript.concat( javascript.map((test) => [