From 747c6edb4fb8e6e6ca43308c33f519bdd9483bef Mon Sep 17 00:00:00 2001 From: mizdra Date: Sun, 5 Jul 2026 22:12:13 +0900 Subject: [PATCH 1/3] feat(core, ts-plugin, codegen, vscode): support scoping container names via `cmkOptions.container` --- .changeset/container-option.md | 8 + AGENTS.md | 4 +- README.md | 17 ++ docs/glossary.md | 2 +- packages/codegen/src/project.ts | 1 + packages/core/src/config.test.ts | 16 ++ packages/core/src/config.ts | 13 ++ packages/core/src/parser/animation-parser.ts | 8 +- packages/core/src/parser/composes-parser.ts | 2 +- .../core/src/parser/container-parser.test.ts | 157 ++++++++++++++++++ packages/core/src/parser/container-parser.ts | 94 +++++++++++ .../core/src/parser/css-module-parser.test.ts | 23 +++ packages/core/src/parser/css-module-parser.ts | 31 +++- .../core/src/parser/dashed-ident-parser.ts | 4 - .../{decl-value-location.ts => util.ts} | 5 + packages/core/src/test/css-module.ts | 3 +- packages/core/src/test/faker.ts | 1 + packages/ts-plugin/src/language-plugin.ts | 1 + packages/vscode/schemas/tsconfig.schema.json | 6 + 19 files changed, 376 insertions(+), 20 deletions(-) create mode 100644 .changeset/container-option.md create mode 100644 packages/core/src/parser/container-parser.test.ts create mode 100644 packages/core/src/parser/container-parser.ts rename packages/core/src/parser/{decl-value-location.ts => util.ts} (65%) diff --git a/.changeset/container-option.md b/.changeset/container-option.md new file mode 100644 index 00000000..45c84268 --- /dev/null +++ b/.changeset/container-option.md @@ -0,0 +1,8 @@ +--- +'@css-modules-kit/core': minor +'@css-modules-kit/ts-plugin': minor +'@css-modules-kit/codegen': minor +'css-modules-kit-vscode': minor +--- + +feat(core, ts-plugin, codegen, vscode): support scoping container names via `cmkOptions.container` diff --git a/AGENTS.md b/AGENTS.md index b6d285c0..71feb2c6 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -110,8 +110,8 @@ function myFunction() { ## Glossary -- **Token**: A generic term for things exported by CSS Modules, such as class names, `@value` definitions, `@keyframes` names, and ``s (custom properties, when `cmkOptions.dashedIdents` is enabled). -- **Token reference**: A usage of a token elsewhere in the CSS. Currently produced for `animation-name: ` and `composes: `. There are two kinds: +- **Token**: A generic term for things exported by CSS Modules, such as class names, `@value` definitions, `@keyframes` names, container names (when `cmkOptions.container` is enabled), and ``s (custom properties, when `cmkOptions.dashedIdents` is enabled). +- **Token reference**: A usage of a token elsewhere in the CSS. Currently produced for `animation-name: `, `composes: `, and `@container (...)`. There are two kinds: - **Local token reference**: References a token available in the current file. The token may be defined in the same file (e.g. `@keyframes`) or imported via `@import` / `@value ... from`. - **External token reference**: References tokens exported by another file, like `composes: ... from ''`. One reference corresponds to one `from` clause and holds an entry per referenced token. - **Diagnostic**: An object representing errors or warnings diff --git a/README.md b/README.md index fad1f866..22e97b47 100644 --- a/README.md +++ b/README.md @@ -248,6 +248,23 @@ Determines whether to generate the [token](docs/glossary.md#token) of ` { prioritizeNamedImports: false, animation: true, dashedIdents: false, + container: false, enabled: false, compilerOptions: expect.any(Object), wildcardDirectories: [{ fileName: iff.rootDir, recursive: true }], @@ -50,6 +51,7 @@ describe('readConfigFile', () => { "prioritizeNamedImports": true, "animation": false, "dashedIdents": true, + "container": true, "enabled": true } } @@ -65,6 +67,7 @@ describe('readConfigFile', () => { prioritizeNamedImports: true, animation: false, dashedIdents: true, + container: true, enabled: true, compilerOptions: expect.objectContaining({ module: ts.ModuleKind.ESNext, @@ -90,6 +93,7 @@ describe('readConfigFile', () => { "prioritizeNamedImports": true, "animation": false, "dashedIdents": true, + "container": true, "enabled": true } } @@ -106,6 +110,7 @@ describe('readConfigFile', () => { prioritizeNamedImports: true, animation: false, dashedIdents: true, + container: true, enabled: true, compilerOptions: expect.objectContaining({ module: ts.ModuleKind.ESNext, @@ -393,6 +398,17 @@ describe('readConfigFile', () => { }, ]); }); + test('reports an error if `container` is not a boolean', async () => { + const iff = await createIFF({ + 'tsconfig.json': '{ "cmkOptions": { "container": 1 } }', + }); + expect(readConfigFile(iff.rootDir).diagnostics).toStrictEqual([ + { + category: 'error', + text: `\`container\` in ${iff.paths['tsconfig.json']} must be a boolean.`, + }, + ]); + }); }); describe('deprecated `keyframes` option', () => { test('adopts the `keyframes` value as `animation` and reports a deprecation warning', async () => { diff --git a/packages/core/src/config.ts b/packages/core/src/config.ts index 36039402..4947195e 100644 --- a/packages/core/src/config.ts +++ b/packages/core/src/config.ts @@ -20,6 +20,7 @@ export interface CMKConfig { prioritizeNamedImports: boolean; animation: boolean; dashedIdents: boolean; + container: boolean; /** * A root directory to resolve relative path entries in the config file to. * This is an absolute path. @@ -81,6 +82,7 @@ interface UnnormalizedRawConfig { animation?: boolean; keyframes?: boolean; dashedIdents?: boolean; + container?: boolean; } /** @@ -196,6 +198,16 @@ function parseRawData(raw: unknown, tsConfigSourceFile: ts.TsConfigSourceFile, b }); } } + if ('container' in raw.cmkOptions) { + if (typeof raw.cmkOptions.container === 'boolean') { + result.config.container = raw.cmkOptions.container; + } else { + result.diagnostics.push({ + category: 'error', + text: `\`container\` in ${tsConfigSourceFile.fileName} must be a boolean.`, + }); + } + } if ('namedExports' in raw.cmkOptions) { if (typeof raw.cmkOptions.namedExports === 'boolean') { result.config.namedExports = raw.cmkOptions.namedExports; @@ -316,6 +328,7 @@ export function readConfigFile(project: string): CMKConfig { prioritizeNamedImports: parsedTsConfig.config.prioritizeNamedImports ?? false, animation: parsedTsConfig.config.animation ?? parsedTsConfig.config.keyframes ?? true, dashedIdents: parsedTsConfig.config.dashedIdents ?? false, + container: parsedTsConfig.config.container ?? false, enabled: parsedTsConfig.config.enabled ?? false, basePath, configFileName, diff --git a/packages/core/src/parser/animation-parser.ts b/packages/core/src/parser/animation-parser.ts index 297d07d8..f4a5fe7a 100644 --- a/packages/core/src/parser/animation-parser.ts +++ b/packages/core/src/parser/animation-parser.ts @@ -1,7 +1,8 @@ import type { Declaration } from 'postcss'; import postcssValueParser from 'postcss-value-parser'; import type { DiagnosticWithDetachedLocation, TokenReference } from '../type.js'; -import { calcDeclValueLoc } from './decl-value-location.js'; +import { calcDeclValueLoc } from './util.js'; +import { VALID_IDENT_RE } from './util.js'; const ANIMATION_NAME_PROP_RE = /^(?:-(?:webkit|moz|o|ms)-)?animation-name$/iu; const ANIMATION_PROP_RE = /^(?:-(?:webkit|moz|o|ms)-)?animation$/iu; @@ -60,11 +61,6 @@ const ANIMATION_SHORTHAND_RESERVED_KEYWORDS = new Set([ 'paused', ]); -// A valid CSS identifier, including ``. Excludes `