@cssdoc/providers 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Danny Wahl
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,30 @@
1
+ # @cssdoc/providers
2
+
3
+ The host-agnostic language-service core. Given a [`@cssdoc/index`](../index), it produces diagnostics,
4
+ completions, hover, and definitions — built from modular per-aspect modules (modifier, custom property,
5
+ structure, function, state, condition, plus record and part). Every cssdoc integration — the Stylelint
6
+ and ESLint plugins, and the language server — is a thin translation of these into its host's API.
7
+
8
+ ## API
9
+
10
+ ```ts
11
+ import { createIndex } from "@cssdoc/index";
12
+ import {
13
+ lintModel, // author-side hygiene
14
+ checkClassUsage, // consumer-side: unknown/deprecated modifiers
15
+ checkPropertyUsage, // consumer-side: unknown var(--…) (opt-in)
16
+ completeClasses, // components, or a component's modifiers
17
+ completeCustomProperties,
18
+ hoverForClass,
19
+ definitionForClass,
20
+ } from "@cssdoc/providers";
21
+
22
+ const index = createIndex(css, { file: "components.css" });
23
+ checkClassUsage([{ base: "btn", tokens: ["btn", "-x"], token: "-x" }], index);
24
+ ```
25
+
26
+ Adding an aspect is one module in `aspects.ts` plus a line in the aggregate — no adapter changes.
27
+
28
+ ## License
29
+
30
+ MIT
@@ -0,0 +1,127 @@
1
+ import { ClassUsage, CssDocIndex, Location, Location as Location$1, PropertyAssignment, PropertyUsage, SourceSpan, SourceSpan as SourceSpan$1 } from "@cssdoc/index";
2
+
3
+ //#region src/types.d.ts
4
+ /** Diagnostic severity. */
5
+ type Severity = "error" | "warning";
6
+ /** One diagnostic from an aspect. */
7
+ interface Diagnostic {
8
+ /** The aspect that produced it (e.g. `modifier`). */
9
+ aspect: string;
10
+ /** The rule name (e.g. `unknown-modifier`). */
11
+ rule: string;
12
+ /** A human-readable message. */
13
+ message: string;
14
+ /** The record it concerns, when applicable. */
15
+ record?: string;
16
+ /** The source span, when known (author-side comes from the index; usage-side from the usage). */
17
+ span?: SourceSpan$1;
18
+ /** Severity (defaults to `warning`). */
19
+ severity: Severity;
20
+ }
21
+ /** What kind of thing a completion inserts. */
22
+ type CompletionKind = "component" | "modifier" | "part" | "property" | "function" | "state";
23
+ /** One completion item. */
24
+ interface Completion {
25
+ /** The text to insert (e.g. `-color-secondary`, `--value`). */
26
+ label: string;
27
+ kind: CompletionKind;
28
+ /** A short type/hint shown beside the label. */
29
+ detail?: string;
30
+ /** Markdown documentation. */
31
+ documentation?: string;
32
+ /** Whether the item is deprecated. */
33
+ deprecated?: boolean;
34
+ }
35
+ /** Hover content (markdown). */
36
+ interface Hover {
37
+ contents: string;
38
+ }
39
+ /** Options that tune usage checks. */
40
+ interface UsageOptions {
41
+ /**
42
+ * When set, a `var(--name)` whose name starts with this prefix but isn't a declared custom property
43
+ * is flagged as unknown. Off by default, since consumed properties are often external tokens.
44
+ */
45
+ propertyPrefix?: string;
46
+ }
47
+ //#endregion
48
+ //#region src/syntax.d.ts
49
+ /** The result of matching a value against a syntax. */
50
+ interface SyntaxMatch {
51
+ /** Whether the value conforms. True when skipped — we don't flag what we can't check. */
52
+ ok: boolean;
53
+ /** True when the value couldn't be checked statically (universal `*`, `var()`/`env()`, CSS-wide keyword). */
54
+ skipped: boolean;
55
+ }
56
+ /**
57
+ * Whether `value` conforms to a `@property` `syntax` string (e.g. `<length>`, `<color> | none`,
58
+ * `<length>+`). Universal syntax (`*`), substitution values, and CSS-wide keywords are skipped.
59
+ *
60
+ * @param syntax - The `@property` `syntax` descriptor.
61
+ * @param value - The CSS value to check.
62
+ * @returns Whether it matches, and whether the check was skipped.
63
+ */
64
+ declare function matchesSyntax(syntax: string, value: string): SyntaxMatch;
65
+ //#endregion
66
+ //#region src/aspects.d.ts
67
+ declare const record: {
68
+ model(index: CssDocIndex): Diagnostic[];
69
+ completions(index: CssDocIndex): Completion[];
70
+ hover(base: string, index: CssDocIndex): Hover | undefined;
71
+ definition(base: string, index: CssDocIndex): Location$1 | undefined;
72
+ };
73
+ declare const modifier: {
74
+ model(index: CssDocIndex): Diagnostic[];
75
+ classUsage(usage: ClassUsage, index: CssDocIndex): Diagnostic[];
76
+ completions(base: string, index: CssDocIndex): Completion[];
77
+ hover(base: string, token: string, index: CssDocIndex): Hover | undefined;
78
+ definition(base: string, token: string, index: CssDocIndex): Location$1 | undefined;
79
+ };
80
+ declare const part: {
81
+ model(index: CssDocIndex): Diagnostic[];
82
+ };
83
+ declare const customProperty: {
84
+ /** Author-side: a registered property's default (`initial-value`/`@defaultValue`) must match its syntax. */model(index: CssDocIndex): Diagnostic[]; /** Consumer-side: an assignment `--name: value` must match the property's declared syntax. */
85
+ assignment(a: PropertyAssignment, index: CssDocIndex): Diagnostic[];
86
+ propertyUsage(usage: PropertyUsage, index: CssDocIndex, options: UsageOptions): Diagnostic[];
87
+ completions(index: CssDocIndex): Completion[];
88
+ hover(name: string, index: CssDocIndex): Hover | undefined;
89
+ definition(name: string, index: CssDocIndex): Location$1 | undefined;
90
+ };
91
+ declare const func: {
92
+ completions(index: CssDocIndex): Completion[];
93
+ hover(name: string, index: CssDocIndex): Hover | undefined;
94
+ definition(name: string, index: CssDocIndex): Location$1 | undefined;
95
+ };
96
+ //#endregion
97
+ //#region src/index.d.ts
98
+ /** The aspect names covered, in a stable order (the extension point for future aspects). */
99
+ declare const ASPECTS: readonly ["record", "modifier", "part", "custom-property", "structure", "function", "state", "condition"];
100
+ /** Author-side hygiene diagnostics over the whole model (missing summaries, undocumented members, drift, invalid defaults). */
101
+ declare function lintModel(index: CssDocIndex): Diagnostic[];
102
+ /** Consumer-side diagnostics for custom-property assignments (`--x: value` must match its `@property` syntax). */
103
+ declare function checkPropertyAssignments(assignments: readonly PropertyAssignment[], index: CssDocIndex): Diagnostic[];
104
+ /** Consumer-side diagnostics for class-attribute usage (unknown or deprecated modifiers). */
105
+ declare function checkClassUsage(usages: readonly ClassUsage[], index: CssDocIndex): Diagnostic[];
106
+ /** Consumer-side diagnostics for `var(--…)` references (unknown custom properties; opt-in via prefix). */
107
+ declare function checkPropertyUsage(usages: readonly PropertyUsage[], index: CssDocIndex, options?: UsageOptions): Diagnostic[];
108
+ /** Completions for a class attribute: modifiers of `base` when given, else the component classes. */
109
+ declare function completeClasses(base: string | undefined, index: CssDocIndex): Completion[];
110
+ /** Completions for `var(--…)`: the declared custom properties. */
111
+ declare function completeCustomProperties(index: CssDocIndex): Completion[];
112
+ /** Completions for a value position: the custom functions. */
113
+ declare function completeFunctions(index: CssDocIndex): Completion[];
114
+ /** Hover for a class token: the modifier's docs, else the component's. */
115
+ declare function hoverForClass(base: string, token: string, index: CssDocIndex): Hover | undefined;
116
+ /** Hover for a `var(--…)` custom property. */
117
+ declare function hoverForCustomProperty(name: string, index: CssDocIndex): Hover | undefined;
118
+ /** Hover for a custom function. */
119
+ declare function hoverForFunction(name: string, index: CssDocIndex): Hover | undefined;
120
+ /** Definition of a class token: the modifier's rule, else the component's. */
121
+ declare function definitionForClass(base: string, token: string, index: CssDocIndex): Location$1 | undefined;
122
+ /** Definition of a custom property (its `@property` rule). */
123
+ declare function definitionForCustomProperty(name: string, index: CssDocIndex): Location$1 | undefined;
124
+ /** Definition of a custom function (its `@function` rule). */
125
+ declare function definitionForFunction(name: string, index: CssDocIndex): Location$1 | undefined;
126
+ //#endregion
127
+ export { ASPECTS, Completion, CompletionKind, Diagnostic, Hover, type Location, Severity, type SourceSpan, SyntaxMatch, UsageOptions, checkClassUsage, checkPropertyAssignments, checkPropertyUsage, completeClasses, completeCustomProperties, completeFunctions, customProperty, definitionForClass, definitionForCustomProperty, definitionForFunction, func, hoverForClass, hoverForCustomProperty, hoverForFunction, lintModel, matchesSyntax, modifier, part, record };
package/dist/index.mjs ADDED
@@ -0,0 +1,401 @@
1
+ import { memberKey } from "@cssdoc/index";
2
+ import { fork } from "css-tree";
3
+ //#region src/syntax.ts
4
+ /**
5
+ * Match a CSS value against a registered custom property's `@property` `syntax` descriptor, using
6
+ * css-tree's lexer. This is what lets the value rules reject `--gap: red` when `--gap` is declared
7
+ * `<length>`. Values that are runtime-substituted (`var()`, `env()`) or CSS-wide keywords can't be
8
+ * validated statically, so they're skipped rather than flagged (we never warn on what we can't check).
9
+ *
10
+ * @module
11
+ */
12
+ const PROBE = "-cssdoc-syntax-probe";
13
+ const CSS_WIDE = /^(?:initial|inherit|unset|revert|revert-layer)$/iu;
14
+ const SUBSTITUTION = /\b(?:var|env)\(/iu;
15
+ const lexerCache = /* @__PURE__ */ new Map();
16
+ function lexerFor(syntax) {
17
+ const cached = lexerCache.get(syntax);
18
+ if (cached !== void 0) return cached;
19
+ let lexer = null;
20
+ try {
21
+ lexer = fork({ properties: { [PROBE]: syntax } }).lexer;
22
+ } catch {
23
+ lexer = null;
24
+ }
25
+ lexerCache.set(syntax, lexer);
26
+ return lexer;
27
+ }
28
+ /**
29
+ * Whether `value` conforms to a `@property` `syntax` string (e.g. `<length>`, `<color> | none`,
30
+ * `<length>+`). Universal syntax (`*`), substitution values, and CSS-wide keywords are skipped.
31
+ *
32
+ * @param syntax - The `@property` `syntax` descriptor.
33
+ * @param value - The CSS value to check.
34
+ * @returns Whether it matches, and whether the check was skipped.
35
+ */
36
+ function matchesSyntax(syntax, value) {
37
+ const s = syntax.trim();
38
+ const v = value.trim();
39
+ if (!s || s === "*") return {
40
+ ok: true,
41
+ skipped: true
42
+ };
43
+ if (!v || CSS_WIDE.test(v) || SUBSTITUTION.test(v)) return {
44
+ ok: true,
45
+ skipped: true
46
+ };
47
+ const lexer = lexerFor(s);
48
+ if (!lexer) return {
49
+ ok: true,
50
+ skipped: true
51
+ };
52
+ return {
53
+ ok: lexer.matchProperty(PROBE, v).error === null,
54
+ skipped: false
55
+ };
56
+ }
57
+ //#endregion
58
+ //#region src/aspects.ts
59
+ /**
60
+ * The aspect modules. Each documented aspect contributes some of: author-side model diagnostics,
61
+ * consumer-side usage diagnostics, completions, hover, and definitions. All six aspects the model
62
+ * carries are represented; the component hover folds in the structure, state, and condition facts, and
63
+ * the generators (phase 3) surface them further. `index.ts` aggregates these into the public API.
64
+ *
65
+ * @module
66
+ */
67
+ const stripDot = (name) => name.replace(/^\./u, "");
68
+ const warn = (d) => ({
69
+ ...d,
70
+ severity: "warning"
71
+ });
72
+ const record = {
73
+ model(index) {
74
+ const out = [];
75
+ for (const info of index.records) if (!info.entry.summary?.trim()) out.push(warn({
76
+ aspect: "record",
77
+ rule: "missing-summary",
78
+ message: `Record "${info.entry.name}" has no @summary.`,
79
+ record: info.entry.name,
80
+ span: info.span
81
+ }));
82
+ return out;
83
+ },
84
+ completions(index) {
85
+ return index.entries.map((entry) => ({
86
+ label: stripDot(entry.className),
87
+ kind: "component",
88
+ detail: entry.kind,
89
+ documentation: entry.summary,
90
+ deprecated: Boolean(entry.deprecated)
91
+ }));
92
+ },
93
+ hover(base, index) {
94
+ const entry = index.componentForClass(base);
95
+ if (!entry) return void 0;
96
+ const lines = [`\`${entry.className}\` — ${entry.kind}`];
97
+ if (entry.summary) lines.push("", entry.summary);
98
+ if (entry.remarks) lines.push("", entry.remarks);
99
+ const facets = [
100
+ entry.modifiers.length && `${entry.modifiers.length} modifiers`,
101
+ entry.parts.length && `${entry.parts.length} parts`,
102
+ entry.states.length && `${entry.states.length} states`,
103
+ entry.cssPropertiesDeclared.length && `${entry.cssPropertiesDeclared.length} custom properties`,
104
+ entry.functions.length && `${entry.functions.length} functions`,
105
+ entry.conditions.length && `${entry.conditions.length} conditions`
106
+ ].filter(Boolean);
107
+ if (facets.length) lines.push("", facets.join(" · "));
108
+ if (entry.deprecated) lines.push("", `**Deprecated** — ${entry.deprecated}`);
109
+ return { contents: lines.join("\n") };
110
+ },
111
+ definition(base, index) {
112
+ const entry = index.componentForClass(base);
113
+ return entry ? index.location(entry.name, "record") : void 0;
114
+ }
115
+ };
116
+ const modifier = {
117
+ model(index) {
118
+ const out = [];
119
+ for (const info of index.records) {
120
+ const name = info.entry.name;
121
+ for (const m of info.entry.modifiers) {
122
+ const span = info.memberSpans.get(memberKey("modifier", m.name)) ?? info.span;
123
+ if (!m.description?.trim() && !m.deprecated) out.push(warn({
124
+ aspect: "modifier",
125
+ rule: "undocumented-modifier",
126
+ message: `Modifier ".${m.name}" of "${name}" has no @modifier description.`,
127
+ record: name,
128
+ span
129
+ }));
130
+ if (m.deprecated && !m.deprecated.canonical && !m.deprecated.note?.trim()) out.push(warn({
131
+ aspect: "modifier",
132
+ rule: "deprecated-requires-canonical",
133
+ message: `Deprecated modifier ".${m.name}" of "${name}" needs a canonical replacement ({@link -x}) or a note.`,
134
+ record: name,
135
+ span
136
+ }));
137
+ }
138
+ for (const authored of info.authoredModifiers) if (!info.selectorText.includes(`.${authored}`)) out.push(warn({
139
+ aspect: "modifier",
140
+ rule: "name-not-in-css",
141
+ message: `Documented modifier ".${authored}" of "${name}" is not defined by any selector.`,
142
+ record: name,
143
+ span: info.span
144
+ }));
145
+ }
146
+ return out;
147
+ },
148
+ classUsage(usage, index) {
149
+ if (!usage.base || !usage.token.startsWith("-")) return [];
150
+ const entry = index.componentForClass(usage.base);
151
+ if (!entry) return [];
152
+ if (!index.isModifier(usage.base, usage.token)) return [warn({
153
+ aspect: "modifier",
154
+ rule: "unknown-modifier",
155
+ message: `".${usage.token}" is not a documented modifier of "${entry.name}".`,
156
+ record: entry.name,
157
+ span: usage.loc
158
+ })];
159
+ const dep = index.deprecationOf(usage.base, usage.token);
160
+ if (dep) {
161
+ const advice = dep.canonical ? `use ".${dep.canonical}"` : dep.note ?? "no replacement given";
162
+ return [warn({
163
+ aspect: "modifier",
164
+ rule: "deprecated-modifier",
165
+ message: `Modifier ".${usage.token}" of "${entry.name}" is deprecated — ${advice}.`,
166
+ record: entry.name,
167
+ span: usage.loc
168
+ })];
169
+ }
170
+ return [];
171
+ },
172
+ completions(base, index) {
173
+ const entry = index.componentForClass(base);
174
+ if (!entry) return [];
175
+ return entry.modifiers.map((m) => ({
176
+ label: `-${m.name.replace(/^-/u, "")}`,
177
+ kind: "modifier",
178
+ detail: m.prop,
179
+ documentation: m.description,
180
+ deprecated: Boolean(m.deprecated)
181
+ }));
182
+ },
183
+ hover(base, token, index) {
184
+ const entry = index.componentForClass(base);
185
+ const m = entry?.modifiers.find((x) => x.name === stripDot(token));
186
+ if (!m) return void 0;
187
+ const lines = [`\`.${m.name}\` — modifier of \`${entry.className}\``];
188
+ if (m.description) lines.push("", m.description);
189
+ if (m.deprecated) {
190
+ const advice = m.deprecated.canonical ? `use \`.${m.deprecated.canonical}\`` : m.deprecated.note ?? "";
191
+ lines.push("", `**Deprecated** — ${advice}`);
192
+ }
193
+ return { contents: lines.join("\n") };
194
+ },
195
+ definition(base, token, index) {
196
+ const entry = index.componentForClass(base);
197
+ return entry ? index.location(entry.name, memberKey("modifier", stripDot(token))) : void 0;
198
+ }
199
+ };
200
+ const part = { model(index) {
201
+ const out = [];
202
+ for (const info of index.records) {
203
+ const name = info.entry.name;
204
+ for (const p of info.entry.parts) if (!p.description?.trim()) out.push(warn({
205
+ aspect: "part",
206
+ rule: "undocumented-part",
207
+ message: `Part ".${p.name}" of "${name}" has no @part description.`,
208
+ record: name,
209
+ span: info.memberSpans.get(memberKey("part", p.name)) ?? info.span
210
+ }));
211
+ for (const authored of info.authoredParts) if (!info.selectorText.includes(`.${authored}`)) out.push(warn({
212
+ aspect: "part",
213
+ rule: "name-not-in-css",
214
+ message: `Documented part ".${authored}" of "${name}" is not defined by any selector.`,
215
+ record: name,
216
+ span: info.span
217
+ }));
218
+ }
219
+ return out;
220
+ } };
221
+ function findProperty(index, name) {
222
+ for (const rec of index.records) {
223
+ const i = rec.entry.cssPropertiesDeclared.findIndex((p) => p.name === name);
224
+ if (i >= 0) return {
225
+ record: rec,
226
+ index: i
227
+ };
228
+ }
229
+ }
230
+ const customProperty = {
231
+ /** Author-side: a registered property's default (`initial-value`/`@defaultValue`) must match its syntax. */
232
+ model(index) {
233
+ const out = [];
234
+ for (const { property, record } of index.allCustomProperties()) {
235
+ if (!property.syntax || property.defaultValue === void 0) continue;
236
+ const m = matchesSyntax(property.syntax, property.defaultValue);
237
+ if (m.skipped || m.ok) continue;
238
+ out.push(warn({
239
+ aspect: "custom-property",
240
+ rule: "invalid-default-value",
241
+ message: `Default \`${property.defaultValue}\` of \`${property.name}\` doesn't match its syntax \`${property.syntax}\`.`,
242
+ record,
243
+ span: index.location(record, memberKey("property", property.name))?.span
244
+ }));
245
+ }
246
+ return out;
247
+ },
248
+ /** Consumer-side: an assignment `--name: value` must match the property's declared syntax. */
249
+ assignment(a, index) {
250
+ const found = findProperty(index, a.name);
251
+ if (!found) return [];
252
+ const property = found.record.entry.cssPropertiesDeclared[found.index];
253
+ if (!property.syntax) return [];
254
+ const m = matchesSyntax(property.syntax, a.value);
255
+ if (m.skipped || m.ok) return [];
256
+ return [warn({
257
+ aspect: "custom-property",
258
+ rule: "invalid-property-value",
259
+ message: `\`${a.value}\` doesn't match the declared syntax \`${property.syntax}\` of \`${a.name}\`.`,
260
+ record: found.record.entry.name,
261
+ span: a.loc
262
+ })];
263
+ },
264
+ propertyUsage(usage, index, options) {
265
+ const out = [];
266
+ if (usage.fallback) {
267
+ const found = findProperty(index, usage.name);
268
+ const property = found?.record.entry.cssPropertiesDeclared[found.index];
269
+ if (property?.syntax) {
270
+ const m = matchesSyntax(property.syntax, usage.fallback);
271
+ if (!m.skipped && !m.ok) out.push(warn({
272
+ aspect: "custom-property",
273
+ rule: "invalid-fallback-value",
274
+ message: `\`var(${usage.name}, …)\` fallback \`${usage.fallback}\` doesn't match the declared syntax \`${property.syntax}\`.`,
275
+ span: usage.loc
276
+ }));
277
+ }
278
+ }
279
+ if (options.propertyPrefix && usage.name.startsWith(options.propertyPrefix) && !findProperty(index, usage.name)) out.push(warn({
280
+ aspect: "custom-property",
281
+ rule: "unknown-custom-property",
282
+ message: `\`${usage.name}\` is not a documented custom property.`,
283
+ span: usage.loc
284
+ }));
285
+ return out;
286
+ },
287
+ completions(index) {
288
+ return index.allCustomProperties().map(({ property }) => ({
289
+ label: property.name,
290
+ kind: "property",
291
+ detail: property.syntax,
292
+ documentation: property.description
293
+ }));
294
+ },
295
+ hover(name, index) {
296
+ const found = findProperty(index, name);
297
+ if (!found) return void 0;
298
+ const p = found.record.entry.cssPropertiesDeclared[found.index];
299
+ const lines = [`\`${p.name}\`${p.syntax ? ` — \`${p.syntax}\`` : ""}`];
300
+ if (p.defaultValue) lines.push("", `Default: \`${p.defaultValue}\``);
301
+ if (p.description) lines.push("", p.description);
302
+ return { contents: lines.join("\n") };
303
+ },
304
+ definition(name, index) {
305
+ const found = findProperty(index, name);
306
+ return found ? index.location(found.record.entry.name, memberKey("property", name)) : void 0;
307
+ }
308
+ };
309
+ const func = {
310
+ completions(index) {
311
+ return index.allFunctions().map(({ fn }) => ({
312
+ label: fn.name,
313
+ kind: "function",
314
+ detail: fn.result ? `(${fn.parameters.join(", ")}) → ${fn.result}` : `(${fn.parameters.join(", ")})`,
315
+ documentation: fn.description
316
+ }));
317
+ },
318
+ hover(name, index) {
319
+ const match = index.allFunctions().find(({ fn }) => fn.name === name);
320
+ if (!match) return void 0;
321
+ const { fn } = match;
322
+ const sig = `\`${fn.name}(${fn.parameters.join(", ")})\`${fn.result ? ` → \`${fn.result}\`` : ""}`;
323
+ return { contents: fn.description ? `${sig}\n\n${fn.description}` : sig };
324
+ },
325
+ definition(name, index) {
326
+ const match = index.allFunctions().find(({ fn }) => fn.name === name);
327
+ return match ? index.location(match.record, memberKey("function", name)) : void 0;
328
+ }
329
+ };
330
+ //#endregion
331
+ //#region src/index.ts
332
+ /** The aspect names covered, in a stable order (the extension point for future aspects). */
333
+ const ASPECTS = [
334
+ "record",
335
+ "modifier",
336
+ "part",
337
+ "custom-property",
338
+ "structure",
339
+ "function",
340
+ "state",
341
+ "condition"
342
+ ];
343
+ /** Author-side hygiene diagnostics over the whole model (missing summaries, undocumented members, drift, invalid defaults). */
344
+ function lintModel(index) {
345
+ return [
346
+ ...record.model(index),
347
+ ...modifier.model(index),
348
+ ...part.model(index),
349
+ ...customProperty.model(index)
350
+ ];
351
+ }
352
+ /** Consumer-side diagnostics for custom-property assignments (`--x: value` must match its `@property` syntax). */
353
+ function checkPropertyAssignments(assignments, index) {
354
+ return assignments.flatMap((a) => customProperty.assignment(a, index));
355
+ }
356
+ /** Consumer-side diagnostics for class-attribute usage (unknown or deprecated modifiers). */
357
+ function checkClassUsage(usages, index) {
358
+ return usages.flatMap((usage) => modifier.classUsage(usage, index));
359
+ }
360
+ /** Consumer-side diagnostics for `var(--…)` references (unknown custom properties; opt-in via prefix). */
361
+ function checkPropertyUsage(usages, index, options = {}) {
362
+ return usages.flatMap((usage) => customProperty.propertyUsage(usage, index, options));
363
+ }
364
+ /** Completions for a class attribute: modifiers of `base` when given, else the component classes. */
365
+ function completeClasses(base, index) {
366
+ return base ? modifier.completions(base, index) : record.completions(index);
367
+ }
368
+ /** Completions for `var(--…)`: the declared custom properties. */
369
+ function completeCustomProperties(index) {
370
+ return customProperty.completions(index);
371
+ }
372
+ /** Completions for a value position: the custom functions. */
373
+ function completeFunctions(index) {
374
+ return func.completions(index);
375
+ }
376
+ /** Hover for a class token: the modifier's docs, else the component's. */
377
+ function hoverForClass(base, token, index) {
378
+ return modifier.hover(base, token, index) ?? record.hover(base, index);
379
+ }
380
+ /** Hover for a `var(--…)` custom property. */
381
+ function hoverForCustomProperty(name, index) {
382
+ return customProperty.hover(name, index);
383
+ }
384
+ /** Hover for a custom function. */
385
+ function hoverForFunction(name, index) {
386
+ return func.hover(name, index);
387
+ }
388
+ /** Definition of a class token: the modifier's rule, else the component's. */
389
+ function definitionForClass(base, token, index) {
390
+ return modifier.definition(base, token, index) ?? record.definition(base, index);
391
+ }
392
+ /** Definition of a custom property (its `@property` rule). */
393
+ function definitionForCustomProperty(name, index) {
394
+ return customProperty.definition(name, index);
395
+ }
396
+ /** Definition of a custom function (its `@function` rule). */
397
+ function definitionForFunction(name, index) {
398
+ return func.definition(name, index);
399
+ }
400
+ //#endregion
401
+ export { ASPECTS, checkClassUsage, checkPropertyAssignments, checkPropertyUsage, completeClasses, completeCustomProperties, completeFunctions, customProperty, definitionForClass, definitionForCustomProperty, definitionForFunction, func, hoverForClass, hoverForCustomProperty, hoverForFunction, lintModel, matchesSyntax, modifier, part, record };
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@cssdoc/providers",
3
+ "version": "0.1.0",
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
+ "keywords": [
6
+ "css",
7
+ "cssdoc",
8
+ "documentation",
9
+ "language-service"
10
+ ],
11
+ "homepage": "https://cssdoc.dev",
12
+ "bugs": "https://github.com/thedannywahl/cssdoc/issues",
13
+ "license": "MIT",
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "git+https://github.com/thedannywahl/cssdoc.git",
17
+ "directory": "packages/providers"
18
+ },
19
+ "files": [
20
+ "dist"
21
+ ],
22
+ "type": "module",
23
+ "exports": {
24
+ ".": "./dist/index.mjs",
25
+ "./package.json": "./package.json"
26
+ },
27
+ "publishConfig": {
28
+ "access": "public"
29
+ },
30
+ "dependencies": {
31
+ "css-tree": "^3.2.1",
32
+ "@cssdoc/core": "0.1.0",
33
+ "@cssdoc/index": "0.1.0"
34
+ },
35
+ "devDependencies": {
36
+ "@types/css-tree": "^2.3.11",
37
+ "@types/node": "^24.13.3",
38
+ "publint": "^0.3.21",
39
+ "typescript": "^6.0.3",
40
+ "vite": "npm:@voidzero-dev/vite-plus-core@0.2.4",
41
+ "vite-plus": "0.2.4"
42
+ },
43
+ "scripts": {
44
+ "build": "vp pack",
45
+ "dev": "vp pack --watch",
46
+ "test": "vp test",
47
+ "check": "vp check",
48
+ "publint": "publint"
49
+ }
50
+ }