diff --git a/.changeset/fiery-turkeys-travel.md b/.changeset/fiery-turkeys-travel.md new file mode 100644 index 00000000..aca5d6c6 --- /dev/null +++ b/.changeset/fiery-turkeys-travel.md @@ -0,0 +1,5 @@ +--- +'@css-modules-kit/core': minor +--- + +feat!: remove unused `isAbsolute` diff --git a/.changeset/orange-readers-report.md b/.changeset/orange-readers-report.md new file mode 100644 index 00000000..2218a068 --- /dev/null +++ b/.changeset/orange-readers-report.md @@ -0,0 +1,5 @@ +--- +'@css-modules-kit/core': patch +--- + +fix: report "Cannot import module" diagnostic for unresolvable bare specifiers diff --git a/.changeset/some-rules-stick.md b/.changeset/some-rules-stick.md new file mode 100644 index 00000000..e4b9e9ee --- /dev/null +++ b/.changeset/some-rules-stick.md @@ -0,0 +1,5 @@ +--- +'@css-modules-kit/core': patch +--- + +fix: return `undefined` for non-existent CSS module paths in resolver diff --git a/packages/codegen/e2e-test/index.test.ts b/packages/codegen/e2e-test/index.test.ts index c96db94c..86eb9321 100644 --- a/packages/codegen/e2e-test/index.test.ts +++ b/packages/codegen/e2e-test/index.test.ts @@ -12,7 +12,7 @@ test('generates .d.ts', async () => { const iff = await createIFF({ 'src/a.module.css': dedent` @import './b.module.css'; - @import './external.css'; + @import './unmatched.module.css'; /* @import '@/c.module.css'; */ /* TODO: Fix this */ .a1 { color: red; } `, @@ -26,8 +26,10 @@ test('generates .d.ts', async () => { // @ts-expect-error styles.a2; `, + 'src/unmatched.module.css': '', 'tsconfig.json': dedent` { + "exclude": ["unmatched.module.css"], "compilerOptions": { "lib": ["ES2015"], "noEmit": true, @@ -47,6 +49,7 @@ test('generates .d.ts', async () => { declare const styles = { a1: '' as readonly string, ...(await import('./b.module.css')).default, + ...(await import('./unmatched.module.css')).default, }; export default styles; " diff --git a/packages/core/src/checker.test.ts b/packages/core/src/checker.test.ts index e7d8d289..a21a4b7f 100644 --- a/packages/core/src/checker.test.ts +++ b/packages/core/src/checker.test.ts @@ -223,7 +223,6 @@ describe('checkCSSModule', () => { }); const check = prepareChecker(); const diagnostics = check(readAndParseCSSModule(iff.paths['a.module.css'])!); - // TODO: Report diagnostics for `package/c.module.css` expect(formatDiagnostics(diagnostics, iff.rootDir)).toMatchInlineSnapshot(` [ { @@ -236,6 +235,16 @@ describe('checkCSSModule', () => { }, "text": "Cannot import module './b.module.css'", }, + { + "category": "error", + "fileName": "/a.module.css", + "length": 20, + "start": { + "column": 10, + "line": 2, + }, + "text": "Cannot import module 'package/c.module.css'", + }, { "category": "error", "fileName": "/a.module.css", @@ -246,6 +255,16 @@ describe('checkCSSModule', () => { }, "text": "Cannot import module './b.module.css'", }, + { + "category": "error", + "fileName": "/a.module.css", + "length": 20, + "start": { + "column": 18, + "line": 4, + }, + "text": "Cannot import module 'package/c.module.css'", + }, ] `); }); diff --git a/packages/core/src/checker.ts b/packages/core/src/checker.ts index 7c545246..ee86b3de 100644 --- a/packages/core/src/checker.ts +++ b/packages/core/src/checker.ts @@ -10,7 +10,7 @@ import type { Resolver, TokenImporter, } from './type.js'; -import { isValidAsJSIdentifier } from './util.js'; +import { isURLSpecifier, isValidAsJSIdentifier } from './util.js'; export interface CheckerArgs { config: CMKConfig; @@ -39,13 +39,15 @@ export function checkCSSModule(cssModule: CSSModule, args: CheckerArgs): Diagnos } for (const tokenImporter of cssModule.tokenImporters) { + if (isURLSpecifier(tokenImporter.from)) continue; const from = args.resolver(tokenImporter.from, { request: cssModule.fileName }); - if (!from || !args.matchesPattern(from)) continue; - const imported = args.getCSSModule(from); - if (!imported) { + if (!from) { diagnostics.push(createCannotImportModuleDiagnostic(cssModule, tokenImporter)); continue; } + if (!args.matchesPattern(from)) continue; + const imported = args.getCSSModule(from); + if (!imported) throw new Error('unreachable: `imported` is undefined'); if (tokenImporter.type === 'value') { const exportRecord = args.getExportRecord(imported); diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index cd93577d..fbe18ca5 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -36,6 +36,6 @@ export { } from './file.js'; export { checkCSSModule, type CheckerArgs } from './checker.js'; export { createExportBuilder } from './export-builder.js'; -export { join, resolve, relative, dirname, basename, parse, isAbsolute } from './path.js'; +export { join, resolve, relative, dirname, basename, parse } from './path.js'; export { findUsedTokenNames } from './util.js'; export { convertDiagnostic, convertDiagnosticWithLocation, convertSystemError } from './diagnostic.js'; diff --git a/packages/core/src/path.ts b/packages/core/src/path.ts index 8302dd12..18b3c1f0 100644 --- a/packages/core/src/path.ts +++ b/packages/core/src/path.ts @@ -32,6 +32,3 @@ export function parse(path: string): ParsedPath { const { root, dir, base, name, ext } = nodePath.parse(path); return { root: slash(root), dir: slash(dir), base, name, ext }; } - -// eslint-disable-next-line @typescript-eslint/unbound-method -export const isAbsolute = nodePath.isAbsolute; diff --git a/packages/core/src/resolver.test.ts b/packages/core/src/resolver.test.ts index 70563816..40e8d8ba 100644 --- a/packages/core/src/resolver.test.ts +++ b/packages/core/src/resolver.test.ts @@ -32,8 +32,7 @@ describe('createResolver', async () => { const resolve = createResolver(normalizeCompilerOptions({}, iff.rootDir), undefined); expect(resolve('./a.module.css', { request })).toBe(iff.paths['a.module.css']); expect(resolve('./dir/a.module.css', { request })).toBe(iff.paths['dir/a.module.css']); - // FIXME: It should return `undefined`. - expect(resolve('./non-existent.module.css', { request })).toBe(iff.join('non-existent.module.css')); + expect(resolve('./non-existent.module.css', { request })).toBe(undefined); }); describe('resolve with `paths` option', () => { test('basic', () => { diff --git a/packages/core/src/resolver.ts b/packages/core/src/resolver.ts index 1bb88e04..d4aa9f4f 100644 --- a/packages/core/src/resolver.ts +++ b/packages/core/src/resolver.ts @@ -1,16 +1,13 @@ -import { fileURLToPath, pathToFileURL } from 'node:url'; import type { CompilerOptions } from 'typescript'; import ts from 'typescript'; -import { isAbsolute, resolve } from './path.js'; +import { resolve } from './path.js'; import type { Resolver, ResolverOptions } from './type.js'; export function createResolver( compilerOptions: CompilerOptions, moduleResolutionCache: ts.ModuleResolutionCache | undefined, ): Resolver { - return (_specifier: string, options: ResolverOptions) => { - let specifier = _specifier; - + return (specifier: string, options: ResolverOptions) => { const host: ts.ModuleResolutionHost = { ...ts.sys, fileExists: (fileName) => { @@ -29,27 +26,8 @@ export function createResolver( ); if (resolvedModule) { // TODO: Logging that the paths is used. - specifier = resolvedModule.resolvedFileName.replace(/\.module\.d\.css\.ts$/u, '.module.css'); - } - if (isAbsolute(specifier)) { - return resolve(specifier); - } else if (isRelativeSpecifier(specifier)) { - // Convert the specifier to an absolute path - // NOTE: Node.js resolves relative specifier with standard relative URL resolution semantics. So we will follow that here as well. - // ref: https://nodejs.org/docs/latest-v23.x/api/esm.html#terminology - return resolve(fileURLToPath(new URL(specifier, pathToFileURL(options.request)).href)); - } else { - // Do not support URL or bare specifiers - // TODO: Logging that the specifier could not resolve. - return undefined; + return resolve(resolvedModule.resolvedFileName.replace(/\.module\.d\.css\.ts$/u, '.module.css')); } + return undefined; }; } - -/** - * Check if the specifier is a relative specifier. - * @see https://nodejs.org/docs/latest-v23.x/api/esm.html#terminology - */ -function isRelativeSpecifier(specifier: string): boolean { - return specifier.startsWith('./') || specifier.startsWith('../'); -} diff --git a/packages/core/src/util.ts b/packages/core/src/util.ts index a0676fcf..c55828a6 100644 --- a/packages/core/src/util.ts +++ b/packages/core/src/util.ts @@ -25,3 +25,7 @@ export function findUsedTokenNames(componentText: string): Set { } return usedClassNames; } + +export function isURLSpecifier(specifier: string): boolean { + return URL.canParse(specifier); +}