@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.
Files changed (2) hide show
  1. package/dist/index.mjs +46 -8
  2. 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
- for (const authored of info.authoredModifiers) if (!info.selectorText.includes(index.matcher.selectorFor(authored))) out.push(warn({
394
- aspect: "modifier",
395
- rule: "name-not-in-css",
396
- message: `Documented modifier "${index.matcher.selectorFor(authored)}" of "${name}" is not defined by any selector.`,
397
- record: name,
398
- span: info.span
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.includes(`.${authored}`)) out.push(warn({
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.4.2",
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.4.2",
33
- "@cssdoc/index": "0.4.2"
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",