gemcss 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,100 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.GemcssParseError = void 0;
7
+ exports.parseCss = parseCss;
8
+ exports.buildModule = buildModule;
9
+ const postcss_1 = __importDefault(require("postcss"));
10
+ const postcss_selector_parser_1 = __importDefault(require("postcss-selector-parser"));
11
+ /** Thrown when a class name violates the naming convention. */
12
+ class GemcssParseError extends Error {
13
+ constructor(message) {
14
+ super(message);
15
+ this.name = 'GemcssParseError';
16
+ }
17
+ }
18
+ exports.GemcssParseError = GemcssParseError;
19
+ function ctx(from) {
20
+ return from ? ` (in ${from})` : '';
21
+ }
22
+ /**
23
+ * Extract class selectors from CSS source and build the model.
24
+ * Classes keep the order of their first appearance.
25
+ */
26
+ function parseCss(css, opts = {}) {
27
+ const root = postcss_1.default.parse(css, { from: opts.from });
28
+ const classNames = [];
29
+ const seen = new Set();
30
+ root.walkRules((rule) => {
31
+ (0, postcss_selector_parser_1.default)((selectors) => {
32
+ selectors.walkClasses((cls) => {
33
+ if (!seen.has(cls.value)) {
34
+ seen.add(cls.value);
35
+ classNames.push(cls.value);
36
+ }
37
+ });
38
+ }).processSync(rule.selector);
39
+ });
40
+ return buildModule(classNames, opts.from);
41
+ }
42
+ /**
43
+ * Build the model from a list of raw class names.
44
+ * Convention: `block`, `block_bool`, `block_key_value`.
45
+ */
46
+ function buildModule(classNames, from) {
47
+ const blocks = {};
48
+ for (const raw of classNames) {
49
+ const segments = raw.split('_');
50
+ const blockName = segments[0];
51
+ if (!blockName) {
52
+ throw new GemcssParseError(`Empty block name in class ".${raw}"${ctx(from)}`);
53
+ }
54
+ const block = (blocks[blockName] ??= {
55
+ name: blockName,
56
+ modifiers: {},
57
+ });
58
+ const tail = segments.slice(1);
59
+ if (tail.length === 0)
60
+ continue;
61
+ if (tail.length === 1) {
62
+ const key = tail[0];
63
+ if (!key)
64
+ throw new GemcssParseError(`Empty modifier in class ".${raw}"${ctx(from)}`);
65
+ assignBool(block, key, raw, from);
66
+ continue;
67
+ }
68
+ if (tail.length === 2) {
69
+ const key = tail[0];
70
+ const value = tail[1];
71
+ if (!key || !value) {
72
+ throw new GemcssParseError(`Empty modifier key/value in class ".${raw}"${ctx(from)}`);
73
+ }
74
+ assignEnum(block, key, value, raw, from);
75
+ continue;
76
+ }
77
+ throw new GemcssParseError(`Class ".${raw}" has more than one modifier segment after block "${blockName}". ` +
78
+ `Allowed: block, block_bool, block_key_value.${ctx(from)}`);
79
+ }
80
+ return { blocks };
81
+ }
82
+ function assignBool(block, key, raw, from) {
83
+ const existing = block.modifiers[key];
84
+ if (existing && existing.kind === 'enum') {
85
+ throw new GemcssParseError(`Modifier "${key}" of block "${block.name}" used as both boolean (".${raw}") and enum.${ctx(from)}`);
86
+ }
87
+ block.modifiers[key] = { kind: 'bool' };
88
+ }
89
+ function assignEnum(block, key, value, raw, from) {
90
+ const existing = block.modifiers[key];
91
+ if (existing && existing.kind === 'bool') {
92
+ throw new GemcssParseError(`Modifier "${key}" of block "${block.name}" used as both enum (".${raw}") and boolean.${ctx(from)}`);
93
+ }
94
+ const mod = existing && existing.kind === 'enum'
95
+ ? existing
96
+ : (block.modifiers[key] = { kind: 'enum', values: [] });
97
+ if (!mod.values.includes(value)) {
98
+ mod.values.push(value);
99
+ }
100
+ }
@@ -0,0 +1 @@
1
+ {"type":"commonjs"}
@@ -0,0 +1,2 @@
1
+ /** Generate the `.d.ts` text for a `*.module.css`. Never throws. */
2
+ export declare function dtsForFile(fileName: string, read?: (f: string) => string | undefined): string;
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.dtsForFile = dtsForFile;
4
+ const node_fs_1 = require("node:fs");
5
+ const dts_js_1 = require("../core/dts.js");
6
+ const parser_js_1 = require("../core/parser.js");
7
+ /** Permissive fallback for CSS that is unreadable or violates the convention —
8
+ * keeps the editor usable on a broken file. */
9
+ const FALLBACK = 'declare const gems: Record<string, (props?: Record<string, string | boolean>) => string>;\n' +
10
+ 'export default gems;\n';
11
+ /** Generate the `.d.ts` text for a `*.module.css`. Never throws. */
12
+ function dtsForFile(fileName, read = defaultRead) {
13
+ const css = read(fileName);
14
+ if (css == null)
15
+ return FALLBACK;
16
+ try {
17
+ return (0, dts_js_1.generateDts)((0, parser_js_1.parseCss)(css, { from: fileName }));
18
+ }
19
+ catch {
20
+ return FALLBACK;
21
+ }
22
+ }
23
+ function defaultRead(f) {
24
+ try {
25
+ return (0, node_fs_1.readFileSync)(f, 'utf8');
26
+ }
27
+ catch {
28
+ return undefined;
29
+ }
30
+ }
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ const node_path_1 = require("node:path");
3
+ const generate_dts_js_1 = require("./ts-plugin/generate-dts.js");
4
+ const MODULE_CSS = /\.module\.css$/;
5
+ /**
6
+ * TS language service plugin: types for `import gems from './x.module.css'`
7
+ * without declaration files on disk. Patches the languageServiceHost:
8
+ * - getScriptKind -> TS for `*.module.css`
9
+ * - getScriptSnapshot -> the generated `.d.ts`
10
+ * - resolveModuleNameLiterals -> resolves the import to that virtual Dts
11
+ */
12
+ function init({ typescript: ts }) {
13
+ function create(info) {
14
+ const host = info.languageServiceHost;
15
+ const read = (f) => host.readFile?.(f);
16
+ const origKind = host.getScriptKind?.bind(host);
17
+ host.getScriptKind = (fileName) => {
18
+ if (MODULE_CSS.test(fileName))
19
+ return ts.ScriptKind.TS;
20
+ return origKind ? origKind(fileName) : ts.ScriptKind.Unknown;
21
+ };
22
+ const origSnapshot = host.getScriptSnapshot.bind(host);
23
+ host.getScriptSnapshot = (fileName) => {
24
+ if (MODULE_CSS.test(fileName)) {
25
+ return ts.ScriptSnapshot.fromString((0, generate_dts_js_1.dtsForFile)(fileName, read));
26
+ }
27
+ return origSnapshot(fileName);
28
+ };
29
+ if (host.resolveModuleNameLiterals) {
30
+ const origResolve = host.resolveModuleNameLiterals.bind(host);
31
+ host.resolveModuleNameLiterals = (literals, containingFile, ...rest) => {
32
+ const resolved = origResolve(literals, containingFile, ...rest);
33
+ return literals.map((literal, i) => {
34
+ const name = literal.text;
35
+ if (MODULE_CSS.test(name)) {
36
+ const fileName = name.startsWith('.') ? (0, node_path_1.resolve)((0, node_path_1.dirname)(containingFile), name) : name;
37
+ return {
38
+ resolvedModule: {
39
+ resolvedFileName: fileName,
40
+ extension: ts.Extension.Dts,
41
+ isExternalLibraryImport: false,
42
+ },
43
+ };
44
+ }
45
+ return resolved[i];
46
+ });
47
+ };
48
+ }
49
+ return info.languageService;
50
+ }
51
+ return { create };
52
+ }
53
+ module.exports = init;
@@ -1,5 +1,4 @@
1
- import tsModule from 'typescript/lib/tsserverlibrary';
2
-
1
+ import type tsModule from 'typescript/lib/tsserverlibrary';
3
2
  interface InitArgs {
4
3
  typescript: typeof tsModule;
5
4
  }
@@ -11,5 +10,4 @@ interface InitArgs {
11
10
  * - resolveModuleNameLiterals -> resolves the import to that virtual Dts
12
11
  */
13
12
  declare function init({ typescript: ts }: InitArgs): tsModule.server.PluginModule;
14
-
15
13
  export = init;
package/dist/runtime.d.ts CHANGED
@@ -1,12 +1,10 @@
1
1
  /** Props passed to a gem function: boolean flags and enum values. */
2
- type GemProps = Record<string, string | boolean | undefined>;
2
+ export type GemProps = Record<string, string | boolean | undefined>;
3
3
  /** A block's class combinator function. */
4
- type Gem = (props?: GemProps) => string;
4
+ export type Gem = (props?: GemProps) => string;
5
5
  /**
6
6
  * Build the `gems` object from a list of block names and a `raw → scoped` map.
7
7
  * Every block is a function: called with no arguments it returns the base class,
8
8
  * called with props it appends the classes of the active modifiers.
9
9
  */
10
- declare function createGems(blocks: string[], scoped: Record<string, string>): Record<string, Gem>;
11
-
12
- export { type Gem, type GemProps, createGems };
10
+ export declare function createGems(blocks: string[], scoped: Record<string, string>): Record<string, Gem>;
package/dist/runtime.js CHANGED
@@ -1,24 +1,27 @@
1
- // src/runtime.ts
2
- function createGems(blocks, scoped) {
3
- const gems = {};
4
- for (const name of blocks) {
5
- const base = scoped[name] ?? name;
6
- gems[name] = (props) => {
7
- if (!props) return base;
8
- let out = base;
9
- for (const key in props) {
10
- const v = props[key];
11
- if (v === true) {
12
- out += " " + (scoped[`${name}_${key}`] ?? `${name}_${key}`);
13
- } else if (v != null && v !== false) {
14
- out += " " + (scoped[`${name}_${key}_${v}`] ?? `${name}_${key}_${v}`);
15
- }
16
- }
17
- return out;
18
- };
19
- }
20
- return gems;
1
+ /**
2
+ * Build the `gems` object from a list of block names and a `raw → scoped` map.
3
+ * Every block is a function: called with no arguments it returns the base class,
4
+ * called with props it appends the classes of the active modifiers.
5
+ */
6
+ export function createGems(blocks, scoped) {
7
+ const gems = {};
8
+ for (const name of blocks) {
9
+ const base = scoped[name] ?? name;
10
+ gems[name] = (props) => {
11
+ if (!props)
12
+ return base;
13
+ let out = base;
14
+ for (const key in props) {
15
+ const v = props[key];
16
+ if (v === true) {
17
+ out += ' ' + (scoped[`${name}_${key}`] ?? `${name}_${key}`); // bool
18
+ }
19
+ else if (v != null && v !== false) {
20
+ out += ' ' + (scoped[`${name}_${key}_${v}`] ?? `${name}_${key}_${v}`); // enum
21
+ }
22
+ }
23
+ return out;
24
+ };
25
+ }
26
+ return gems;
21
27
  }
22
- export {
23
- createGems
24
- };
package/dist/vite.d.ts CHANGED
@@ -1,11 +1,10 @@
1
- import { Plugin } from 'vite';
2
- import { C as CompileOptions } from './compile-Br7cHtI1.js';
3
-
1
+ import type { Plugin } from 'vite';
2
+ import { type CompileOptions } from './core/compile.js';
4
3
  /**
5
4
  * Compile a CSS module into a gems JS module plus a virtual CSS id.
6
5
  * A pure function — testable without Vite.
7
6
  */
8
- declare function buildGemsModule(file: string, code: string, options?: CompileOptions, runtimeImport?: string): Promise<{
7
+ export declare function buildGemsModule(file: string, code: string, options?: CompileOptions, runtimeImport?: string): Promise<{
9
8
  js: string;
10
9
  cssId: string;
11
10
  css: string;
@@ -15,6 +14,5 @@ declare function buildGemsModule(file: string, code: string, options?: CompileOp
15
14
  * The import resolves to a virtual JS module; the CSS goes back into the Vite
16
15
  * pipeline as a separate virtual `.css` import so it still gets injected.
17
16
  */
18
- declare function gemcss(options?: CompileOptions): Plugin;
19
-
20
- export { buildGemsModule, gemcss as default, gemcss };
17
+ export declare function gemcss(options?: CompileOptions): Plugin;
18
+ export default gemcss;
package/dist/vite.js CHANGED
@@ -1,60 +1,62 @@
1
- import {
2
- compile,
3
- runtimePath
4
- } from "./chunk-DMYDRHTS.js";
5
- import "./chunk-CNXDOGPX.js";
6
-
7
- // src/vite.ts
8
- import { readFile } from "fs/promises";
9
- var MODULE_CSS_RE = /\.module\.css$/;
10
- var VIRTUAL_JS = "\0gemcss-js:";
11
- var VIRTUAL_CSS = "\0gemcss-css:";
1
+ import { readFile } from 'node:fs/promises';
2
+ import { compile } from './core/compile.js';
3
+ import { runtimePath } from './core/runtime-path.js';
4
+ const MODULE_CSS_RE = /\.module\.css$/;
5
+ /** Virtual gems JS module. No `.css` in the id, or Vite would claim it as CSS. */
6
+ const VIRTUAL_JS = '\0gemcss-js:';
7
+ /** Virtual CSS for injection. The id is base64 — no `.module.`, or css-modules runs twice. */
8
+ const VIRTUAL_CSS = '\0gemcss-css:';
12
9
  function isModuleCss(source) {
13
- return MODULE_CSS_RE.test(source.split("?", 1)[0] ?? source);
10
+ return MODULE_CSS_RE.test(source.split('?', 1)[0] ?? source);
14
11
  }
15
- var enc = (s) => Buffer.from(s).toString("base64url");
16
- var dec = (s) => Buffer.from(s, "base64url").toString("utf8");
17
- async function buildGemsModule(file, code, options = {}, runtimeImport = "gemcss/runtime") {
18
- const { css, scoped, module } = await compile(code, file, options);
19
- const cssId = `${VIRTUAL_CSS}${enc(file)}.css`;
20
- const js = `import ${JSON.stringify(cssId)};
21
- import { createGems } from ${JSON.stringify(runtimeImport)};
22
- export default createGems(${JSON.stringify(Object.keys(module.blocks))}, ${JSON.stringify(scoped)});
23
- `;
24
- return { js, cssId, css };
12
+ const enc = (s) => Buffer.from(s).toString('base64url');
13
+ const dec = (s) => Buffer.from(s, 'base64url').toString('utf8');
14
+ /**
15
+ * Compile a CSS module into a gems JS module plus a virtual CSS id.
16
+ * A pure function — testable without Vite.
17
+ */
18
+ export async function buildGemsModule(file, code, options = {}, runtimeImport = 'gemcss/runtime') {
19
+ const { css, scoped, module } = await compile(code, file, options);
20
+ const cssId = `${VIRTUAL_CSS}${enc(file)}.css`;
21
+ const js = `import ${JSON.stringify(cssId)};\n` +
22
+ `import { createGems } from ${JSON.stringify(runtimeImport)};\n` +
23
+ `export default createGems(${JSON.stringify(Object.keys(module.blocks))}, ${JSON.stringify(scoped)});\n`;
24
+ return { js, cssId, css };
25
25
  }
26
- function gemcss(options = {}) {
27
- const cssStore = /* @__PURE__ */ new Map();
28
- return {
29
- name: "gemcss",
30
- enforce: "pre",
31
- async resolveId(source, importer) {
32
- if (source.startsWith(VIRTUAL_JS) || source.startsWith(VIRTUAL_CSS)) return source;
33
- if (isModuleCss(source)) {
34
- const resolved = await this.resolve(source, importer, { skipSelf: true });
35
- if (resolved) return VIRTUAL_JS + enc(resolved.id);
36
- }
37
- return null;
38
- },
39
- async load(id) {
40
- if (id.startsWith(VIRTUAL_CSS)) {
41
- return cssStore.get(id) ?? "";
42
- }
43
- if (id.startsWith(VIRTUAL_JS)) {
44
- const file = dec(id.slice(VIRTUAL_JS.length));
45
- this.addWatchFile(file);
46
- const code = await readFile(file, "utf8");
47
- const { js, cssId, css } = await buildGemsModule(file, code, options, runtimePath());
48
- cssStore.set(cssId, css);
49
- return js;
50
- }
51
- return null;
52
- }
53
- };
26
+ /**
27
+ * gemcss Vite plugin: `*.module.css` -> a typed `gems` object.
28
+ * The import resolves to a virtual JS module; the CSS goes back into the Vite
29
+ * pipeline as a separate virtual `.css` import so it still gets injected.
30
+ */
31
+ export function gemcss(options = {}) {
32
+ const cssStore = new Map();
33
+ return {
34
+ name: 'gemcss',
35
+ enforce: 'pre',
36
+ async resolveId(source, importer) {
37
+ if (source.startsWith(VIRTUAL_JS) || source.startsWith(VIRTUAL_CSS))
38
+ return source;
39
+ if (isModuleCss(source)) {
40
+ const resolved = await this.resolve(source, importer, { skipSelf: true });
41
+ if (resolved)
42
+ return VIRTUAL_JS + enc(resolved.id);
43
+ }
44
+ return null;
45
+ },
46
+ async load(id) {
47
+ if (id.startsWith(VIRTUAL_CSS)) {
48
+ return cssStore.get(id) ?? '';
49
+ }
50
+ if (id.startsWith(VIRTUAL_JS)) {
51
+ const file = dec(id.slice(VIRTUAL_JS.length));
52
+ this.addWatchFile(file);
53
+ const code = await readFile(file, 'utf8');
54
+ const { js, cssId, css } = await buildGemsModule(file, code, options, runtimePath());
55
+ cssStore.set(cssId, css);
56
+ return js;
57
+ }
58
+ return null;
59
+ },
60
+ };
54
61
  }
55
- var vite_default = gemcss;
56
- export {
57
- buildGemsModule,
58
- vite_default as default,
59
- gemcss
60
- };
62
+ export default gemcss;
package/dist/webpack.d.ts CHANGED
@@ -1,13 +1,10 @@
1
- import { LoaderContext } from 'webpack';
2
- import { C as CompileOptions } from './compile-Br7cHtI1.js';
3
-
4
- type GemcssLoaderOptions = CompileOptions;
1
+ import type { LoaderContext } from 'webpack';
2
+ import { type CompileOptions } from './core/compile.js';
3
+ export type GemcssLoaderOptions = CompileOptions;
5
4
  /**
6
5
  * Build the JS module for webpack: runtime CSS injection + a `gems` default export.
7
6
  * A pure function — testable without webpack.
8
7
  */
9
- declare function buildGemsModule(code: string, resourcePath: string, options?: CompileOptions, runtimeImport?: string): Promise<string>;
8
+ export declare function buildGemsModule(code: string, resourcePath: string, options?: CompileOptions, runtimeImport?: string): Promise<string>;
10
9
  /** Webpack loader: `*.module.css` -> a `gems` JS module that injects its CSS at runtime. */
11
- declare function gemcssLoader(this: LoaderContext<GemcssLoaderOptions>, source: string): void;
12
-
13
- export { type GemcssLoaderOptions, buildGemsModule, gemcssLoader as default };
10
+ export default function gemcssLoader(this: LoaderContext<GemcssLoaderOptions>, source: string): void;
package/dist/webpack.js CHANGED
@@ -1,32 +1,24 @@
1
- import {
2
- compile,
3
- runtimePath
4
- } from "./chunk-DMYDRHTS.js";
5
- import "./chunk-CNXDOGPX.js";
6
-
7
- // src/webpack.ts
8
- async function buildGemsModule(code, resourcePath, options = {}, runtimeImport = runtimePath()) {
9
- const { css, scoped, module } = await compile(code, resourcePath, options);
10
- return `import { createGems } from ${JSON.stringify(runtimeImport)};
11
- const __css = ${JSON.stringify(css)};
12
- if (typeof document !== 'undefined') {
13
- const __el = document.createElement('style');
14
- __el.setAttribute('data-gemcss', ${JSON.stringify(resourcePath)});
15
- __el.textContent = __css;
16
- document.head.appendChild(__el);
1
+ import { compile } from './core/compile.js';
2
+ import { runtimePath } from './core/runtime-path.js';
3
+ /**
4
+ * Build the JS module for webpack: runtime CSS injection + a `gems` default export.
5
+ * A pure function — testable without webpack.
6
+ */
7
+ export async function buildGemsModule(code, resourcePath, options = {}, runtimeImport = runtimePath()) {
8
+ const { css, scoped, module } = await compile(code, resourcePath, options);
9
+ return (`import { createGems } from ${JSON.stringify(runtimeImport)};\n` +
10
+ `const __css = ${JSON.stringify(css)};\n` +
11
+ `if (typeof document !== 'undefined') {\n` +
12
+ ` const __el = document.createElement('style');\n` +
13
+ ` __el.setAttribute('data-gemcss', ${JSON.stringify(resourcePath)});\n` +
14
+ ` __el.textContent = __css;\n` +
15
+ ` document.head.appendChild(__el);\n` +
16
+ `}\n` +
17
+ `export default createGems(${JSON.stringify(Object.keys(module.blocks))}, ${JSON.stringify(scoped)});\n`);
17
18
  }
18
- export default createGems(${JSON.stringify(Object.keys(module.blocks))}, ${JSON.stringify(scoped)});
19
- `;
19
+ /** Webpack loader: `*.module.css` -> a `gems` JS module that injects its CSS at runtime. */
20
+ export default function gemcssLoader(source) {
21
+ const callback = this.async();
22
+ const options = (this.getOptions?.() ?? {});
23
+ buildGemsModule(source, this.resourcePath, options).then((js) => callback(null, js), (err) => callback(err instanceof Error ? err : new Error(String(err))));
20
24
  }
21
- function gemcssLoader(source) {
22
- const callback = this.async();
23
- const options = this.getOptions?.() ?? {};
24
- buildGemsModule(source, this.resourcePath, options).then(
25
- (js) => callback(null, js),
26
- (err) => callback(err instanceof Error ? err : new Error(String(err)))
27
- );
28
- }
29
- export {
30
- buildGemsModule,
31
- gemcssLoader as default
32
- };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gemcss",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "type": "module",
5
5
  "description": "Typed BEM classes from CSS modules — every block becomes a modifier-combinator function, with types inferred from the CSS",
6
6
  "keywords": [
@@ -41,8 +41,8 @@
41
41
  "default": "./dist/webpack.js"
42
42
  },
43
43
  "./ts-plugin": {
44
- "types": "./dist/ts-plugin.d.cts",
45
- "default": "./dist/ts-plugin.cjs"
44
+ "types": "./dist/plugin/ts-plugin.d.cts",
45
+ "default": "./dist/plugin/ts-plugin.cjs"
46
46
  }
47
47
  },
48
48
  "files": [
@@ -56,7 +56,7 @@
56
56
  "postcss-selector-parser": "^7.0.0"
57
57
  },
58
58
  "peerDependencies": {
59
- "typescript": "^5",
59
+ "typescript": "^5 || ^6",
60
60
  "vite": "^5 || ^6 || ^7",
61
61
  "webpack": "^5"
62
62
  },
@@ -79,20 +79,19 @@
79
79
  "gemcss": "link:.",
80
80
  "prettier": "^3.4.2",
81
81
  "publint": "0.3.21",
82
- "tsup": "^8.3.5",
83
- "typescript": "^5.7.2",
84
- "typescript-eslint": "^8.18.1",
82
+ "typescript": "^6",
83
+ "typescript-eslint": "^8.63.0",
85
84
  "vite": "^6.0.0",
86
85
  "vitest": "^2.1.8",
87
86
  "webpack": "^5.97.1"
88
87
  },
89
88
  "scripts": {
90
89
  "build": "pnpm build:lib && pnpm -r --filter 'example-*' build",
91
- "build:lib": "pnpm clean && tsup && node scripts/fix-ts-plugin-dts.mjs",
90
+ "build:lib": "pnpm clean && tsc -p tsconfig.build.json && tsc -p tsconfig.plugin.json && node scripts/write-plugin-pkgjson.mjs",
92
91
  "clean": "node -e \"fs.rmSync('dist',{recursive:true,force:true})\"",
93
92
  "test": "vitest run",
94
93
  "test:watch": "vitest",
95
- "typecheck": "tsc --noEmit",
94
+ "typecheck": "tsc --noEmit -p tsconfig.json && tsc --noEmit -p tsconfig.plugin.json",
96
95
  "lint": "eslint .",
97
96
  "format": "prettier --write .",
98
97
  "format:check": "prettier --check .",
@@ -1,89 +0,0 @@
1
- // src/core/parser.ts
2
- import postcss from "postcss";
3
- import selectorParser from "postcss-selector-parser";
4
- var GemcssParseError = class extends Error {
5
- constructor(message) {
6
- super(message);
7
- this.name = "GemcssParseError";
8
- }
9
- };
10
- function ctx(from) {
11
- return from ? ` (in ${from})` : "";
12
- }
13
- function parseCss(css, opts = {}) {
14
- const root = postcss.parse(css, { from: opts.from });
15
- const classNames = [];
16
- const seen = /* @__PURE__ */ new Set();
17
- root.walkRules((rule) => {
18
- selectorParser((selectors) => {
19
- selectors.walkClasses((cls) => {
20
- if (!seen.has(cls.value)) {
21
- seen.add(cls.value);
22
- classNames.push(cls.value);
23
- }
24
- });
25
- }).processSync(rule.selector);
26
- });
27
- return buildModule(classNames, opts.from);
28
- }
29
- function buildModule(classNames, from) {
30
- const blocks = {};
31
- for (const raw of classNames) {
32
- const segments = raw.split("_");
33
- const blockName = segments[0];
34
- if (!blockName) {
35
- throw new GemcssParseError(`Empty block name in class ".${raw}"${ctx(from)}`);
36
- }
37
- const block = blocks[blockName] ??= {
38
- name: blockName,
39
- modifiers: {}
40
- };
41
- const tail = segments.slice(1);
42
- if (tail.length === 0) continue;
43
- if (tail.length === 1) {
44
- const key = tail[0];
45
- if (!key) throw new GemcssParseError(`Empty modifier in class ".${raw}"${ctx(from)}`);
46
- assignBool(block, key, raw, from);
47
- continue;
48
- }
49
- if (tail.length === 2) {
50
- const key = tail[0];
51
- const value = tail[1];
52
- if (!key || !value) {
53
- throw new GemcssParseError(`Empty modifier key/value in class ".${raw}"${ctx(from)}`);
54
- }
55
- assignEnum(block, key, value, raw, from);
56
- continue;
57
- }
58
- throw new GemcssParseError(
59
- `Class ".${raw}" has more than one modifier segment after block "${blockName}". Allowed: block, block_bool, block_key_value.${ctx(from)}`
60
- );
61
- }
62
- return { blocks };
63
- }
64
- function assignBool(block, key, raw, from) {
65
- const existing = block.modifiers[key];
66
- if (existing && existing.kind === "enum") {
67
- throw new GemcssParseError(
68
- `Modifier "${key}" of block "${block.name}" used as both boolean (".${raw}") and enum.${ctx(from)}`
69
- );
70
- }
71
- block.modifiers[key] = { kind: "bool" };
72
- }
73
- function assignEnum(block, key, value, raw, from) {
74
- const existing = block.modifiers[key];
75
- if (existing && existing.kind === "bool") {
76
- throw new GemcssParseError(
77
- `Modifier "${key}" of block "${block.name}" used as both enum (".${raw}") and boolean.${ctx(from)}`
78
- );
79
- }
80
- const mod = existing && existing.kind === "enum" ? existing : block.modifiers[key] = { kind: "enum", values: [] };
81
- if (!mod.values.includes(value)) {
82
- mod.values.push(value);
83
- }
84
- }
85
-
86
- export {
87
- GemcssParseError,
88
- parseCss
89
- };