diff --git a/.changeset/modern-snails-hope.md b/.changeset/modern-snails-hope.md new file mode 100644 index 00000000..7b92ce89 --- /dev/null +++ b/.changeset/modern-snails-hope.md @@ -0,0 +1,5 @@ +--- +'@css-modules-kit/core': patch +--- + +fix: prevent `styles` from becoming `any` type when importing unresolvable or unmatched modules diff --git a/packages/codegen/e2e-test/index.test.ts b/packages/codegen/e2e-test/index.test.ts index 86eb9321..bf3402cf 100644 --- a/packages/codegen/e2e-test/index.test.ts +++ b/packages/codegen/e2e-test/index.test.ts @@ -46,10 +46,11 @@ test('generates .d.ts', async () => { expect(cmk.status).toBe(0); expect(await iff.readFile('generated/src/a.module.css.d.ts')).toMatchInlineSnapshot(` "// @ts-nocheck + function blockErrorType(val: T): [0] extends [(1 & T)] ? {} : T; declare const styles = { a1: '' as readonly string, - ...(await import('./b.module.css')).default, - ...(await import('./unmatched.module.css')).default, + ...blockErrorType((await import('./b.module.css')).default), + ...blockErrorType((await import('./unmatched.module.css')).default), }; export default styles; " @@ -155,27 +156,30 @@ test('generates .d.ts with circular import', async () => { expect(cmk.status).toBe(0); expect(await iff.readFile('generated/src/a.module.css.d.ts')).toMatchInlineSnapshot(` "// @ts-nocheck + function blockErrorType(val: T): [0] extends [(1 & T)] ? {} : T; declare const styles = { a1: '' as readonly string, - ...(await import('./b.module.css')).default, + ...blockErrorType((await import('./b.module.css')).default), }; export default styles; " `); expect(await iff.readFile('generated/src/b.module.css.d.ts')).toMatchInlineSnapshot(` "// @ts-nocheck + function blockErrorType(val: T): [0] extends [(1 & T)] ? {} : T; declare const styles = { b1: '' as readonly string, - ...(await import('./a.module.css')).default, + ...blockErrorType((await import('./a.module.css')).default), }; export default styles; " `); expect(await iff.readFile('generated/src/c.module.css.d.ts')).toMatchInlineSnapshot(` "// @ts-nocheck + function blockErrorType(val: T): [0] extends [(1 & T)] ? {} : T; declare const styles = { c1: '' as readonly string, - ...(await import('./c.module.css')).default, + ...blockErrorType((await import('./c.module.css')).default), }; export default styles; " diff --git a/packages/core/src/dts-generator.test.ts b/packages/core/src/dts-generator.test.ts index f340bd8d..f2de2c39 100644 --- a/packages/core/src/dts-generator.test.ts +++ b/packages/core/src/dts-generator.test.ts @@ -49,8 +49,9 @@ describe('generateDts', () => { }); expect(generateDts(readAndParseCSSModule(iff.paths['test.module.css'])!, options).text).toMatchInlineSnapshot(` "// @ts-nocheck + function blockErrorType(val: T): [0] extends [(1 & T)] ? {} : T; declare const styles = { - ...(await import('./a.module.css')).default, + ...blockErrorType((await import('./a.module.css')).default), imported1: (await import('./b.module.css')).default.imported1, aliasedImported2: (await import('./b.module.css')).default.imported2, }; diff --git a/packages/core/src/dts-generator.ts b/packages/core/src/dts-generator.ts index 3aed37f8..8034a08c 100644 --- a/packages/core/src/dts-generator.ts +++ b/packages/core/src/dts-generator.ts @@ -243,8 +243,18 @@ function generateDefaultExportDts( // // If `--skipLibCheck` is false, those errors will be reported by `tsc`. However, these are negligible errors. // Therefore, `@ts-nocheck` is added to the generated type definition file. - let text = `// @ts-nocheck\ndeclare const ${STYLES_EXPORT_NAME} = {\n`; + let text = `// @ts-nocheck\n`; + + // This is a workaround to avoid the issue described as a "Drawbacks" in https://github.com/mizdra/css-modules-kit/pull/302. + // It uses the technique from https://stackoverflow.com/a/55541672 to fall back from `any` to `{}`. + // However, the import type for an unresolvable specifier becomes a special `any` type called `errorType`. + // The technique from https://stackoverflow.com/a/55541672 does not work with `errorType`. + // Therefore, this combines it with the approach from https://github.com/microsoft/TypeScript/issues/62972. + if (tokenImporters.some((importer) => importer.type === 'import')) { + text += `function blockErrorType(val: T): [0] extends [(1 & T)] ? {} : T;\n`; + } + text += `declare const ${STYLES_EXPORT_NAME} = {\n`; for (const token of localTokens) { text += ` `; mapping.sourceOffsets.push(token.loc.start.offset); @@ -254,11 +264,11 @@ function generateDefaultExportDts( } for (const tokenImporter of tokenImporters) { if (tokenImporter.type === 'import') { - text += ` ...(await import(`; + text += ` ...blockErrorType((await import(`; mapping.sourceOffsets.push(tokenImporter.fromLoc.start.offset - 1); mapping.lengths.push(tokenImporter.from.length + 2); mapping.generatedOffsets.push(text.length); - text += `'${tokenImporter.from}')).default,\n`; + text += `'${tokenImporter.from}')).default),\n`; } else { // eslint-disable-next-line no-loop-func tokenImporter.values.forEach((value, i) => {