diff --git a/src/constants.ts b/src/constants.ts index 8c63a59..889bbc9 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,6 +1,3 @@ -/** - * Export locals convention types matching css-loader - */ export type ExportLocalsConvention = | "as-is" | "camel-case" @@ -8,9 +5,6 @@ export type ExportLocalsConvention = | "dashes" | "dashes-only"; -/** - * Loader options interface - */ export interface LoaderOptions { /** @deprecated Use exportLocalsConvention instead. Will be removed in v2.0 */ camelCase?: boolean; @@ -24,34 +18,21 @@ export interface LoaderOptions { banner?: string; } -/** - * Export format marker interface - */ export interface ExportMarker { pattern: string; isNamedExport: boolean; } -/** - * CSS module pattern interface - */ export interface CssModulePatterns { OBJECT_EXPORT: RegExp; NAMED_EXPORT: RegExp; ALIASED_EXPORT: RegExp; } - -/** - * JSON Schema property definition - */ interface SchemaProperty { type: "boolean" | "string" | "number"; enum?: string[]; } -/** - * JSON Schema definition for validation - */ interface SchemaDefinition { type: "object"; properties: Record; @@ -65,7 +46,6 @@ export const STYLE_EXT_REGEX = /\.(css|postcss|pcss|scss|sass|less|styl|sss)$/; /** * Schema for loader options validation. - * Conforms to JSON Schema Draft-07 specification for use with schema-utils. */ export const SCHEMA: SchemaDefinition = { type: "object", diff --git a/src/messages.ts b/src/messages.ts index 3dd4912..fb83f53 100644 --- a/src/messages.ts +++ b/src/messages.ts @@ -1,7 +1,3 @@ -/** - * Error and info message templates for the loader. - */ - export const ERRORS = { FILE_NOT_FOUND: (filePath: string): string => `CSS DTS Loader: File "${filePath}" not found. Run build in "emit" mode to create it.`, diff --git a/src/utils.ts b/src/utils.ts index d9caa3f..6a89ec3 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -177,11 +177,21 @@ export function applyExportLocalsConvention( * @internal */ function detectExportFormat(source: string): { startIndex: number; isNamedExport: boolean } { - const match = EXPORT_MARKERS.find(marker => source.includes(marker.pattern)); + // Find the earliest export marker in the source + let earliestIndex = -1; + let earliestMarker: typeof EXPORT_MARKERS[0] | null = null; + + for (const marker of EXPORT_MARKERS) { + const index = source.indexOf(marker.pattern); + if (index !== -1 && (earliestIndex === -1 || index < earliestIndex)) { + earliestIndex = index; + earliestMarker = marker; + } + } return { - startIndex: match ? source.indexOf(match.pattern) : -1, - isNamedExport: match?.isNamedExport || false + startIndex: earliestIndex, + isNamedExport: earliestMarker?.isNamedExport || false }; } diff --git a/test/__snapshots__/index.test.ts.snap b/test/__snapshots__/index.test.ts.snap index 63083cc..eb98897 100644 --- a/test/__snapshots__/index.test.ts.snap +++ b/test/__snapshots__/index.test.ts.snap @@ -41,6 +41,26 @@ export { __dts_import as "import" }; " `; +exports[`css-modules-dts-loader > JavaScript Keywords as Class Names > should handle mixed keywords and normal classes 1`] = ` +"// This file is automatically generated. +// Please do not change this file! +export const container: string; +export const title: string; + +declare const __dts_class: string; +export { __dts_class as "class" }; +" +`; + +exports[`css-modules-dts-loader > JavaScript Keywords as Class Names > should handle mixed keywords and normal classes with only keyword exported 1`] = ` +"// This file is automatically generated. +// Please do not change this file! + +declare const __dts_class: string; +export { __dts_class as "class" }; +" +`; + exports[`css-modules-dts-loader > Options: camelCase > should convert kebab-case class names to camelCase 1`] = ` "// This file is automatically generated. // Please do not change this file! diff --git a/test/index.test.ts b/test/index.test.ts index 4c72ceb..5c19e1f 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -283,6 +283,39 @@ describe("css-modules-dts-loader", () => { expect(dtsContent).toContain('"export"'); expect(dtsContent).toContain('"for"'); }); + + it("should handle mixed keywords and normal classes", async () => { + const files = { + "index.js": "import styles from './styles.module.css';", + "styles.module.css": ` + .container { + height: var(--ds-color-surface-main); + } + .title { + color: var(--ds-color-text-main); + } + .class { + background-color: var(--ds-color-background-accent); + } + ` + }; + + const { tmpDir } = await compileProject({ + files, + loaderOptions: { namedExport: true } + }); + + const dtsContent = readFile(tmpDir, "styles.module.css.d.ts"); + expect(normalizeLineEndings(dtsContent)).toMatchSnapshot(); + + // Should export normal classnames as regular named exports + expect(dtsContent).toContain("export const container: string;"); + expect(dtsContent).toContain("export const title: string;"); + + // Should export the keyword with aliased export + expect(dtsContent).toContain("declare const __dts_class: string;"); + expect(dtsContent).toContain('export { __dts_class as "class" };'); + }); }); describe("File Extensions", () => {