@cssdoc/providers 0.4.2 → 0.5.1
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 +10 -2
- package/dist/index.mjs +57 -13
- package/package.json +3 -3
package/dist/index.d.mts
CHANGED
|
@@ -38,6 +38,14 @@ interface Diagnostic {
|
|
|
38
38
|
span?: SourceSpan$1;
|
|
39
39
|
/** Severity (defaults to `warning`). */
|
|
40
40
|
severity: Severity;
|
|
41
|
+
/**
|
|
42
|
+
* Optional structured payload for consumers. `maskedName` is the class token as it appears in the
|
|
43
|
+
* linted (possibly projected) source, so the language server can restore an embedded `${…}`
|
|
44
|
+
* interpolation for display instead of the masked filler.
|
|
45
|
+
*/
|
|
46
|
+
data?: {
|
|
47
|
+
maskedName?: string;
|
|
48
|
+
};
|
|
41
49
|
}
|
|
42
50
|
/** What kind of thing a completion inserts. */
|
|
43
51
|
type CompletionKind = "component" | "modifier" | "part" | "property" | "function" | "state";
|
|
@@ -177,7 +185,7 @@ declare function applyDirectives(diagnostics: Diagnostic[], source: string): Dia
|
|
|
177
185
|
//#endregion
|
|
178
186
|
//#region src/aspects.d.ts
|
|
179
187
|
declare const record: {
|
|
180
|
-
model(index: CssDocIndex, naming?: ResolvedNaming, structureIgnore?: readonly string[]): Diagnostic[];
|
|
188
|
+
model(index: CssDocIndex, naming?: ResolvedNaming, structureIgnore?: readonly string[], siblingIndex?: CssDocIndex): Diagnostic[];
|
|
181
189
|
completions(index: CssDocIndex): Completion[];
|
|
182
190
|
hover(base: string, index: CssDocIndex, detail?: HoverDetail, sections?: HoverSections, sectionOrder?: HoverSectionOrder): Hover | undefined;
|
|
183
191
|
definition(base: string, index: CssDocIndex): Location$1 | undefined;
|
|
@@ -217,7 +225,7 @@ declare const func: {
|
|
|
217
225
|
/** The aspect names covered, in a stable order (the extension point for future aspects). */
|
|
218
226
|
declare const ASPECTS: readonly ["record", "modifier", "part", "css-part", "custom-property", "structure", "function", "state", "condition"];
|
|
219
227
|
/** Author-side hygiene diagnostics over the whole model (missing summaries, undocumented members, drift, invalid defaults). */
|
|
220
|
-
declare function lintModel(index: CssDocIndex, severities?: RuleSeverities, naming?: ResolvedNaming, structureIgnore?: readonly string[]): Diagnostic[];
|
|
228
|
+
declare function lintModel(index: CssDocIndex, severities?: RuleSeverities, naming?: ResolvedNaming, structureIgnore?: readonly string[], siblingIndex?: CssDocIndex): Diagnostic[];
|
|
221
229
|
/** Consumer-side diagnostics for custom-property assignments (`--x: value` must match its `@property` syntax). */
|
|
222
230
|
declare function checkPropertyAssignments(assignments: readonly PropertyAssignment[], index: CssDocIndex, severities?: RuleSeverities): Diagnostic[];
|
|
223
231
|
/**
|
package/dist/index.mjs
CHANGED
|
@@ -177,6 +177,38 @@ const warn = (d) => ({
|
|
|
177
177
|
...d,
|
|
178
178
|
severity: "warning"
|
|
179
179
|
});
|
|
180
|
+
const escapeRe = (s) => s.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
181
|
+
/** A `*`-glob name (e.g. `-icon-*`) as a regex source over one class token (`*` → any `[\w-]` run). */
|
|
182
|
+
const globSource = (name) => name.split("*").map(escapeRe).join("[\\w-]*");
|
|
183
|
+
/** `class` attribute selectors, capturing the operator (`~`/`^`/`$`/`*`/`|`/none) and the value. */
|
|
184
|
+
const CLASS_ATTR_RE = /\[\s*class\s*([~^$*|]?)=\s*(?:"([^"]*)"|'([^']*)'|([^\]\s]*))\s*\]/gu;
|
|
185
|
+
/**
|
|
186
|
+
* Whether `selectorText` (the record's concatenated selectors) defines the modifier/part `selector`
|
|
187
|
+
* (`.name`, where `name` may be a `*` glob for a family like `.-icon-*`; a name with no `*` is exact).
|
|
188
|
+
* Beyond a literal class token, `class` attribute selectors count with their real CSS operator
|
|
189
|
+
* semantics — `[class~=v]`/`[class=v]` (exact word), `[class*=v]` (substring), and `[class$=v]` (suffix)
|
|
190
|
+
* can define a chained modifier; `[class^=v]` cannot, since `^=` anchors to the start of the whole
|
|
191
|
+
* attribute (the base class), never a chained modifier.
|
|
192
|
+
*/
|
|
193
|
+
const selectorDefines = (selectorText, selector) => {
|
|
194
|
+
if (!selector.startsWith(".")) return selectorText.includes(selector);
|
|
195
|
+
const name = stripDot(selector);
|
|
196
|
+
const wild = name.includes("*");
|
|
197
|
+
const prefix = name.split("*")[0];
|
|
198
|
+
if (new RegExp(`\\.${globSource(name)}(?![\\w-])`, "u").test(selectorText)) return true;
|
|
199
|
+
const glob = new RegExp(`^${globSource(name)}$`, "u");
|
|
200
|
+
for (const m of selectorText.matchAll(CLASS_ATTR_RE)) {
|
|
201
|
+
const op = m[1];
|
|
202
|
+
const v = m[2] ?? m[3] ?? m[4] ?? "";
|
|
203
|
+
if (!v || op === "^") continue;
|
|
204
|
+
if (op === "*") {
|
|
205
|
+
if (wild ? prefix.includes(v) || v.includes(prefix) : name.includes(v)) return true;
|
|
206
|
+
} else if (op === "$") {
|
|
207
|
+
if (wild ? v.endsWith(prefix) || prefix.endsWith(v) : name.endsWith(v)) return true;
|
|
208
|
+
} else if (glob.test(v)) return true;
|
|
209
|
+
}
|
|
210
|
+
return false;
|
|
211
|
+
};
|
|
180
212
|
/**
|
|
181
213
|
* Match a class name against a `structureIgnore` pattern — a literal name or a simple glob where `*`
|
|
182
214
|
* stands for any run of characters (e.g. `util-*`, `*--legacy`, `*`). Matched literally otherwise.
|
|
@@ -199,8 +231,10 @@ const renderStructureTree = (nodes, depth = 0) => nodes.flatMap((n) => {
|
|
|
199
231
|
] : [`${pad}${n.selector}`];
|
|
200
232
|
});
|
|
201
233
|
const record = {
|
|
202
|
-
model(index, naming, structureIgnore = []) {
|
|
234
|
+
model(index, naming, structureIgnore = [], siblingIndex = index) {
|
|
203
235
|
const out = [];
|
|
236
|
+
const siblingClasses = new Set(siblingIndex.records.map((r) => stripDot(r.entry.className)));
|
|
237
|
+
const siblingNames = new Set(siblingIndex.records.flatMap((r) => r.entry.kind === "component" ? [r.entry.name] : []));
|
|
204
238
|
for (const info of index.records) {
|
|
205
239
|
if (!info.entry.summary?.trim()) out.push(warn({
|
|
206
240
|
aspect: "record",
|
|
@@ -221,25 +255,30 @@ const record = {
|
|
|
221
255
|
if (info.entry.structure?.length) {
|
|
222
256
|
const known = /* @__PURE__ */ new Set([
|
|
223
257
|
stripDot(info.entry.className),
|
|
258
|
+
...siblingClasses,
|
|
224
259
|
...info.entry.parts.flatMap((p) => [stripDot(p.name), ...(p.modifiers ?? []).map((m) => m.name)]),
|
|
225
260
|
...info.entry.shadowParts.map((p) => stripDot(p.name)),
|
|
226
261
|
...info.entry.states.map((s) => s.name),
|
|
227
262
|
...info.entry.modifiers.map((m) => m.name),
|
|
228
263
|
...info.entry.slots.map((s) => s.name)
|
|
229
264
|
]);
|
|
265
|
+
const own = stripDot(info.entry.className);
|
|
266
|
+
const prefix = info.entry.name && own.endsWith(info.entry.name) ? own.slice(0, own.length - info.entry.name.length) : "";
|
|
267
|
+
const isSibling = (cls) => prefix !== "" && cls.startsWith(prefix) && siblingNames.has(cls.slice(prefix.length));
|
|
230
268
|
const seen = /* @__PURE__ */ new Set();
|
|
231
269
|
const walk = (nodes) => {
|
|
232
270
|
for (const node of nodes) {
|
|
233
271
|
for (const m of node.selector.matchAll(/\.([\w-]+)/gu)) {
|
|
234
272
|
const cls = m[1];
|
|
235
|
-
if (seen.has(cls) || known.has(cls) || structureIgnore.some((g) => globMatch(g, cls))) continue;
|
|
273
|
+
if (seen.has(cls) || known.has(cls) || isSibling(cls) || structureIgnore.some((g) => globMatch(g, cls))) continue;
|
|
236
274
|
seen.add(cls);
|
|
237
275
|
out.push(warn({
|
|
238
276
|
aspect: "record",
|
|
239
277
|
rule: "structure-unknown-selector",
|
|
240
278
|
message: `@structure references ".${cls}", which isn't the component class or a documented member (add it, or list it under structureIgnore).`,
|
|
241
279
|
record: info.entry.name,
|
|
242
|
-
span: info.span
|
|
280
|
+
span: info.span,
|
|
281
|
+
data: { maskedName: cls }
|
|
243
282
|
}));
|
|
244
283
|
}
|
|
245
284
|
walk(node.children);
|
|
@@ -390,13 +429,18 @@ const modifier = {
|
|
|
390
429
|
span
|
|
391
430
|
}));
|
|
392
431
|
}
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
432
|
+
const deprecated = new Set(info.entry.modifiers.filter((m) => m.deprecated).map((m) => m.name));
|
|
433
|
+
for (const authored of info.authoredModifiers) {
|
|
434
|
+
if (deprecated.has(authored)) continue;
|
|
435
|
+
const sel = index.matcher.selectorFor(authored);
|
|
436
|
+
if (!selectorDefines(info.selectorText, sel)) out.push(warn({
|
|
437
|
+
aspect: "modifier",
|
|
438
|
+
rule: "name-not-in-css",
|
|
439
|
+
message: `Documented modifier "${sel}" of "${name}" is not defined by any selector.`,
|
|
440
|
+
record: name,
|
|
441
|
+
span: info.span
|
|
442
|
+
}));
|
|
443
|
+
}
|
|
400
444
|
}
|
|
401
445
|
return out;
|
|
402
446
|
},
|
|
@@ -473,7 +517,7 @@ const part = { model(index, naming) {
|
|
|
473
517
|
span: info.memberSpans.get(memberKey("part", p.name)) ?? info.span
|
|
474
518
|
}));
|
|
475
519
|
}
|
|
476
|
-
for (const authored of info.authoredParts) if (!info.selectorText
|
|
520
|
+
for (const authored of info.authoredParts) if (!selectorDefines(info.selectorText, `.${authored}`)) out.push(warn({
|
|
477
521
|
aspect: "part",
|
|
478
522
|
rule: "name-not-in-css",
|
|
479
523
|
message: `Documented part ".${authored}" of "${name}" is not defined by any selector.`,
|
|
@@ -762,9 +806,9 @@ const ASPECTS = [
|
|
|
762
806
|
"condition"
|
|
763
807
|
];
|
|
764
808
|
/** Author-side hygiene diagnostics over the whole model (missing summaries, undocumented members, drift, invalid defaults). */
|
|
765
|
-
function lintModel(index, severities = DEFAULT_RULE_SEVERITIES, naming, structureIgnore) {
|
|
809
|
+
function lintModel(index, severities = DEFAULT_RULE_SEVERITIES, naming, structureIgnore, siblingIndex) {
|
|
766
810
|
return applySeverities([
|
|
767
|
-
...record.model(index, naming, structureIgnore),
|
|
811
|
+
...record.model(index, naming, structureIgnore, siblingIndex),
|
|
768
812
|
...modifier.model(index),
|
|
769
813
|
...part.model(index, naming),
|
|
770
814
|
...cssPart.model(index),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cssdoc/providers",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "Host-agnostic aspect providers over the @cssdoc/index — diagnostics, completions, hover, and definitions for modifiers, custom properties, structure, functions, states, and conditions.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"css",
|
|
@@ -29,8 +29,8 @@
|
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"css-tree": "^3.2.1",
|
|
32
|
-
"@cssdoc/core": "0.
|
|
33
|
-
"@cssdoc/index": "0.
|
|
32
|
+
"@cssdoc/core": "0.5.1",
|
|
33
|
+
"@cssdoc/index": "0.5.1"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
36
|
"@types/css-tree": "^2.3.11",
|