@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 +22 -10
- package/dist/index.mjs +44 -17
- package/package.json +2 -2
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
|
|
21
|
-
* resolved `base` component class (when one of the tokens names a documented component).
|
|
22
|
-
*
|
|
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 (
|
|
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
|
|
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` (
|
|
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
|
|
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` (
|
|
93
|
+
/** Whether `modifier` (a class token or attribute expression) is a documented modifier of `base`. */
|
|
89
94
|
isModifier(base, modifier) {
|
|
90
|
-
const wanted =
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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
|
|
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.
|
|
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.
|
|
33
|
+
"@cssdoc/core": "0.3.0"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
36
|
"@types/node": "^24.13.3",
|