@cssdoc/providers 0.4.2 → 0.5.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.mjs +46 -8
- package/package.json +3 -3
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.
|
|
@@ -221,6 +253,7 @@ const record = {
|
|
|
221
253
|
if (info.entry.structure?.length) {
|
|
222
254
|
const known = /* @__PURE__ */ new Set([
|
|
223
255
|
stripDot(info.entry.className),
|
|
256
|
+
...index.records.map((r) => stripDot(r.entry.className)),
|
|
224
257
|
...info.entry.parts.flatMap((p) => [stripDot(p.name), ...(p.modifiers ?? []).map((m) => m.name)]),
|
|
225
258
|
...info.entry.shadowParts.map((p) => stripDot(p.name)),
|
|
226
259
|
...info.entry.states.map((s) => s.name),
|
|
@@ -390,13 +423,18 @@ const modifier = {
|
|
|
390
423
|
span
|
|
391
424
|
}));
|
|
392
425
|
}
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
426
|
+
const deprecated = new Set(info.entry.modifiers.filter((m) => m.deprecated).map((m) => m.name));
|
|
427
|
+
for (const authored of info.authoredModifiers) {
|
|
428
|
+
if (deprecated.has(authored)) continue;
|
|
429
|
+
const sel = index.matcher.selectorFor(authored);
|
|
430
|
+
if (!selectorDefines(info.selectorText, sel)) out.push(warn({
|
|
431
|
+
aspect: "modifier",
|
|
432
|
+
rule: "name-not-in-css",
|
|
433
|
+
message: `Documented modifier "${sel}" of "${name}" is not defined by any selector.`,
|
|
434
|
+
record: name,
|
|
435
|
+
span: info.span
|
|
436
|
+
}));
|
|
437
|
+
}
|
|
400
438
|
}
|
|
401
439
|
return out;
|
|
402
440
|
},
|
|
@@ -473,7 +511,7 @@ const part = { model(index, naming) {
|
|
|
473
511
|
span: info.memberSpans.get(memberKey("part", p.name)) ?? info.span
|
|
474
512
|
}));
|
|
475
513
|
}
|
|
476
|
-
for (const authored of info.authoredParts) if (!info.selectorText
|
|
514
|
+
for (const authored of info.authoredParts) if (!selectorDefines(info.selectorText, `.${authored}`)) out.push(warn({
|
|
477
515
|
aspect: "part",
|
|
478
516
|
rule: "name-not-in-css",
|
|
479
517
|
message: `Documented part ".${authored}" of "${name}" is not defined by any selector.`,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cssdoc/providers",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
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.0",
|
|
33
|
+
"@cssdoc/index": "0.5.0"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
36
|
"@types/css-tree": "^2.3.11",
|