@cssdoc/index 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.
package/dist/index.d.mts CHANGED
@@ -1,4 +1,5 @@
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
+ import postcss from "postcss";
2
3
 
3
4
  //#region src/index.d.ts
4
5
  /** A 1-based line/column position (matching PostCSS). */
@@ -17,16 +18,18 @@ interface Location {
17
18
  span: SourceSpan;
18
19
  }
19
20
  /**
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.
21
+ * A member usage on one element: the classes on the element, the specific `token` under inspection,
22
+ * and the resolved `base` component class (when one of the tokens names a documented component).
23
+ * `token` is normally a class token, but for attribute conventions (CUBE) it may be an attribute
24
+ * expression like `data-variant="ghost"`. Producers for HTML, JSX, and CSS selectors all emit this
25
+ * shape.
23
26
  */
24
27
  interface ClassUsage {
25
28
  /** The base component class among the tokens, if any (e.g. `.button` → `button`). */
26
29
  base?: string;
27
30
  /** Every class token on the element. */
28
31
  tokens: string[];
29
- /** The specific token this usage is about (e.g. a `-modifier`). */
32
+ /** The specific token/expression this usage is about (a modifier candidate). */
30
33
  token: string;
31
34
  /** Where the token sits in the host document. */
32
35
  loc?: SourceSpan;
@@ -50,7 +53,7 @@ interface CssValueSites {
50
53
  usages: PropertyUsage[];
51
54
  }
52
55
  /** The kinds of record member a span can be keyed to. */
53
- type MemberKind = "record" | "modifier" | "part" | "property" | "function" | "animation" | "layer" | "state" | "condition";
56
+ type MemberKind = "record" | "modifier" | "part" | "shadow-part" | "property" | "function" | "animation" | "layer" | "state" | "condition";
54
57
  /** Build a stable span key for a member. */
55
58
  declare function memberKey(kind: MemberKind, id?: string): string;
56
59
  /** One record in the index: its model entry plus source spans and the facts drift checks need. */
@@ -62,8 +65,10 @@ interface RecordInfo {
62
65
  memberSpans: Map<string, SourceSpan>;
63
66
  /** Modifier names authored via `@modifier` (used for drift detection). */
64
67
  authoredModifiers: Set<string>;
65
- /** Part names authored via `@part`/`@csspart` (used for drift detection). */
68
+ /** Part names authored via `@part` (used for drift detection). */
66
69
  authoredParts: Set<string>;
70
+ /** Shadow-part names authored via `@csspart` (used for drift detection). */
71
+ authoredShadowParts: Set<string>;
67
72
  /** The concatenated selector text of the record's rules (used for drift detection). */
68
73
  selectorText: string;
69
74
  }
@@ -77,9 +82,11 @@ interface CssDocManifest {
77
82
  declare class CssDocIndex {
78
83
  readonly file?: string;
79
84
  readonly records: readonly RecordInfo[];
85
+ /** The modifier matcher for this index's convention — how members are matched and rendered. */
86
+ readonly matcher: ModifierMatcher;
80
87
  private readonly byName;
81
88
  private readonly byClass;
82
- constructor(records: RecordInfo[], file?: string);
89
+ constructor(records: RecordInfo[], file?: string, matcher?: ModifierMatcher);
83
90
  /** Every documented record's model entry. */
84
91
  get entries(): CssDocEntry[];
85
92
  /** The record whose base class matches `className` (with or without a leading dot). */
@@ -88,13 +95,14 @@ declare class CssDocIndex {
88
95
  recordInfo(name: string): RecordInfo | undefined;
89
96
  modifiersFor(name: string): CssModifier[];
90
97
  partsFor(name: string): CssPart[];
98
+ shadowPartsFor(name: string): CssPart[];
91
99
  customPropertiesFor(name: string): CssPropertyDeclared[];
92
100
  functionsFor(name: string): CssFunction[];
93
101
  statesFor(name: string): CssState[];
94
102
  conditionsFor(name: string): CssCondition[];
95
103
  animationsFor(name: string): CssAnimation[];
96
104
  structureFor(name: string): StructureNode[] | undefined;
97
- /** Whether `modifier` (with or without a leading dot) is a documented modifier of `base`. */
105
+ /** Whether `modifier` (a class token or attribute expression) is a documented modifier of `base`. */
98
106
  isModifier(base: string, modifier: string): boolean;
99
107
  /** The deprecation of a modifier on `base`, if it is deprecated. */
100
108
  deprecationOf(base: string, modifier: string): {
@@ -129,6 +137,8 @@ declare function indexFromEntries(entries: CssDocEntry[], file?: string): CssDoc
129
137
  declare function createIndex(css: string, options?: {
130
138
  file?: string;
131
139
  configuration?: CssDocConfiguration;
140
+ modifierConvention?: ModifierConventionInput; /** The PostCSS parser (inject a dialect parser for `.scss`/`.less`; default `postcss.parse`). */
141
+ parse?: (css: string) => ReturnType<typeof postcss.parse>;
132
142
  }): CssDocIndex;
133
143
  /**
134
144
  * Extract custom-property assignments (`--name: value`) and `var(--name, fallback)` references from
@@ -138,6 +148,8 @@ declare function createIndex(css: string, options?: {
138
148
  * @param css - The CSS source.
139
149
  * @returns The assignments and `var(…)` usages found.
140
150
  */
141
- declare function cssValueSites(css: string): CssValueSites;
151
+ declare function cssValueSites(css: string, options?: {
152
+ parse?: (css: string) => ReturnType<typeof postcss.parse>;
153
+ }): CssValueSites;
142
154
  //#endregion
143
155
  export { ClassUsage, CssDocIndex, CssDocManifest, CssValueSites, Location, MemberKind, Position, PropertyAssignment, PropertyUsage, RecordInfo, SourceSpan, createIndex, cssValueSites, indexFromEntries, memberKey };
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);
@@ -67,6 +69,9 @@ var CssDocIndex = class {
67
69
  partsFor(name) {
68
70
  return this.byName.get(name)?.entry.parts ?? [];
69
71
  }
72
+ shadowPartsFor(name) {
73
+ return this.byName.get(name)?.entry.shadowParts ?? [];
74
+ }
70
75
  customPropertiesFor(name) {
71
76
  return this.byName.get(name)?.entry.cssPropertiesDeclared ?? [];
72
77
  }
@@ -85,14 +90,14 @@ var CssDocIndex = class {
85
90
  structureFor(name) {
86
91
  return this.byName.get(name)?.entry.structure;
87
92
  }
88
- /** Whether `modifier` (with or without a leading dot) is a documented modifier of `base`. */
93
+ /** Whether `modifier` (a class token or attribute expression) is a documented modifier of `base`. */
89
94
  isModifier(base, modifier) {
90
- const wanted = stripDot(modifier);
95
+ const wanted = this.matcher.normalizeMember(modifier);
91
96
  return this.byClass.get(stripDot(base))?.entry.modifiers.some((m) => m.name === wanted) ?? false;
92
97
  }
93
98
  /** The deprecation of a modifier on `base`, if it is deprecated. */
94
99
  deprecationOf(base, modifier) {
95
- const wanted = stripDot(modifier);
100
+ const wanted = this.matcher.normalizeMember(modifier);
96
101
  return this.byClass.get(stripDot(base))?.entry.modifiers.find((m) => m.name === wanted)?.deprecated;
97
102
  }
98
103
  /** Every declared custom property, paired with the record that declares it (for `var(...)` completion). */
@@ -135,12 +140,13 @@ function indexFromEntries(entries, file) {
135
140
  memberSpans: /* @__PURE__ */ new Map(),
136
141
  authoredModifiers: /* @__PURE__ */ new Set(),
137
142
  authoredParts: /* @__PURE__ */ new Set(),
143
+ authoredShadowParts: /* @__PURE__ */ new Set(),
138
144
  selectorText: ""
139
145
  })), file);
140
146
  }
141
147
  /** 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");
148
+ function scanNodes(nodes, build, base, matcher) {
149
+ const baseNoDot = stripDot(base);
144
150
  const set = (key, node) => {
145
151
  const span = spanOf(node);
146
152
  if (span && !build.memberSpans.has(key)) build.memberSpans.set(key, span);
@@ -149,10 +155,24 @@ function scanNodes(nodes, build, base) {
149
155
  build.selectorText += ` ${node.selector}`;
150
156
  if (`.${stripDot(node.selector.trim())}` === base && !build.span) build.span = spanOf(node);
151
157
  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
158
  for (const s of selector.matchAll(/:state\(\s*([\w-]+)\s*\)/gu)) set(memberKey("state", s[1]), node);
159
+ for (const ps of matcher.pseudoStatesIn(selector)) set(memberKey("state", ps.name), node);
160
+ for (const sp of selector.matchAll(/::part\(\s*([\w-]+)\s*\)/gu)) set(memberKey("shadow-part", sp[1]), node);
154
161
  const bare = selector.replace(/::?[\w-]+(\([^)]*\))?/gu, "");
155
- for (const p of bare.matchAll(/\.([a-z][\w-]*)/gu)) set(memberKey("part", p[1]), node);
162
+ const modNames = /* @__PURE__ */ new Set();
163
+ for (const mod of matcher.modifiersIn(bare, baseNoDot)) {
164
+ modNames.add(mod.name);
165
+ set(memberKey("modifier", mod.name), node);
166
+ }
167
+ for (const el of matcher.elementsIn(bare, baseNoDot)) {
168
+ set(memberKey("part", el.name), node);
169
+ for (const m of el.modifiers) set(memberKey("modifier", m.name), node);
170
+ }
171
+ for (const st of matcher.statesIn(bare, baseNoDot)) set(memberKey("state", st.name), node);
172
+ for (const p of bare.matchAll(/\.([a-z][\w-]*)/gu)) {
173
+ if (modNames.has(p[1])) continue;
174
+ set(memberKey("part", p[1]), node);
175
+ }
156
176
  }
157
177
  } else if (node.type === "atrule") {
158
178
  if (node.name === "property") set(memberKey("property", node.params.trim()), node);
@@ -165,7 +185,7 @@ function scanNodes(nodes, build, base) {
165
185
  if (layer) set(memberKey("layer", layer), node);
166
186
  }
167
187
  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);
188
+ if (node.nodes) scanNodes(node.nodes, build, base, matcher);
169
189
  }
170
190
  }
171
191
  /**
@@ -177,10 +197,15 @@ function scanNodes(nodes, build, base) {
177
197
  * @returns The index.
178
198
  */
179
199
  function createIndex(css, options = {}) {
180
- const entries = parseCssDocs(css, { configuration: options.configuration });
200
+ const matcher = new ModifierMatcher(resolveModifierConvention(options.modifierConvention ?? options.configuration?.modifierConvention));
201
+ const entries = parseCssDocs(css, {
202
+ configuration: options.configuration,
203
+ modifierConvention: options.modifierConvention,
204
+ parse: options.parse
205
+ });
181
206
  const byName = new Map(entries.map((e) => [e.name, e]));
182
207
  const builds = /* @__PURE__ */ new Map();
183
- const root = postcss.parse(css);
208
+ const root = (options.parse ?? postcss.parse)(css);
184
209
  let current;
185
210
  for (const node of root.nodes) {
186
211
  if (node.type === "comment") {
@@ -194,21 +219,23 @@ function createIndex(css, options = {}) {
194
219
  memberSpans: /* @__PURE__ */ new Map(),
195
220
  authoredModifiers: new Set(doc.modifiers.keys()),
196
221
  authoredParts: new Set(doc.parts.keys()),
222
+ authoredShadowParts: new Set(doc.cssParts.keys()),
197
223
  selectorText: ""
198
224
  };
199
225
  builds.set(name, current);
200
226
  continue;
201
227
  }
202
228
  }
203
- if (current) scanNodes([node], current, current.entry.className);
229
+ if (current) scanNodes([node], current, current.entry.className, matcher);
204
230
  }
205
231
  return new CssDocIndex(entries.map((entry) => builds.get(entry.name) ?? {
206
232
  entry,
207
233
  memberSpans: /* @__PURE__ */ new Map(),
208
234
  authoredModifiers: /* @__PURE__ */ new Set(),
209
235
  authoredParts: /* @__PURE__ */ new Set(),
236
+ authoredShadowParts: /* @__PURE__ */ new Set(),
210
237
  selectorText: ""
211
- }), options.file);
238
+ }), options.file, matcher);
212
239
  }
213
240
  /**
214
241
  * Extract custom-property assignments (`--name: value`) and `var(--name, fallback)` references from
@@ -218,10 +245,10 @@ function createIndex(css, options = {}) {
218
245
  * @param css - The CSS source.
219
246
  * @returns The assignments and `var(…)` usages found.
220
247
  */
221
- function cssValueSites(css) {
248
+ function cssValueSites(css, options = {}) {
222
249
  const assignments = [];
223
250
  const usages = [];
224
- postcss.parse(css).walkDecls((decl) => {
251
+ (options.parse ?? postcss.parse)(css).walkDecls((decl) => {
225
252
  const loc = spanOf(decl);
226
253
  if (decl.prop.startsWith("--")) assignments.push({
227
254
  name: decl.prop,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cssdoc/index",
3
- "version": "0.1.0",
3
+ "version": "0.3.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.3.0"
34
34
  },
35
35
  "devDependencies": {
36
36
  "@types/node": "^24.13.3",