diff --git a/packages/cli/src/linter/tailwind/v4/handler.test.ts b/packages/cli/src/linter/tailwind/v4/handler.test.ts index dbea34a..9ce025c 100644 --- a/packages/cli/src/linter/tailwind/v4/handler.test.ts +++ b/packages/cli/src/linter/tailwind/v4/handler.test.ts @@ -99,6 +99,20 @@ describe('TailwindV4EmitterHandler', () => { if (!result.success) throw new Error('Expected success'); expect(result.data.theme.fontFamily?.['fancy']).toBe('"Fancy \\"Font\\""'); }); + + it('escapes line terminators in font-family values', () => { + const state = buildState({ + typography: { + multi: { fontFamily: 'Evil\nFamily', fontSize: '16px' }, + }, + }); + const result = emitter.execute(state); + if (!result.success) throw new Error('Expected success'); + const value = result.data.theme.fontFamily?.['multi']; + // A raw newline must not survive into the emitted CSS string. + expect(value).not.toContain('\n'); + expect(value).toBe('"Evil\\a Family"'); + }); }); describe('dimensions mapping', () => { diff --git a/packages/cli/src/linter/tailwind/v4/handler.ts b/packages/cli/src/linter/tailwind/v4/handler.ts index 8d3eb68..0f2ef47 100644 --- a/packages/cli/src/linter/tailwind/v4/handler.ts +++ b/packages/cli/src/linter/tailwind/v4/handler.ts @@ -100,7 +100,13 @@ function dimToString(dim: ResolvedDimension): string { /** * Wrap a string value in double quotes, escaping embedded `\` and `"`. * Produces a CSS-safe string literal suitable for font-family values. + * Line terminators (newline, carriage return, form feed) are illegal raw inside + * a CSS string and could break the value out of the quoted token, so they are + * emitted as CSS hex escapes (e.g. `\a `). */ function cssStringLiteral(value: string): string { - return `"${value.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`; + return `"${value + .replace(/\\/g, '\\\\') + .replace(/"/g, '\\"') + .replace(/[\n\r\f]/g, (c) => `\\${c.charCodeAt(0).toString(16)} `)}"`; }