@cssdoc/core 0.3.2 → 0.4.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/README.md CHANGED
@@ -83,12 +83,16 @@ interface CssDocEntry {
83
83
  deprecated?: { canonical: string };
84
84
  }[];
85
85
  parts: { name: string; description?: string }[];
86
- cssPropertiesConsumed: string[];
86
+ cssPropertiesConsumed: { name: string; description?: string }[];
87
87
  cssPropertiesDeclared: { name: string; syntax?: string; description?: string }[];
88
88
  examples: string[];
89
89
  demo?: string;
90
90
  deprecated?: string;
91
91
  see: string[];
92
+ usage?: string;
93
+ compat: string[];
94
+ related: { name: string; description?: string }[];
95
+ source?: { file?: string; line?: number; column?: number };
92
96
  }
93
97
  ```
94
98
 
package/dist/index.d.mts CHANGED
@@ -423,6 +423,34 @@ interface StructureNode {
423
423
  /** Child nodes (rules nested one brace level deeper). */
424
424
  children: StructureNode[];
425
425
  }
426
+ /**
427
+ * A design token the component consumes via `var(--*)`. The set is derived from the CSS; an authored
428
+ * `@tokens` tag annotates one with prose (and may add a token not literally found via `var()`). Type and
429
+ * resolved value are not modeled here — an emitter resolves them via its own token source (e.g. a
430
+ * `resolveToken` hook).
431
+ */
432
+ interface CssTokenConsumed {
433
+ /** The custom-property name, e.g. `--color-primary`. */
434
+ name: string;
435
+ /** Prose from an `@tokens` doc tag, when authored. */
436
+ description?: string;
437
+ }
438
+ /** A related component cross-reference (`@related`). */
439
+ interface CssRelated {
440
+ /** The related record's name, e.g. `card`. */
441
+ name: string;
442
+ /** Prose from the `@related` tag, when authored. */
443
+ description?: string;
444
+ }
445
+ /** Where a record was authored, for source links. Positions are 1-based, matching PostCSS. */
446
+ interface CssSource {
447
+ /** The file the record was parsed from, when {@link ParseOptions.fileName} was supplied. */
448
+ file?: string;
449
+ /** The 1-based line of the record's opening doc comment. */
450
+ line?: number;
451
+ /** The 1-based column of the record's opening doc comment. */
452
+ column?: number;
453
+ }
426
454
  /** One documented CSS record: its base class plus everything derived from the CSS + doc comments. */
427
455
  interface CssDocEntry {
428
456
  /** The record name from `@component`/`@utility`/`@rule`/`@declaration`/`@name`, e.g. `button`. */
@@ -455,8 +483,12 @@ interface CssDocEntry {
455
483
  states: CssState[];
456
484
  /** Named slots the component shell exposes, from `@slot`. */
457
485
  slots: CssSlot[];
458
- /** Every `--*` custom property referenced via `var(...)` inside this component's rules. */
459
- cssPropertiesConsumed: string[];
486
+ /**
487
+ * Design tokens this component consumes: every `--*` custom property referenced via `var(...)` inside
488
+ * its rules, each annotated with `@tokens` prose where authored (and including any `@tokens`-declared
489
+ * token not literally found via `var()`).
490
+ */
491
+ cssPropertiesConsumed: CssTokenConsumed[];
460
492
  /** Custom properties this component declares (`@property`) or documents (`@cssproperty`). */
461
493
  cssPropertiesDeclared: CssPropertyDeclared[];
462
494
  /** CSS custom functions (`@function`) this component defines. */
@@ -479,6 +511,14 @@ interface CssDocEntry {
479
511
  deprecated?: string;
480
512
  /** `@see <ref>` cross-references. */
481
513
  see: string[];
514
+ /** Usage prose from `@usage` — how to include the stylesheet / use the component. */
515
+ usage?: string;
516
+ /** Browser-support / feature-compatibility notes from `@compat`. */
517
+ compat: string[];
518
+ /** Related components from `@related`. */
519
+ related: CssRelated[];
520
+ /** Where the record was authored, when position info is available (for source links). */
521
+ source?: CssSource;
482
522
  /**
483
523
  * Content of registered custom (block) tags, keyed by tag name without its `@`. Populated only for
484
524
  * tags added via configuration; unregistered unknown tags are ignored. Absent when none were found.
@@ -512,11 +552,16 @@ interface ParseOptions {
512
552
  * parser (e.g. `postcss-scss`/`postcss-less` via `@cssdoc/dialects`) to document `.scss`/`.less`.
513
553
  */
514
554
  parse?: CssParse;
555
+ /**
556
+ * The source file name to record on each entry's {@link CssSource}, enabling source links. The parser
557
+ * always records line/column; supply this to also record the file.
558
+ */
559
+ fileName?: string;
515
560
  }
516
561
  //#endregion
517
562
  //#region src/parse.d.ts
518
563
  /**
519
- * Parse a CSS string into a documentation model. Records are delimited by doc comments carrying a
564
+ * Parse a CSS string into a documentation model. Records are delimited by `/**` doc comments carrying a
520
565
  * record tag (`@component`/`@name` by default; override via {@link ParseOptions.isRecordBoundary});
521
566
  * everything from one boundary comment to the next belongs to that record.
522
567
  *
@@ -594,6 +639,8 @@ interface ParsedDoc {
594
639
  modifiers: Map<string, DocModifier>;
595
640
  /** `@part` descriptions, keyed by the class part name without its dot (e.g. `item`). */
596
641
  parts: Map<string, string>;
642
+ /** `@tokens` descriptions, keyed by custom-property name (e.g. `--color-primary`). */
643
+ tokens: Map<string, string>;
597
644
  /** `@csspart` descriptions (shadow-DOM `::part()`), keyed by the bare part name (e.g. `header`). */
598
645
  cssParts: Map<string, string>;
599
646
  /** `@cssproperty` declarations. */
@@ -622,6 +669,12 @@ interface ParsedDoc {
622
669
  demo?: string;
623
670
  /** `@see <ref>` entries. */
624
671
  see: string[];
672
+ /** `@usage` prose — how to include the stylesheet / use the component. */
673
+ usage?: string;
674
+ /** `@compat` browser-support / feature-compatibility notes. */
675
+ compat: string[];
676
+ /** `@related` component cross-references. */
677
+ related: CssRelated[];
625
678
  /** Content of registered custom (block) tags, keyed by tag name without its `@`. */
626
679
  customBlocks: Map<string, string[]>;
627
680
  }
package/dist/index.mjs CHANGED
@@ -540,6 +540,7 @@ function parseDocComment(raw, configuration = new CssDocConfiguration()) {
540
540
  const doc = {
541
541
  modifiers: /* @__PURE__ */ new Map(),
542
542
  parts: /* @__PURE__ */ new Map(),
543
+ tokens: /* @__PURE__ */ new Map(),
543
544
  cssParts: /* @__PURE__ */ new Map(),
544
545
  cssProperties: [],
545
546
  cssStates: /* @__PURE__ */ new Map(),
@@ -550,6 +551,8 @@ function parseDocComment(raw, configuration = new CssDocConfiguration()) {
550
551
  conditions: [],
551
552
  examples: [],
552
553
  see: [],
554
+ compat: [],
555
+ related: [],
553
556
  customBlocks: /* @__PURE__ */ new Map()
554
557
  };
555
558
  const blocks = [];
@@ -616,6 +619,11 @@ function applyBlockTag(doc, canonical, tagName, rest) {
616
619
  doc.parts.set(head.replace(/^\./u, ""), description ?? "");
617
620
  break;
618
621
  }
622
+ case "tokens": {
623
+ const { head, description } = splitDesc(rest);
624
+ doc.tokens.set(head, description ?? "");
625
+ break;
626
+ }
619
627
  case "csspart": {
620
628
  const { head, description } = splitDesc(rest);
621
629
  doc.cssParts.set(head.replace(/^\./u, ""), description ?? "");
@@ -684,6 +692,20 @@ function applyBlockTag(doc, canonical, tagName, rest) {
684
692
  case "see":
685
693
  doc.see.push(rest);
686
694
  break;
695
+ case "usage":
696
+ doc.usage = rest.trim();
697
+ break;
698
+ case "compat":
699
+ doc.compat.push(rest.trim());
700
+ break;
701
+ case "related": {
702
+ const { head, description } = splitDesc(rest);
703
+ doc.related.push({
704
+ name: head.replace(/^\./u, ""),
705
+ description
706
+ });
707
+ break;
708
+ }
687
709
  default: {
688
710
  const list = doc.customBlocks.get(tagName) ?? [];
689
711
  list.push(rest);
@@ -907,7 +929,7 @@ function collect(nodes, acc, matcher, baseNoDot, prefixNoDot, inScope) {
907
929
  }
908
930
  const byName = (a, b) => a.name.localeCompare(b.name);
909
931
  /** Build one entry from its record name, doc comment, and nodes. */
910
- function buildEntry(name, doc, nodes, matcher) {
932
+ function buildEntry(name, doc, nodes, matcher, source) {
911
933
  let className = doc.className ?? "";
912
934
  if (!className) {
913
935
  const bare = nodes.filter((n) => n.type === "rule").map((n) => n.selector.trim()).filter((sel) => /^\.[a-z][\w-]*$/u.test(sel));
@@ -1025,6 +1047,16 @@ function buildEntry(name, doc, nodes, matcher) {
1025
1047
  });
1026
1048
  }
1027
1049
  const modifiers = [...acc.modifiers.values()].sort((a, b) => a.prop.localeCompare(b.prop) || (a.value ?? "").localeCompare(b.value ?? ""));
1050
+ const consumedTokens = /* @__PURE__ */ new Map();
1051
+ for (const tokenName of acc.consumed) consumedTokens.set(tokenName, { name: tokenName });
1052
+ for (const [tokenName, description] of doc.tokens) {
1053
+ const existing = consumedTokens.get(tokenName);
1054
+ if (existing) existing.description = description || existing.description;
1055
+ else consumedTokens.set(tokenName, {
1056
+ name: tokenName,
1057
+ description: description || void 0
1058
+ });
1059
+ }
1028
1060
  return {
1029
1061
  name,
1030
1062
  kind: doc.kind ?? "component",
@@ -1047,7 +1079,7 @@ function buildEntry(name, doc, nodes, matcher) {
1047
1079
  name: slotName,
1048
1080
  description: description || void 0
1049
1081
  })).sort(byName),
1050
- cssPropertiesConsumed: [...acc.consumed].sort(),
1082
+ cssPropertiesConsumed: [...consumedTokens.values()].sort(byName),
1051
1083
  cssPropertiesDeclared: [...acc.declared.values()].sort(byName),
1052
1084
  functions: [...acc.functions.values()].sort(byName),
1053
1085
  animations: [...acc.animations.values()].sort(byName),
@@ -1059,11 +1091,26 @@ function buildEntry(name, doc, nodes, matcher) {
1059
1091
  demo: doc.demo,
1060
1092
  deprecated: doc.deprecated,
1061
1093
  see: doc.see,
1094
+ usage: doc.usage,
1095
+ compat: doc.compat,
1096
+ related: doc.related,
1097
+ ...source ? { source } : {},
1062
1098
  ...doc.customBlocks.size > 0 ? { customBlocks: Object.fromEntries(doc.customBlocks) } : {}
1063
1099
  };
1064
1100
  }
1065
1101
  /**
1066
- * Parse a CSS string into a documentation model. Records are delimited by doc comments carrying a
1102
+ * A cssdoc record is opened only by a JSDoc-style doc comment (its opener is `/**`) never a plain
1103
+ * block comment. PostCSS doesn't distinguish the two, so an ordinary comment that merely mentions
1104
+ * `@component` (a TODO, a banner) would otherwise mint a phantom, summary-less record. We check the raw
1105
+ * source at the comment's offset for the `/**` opener, falling back to the leftover `*` that a `/**`
1106
+ * leaves at the start of the comment body when no offset is available.
1107
+ */
1108
+ function isDocComment(node, source) {
1109
+ const offset = node.source?.start?.offset;
1110
+ return offset === void 0 ? node.text.startsWith("*") : source.startsWith("/**", offset);
1111
+ }
1112
+ /**
1113
+ * Parse a CSS string into a documentation model. Records are delimited by `/**` doc comments carrying a
1067
1114
  * record tag (`@component`/`@name` by default; override via {@link ParseOptions.isRecordBoundary});
1068
1115
  * everything from one boundary comment to the next belongs to that record.
1069
1116
  *
@@ -1087,13 +1134,20 @@ function parseCssDocs(css, options = {}) {
1087
1134
  const records = [];
1088
1135
  let current = null;
1089
1136
  for (const node of root.nodes) {
1090
- if (node.type === "comment") {
1137
+ if (node.type === "comment" && isDocComment(node, css)) {
1091
1138
  const name = boundary(node.text);
1092
1139
  if (name) {
1140
+ const start = node.source?.start;
1141
+ const source = options.fileName || start ? {
1142
+ file: options.fileName,
1143
+ line: start?.line,
1144
+ column: start?.column
1145
+ } : void 0;
1093
1146
  current = {
1094
1147
  name,
1095
1148
  doc: parseDocComment(node.text, configuration),
1096
- nodes: []
1149
+ nodes: [],
1150
+ source
1097
1151
  };
1098
1152
  records.push(current);
1099
1153
  continue;
@@ -1101,7 +1155,7 @@ function parseCssDocs(css, options = {}) {
1101
1155
  }
1102
1156
  if (current) current.nodes.push(node);
1103
1157
  }
1104
- return records.map((r) => buildEntry(r.name, r.doc, r.nodes, matcher));
1158
+ return records.map((r) => buildEntry(r.name, r.doc, r.nodes, matcher, r.source));
1105
1159
  }
1106
1160
  //#endregion
1107
1161
  //#region src/mermaid.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cssdoc/core",
3
- "version": "0.3.2",
3
+ "version": "0.4.0",
4
4
  "description": "A generic CSS documentation extractor: parse doc-comments + the CSS AST into a serializable model (TSDoc, for CSS).",
5
5
  "keywords": [
6
6
  "css",
@@ -30,8 +30,8 @@
30
30
  "access": "public"
31
31
  },
32
32
  "dependencies": {
33
- "postcss": "^8.5.16",
34
- "@cssdoc/spec": "0.3.2"
33
+ "postcss": "^8.5.17",
34
+ "@cssdoc/spec": "0.4.0"
35
35
  },
36
36
  "devDependencies": {
37
37
  "@types/node": "^24.13.3",