@cssdoc/index 0.1.0 → 0.2.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.
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { CssAnimation, CssCondition, CssDocConfiguration, CssDocEntry, CssFunction, CssModifier, CssPart, CssPropertyDeclared, CssState, StructureNode } from "@cssdoc/core";
1
+ import { CssAnimation, CssCondition, CssDocConfiguration, CssDocEntry, CssFunction, CssModifier, CssPart, CssPropertyDeclared, CssState, ModifierConventionInput, ModifierMatcher, StructureNode } from "@cssdoc/core";
2
2
 
3
3
  //#region src/index.d.ts
4
4
  /** A 1-based line/column position (matching PostCSS). */
@@ -17,16 +17,18 @@ interface Location {
17
17
  span: SourceSpan;
18
18
  }
19
19
  /**
20
- * A class-attribute usage: the classes on one element, the specific `token` under inspection, and the
21
- * resolved `base` component class (when one of the tokens names a documented component). Producers for
22
- * HTML, JSX, and CSS selectors all emit this shape.
20
+ * A member usage on one element: the classes on the element, the specific `token` under inspection,
21
+ * and the resolved `base` component class (when one of the tokens names a documented component).
22
+ * `token` is normally a class token, but for attribute conventions (CUBE) it may be an attribute
23
+ * expression like `data-variant="ghost"`. Producers for HTML, JSX, and CSS selectors all emit this
24
+ * shape.
23
25
  */
24
26
  interface ClassUsage {
25
27
  /** The base component class among the tokens, if any (e.g. `.button` → `button`). */
26
28
  base?: string;
27
29
  /** Every class token on the element. */
28
30
  tokens: string[];
29
- /** The specific token this usage is about (e.g. a `-modifier`). */
31
+ /** The specific token/expression this usage is about (a modifier candidate). */
30
32
  token: string;
31
33
  /** Where the token sits in the host document. */
32
34
  loc?: SourceSpan;
@@ -77,9 +79,11 @@ interface CssDocManifest {
77
79
  declare class CssDocIndex {
78
80
  readonly file?: string;
79
81
  readonly records: readonly RecordInfo[];
82
+ /** The modifier matcher for this index's convention — how members are matched and rendered. */
83
+ readonly matcher: ModifierMatcher;
80
84
  private readonly byName;
81
85
  private readonly byClass;
82
- constructor(records: RecordInfo[], file?: string);
86
+ constructor(records: RecordInfo[], file?: string, matcher?: ModifierMatcher);
83
87
  /** Every documented record's model entry. */
84
88
  get entries(): CssDocEntry[];
85
89
  /** The record whose base class matches `className` (with or without a leading dot). */
@@ -94,7 +98,7 @@ declare class CssDocIndex {
94
98
  conditionsFor(name: string): CssCondition[];
95
99
  animationsFor(name: string): CssAnimation[];
96
100
  structureFor(name: string): StructureNode[] | undefined;
97
- /** Whether `modifier` (with or without a leading dot) is a documented modifier of `base`. */
101
+ /** Whether `modifier` (a class token or attribute expression) is a documented modifier of `base`. */
98
102
  isModifier(base: string, modifier: string): boolean;
99
103
  /** The deprecation of a modifier on `base`, if it is deprecated. */
100
104
  deprecationOf(base: string, modifier: string): {
@@ -129,6 +133,7 @@ declare function indexFromEntries(entries: CssDocEntry[], file?: string): CssDoc
129
133
  declare function createIndex(css: string, options?: {
130
134
  file?: string;
131
135
  configuration?: CssDocConfiguration;
136
+ modifierConvention?: ModifierConventionInput;
132
137
  }): CssDocIndex;
133
138
  /**
134
139
  * Extract custom-property assignments (`--name: value`) and `var(--name, fallback)` references from
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { parseCssDocs, parseDocComment, recordNameOf } from "@cssdoc/core";
1
+ import { DEFAULT_MODIFIER_CONVENTION, ModifierMatcher, parseCssDocs, parseDocComment, recordNameOf, resolveModifierConvention } from "@cssdoc/core";
2
2
  import postcss from "postcss";
3
3
  import valueParser from "postcss-value-parser";
4
4
  //#region src/index.ts
@@ -18,7 +18,6 @@ import valueParser from "postcss-value-parser";
18
18
  function memberKey(kind, id = "") {
19
19
  return id ? `${kind}:${id}` : kind;
20
20
  }
21
- const escapeRe = (value) => value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
22
21
  const stripDot = (name) => name.replace(/^\./u, "");
23
22
  const spanOf = (node) => {
24
23
  const { source } = node;
@@ -39,11 +38,14 @@ const spanOf = (node) => {
39
38
  var CssDocIndex = class {
40
39
  file;
41
40
  records;
41
+ /** The modifier matcher for this index's convention — how members are matched and rendered. */
42
+ matcher;
42
43
  byName = /* @__PURE__ */ new Map();
43
44
  byClass = /* @__PURE__ */ new Map();
44
- constructor(records, file) {
45
+ constructor(records, file, matcher) {
45
46
  this.records = records;
46
47
  this.file = file;
48
+ this.matcher = matcher ?? new ModifierMatcher(DEFAULT_MODIFIER_CONVENTION);
47
49
  for (const record of records) {
48
50
  this.byName.set(record.entry.name, record);
49
51
  this.byClass.set(stripDot(record.entry.className), record);
@@ -85,14 +87,14 @@ var CssDocIndex = class {
85
87
  structureFor(name) {
86
88
  return this.byName.get(name)?.entry.structure;
87
89
  }
88
- /** Whether `modifier` (with or without a leading dot) is a documented modifier of `base`. */
90
+ /** Whether `modifier` (a class token or attribute expression) is a documented modifier of `base`. */
89
91
  isModifier(base, modifier) {
90
- const wanted = stripDot(modifier);
92
+ const wanted = this.matcher.normalizeMember(modifier);
91
93
  return this.byClass.get(stripDot(base))?.entry.modifiers.some((m) => m.name === wanted) ?? false;
92
94
  }
93
95
  /** The deprecation of a modifier on `base`, if it is deprecated. */
94
96
  deprecationOf(base, modifier) {
95
- const wanted = stripDot(modifier);
97
+ const wanted = this.matcher.normalizeMember(modifier);
96
98
  return this.byClass.get(stripDot(base))?.entry.modifiers.find((m) => m.name === wanted)?.deprecated;
97
99
  }
98
100
  /** Every declared custom property, paired with the record that declares it (for `var(...)` completion). */
@@ -139,8 +141,8 @@ function indexFromEntries(entries, file) {
139
141
  })), file);
140
142
  }
141
143
  /** Scan a record's nodes for member spans, recording each member's first occurrence. */
142
- function scanNodes(nodes, build, base) {
143
- const modRe = new RegExp(`(?:${escapeRe(base)}|:scope)((?:\\.-[\\w-]+)+)`, "gu");
144
+ function scanNodes(nodes, build, base, matcher) {
145
+ const baseNoDot = stripDot(base);
144
146
  const set = (key, node) => {
145
147
  const span = spanOf(node);
146
148
  if (span && !build.memberSpans.has(key)) build.memberSpans.set(key, span);
@@ -149,10 +151,17 @@ function scanNodes(nodes, build, base) {
149
151
  build.selectorText += ` ${node.selector}`;
150
152
  if (`.${stripDot(node.selector.trim())}` === base && !build.span) build.span = spanOf(node);
151
153
  for (const selector of node.selector.split(",")) {
152
- for (const m of selector.matchAll(modRe)) for (const mod of m[1].matchAll(/\.(-[\w-]+)/gu)) set(memberKey("modifier", mod[1]), node);
153
154
  for (const s of selector.matchAll(/:state\(\s*([\w-]+)\s*\)/gu)) set(memberKey("state", s[1]), node);
154
155
  const bare = selector.replace(/::?[\w-]+(\([^)]*\))?/gu, "");
155
- for (const p of bare.matchAll(/\.([a-z][\w-]*)/gu)) set(memberKey("part", p[1]), node);
156
+ const modNames = /* @__PURE__ */ new Set();
157
+ for (const mod of matcher.modifiersIn(bare, baseNoDot)) {
158
+ modNames.add(mod.name);
159
+ set(memberKey("modifier", mod.name), node);
160
+ }
161
+ for (const p of bare.matchAll(/\.([a-z][\w-]*)/gu)) {
162
+ if (modNames.has(p[1])) continue;
163
+ set(memberKey("part", p[1]), node);
164
+ }
156
165
  }
157
166
  } else if (node.type === "atrule") {
158
167
  if (node.name === "property") set(memberKey("property", node.params.trim()), node);
@@ -165,7 +174,7 @@ function scanNodes(nodes, build, base) {
165
174
  if (layer) set(memberKey("layer", layer), node);
166
175
  }
167
176
  else if (node.name === "container" || node.name === "supports" || node.name === "media") set(memberKey("condition", `${node.name}:${node.params.trim()}`), node);
168
- if (node.nodes) scanNodes(node.nodes, build, base);
177
+ if (node.nodes) scanNodes(node.nodes, build, base, matcher);
169
178
  }
170
179
  }
171
180
  /**
@@ -177,7 +186,11 @@ function scanNodes(nodes, build, base) {
177
186
  * @returns The index.
178
187
  */
179
188
  function createIndex(css, options = {}) {
180
- const entries = parseCssDocs(css, { configuration: options.configuration });
189
+ const matcher = new ModifierMatcher(resolveModifierConvention(options.modifierConvention ?? options.configuration?.modifierConvention));
190
+ const entries = parseCssDocs(css, {
191
+ configuration: options.configuration,
192
+ modifierConvention: options.modifierConvention
193
+ });
181
194
  const byName = new Map(entries.map((e) => [e.name, e]));
182
195
  const builds = /* @__PURE__ */ new Map();
183
196
  const root = postcss.parse(css);
@@ -200,7 +213,7 @@ function createIndex(css, options = {}) {
200
213
  continue;
201
214
  }
202
215
  }
203
- if (current) scanNodes([node], current, current.entry.className);
216
+ if (current) scanNodes([node], current, current.entry.className, matcher);
204
217
  }
205
218
  return new CssDocIndex(entries.map((entry) => builds.get(entry.name) ?? {
206
219
  entry,
@@ -208,7 +221,7 @@ function createIndex(css, options = {}) {
208
221
  authoredModifiers: /* @__PURE__ */ new Set(),
209
222
  authoredParts: /* @__PURE__ */ new Set(),
210
223
  selectorText: ""
211
- }), options.file);
224
+ }), options.file, matcher);
212
225
  }
213
226
  /**
214
227
  * Extract custom-property assignments (`--name: value`) and `var(--name, fallback)` references from
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cssdoc/index",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "A queryable semantic index over the @cssdoc/core model, plus a host-agnostic Usage abstraction and source spans — the shared data layer for cssdoc's linters and language server.",
5
5
  "keywords": [
6
6
  "css",
@@ -30,7 +30,7 @@
30
30
  "dependencies": {
31
31
  "postcss": "^8.5.16",
32
32
  "postcss-value-parser": "^4.2.0",
33
- "@cssdoc/core": "0.1.0"
33
+ "@cssdoc/core": "0.2.0"
34
34
  },
35
35
  "devDependencies": {
36
36
  "@types/node": "^24.13.3",