diff --git a/packages/babel-plugin-react-pug/test/unit/transform.test.ts b/packages/babel-plugin-react-pug/test/unit/transform.test.ts index 190649a..8e5eb65 100644 --- a/packages/babel-plugin-react-pug/test/unit/transform.test.ts +++ b/packages/babel-plugin-react-pug/test/unit/transform.test.ts @@ -607,6 +607,19 @@ describe('babel-plugin-react-pug transform', () => { `); }); + it('preserves dashed computed keys in classnames-style styleName objects', () => { + const out = transform([ + 'import { pug } from "startupjs";', + "const view = pug`Div.button(styleName={ ['non-responsive']: disabled })`;", + ].join('\n')); + expect(out).toMatchInlineSnapshot(` + "import "startupjs"; + const view =
;" + `); + }); + it('allows forcing class shorthand property and merge strategy', () => { const transformed = transformReactPugSourceForBabel( 'const view = pug`span.title(class=isActive)`;', diff --git a/packages/esbuild-plugin-react-pug/test/unit/plugin.test.ts b/packages/esbuild-plugin-react-pug/test/unit/plugin.test.ts index 91823a0..d45bab0 100644 --- a/packages/esbuild-plugin-react-pug/test/unit/plugin.test.ts +++ b/packages/esbuild-plugin-react-pug/test/unit/plugin.test.ts @@ -45,6 +45,17 @@ describe('esbuild-plugin-react-pug', () => { `); }); + it('preserves dashed computed keys in classnames-style styleName objects', () => { + const transformed = transformReactPugSourceForEsbuild([ + 'import { pug } from "startupjs";', + "const view = pug`Div.button(styleName={ ['non-responsive']: disabled })`;", + ].join('\n'), 'fixture.tsx'); + expect(transformed.code).toMatchInlineSnapshot(` + "import "startupjs"; + const view = (
);" + `); + }); + it('allows forcing class shorthand property and merge strategy', () => { const transformed = transformReactPugSourceForEsbuild( 'const view = pug`span.title(class=isActive)`;', diff --git a/packages/eslint-plugin-react-pug/src/index.ts b/packages/eslint-plugin-react-pug/src/index.ts index 48679d2..794ff0f 100644 --- a/packages/eslint-plugin-react-pug/src/index.ts +++ b/packages/eslint-plugin-react-pug/src/index.ts @@ -929,6 +929,19 @@ function shouldSuppressOriginalRangeMessage( return false; } +function isRangeFullyWithinEmbeddedLintSite( + cached: CachedLintState, + start: number, + end: number, +): boolean { + if (!cached.transformed) return false; + + return cached.transformed.embeddedJsLintSites.some((site) => ( + start >= site.originalStart + && end <= site.originalEnd + )); +} + function shouldSuppressGeneratedRangeMessage( cached: CachedLintState, message: EslintLintMessage, @@ -1009,6 +1022,13 @@ function mapLintMessage( if (shouldSuppressOriginalRangeMessage(cached, message, mapped.start, mapped.end)) { return null; } + if ( + typeof message.ruleId === 'string' + && message.ruleId.startsWith('@stylistic/') + && isRangeFullyWithinEmbeddedLintSite(cached, mapped.start, mapped.end) + ) { + return null; + } const startLc = offsetToLineColumn(cached.originalText, mapped.start); const endLc = offsetToLineColumn(cached.originalText, mapped.end); diff --git a/packages/eslint-plugin-react-pug/test/integration/diagnostics.test.ts b/packages/eslint-plugin-react-pug/test/integration/diagnostics.test.ts index 763ef71..dc3d80a 100644 --- a/packages/eslint-plugin-react-pug/test/integration/diagnostics.test.ts +++ b/packages/eslint-plugin-react-pug/test/integration/diagnostics.test.ts @@ -356,6 +356,31 @@ describe('eslint processor diagnostic mapping', () => { ])) }) + it('does not report transformed-surface indent noise for single-line call expressions with object literal args', async () => { + const filePath = resolve(repoRoot, 'embedded-children-call-indent.ts') + const input = [ + "import { pug } from 'startupjs'", + 'const loading = false', + 'const connectInfo = { clientId: true }', + 'const startFlow = () => {}', + 'const children = () => null', + '', + 'export default pug`', + ' Div', + " if typeof children === 'function'", + ' = children({ isLoading: loading || !connectInfo.clientId, onConnect: startFlow })', + ' else', + ' = children', + '`', + '', + ].join('\n') + + const eslint = createProcessorEslint() + const [result] = await eslint.lintText(input, { filePath }) + + expect(result.messages.filter(message => message.ruleId === '@stylistic/indent')).toEqual([]) + }) + it('maps exact no-undef ranges across the embedded JS site matrix, including unbuffered statement lines', async () => { const filePath = resolve(repoRoot, 'embedded-site-matrix.js') const input = [ diff --git a/packages/eslint-plugin-react-pug/test/unit/processor.test.ts b/packages/eslint-plugin-react-pug/test/unit/processor.test.ts index f9ad62f..3307012 100644 --- a/packages/eslint-plugin-react-pug/test/unit/processor.test.ts +++ b/packages/eslint-plugin-react-pug/test/unit/processor.test.ts @@ -300,6 +300,29 @@ describe('eslint-plugin-react-pug processor', () => { `); }); + it('preserves dashed computed keys in classnames-style styleName objects', () => { + const processor = createReactPugProcessor(); + const input = [ + 'import { pug } from "startupjs";', + "const view = pug`Div.button(styleName={ ['non-responsive']: disabled })`;", + ].join('\n'); + const [block] = processor.preprocess(input, 'file.jsx'); + const code = typeof block === 'string' ? block : block.text; + expect(code).toMatchInlineSnapshot(` + "import "startupjs"; + const view = ( +
+ );" + `); + }); + it('supports forcing class shorthand property and merge strategy', () => { const processor = createReactPugProcessor({ classShorthandProperty: 'class', diff --git a/packages/react-pug-core/test/unit/pugToTsx.test.ts b/packages/react-pug-core/test/unit/pugToTsx.test.ts index 6523552..d0b6f6e 100644 --- a/packages/react-pug-core/test/unit/pugToTsx.test.ts +++ b/packages/react-pug-core/test/unit/pugToTsx.test.ts @@ -1525,6 +1525,22 @@ describe('class shorthand strategy', () => { expect(result.tsx).toMatchInlineSnapshot(`"()"`); }); + it('preserves dashed computed keys in classnames-style styleName objects', () => { + const result = compilePugToTsx("Div.button(styleName={ ['non-responsive']: disabled })", { + classAttribute: 'styleName', + classMerge: 'classnames', + }); + expect(result.tsx).toMatchInlineSnapshot(`"(
)"`); + }); + + it('preserves nested classnames-style arrays and objects when merging shorthand classes', () => { + const result = compilePugToTsx("Div.button(styleName=['base', { ['non-responsive']: disabled }, isActive && 'active'])", { + classAttribute: 'styleName', + classMerge: 'classnames', + }); + expect(result.tsx).toMatchInlineSnapshot(`"(
)"`); + }); + it('keeps mapping for existing className attr when merged with shorthand class', () => { const pug = "h1.active(className='hello')"; const result = compilePugToTsx(pug); diff --git a/packages/react-pug-core/test/unit/shadowDocument.test.ts b/packages/react-pug-core/test/unit/shadowDocument.test.ts index 2bea22a..26e4c49 100644 --- a/packages/react-pug-core/test/unit/shadowDocument.test.ts +++ b/packages/react-pug-core/test/unit/shadowDocument.test.ts @@ -136,6 +136,15 @@ describe('single pug region', () => { expect(doc.shadowText).not.toContain('className="title"'); }); + it('preserves dashed computed keys in classnames-style styleName objects', () => { + const text = [ + 'import { pug } from "startupjs";', + "const v = pug`Div.button(styleName={ ['non-responsive']: disabled })`;", + ].join('\n'); + const doc = buildShadowDocument(text, 'test.tsx'); + expect(doc.shadowText).toContain("styleName={['button', { ['non-responsive']: disabled }]}"); + }); + it('removes the pug import binding from shadow output', () => { const text = [ 'import { pug, observer } from "startupjs";', diff --git a/packages/react-pug-core/test/unit/sourceTransform.test.ts b/packages/react-pug-core/test/unit/sourceTransform.test.ts index 1123dc7..d7aac0a 100644 --- a/packages/react-pug-core/test/unit/sourceTransform.test.ts +++ b/packages/react-pug-core/test/unit/sourceTransform.test.ts @@ -122,6 +122,18 @@ describe('transformSourceFile', () => { `); }); + it('preserves dashed computed keys in classnames-style styleName objects', () => { + const source = [ + "import { pug } from 'startupjs';", + "const view = pug`Div.button(styleName={ ['non-responsive']: disabled })`;", + ].join('\n'); + const result = transformSourceFile(source, 'file.tsx', { compileMode: 'runtime' }); + expect(result.code).toMatchInlineSnapshot(` + "import 'startupjs'; + const view = (
);" + `); + }); + it('can force class target and merge strategy explicitly', () => { const source = 'const view = pug`span.title(styleName=active)`;'; const result = transformSourceFile(source, 'file.tsx', { diff --git a/packages/swc-plugin-react-pug/test/unit/transform.test.ts b/packages/swc-plugin-react-pug/test/unit/transform.test.ts index 309f1f5..ea5cd8f 100644 --- a/packages/swc-plugin-react-pug/test/unit/transform.test.ts +++ b/packages/swc-plugin-react-pug/test/unit/transform.test.ts @@ -37,6 +37,15 @@ describe('swc-plugin-react-pug transform', () => { expect(result.code).toContain("styleName={['title', active]}"); }); + it('preserves dashed computed keys in classnames-style styleName objects', () => { + const source = [ + 'import { pug } from "startupjs";', + "const view = pug`Div.button(styleName={ ['non-responsive']: disabled })`;", + ].join('\n'); + const result = transformReactPugSourceForSwc(source, 'fixture.tsx'); + expect(result.code).toContain("styleName={['button', { ['non-responsive']: disabled }]}"); + }); + it('allows forcing class shorthand property and merge strategy', () => { const source = 'const view = pug`span.title(class=isActive)`;'; const result = transformReactPugSourceForSwc(source, 'fixture.tsx', {