@cssdoc/config 0.1.0 → 0.3.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.
@@ -6,11 +6,15 @@
6
6
  "type": "object",
7
7
  "additionalProperties": false,
8
8
  "properties": {
9
- "$schema": { "type": "string" },
9
+ "$schema": {
10
+ "type": "string"
11
+ },
10
12
  "extends": {
11
13
  "description": "Paths to other cssdoc.json files (or packages) to inherit tag definitions from.",
12
14
  "type": "array",
13
- "items": { "type": "string" }
15
+ "items": {
16
+ "type": "string"
17
+ }
14
18
  },
15
19
  "noStandardTags": {
16
20
  "description": "Disable every built-in standard tag; only tagDefinitions remain supported.",
@@ -24,18 +28,123 @@
24
28
  "additionalProperties": false,
25
29
  "required": ["tagName", "syntaxKind"],
26
30
  "properties": {
27
- "tagName": { "type": "string", "pattern": "^@?[a-zA-Z][a-zA-Z0-9-]*$" },
28
- "syntaxKind": { "enum": ["record", "block", "modifier", "inline"] },
29
- "allowMultiple": { "type": "boolean" },
30
- "recordKind": { "enum": ["component", "utility", "rule", "declaration"] },
31
- "aliasFor": { "type": "string" }
31
+ "tagName": {
32
+ "type": "string",
33
+ "pattern": "^@?[a-zA-Z][a-zA-Z0-9-]*$"
34
+ },
35
+ "syntaxKind": {
36
+ "enum": ["record", "block", "modifier", "inline"]
37
+ },
38
+ "allowMultiple": {
39
+ "type": "boolean"
40
+ },
41
+ "recordKind": {
42
+ "enum": ["component", "utility", "rule", "declaration"]
43
+ },
44
+ "aliasFor": {
45
+ "type": "string"
46
+ }
32
47
  }
33
48
  }
34
49
  },
35
50
  "supportForTags": {
36
51
  "description": "Enable or disable specific tags by name.",
37
52
  "type": "object",
38
- "additionalProperties": { "type": "boolean" }
53
+ "additionalProperties": {
54
+ "type": "boolean"
55
+ }
56
+ },
57
+ "modifierConvention": {
58
+ "description": "How modifier classes are spelled: a preset name (bem, rscss, bare) or a custom convention.",
59
+ "oneOf": [
60
+ {
61
+ "enum": ["bem", "rscss", "bare"]
62
+ },
63
+ {
64
+ "type": "object",
65
+ "additionalProperties": false,
66
+ "required": ["structure", "separator"],
67
+ "properties": {
68
+ "structure": {
69
+ "enum": ["chained", "suffix", "attribute"]
70
+ },
71
+ "separator": {
72
+ "oneOf": [
73
+ {
74
+ "type": "string"
75
+ },
76
+ {
77
+ "type": "array",
78
+ "items": {
79
+ "type": "string"
80
+ }
81
+ }
82
+ ]
83
+ },
84
+ "elementSeparator": {
85
+ "description": "BEM-style element delimiter (e.g. \"__\"); matched classes become parts.",
86
+ "oneOf": [
87
+ {
88
+ "type": "string"
89
+ },
90
+ {
91
+ "type": "array",
92
+ "items": {
93
+ "type": "string"
94
+ }
95
+ }
96
+ ]
97
+ },
98
+ "statePrefixes": {
99
+ "description": "Class prefixes that mark a state (e.g. [\"is-\",\"has-\"]).",
100
+ "type": "array",
101
+ "items": {
102
+ "type": "string"
103
+ }
104
+ },
105
+ "statePseudoClasses": {
106
+ "description": "Native pseudo-classes (without the colon) recognized as states, e.g. [\"disabled\",\"checked\"]. Overrides the built-in default set.",
107
+ "type": "array",
108
+ "items": {
109
+ "type": "string"
110
+ }
111
+ },
112
+ "propValue": {
113
+ "type": "boolean"
114
+ },
115
+ "propValueSeparator": {
116
+ "type": "string"
117
+ }
118
+ }
119
+ }
120
+ ]
121
+ },
122
+ "rules": {
123
+ "description": "Per-rule severity overrides (off/warn/error).",
124
+ "type": "object",
125
+ "additionalProperties": {
126
+ "enum": ["off", "warn", "error"]
127
+ }
128
+ },
129
+ "naming": {
130
+ "description": "Enforce a name case on class names: a preset (pascalCase, camelCase, lowercase) or a custom regex.",
131
+ "type": "object",
132
+ "additionalProperties": false,
133
+ "properties": {
134
+ "component": {
135
+ "type": "string"
136
+ },
137
+ "part": {
138
+ "type": "string"
139
+ }
140
+ }
141
+ },
142
+ "structureIgnore": {
143
+ "description": "Class names to exempt from the structure-unknown-selector rule — legitimately-external classes (utilities, cross-component refs) referenced in @structure. Literal names or simple globs where * matches any run of characters (e.g. util-*).",
144
+ "type": "array",
145
+ "items": {
146
+ "type": "string"
147
+ }
39
148
  }
40
149
  }
41
150
  }
package/dist/index.d.mts CHANGED
@@ -1,8 +1,20 @@
1
- import { CssDocConfiguration, CssDocTagDefinition } from "@cssdoc/core";
1
+ import { CssDocConfiguration, CssDocTagDefinition, ModifierConventionInput } from "@cssdoc/core";
2
2
 
3
3
  //#region src/CssDocConfigFile.d.ts
4
- /** The conventional file name loaders look for. */
5
- declare const CSSDOC_CONFIG_FILENAME = "cssdoc.json";
4
+ /** A per-rule severity override, as spelled in `cssdoc.json`. */
5
+ type RuleSeverityOverride = "off" | "warn" | "error";
6
+ /** The `naming` block: a name case (preset or custom regex) per class kind, as spelled in `cssdoc.json`. */
7
+ interface NamingOverride {
8
+ component?: string;
9
+ part?: string;
10
+ }
11
+ /**
12
+ * The conventional file names loaders look for, in preference order. Both are parsed the same way
13
+ * (JSON with comments); `.jsonc` just makes the comments explicit.
14
+ */
15
+ declare const CSSDOC_CONFIG_FILENAMES: readonly ["cssdoc.json", "cssdoc.jsonc"];
16
+ /** The primary config file name (`cssdoc.json`). See {@link CSSDOC_CONFIG_FILENAMES} for all names. */
17
+ declare const CSSDOC_CONFIG_FILENAME: "cssdoc.json";
6
18
  /** A loaded `cssdoc.json` (plus the files it `extends`), ready to configure a parser. */
7
19
  declare class CssDocConfigFile {
8
20
  /** The absolute path the file was loaded from (or would be, when not found). */
@@ -19,6 +31,23 @@ declare class CssDocConfigFile {
19
31
  readonly supportForTags: ReadonlyMap<string, boolean>;
20
32
  /** The resolved files this one `extends`, in declaration order. */
21
33
  readonly extendsFiles: readonly CssDocConfigFile[];
34
+ /** The modifier convention declared by this file, if any. */
35
+ readonly modifierConvention?: ModifierConventionInput;
36
+ /**
37
+ * The per-rule severity overrides, merged across the `extends` chain (this file wins). Not the
38
+ * resolved severities — pass these to `resolveRuleSeverities` in `@cssdoc/providers`.
39
+ */
40
+ readonly ruleSeverities: Readonly<Record<string, RuleSeverityOverride>>;
41
+ /**
42
+ * The name-case conventions, merged across the `extends` chain (this file wins). Pass to
43
+ * `resolveNaming` in `@cssdoc/providers`.
44
+ */
45
+ readonly naming: Readonly<NamingOverride>;
46
+ /**
47
+ * Class names exempt from the `structure-unknown-selector` rule, merged (union) across the `extends`
48
+ * chain. Pass to `lintModel` in `@cssdoc/providers`.
49
+ */
50
+ readonly structureIgnore: readonly string[];
22
51
  private constructor();
23
52
  /** Whether this file — or any file it extends — reported an error. */
24
53
  get hasErrors(): boolean;
@@ -120,7 +149,90 @@ declare const cssDocSchema: {
120
149
  readonly type: "boolean";
121
150
  };
122
151
  };
152
+ readonly modifierConvention: {
153
+ readonly description: "How modifier classes are spelled: a preset name (bem, rscss, bare) or a custom convention.";
154
+ readonly oneOf: readonly [{
155
+ readonly enum: readonly ["bem", "rscss", "bare"];
156
+ }, {
157
+ readonly type: "object";
158
+ readonly additionalProperties: false;
159
+ readonly required: readonly ["structure", "separator"];
160
+ readonly properties: {
161
+ readonly structure: {
162
+ readonly enum: readonly ["chained", "suffix", "attribute"];
163
+ };
164
+ readonly separator: {
165
+ readonly oneOf: readonly [{
166
+ readonly type: "string";
167
+ }, {
168
+ readonly type: "array";
169
+ readonly items: {
170
+ readonly type: "string";
171
+ };
172
+ }];
173
+ };
174
+ readonly elementSeparator: {
175
+ readonly description: "BEM-style element delimiter (e.g. \"__\"); matched classes become parts.";
176
+ readonly oneOf: readonly [{
177
+ readonly type: "string";
178
+ }, {
179
+ readonly type: "array";
180
+ readonly items: {
181
+ readonly type: "string";
182
+ };
183
+ }];
184
+ };
185
+ readonly statePrefixes: {
186
+ readonly description: "Class prefixes that mark a state (e.g. [\"is-\",\"has-\"]).";
187
+ readonly type: "array";
188
+ readonly items: {
189
+ readonly type: "string";
190
+ };
191
+ };
192
+ readonly statePseudoClasses: {
193
+ readonly description: "Native pseudo-classes (without the colon) recognized as states, e.g. [\"disabled\",\"checked\"]. Overrides the built-in default set.";
194
+ readonly type: "array";
195
+ readonly items: {
196
+ readonly type: "string";
197
+ };
198
+ };
199
+ readonly propValue: {
200
+ readonly type: "boolean";
201
+ };
202
+ readonly propValueSeparator: {
203
+ readonly type: "string";
204
+ };
205
+ };
206
+ }];
207
+ };
208
+ readonly rules: {
209
+ readonly description: "Per-rule severity overrides (off/warn/error).";
210
+ readonly type: "object";
211
+ readonly additionalProperties: {
212
+ readonly enum: readonly ["off", "warn", "error"];
213
+ };
214
+ };
215
+ readonly naming: {
216
+ readonly description: "Enforce a name case on class names: a preset (pascalCase, camelCase, lowercase) or a custom regex.";
217
+ readonly type: "object";
218
+ readonly additionalProperties: false;
219
+ readonly properties: {
220
+ readonly component: {
221
+ readonly type: "string";
222
+ };
223
+ readonly part: {
224
+ readonly type: "string";
225
+ };
226
+ };
227
+ };
228
+ readonly structureIgnore: {
229
+ readonly description: "Class names to exempt from the structure-unknown-selector rule — legitimately-external classes (utilities, cross-component refs) referenced in @structure. Literal names or simple globs where * matches any run of characters (e.g. util-*).";
230
+ readonly type: "array";
231
+ readonly items: {
232
+ readonly type: "string";
233
+ };
234
+ };
123
235
  };
124
236
  };
125
237
  //#endregion
126
- export { CSSDOC_CONFIG_FILENAME, CssDocConfigFile, cssDocSchema };
238
+ export { CSSDOC_CONFIG_FILENAME, CSSDOC_CONFIG_FILENAMES, CssDocConfigFile, cssDocSchema };
package/dist/index.mjs CHANGED
@@ -2,6 +2,7 @@ import { createRequire } from "node:module";
2
2
  import { existsSync, readFileSync } from "node:fs";
3
3
  import { dirname, parse, resolve } from "node:path";
4
4
  import { Ajv } from "ajv";
5
+ import { parse as parse$1, printParseErrorCode } from "jsonc-parser";
5
6
  import { CssDocConfiguration, CssDocTagDefinition } from "@cssdoc/core";
6
7
  //#region src/schema.ts
7
8
  /**
@@ -63,6 +64,71 @@ const cssDocSchema = {
63
64
  description: "Enable or disable specific tags by name.",
64
65
  type: "object",
65
66
  additionalProperties: { type: "boolean" }
67
+ },
68
+ modifierConvention: {
69
+ description: "How modifier classes are spelled: a preset name (bem, rscss, bare) or a custom convention.",
70
+ oneOf: [{ enum: [
71
+ "bem",
72
+ "rscss",
73
+ "bare"
74
+ ] }, {
75
+ type: "object",
76
+ additionalProperties: false,
77
+ required: ["structure", "separator"],
78
+ properties: {
79
+ structure: { enum: [
80
+ "chained",
81
+ "suffix",
82
+ "attribute"
83
+ ] },
84
+ separator: { oneOf: [{ type: "string" }, {
85
+ type: "array",
86
+ items: { type: "string" }
87
+ }] },
88
+ elementSeparator: {
89
+ description: "BEM-style element delimiter (e.g. \"__\"); matched classes become parts.",
90
+ oneOf: [{ type: "string" }, {
91
+ type: "array",
92
+ items: { type: "string" }
93
+ }]
94
+ },
95
+ statePrefixes: {
96
+ description: "Class prefixes that mark a state (e.g. [\"is-\",\"has-\"]).",
97
+ type: "array",
98
+ items: { type: "string" }
99
+ },
100
+ statePseudoClasses: {
101
+ description: "Native pseudo-classes (without the colon) recognized as states, e.g. [\"disabled\",\"checked\"]. Overrides the built-in default set.",
102
+ type: "array",
103
+ items: { type: "string" }
104
+ },
105
+ propValue: { type: "boolean" },
106
+ propValueSeparator: { type: "string" }
107
+ }
108
+ }]
109
+ },
110
+ rules: {
111
+ description: "Per-rule severity overrides (off/warn/error).",
112
+ type: "object",
113
+ additionalProperties: { enum: [
114
+ "off",
115
+ "warn",
116
+ "error"
117
+ ] }
118
+ },
119
+ naming: {
120
+ description: "Enforce a name case on class names: a preset (pascalCase, camelCase, lowercase) or a custom regex.",
121
+ type: "object",
122
+ additionalProperties: false,
123
+ properties: {
124
+ component: { type: "string" },
125
+ part: { type: "string" }
126
+ }
127
+ },
128
+ structureIgnore: {
129
+ description: "Class names to exempt from the structure-unknown-selector rule — legitimately-external classes (utilities, cross-component refs) referenced in @structure. Literal names or simple globs where * matches any run of characters (e.g. util-*).",
130
+ type: "array",
131
+ items: { type: "string" }
66
132
  }
67
133
  }
68
134
  };
@@ -78,8 +144,13 @@ const cssDocSchema = {
78
144
  * @module
79
145
  */
80
146
  const validateSchema = new Ajv({ allErrors: true }).compile(cssDocSchema);
81
- /** The conventional file name loaders look for. */
82
- const CSSDOC_CONFIG_FILENAME = "cssdoc.json";
147
+ /**
148
+ * The conventional file names loaders look for, in preference order. Both are parsed the same way
149
+ * (JSON with comments); `.jsonc` just makes the comments explicit.
150
+ */
151
+ const CSSDOC_CONFIG_FILENAMES = ["cssdoc.json", "cssdoc.jsonc"];
152
+ /** The primary config file name (`cssdoc.json`). See {@link CSSDOC_CONFIG_FILENAMES} for all names. */
153
+ const CSSDOC_CONFIG_FILENAME = CSSDOC_CONFIG_FILENAMES[0];
83
154
  /** A loaded `cssdoc.json` (plus the files it `extends`), ready to configure a parser. */
84
155
  var CssDocConfigFile = class CssDocConfigFile {
85
156
  /** The absolute path the file was loaded from (or would be, when not found). */
@@ -96,6 +167,23 @@ var CssDocConfigFile = class CssDocConfigFile {
96
167
  supportForTags;
97
168
  /** The resolved files this one `extends`, in declaration order. */
98
169
  extendsFiles;
170
+ /** The modifier convention declared by this file, if any. */
171
+ modifierConvention;
172
+ /**
173
+ * The per-rule severity overrides, merged across the `extends` chain (this file wins). Not the
174
+ * resolved severities — pass these to `resolveRuleSeverities` in `@cssdoc/providers`.
175
+ */
176
+ ruleSeverities;
177
+ /**
178
+ * The name-case conventions, merged across the `extends` chain (this file wins). Pass to
179
+ * `resolveNaming` in `@cssdoc/providers`.
180
+ */
181
+ naming;
182
+ /**
183
+ * Class names exempt from the `structure-unknown-selector` rule, merged (union) across the `extends`
184
+ * chain. Pass to `lintModel` in `@cssdoc/providers`.
185
+ */
186
+ structureIgnore;
99
187
  constructor(init) {
100
188
  this.filePath = init.filePath;
101
189
  this.fileNotFound = init.fileNotFound;
@@ -104,6 +192,21 @@ var CssDocConfigFile = class CssDocConfigFile {
104
192
  this.tagDefinitions = init.tagDefinitions;
105
193
  this.supportForTags = init.supportForTags;
106
194
  this.extendsFiles = init.extendsFiles;
195
+ this.modifierConvention = init.modifierConvention;
196
+ const severities = {};
197
+ const naming = {};
198
+ const structureIgnore = /* @__PURE__ */ new Set();
199
+ for (const extended of init.extendsFiles) {
200
+ Object.assign(severities, extended.ruleSeverities);
201
+ Object.assign(naming, extended.naming);
202
+ for (const g of extended.structureIgnore) structureIgnore.add(g);
203
+ }
204
+ Object.assign(severities, init.rules);
205
+ Object.assign(naming, init.naming);
206
+ for (const g of init.structureIgnore) structureIgnore.add(g);
207
+ this.ruleSeverities = severities;
208
+ this.naming = naming;
209
+ this.structureIgnore = [...structureIgnore];
107
210
  }
108
211
  /** Whether this file — or any file it extends — reported an error. */
109
212
  get hasErrors() {
@@ -127,6 +230,7 @@ var CssDocConfigFile = class CssDocConfigFile {
127
230
  const definition = configuration.tryGetTagDefinition(tagName);
128
231
  if (definition) configuration.setSupportForTag(definition, supported);
129
232
  }
233
+ if (this.modifierConvention !== void 0) configuration.setModifierConvention(this.modifierConvention);
130
234
  }
131
235
  /**
132
236
  * Build a {@link CssDocConfiguration} from this file. Convenience for the common case.
@@ -158,8 +262,10 @@ var CssDocConfigFile = class CssDocConfigFile {
158
262
  let current = resolve(folderPath);
159
263
  const root = parse(current).root;
160
264
  for (;;) {
161
- const candidate = resolve(current, CSSDOC_CONFIG_FILENAME);
162
- if (existsSync(candidate)) return CssDocConfigFile._loadFile(candidate, /* @__PURE__ */ new Set());
265
+ for (const name of CSSDOC_CONFIG_FILENAMES) {
266
+ const candidate = resolve(current, name);
267
+ if (existsSync(candidate)) return CssDocConfigFile._loadFile(candidate, /* @__PURE__ */ new Set());
268
+ }
163
269
  if (current === root) break;
164
270
  current = dirname(current);
165
271
  }
@@ -170,7 +276,10 @@ var CssDocConfigFile = class CssDocConfigFile {
170
276
  noStandardTags: false,
171
277
  tagDefinitions: [],
172
278
  supportForTags: /* @__PURE__ */ new Map(),
173
- extendsFiles: []
279
+ extendsFiles: [],
280
+ rules: {},
281
+ naming: {},
282
+ structureIgnore: []
174
283
  });
175
284
  }
176
285
  static _loadFile(filePath, visited) {
@@ -182,7 +291,10 @@ var CssDocConfigFile = class CssDocConfigFile {
182
291
  noStandardTags: false,
183
292
  tagDefinitions: [],
184
293
  supportForTags: /* @__PURE__ */ new Map(),
185
- extendsFiles: []
294
+ extendsFiles: [],
295
+ rules: {},
296
+ naming: {},
297
+ structureIgnore: []
186
298
  });
187
299
  if (visited.has(filePath)) {
188
300
  messages.push("Circular extends reference; skipped.");
@@ -190,11 +302,11 @@ var CssDocConfigFile = class CssDocConfigFile {
190
302
  }
191
303
  visited.add(filePath);
192
304
  if (!existsSync(filePath)) return empty(true);
193
- let raw;
194
- try {
195
- raw = JSON.parse(readFileSync(filePath, "utf8"));
196
- } catch (error) {
197
- messages.push(`Invalid JSON: ${error.message}`);
305
+ const parseErrors = [];
306
+ const raw = parse$1(readFileSync(filePath, "utf8"), parseErrors, { allowTrailingComma: true });
307
+ if (parseErrors.length > 0 || raw === void 0) {
308
+ for (const err of parseErrors) messages.push(`Invalid JSON at offset ${err.offset}: ${printParseErrorCode(err.error)}`);
309
+ if (parseErrors.length === 0) messages.push("Invalid JSON: empty or unparseable config.");
198
310
  return empty(false);
199
311
  }
200
312
  if (!validateSchema(raw)) {
@@ -226,9 +338,13 @@ var CssDocConfigFile = class CssDocConfigFile {
226
338
  noStandardTags: raw.noStandardTags ?? false,
227
339
  tagDefinitions,
228
340
  supportForTags: new Map(Object.entries(raw.supportForTags ?? {})),
229
- extendsFiles
341
+ extendsFiles,
342
+ modifierConvention: raw.modifierConvention,
343
+ rules: raw.rules ?? {},
344
+ naming: raw.naming ?? {},
345
+ structureIgnore: raw.structureIgnore ?? []
230
346
  });
231
347
  }
232
348
  };
233
349
  //#endregion
234
- export { CSSDOC_CONFIG_FILENAME, CssDocConfigFile, cssDocSchema };
350
+ export { CSSDOC_CONFIG_FILENAME, CSSDOC_CONFIG_FILENAMES, CssDocConfigFile, cssDocSchema };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cssdoc/config",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "Load a cssdoc.json configuration file (custom tags, extends) into an @cssdoc/core CssDocConfiguration — TSDoc-config, for CSS.",
5
5
  "keywords": [
6
6
  "config",
@@ -31,7 +31,8 @@
31
31
  },
32
32
  "dependencies": {
33
33
  "ajv": "^8.20.0",
34
- "@cssdoc/core": "0.1.0"
34
+ "jsonc-parser": "^3.3.1",
35
+ "@cssdoc/core": "0.3.0"
35
36
  },
36
37
  "devDependencies": {
37
38
  "@types/node": "^24.13.3",