@master/css-tooling 0.0.0-site.1793659883.g69c9aebcf628

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 (122) hide show
  1. package/README.md +58 -0
  2. package/dist/browser.d.ts +2 -0
  3. package/dist/browser.js +1 -0
  4. package/dist/css-tree-version.d.ts +2 -0
  5. package/dist/css-tree-version.js +4 -0
  6. package/dist/css.d.ts +32 -0
  7. package/dist/css.js +120 -0
  8. package/dist/immutable.d.ts +2 -0
  9. package/dist/immutable.js +7 -0
  10. package/dist/index.d.ts +3 -0
  11. package/dist/index.js +1 -0
  12. package/dist/language/binding-session.d.ts +6 -0
  13. package/dist/language/binding-session.js +8 -0
  14. package/dist/language/browser.d.ts +10 -0
  15. package/dist/language/browser.js +12 -0
  16. package/dist/language/common.d.ts +12 -0
  17. package/dist/language/common.js +55 -0
  18. package/dist/language/contracts.d.ts +197 -0
  19. package/dist/language/contracts.js +1 -0
  20. package/dist/language/index.d.ts +29 -0
  21. package/dist/language/index.js +39 -0
  22. package/dist/language/master-css.d.ts +13 -0
  23. package/dist/language/master-css.js +35 -0
  24. package/dist/language/node.d.ts +21 -0
  25. package/dist/language/node.js +36 -0
  26. package/dist/language/semantic/scopes.d.ts +55 -0
  27. package/dist/language/semantic/scopes.js +73 -0
  28. package/dist/language/semantic/types.d.ts +11 -0
  29. package/dist/language/semantic/types.js +1 -0
  30. package/dist/language/session.d.ts +37 -0
  31. package/dist/language/session.js +87 -0
  32. package/dist/language/settings.d.ts +13 -0
  33. package/dist/language/settings.js +19 -0
  34. package/dist/language/utils/mdn-css-data.d.ts +5 -0
  35. package/dist/language/utils/mdn-css-data.js +63 -0
  36. package/dist/lexer/browser.d.ts +6 -0
  37. package/dist/lexer/browser.js +11 -0
  38. package/dist/lexer/contracts.d.ts +41 -0
  39. package/dist/lexer/contracts.js +1 -0
  40. package/dist/lexer/index.d.ts +8 -0
  41. package/dist/lexer/index.js +11 -0
  42. package/dist/lexer/node.d.ts +7 -0
  43. package/dist/lexer/node.js +11 -0
  44. package/dist/lexer/session.d.ts +17 -0
  45. package/dist/lexer/session.js +25 -0
  46. package/dist/lint/analysis.d.ts +90 -0
  47. package/dist/lint/analysis.js +1 -0
  48. package/dist/lint/binding-session.d.ts +21 -0
  49. package/dist/lint/binding-session.js +97 -0
  50. package/dist/lint/constants.d.ts +7 -0
  51. package/dist/lint/constants.js +18 -0
  52. package/dist/lint/contracts.d.ts +17 -0
  53. package/dist/lint/contracts.js +6 -0
  54. package/dist/lint/diagnostic-format.d.ts +5 -0
  55. package/dist/lint/diagnostic-format.js +15 -0
  56. package/dist/lint/diagnostics.d.ts +42 -0
  57. package/dist/lint/diagnostics.js +1 -0
  58. package/dist/lint/index.d.ts +21 -0
  59. package/dist/lint/index.js +30 -0
  60. package/dist/lint/node.d.ts +6 -0
  61. package/dist/lint/node.js +11 -0
  62. package/dist/lint/rule-signatures.d.ts +23 -0
  63. package/dist/lint/rule-signatures.js +27 -0
  64. package/dist/lint/source.d.ts +70 -0
  65. package/dist/lint/source.js +261 -0
  66. package/dist/math-types.d.ts +11 -0
  67. package/dist/math-types.js +236 -0
  68. package/dist/math-validation.d.ts +7 -0
  69. package/dist/math-validation.js +74 -0
  70. package/dist/node.d.ts +12 -0
  71. package/dist/node.js +32 -0
  72. package/dist/scanner/binding-session.d.ts +29 -0
  73. package/dist/scanner/binding-session.js +37 -0
  74. package/dist/scanner/core.d.ts +130 -0
  75. package/dist/scanner/core.js +480 -0
  76. package/dist/scanner/index.d.ts +3 -0
  77. package/dist/scanner/index.js +3 -0
  78. package/dist/scanner/node.d.ts +5 -0
  79. package/dist/scanner/node.js +4 -0
  80. package/dist/scanner/options/index.d.ts +16 -0
  81. package/dist/scanner/options/index.js +57 -0
  82. package/dist/scanner/source-policy.d.ts +15 -0
  83. package/dist/scanner/source-policy.js +131 -0
  84. package/dist/source/adapters/optional-peer.d.ts +5 -0
  85. package/dist/source/adapters/optional-peer.js +29 -0
  86. package/dist/source/adapters/oxc.d.ts +2 -0
  87. package/dist/source/adapters/oxc.js +6 -0
  88. package/dist/source/adapters/svelte.d.ts +5 -0
  89. package/dist/source/adapters/svelte.js +82 -0
  90. package/dist/source/adapters/types.d.ts +11 -0
  91. package/dist/source/adapters/types.js +9 -0
  92. package/dist/source/adapters/vue.d.ts +5 -0
  93. package/dist/source/adapters/vue.js +50 -0
  94. package/dist/source/browser.d.ts +7 -0
  95. package/dist/source/browser.js +11 -0
  96. package/dist/source/contracts.d.ts +35 -0
  97. package/dist/source/contracts.js +1 -0
  98. package/dist/source/index.d.ts +8 -0
  99. package/dist/source/index.js +11 -0
  100. package/dist/source/native.d.ts +5 -0
  101. package/dist/source/native.js +53 -0
  102. package/dist/source/node.d.ts +7 -0
  103. package/dist/source/node.js +11 -0
  104. package/dist/source/session.d.ts +25 -0
  105. package/dist/source/session.js +37 -0
  106. package/dist/syntax-validation.d.ts +10 -0
  107. package/dist/syntax-validation.js +54 -0
  108. package/dist/tooling-session.d.ts +66 -0
  109. package/dist/tooling-session.js +188 -0
  110. package/dist/validator/binding-session.d.ts +17 -0
  111. package/dist/validator/binding-session.js +29 -0
  112. package/dist/validator/contracts.d.ts +24 -0
  113. package/dist/validator/contracts.js +1 -0
  114. package/dist/validator/index.d.ts +10 -0
  115. package/dist/validator/index.js +13 -0
  116. package/dist/validator/node.d.ts +6 -0
  117. package/dist/validator/node.js +11 -0
  118. package/dist/validator/types/syntax-error.d.ts +22 -0
  119. package/dist/validator/types/syntax-error.js +1 -0
  120. package/dist/value-validation.d.ts +46 -0
  121. package/dist/value-validation.js +162 -0
  122. package/package.json +127 -0
@@ -0,0 +1,261 @@
1
+ // src/lint/source.ts
2
+ const MAX_FIX_PASSES = 10;
3
+ const masterCSSLintRuleIds = Object.freeze([
4
+ "sort-classes",
5
+ "no-invalid-classes",
6
+ "no-conflicting-classes",
7
+ "prefer-canonical-classes",
8
+ "no-unapproved-raw-values",
9
+ "prefer-component-classes"
10
+ ]);
11
+ const defaultMasterCSSLintRules = Object.freeze({
12
+ "sort-classes": true,
13
+ "no-invalid-classes": true,
14
+ "no-conflicting-classes": true,
15
+ "prefer-canonical-classes": true,
16
+ "no-unapproved-raw-values": false,
17
+ "prefer-component-classes": false
18
+ });
19
+ const languageByExtension = {
20
+ ".astro": "astro",
21
+ ".css": "css",
22
+ ".htm": "html",
23
+ ".html": "html",
24
+ ".js": "javascript",
25
+ ".mjs": "javascript",
26
+ ".cjs": "javascript",
27
+ ".jsx": "javascriptreact",
28
+ ".less": "less",
29
+ ".md": "markdown",
30
+ ".mdx": "mdx",
31
+ ".mts": "typescript",
32
+ ".cts": "typescript",
33
+ ".php": "html",
34
+ ".pug": "html",
35
+ ".scss": "scss",
36
+ ".svelte": "svelte",
37
+ ".ts": "typescript",
38
+ ".tsx": "typescriptreact",
39
+ ".vue": "vue"
40
+ };
41
+ const stylesheetLanguageIds = /* @__PURE__ */ new Set([
42
+ "css",
43
+ "scss",
44
+ "less"
45
+ ]);
46
+ function resolveMasterCSSLintRules(option) {
47
+ if (!option) return { ...defaultMasterCSSLintRules };
48
+ const resolved = Object.fromEntries(masterCSSLintRuleIds.map((ruleId) => [ruleId, false]));
49
+ for (const rule of option.split(",").map((item) => item.trim()).filter(Boolean)) {
50
+ if (rule === "recommended") {
51
+ Object.assign(resolved, defaultMasterCSSLintRules);
52
+ continue;
53
+ }
54
+ if (rule === "all") {
55
+ for (const ruleId of masterCSSLintRuleIds) resolved[ruleId] = true;
56
+ continue;
57
+ }
58
+ if (!masterCSSLintRuleIds.includes(rule)) throw new Error(`Unknown Master CSS lint rule "${rule}".`);
59
+ resolved[rule] = true;
60
+ }
61
+ return resolved;
62
+ }
63
+ function resolveRules(rules) {
64
+ return {
65
+ ...defaultMasterCSSLintRules,
66
+ ...rules
67
+ };
68
+ }
69
+ function getLanguageId(filePath) {
70
+ const separator = Math.max(filePath.lastIndexOf("/"), filePath.lastIndexOf("\\"));
71
+ const dot = filePath.lastIndexOf(".");
72
+ const extension = dot > separator ? filePath.slice(dot).toLowerCase() : "";
73
+ return languageByExtension[extension] || "html";
74
+ }
75
+ function getFileSourceKind(languageId) {
76
+ return stylesheetLanguageIds.has(languageId) ? "stylesheet" : "source";
77
+ }
78
+ function detectClassListUnescape(content, range) {
79
+ const quote = content[range.start - 1];
80
+ return quote === "\"" || quote === "'" || quote === "`" ? quote : false;
81
+ }
82
+ function inferClassSourceKind(content, range) {
83
+ const prefix = content.slice(Math.max(0, range.start - 80), range.start);
84
+ return /(?:^|[\s{<])(?:class|className|class:list|:class|v-bind:class|\[class\]|\[className\]|\[ngClass\])\s*=\s*(?:"|'|`|\{[^]*$)/.test(prefix) ? "class-attribute" : "class-expression";
85
+ }
86
+ function collectClassListContexts(content, filePath, lintSession, analysis = lintSession.analyzeLintDocument(content, getLanguageId(filePath))) {
87
+ const positions = analysis.classPositions;
88
+ const contexts = /* @__PURE__ */ new Map();
89
+ for (const position of positions) {
90
+ const contextRange = position.contextRange;
91
+ const key = `${contextRange.start}:${contextRange.end}`;
92
+ const existing = contexts.get(key);
93
+ if (existing) {
94
+ existing.classNames.push(position.token);
95
+ existing.transformed ||= position.raw !== position.token;
96
+ continue;
97
+ }
98
+ contexts.set(key, {
99
+ range: contextRange,
100
+ text: content.slice(contextRange.start, contextRange.end),
101
+ sourceKind: inferClassSourceKind(content, contextRange),
102
+ unescape: detectClassListUnescape(content, contextRange),
103
+ classNames: [position.token],
104
+ transformed: position.raw !== position.token
105
+ });
106
+ }
107
+ return [...contexts.values()].map((context) => {
108
+ const tokens = lintSession.tokenizeClassList(context.text, context.unescape).map((item) => item.token);
109
+ context.transformed ||= tokens.length !== context.classNames.length || tokens.some((token, index) => token !== context.classNames[index]);
110
+ return context;
111
+ }).sort((a, b) => a.range.start - b.range.start || a.range.end - b.range.end);
112
+ }
113
+ function offsetToLocation(content, offset) {
114
+ let line = 1;
115
+ let lineStart = 0;
116
+ for (let index = 0; index < offset; index++) if (content[index] === "\n") {
117
+ line++;
118
+ lineStart = index + 1;
119
+ }
120
+ return {
121
+ line,
122
+ column: offset - lineStart + 1
123
+ };
124
+ }
125
+ function toLocationRange(content, range) {
126
+ return {
127
+ start: offsetToLocation(content, range.start),
128
+ end: offsetToLocation(content, range.end)
129
+ };
130
+ }
131
+ function createContextLintDiagnostics(context, rules, options) {
132
+ return Object.values(rules).some(Boolean) ? options.lintSession.analyzeLintClassList(context.text, context.classNames, {
133
+ disallowUnknownClass: options.ruleOptions?.["no-invalid-classes"]?.disallowUnknownClass,
134
+ rawValuePolicy: rules["no-unapproved-raw-values"] ? options.ruleOptions?.["no-unapproved-raw-values"] || {} : void 0,
135
+ canonicalOptions: rules["prefer-canonical-classes"] ? options.ruleOptions?.["prefer-canonical-classes"] || {} : void 0,
136
+ remoteStylingPolicy: rules["prefer-component-classes"] ? { max: options.ruleOptions?.["prefer-component-classes"]?.max } : void 0
137
+ }).diagnostics.filter(({ ruleId }) => rules[ruleId]).map((diagnostic) => ({
138
+ ...diagnostic,
139
+ severity: options.severities?.[diagnostic.ruleId] || (diagnostic.ruleId === "no-invalid-classes" ? "error" : "warning"),
140
+ fix: diagnostic.fix
141
+ })) : [];
142
+ }
143
+ function toSourceFix(context, fix) {
144
+ if (context.transformed) return;
145
+ return {
146
+ kind: "class-list",
147
+ safety: "safe",
148
+ range: {
149
+ start: context.range.start + fix.range.start,
150
+ end: context.range.start + fix.range.end
151
+ },
152
+ text: fix.text,
153
+ description: "Replace the class list text.",
154
+ requiresFormatting: false
155
+ };
156
+ }
157
+ function toSourceDiagnostic(content, context, diagnostic) {
158
+ const range = context.transformed ? context.range : {
159
+ start: context.range.start + diagnostic.range.start,
160
+ end: context.range.start + diagnostic.range.end
161
+ };
162
+ const fix = diagnostic.fix && toSourceFix(context, diagnostic.fix);
163
+ return {
164
+ ruleId: diagnostic.ruleId,
165
+ code: diagnostic.code,
166
+ severity: diagnostic.severity,
167
+ message: diagnostic.message,
168
+ range,
169
+ loc: toLocationRange(content, range),
170
+ source: "Master CSS",
171
+ sourceKind: context.sourceKind,
172
+ ...diagnostic.data ? { data: diagnostic.data } : {},
173
+ ...fix ? { fixes: [fix] } : {}
174
+ };
175
+ }
176
+ function lintMasterCSSContent(options) {
177
+ const diagnostics = [];
178
+ const rules = resolveRules(options.rules);
179
+ const document = options.lintSession.analyzeLintDocument(options.content, getLanguageId(options.filePath));
180
+ for (const diagnostic of document.diagnostics ?? []) {
181
+ if (diagnostic.severity === "info") continue;
182
+ const range = diagnostic.range ?? {
183
+ start: 0,
184
+ end: 0
185
+ };
186
+ diagnostics.push({
187
+ code: diagnostic.code,
188
+ severity: diagnostic.severity,
189
+ message: diagnostic.message,
190
+ range,
191
+ loc: toLocationRange(options.content, range),
192
+ source: "Master CSS",
193
+ sourceKind: "class-expression"
194
+ });
195
+ }
196
+ for (const context of collectClassListContexts(options.content, options.filePath, options.lintSession, document)) diagnostics.push(...createContextLintDiagnostics(context, rules, options).map((diagnostic) => toSourceDiagnostic(options.content, context, diagnostic)));
197
+ const languageId = getLanguageId(options.filePath);
198
+ return {
199
+ filePath: options.filePath,
200
+ languageId,
201
+ sourceKind: getFileSourceKind(languageId),
202
+ diagnostics
203
+ };
204
+ }
205
+ function applyReplacement(content, range, text) {
206
+ return content.slice(0, range.start) + text + content.slice(range.end);
207
+ }
208
+ function applyFixes(content, fixes) {
209
+ let fixed = content;
210
+ for (const fix of fixes) fixed = applyReplacement(fixed, fix.range, fix.text);
211
+ return fixed;
212
+ }
213
+ function fixClassListText(context, rules, options) {
214
+ if (context.transformed) return context.text;
215
+ let fixed = context.text;
216
+ for (let pass = 0; pass < MAX_FIX_PASSES; pass++) {
217
+ const fix = createContextLintDiagnostics({
218
+ ...context,
219
+ range: {
220
+ start: 0,
221
+ end: fixed.length
222
+ },
223
+ text: fixed,
224
+ classNames: options.lintSession.tokenizeClassList(fixed, context.unescape).map(({ token }) => token)
225
+ }, rules, options).map((diagnostic) => diagnostic.fix).find((fix) => Boolean(fix));
226
+ if (!fix) return fixed;
227
+ const next = applyReplacement(fixed, fix.range, fix.text);
228
+ if (next === fixed) return fixed;
229
+ fixed = next;
230
+ }
231
+ return fixed;
232
+ }
233
+ function fixSafeClassLists(content, filePath, rules, options) {
234
+ return applyFixes(content, collectClassListContexts(content, filePath, options.lintSession).map((context) => ({
235
+ range: context.range,
236
+ text: fixClassListText(context, rules, options)
237
+ })).filter((replacement) => content.slice(replacement.range.start, replacement.range.end) !== replacement.text).sort((a, b) => b.range.start - a.range.start || b.range.end - a.range.end).map((replacement) => ({
238
+ kind: "class-list",
239
+ safety: "safe",
240
+ range: replacement.range,
241
+ text: replacement.text,
242
+ description: "Replace the class list text.",
243
+ requiresFormatting: false
244
+ })));
245
+ }
246
+ function fixMasterCSSContent(options) {
247
+ return fixSafeClassLists(options.content, options.filePath, resolveRules(options.rules), options);
248
+ }
249
+ function summarizeMasterCSSLintFiles(files) {
250
+ const diagnostics = files.flatMap((file) => file.diagnostics);
251
+ const fixes = diagnostics.flatMap((diagnostic) => diagnostic.fixes ?? []);
252
+ return {
253
+ files: files.length,
254
+ diagnostics: diagnostics.length,
255
+ errors: diagnostics.filter((diagnostic) => diagnostic.severity === "error").length,
256
+ warnings: diagnostics.filter((diagnostic) => diagnostic.severity === "warning").length,
257
+ fixable: diagnostics.filter((diagnostic) => diagnostic.fixes?.length).length,
258
+ safeFixes: fixes.filter((fix) => fix.safety === "safe").length
259
+ };
260
+ }
261
+ export { defaultMasterCSSLintRules, fixMasterCSSContent, lintMasterCSSContent, masterCSSLintRuleIds, resolveMasterCSSLintRules, summarizeMasterCSSLintFiles };
@@ -0,0 +1,11 @@
1
+ import { CssNode } from "@eslint/css-tree";
2
+ declare const mathFunctions: Set<string>;
3
+ /** Substitute only proven result types into a disposable grammar AST. Never
4
+ * evaluate values, ranges, variable substitutions, or mutate emitted CSS.
5
+ * A nested calc may have a compound intermediate type which its parent cancels.
6
+ */
7
+ declare function validateMathTypes(ast: CssNode, matches: (value: CssNode) => boolean): {
8
+ ast: CssNode;
9
+ status?: 'valid' | 'invalid' | 'unknown';
10
+ };
11
+ export { mathFunctions, validateMathTypes };
@@ -0,0 +1,236 @@
1
+ import { clone, lexer, walk } from "@eslint/css-tree";
2
+ // src/math-types.ts
3
+ const unknown = { kind: "unknown" };
4
+ const invalid = { kind: "invalid" };
5
+ const number = () => ({
6
+ kind: "known",
7
+ powers: {}
8
+ });
9
+ const dimension = (name) => ({
10
+ kind: "known",
11
+ powers: { [name]: 1 }
12
+ });
13
+ const examples = {
14
+ length: "px",
15
+ angle: "deg",
16
+ time: "s",
17
+ frequency: "hz",
18
+ resolution: "dppx",
19
+ flex: "fr"
20
+ };
21
+ const units = new Map(Object.entries(lexer.units).filter(([type]) => type in examples).flatMap(([type, names]) => names.map((unit) => [unit.toLowerCase(), type])));
22
+ const mathFunctions = /* @__PURE__ */ new Set([
23
+ "calc",
24
+ "min",
25
+ "max",
26
+ "clamp",
27
+ "mod",
28
+ "rem",
29
+ "sin",
30
+ "cos",
31
+ "tan",
32
+ "asin",
33
+ "acos",
34
+ "atan",
35
+ "atan2",
36
+ "pow",
37
+ "sqrt",
38
+ "hypot",
39
+ "exp",
40
+ "abs",
41
+ "sign",
42
+ "log",
43
+ "round"
44
+ ]);
45
+ function same(a, b) {
46
+ return Object.keys({
47
+ ...a.powers,
48
+ ...b.powers
49
+ }).every((key) => a.powers[key] === b.powers[key]);
50
+ }
51
+ function combine(a, b, operator, hint) {
52
+ if (a.kind === "invalid" || b.kind === "invalid") return invalid;
53
+ if (a.kind === "unknown" || b.kind === "unknown") return unknown;
54
+ if (operator === "+" || operator === "-") {
55
+ if (same(a, b)) return a;
56
+ if (hint === void 0 && (a.powers.percentage || b.powers.percentage)) return unknown;
57
+ return invalid;
58
+ }
59
+ const powers = { ...a.powers };
60
+ for (const [dimension, exponent] of Object.entries(b.powers)) {
61
+ const key = dimension;
62
+ powers[key] = (powers[key] ?? 0) + exponent * (operator === "/" ? -1 : 1);
63
+ if (powers[key] === 0) delete powers[key];
64
+ }
65
+ return {
66
+ kind: "known",
67
+ powers
68
+ };
69
+ }
70
+ function expression(nodes, hint) {
71
+ let offset = 0;
72
+ const atom = () => {
73
+ const node = nodes[offset++];
74
+ if (!node) return invalid;
75
+ switch (node.type) {
76
+ case "Number": return number();
77
+ case "Dimension": return units.has(node.unit.toLowerCase()) ? dimension(units.get(node.unit.toLowerCase())) : unknown;
78
+ case "Percentage": return dimension(hint ?? "percentage");
79
+ case "Identifier": return [
80
+ "e",
81
+ "pi",
82
+ "infinity",
83
+ "-infinity",
84
+ "nan"
85
+ ].includes(node.name.toLowerCase()) ? number() : unknown;
86
+ case "Parentheses": return expression([...node.children], hint);
87
+ case "Function": return inferFunction(node, hint);
88
+ default: return unknown;
89
+ }
90
+ };
91
+ const operator = () => {
92
+ const node = nodes[offset];
93
+ return node?.type === "Operator" ? node.value.trim() : "";
94
+ };
95
+ const product = () => {
96
+ let result = atom();
97
+ while (["*", "/"].includes(operator())) {
98
+ const op = operator();
99
+ offset++;
100
+ result = combine(result, atom(), op, hint);
101
+ }
102
+ return result;
103
+ };
104
+ let result = product();
105
+ let knownSum = result.kind === "known" ? result : void 0;
106
+ while (["+", "-"].includes(operator())) {
107
+ const op = operator();
108
+ offset++;
109
+ const right = product();
110
+ if (right.kind === "known") {
111
+ const sum = knownSum ? combine(knownSum, right, op, hint) : right;
112
+ if (sum.kind === "invalid") return invalid;
113
+ knownSum = sum.kind === "known" ? sum : void 0;
114
+ }
115
+ result = combine(result, right, op, hint);
116
+ }
117
+ return offset === nodes.length ? result : unknown;
118
+ }
119
+ function primitive(value) {
120
+ const entries = Object.entries(value.powers);
121
+ return !entries.length ? "number" : entries.length === 1 && entries[0][1] === 1 ? entries[0][0] : void 0;
122
+ }
123
+ function inferFunction(node, hint) {
124
+ const name = node.name.toLowerCase();
125
+ if (!mathFunctions.has(name)) return unknown;
126
+ const args = [[]];
127
+ for (const child of node.children) if (child.type === "Operator" && child.value.trim() === ",") args.push([]);
128
+ else args.at(-1).push(child);
129
+ if (name === "round" && args[0]?.length === 1 && args[0][0].type === "Identifier" && [
130
+ "nearest",
131
+ "up",
132
+ "down",
133
+ "to-zero"
134
+ ].includes(args[0][0].name)) args.shift();
135
+ const values = args.map((nodes, index) => name === "clamp" && index !== 1 && nodes.length === 1 && nodes[0].type === "Identifier" && nodes[0].name === "none" ? void 0 : expression(nodes, hint)).filter((value) => value !== void 0);
136
+ if (values.some((value) => value.kind === "invalid")) return invalid;
137
+ const known = values.filter((value) => value.kind === "known");
138
+ const uncertain = known.length !== values.length;
139
+ const accepts = (types) => known.every((value) => {
140
+ const type = primitive(value);
141
+ return type !== void 0 && types.includes(type);
142
+ });
143
+ let result;
144
+ switch (name) {
145
+ case "calc": return values[0] ?? invalid;
146
+ case "sin":
147
+ case "cos":
148
+ case "tan":
149
+ result = accepts(["number", "angle"]) ? number() : invalid;
150
+ break;
151
+ case "asin":
152
+ case "acos":
153
+ case "atan":
154
+ result = accepts(["number"]) ? dimension("angle") : invalid;
155
+ break;
156
+ case "pow":
157
+ case "sqrt":
158
+ case "exp":
159
+ case "log":
160
+ result = accepts(["number"]) ? number() : invalid;
161
+ break;
162
+ case "sign":
163
+ result = known.every((value) => primitive(value) !== void 0) ? number() : invalid;
164
+ break;
165
+ case "abs":
166
+ result = values[0] ?? invalid;
167
+ break;
168
+ default:
169
+ result = known.reduce((result, value) => combine(result, value, "+", hint), known[0] ?? unknown);
170
+ if (name === "round" && values.length === 1 && !accepts(["number"])) result = invalid;
171
+ if (name === "atan2" && result.kind === "known") result = dimension("angle");
172
+ }
173
+ return result.kind === "invalid" ? invalid : uncertain ? unknown : result;
174
+ }
175
+ function representative(type) {
176
+ if (type === "number") return {
177
+ type: "Number",
178
+ value: "1"
179
+ };
180
+ if (type === "percentage") return {
181
+ type: "Percentage",
182
+ value: "1"
183
+ };
184
+ return {
185
+ type: "Dimension",
186
+ value: "1",
187
+ unit: examples[type]
188
+ };
189
+ }
190
+ /** Substitute only proven result types into a disposable grammar AST. Never
191
+ * evaluate values, ranges, variable substitutions, or mutate emitted CSS.
192
+ * A nested calc may have a compound intermediate type which its parent cancels.
193
+ */
194
+ function validateMathTypes(ast, matches) {
195
+ const normalized = clone(ast);
196
+ let status = "valid";
197
+ let found = false;
198
+ walk(normalized, (node, item, list) => {
199
+ if (node.type !== "Function" || !mathFunctions.has(node.name.toLowerCase()) || !item || !list) return;
200
+ found = true;
201
+ const original = item.data;
202
+ const accepts = (type) => {
203
+ item.data = representative(type);
204
+ const result = matches(normalized);
205
+ item.data = original;
206
+ return result;
207
+ };
208
+ let hint = null;
209
+ let hasPercentage = false;
210
+ walk(node, (child) => {
211
+ if (child.type === "Percentage") hasPercentage = true;
212
+ });
213
+ if (hasPercentage) {
214
+ const bases = Object.keys(examples).filter((type) => accepts(type));
215
+ hint = accepts("percentage") && bases.length === 1 ? bases[0] : bases.length ? null : void 0;
216
+ }
217
+ const inferred = inferFunction(node, hint);
218
+ if (inferred.kind === "invalid") status = "invalid";
219
+ else if (inferred.kind === "unknown") {
220
+ if (status !== "invalid") status = "unknown";
221
+ } else {
222
+ const type = primitive(inferred);
223
+ if (!type) {
224
+ if (hasPercentage && hint === void 0) {
225
+ if (status !== "invalid") status = "unknown";
226
+ } else status = "invalid";
227
+ } else item.data = representative(type);
228
+ }
229
+ return walk.skip;
230
+ });
231
+ return {
232
+ ast: normalized,
233
+ status: found ? status : void 0
234
+ };
235
+ }
236
+ export { mathFunctions, validateMathTypes };
@@ -0,0 +1,7 @@
1
+ import { FunctionNode } from "@eslint/css-tree";
2
+ /** Check operator structure and arity, without inferring the result's dimension.
3
+ * CSS Values 4 allows dimensional division. Normalize operands for grammar-only
4
+ * checks rather than applying the lexer's numeric-divisor restriction.
5
+ */
6
+ declare function mathFunctionStatus(node: FunctionNode): 'valid' | 'invalid' | 'unknown' | undefined;
7
+ export { mathFunctionStatus };
@@ -0,0 +1,74 @@
1
+ import { mathFunctions } from "./math-types.js";
2
+ import { List, clone, generate, lexer, walk } from "@eslint/css-tree";
3
+ // src/math-validation.ts
4
+ /** Check operator structure and arity, without inferring the result's dimension.
5
+ * CSS Values 4 allows dimensional division. Normalize operands for grammar-only
6
+ * checks rather than applying the lexer's numeric-divisor restriction.
7
+ */
8
+ function mathFunctionStatus(node) {
9
+ const name = node.name.toLowerCase();
10
+ if (!mathFunctions.has(name)) return;
11
+ const normalized = clone(node);
12
+ let uncertain = false;
13
+ walk(normalized, (child, item, list) => {
14
+ if (child === normalized) return;
15
+ if (child.type === "Function") {
16
+ if (!mathFunctions.has(child.name.toLowerCase())) uncertain = true;
17
+ if (item && list) list.replace(item, list.createItem({
18
+ type: "Number",
19
+ value: "1"
20
+ }));
21
+ return walk.skip;
22
+ }
23
+ if ((child.type === "Dimension" || child.type === "Percentage") && item && list) list.replace(item, list.createItem({
24
+ type: "Number",
25
+ value: "1"
26
+ }));
27
+ });
28
+ if (uncertain) {
29
+ const arguments_ = [[]];
30
+ for (const child of node.children) if (child.type === "Operator" && child.value.trim() === ",") arguments_.push([]);
31
+ else arguments_.at(-1).push(child);
32
+ for (const argument of arguments_) {
33
+ const last = argument.at(-1);
34
+ if (last?.type === "Operator" && [
35
+ "+",
36
+ "-",
37
+ "*",
38
+ "/"
39
+ ].includes(last.value.trim())) return "invalid";
40
+ let dependent = false;
41
+ const children = new List().fromArray(argument);
42
+ walk({
43
+ type: "Value",
44
+ children
45
+ }, (child) => {
46
+ if (child.type === "Function") dependent = true;
47
+ });
48
+ if (!dependent && mathFunctionStatus({
49
+ type: "Function",
50
+ name: "calc",
51
+ children
52
+ }) === "invalid") return "invalid";
53
+ }
54
+ return "unknown";
55
+ }
56
+ if (name === "log" || name === "round") {
57
+ const parts = [[]];
58
+ for (const child of normalized.children) if (child.type === "Operator" && child.value.trim() === ",") parts.push([]);
59
+ else parts.at(-1).push(child);
60
+ if (name === "round" && parts[0]?.length === 1 && parts[0][0].type === "Identifier" && [
61
+ "nearest",
62
+ "up",
63
+ "down",
64
+ "to-zero"
65
+ ].includes(parts[0][0].name)) parts.shift();
66
+ if (parts.length < 1 || parts.length > 2) return "invalid";
67
+ return parts.some((children) => lexer.matchType("calc-sum", generate({
68
+ type: "Value",
69
+ children: new List().fromArray(children)
70
+ })).error) ? "invalid" : "valid";
71
+ }
72
+ return lexer.matchType(`${name}()`, generate(normalized)).error ? "invalid" : "valid";
73
+ }
74
+ export { mathFunctionStatus };
package/dist/node.d.ts ADDED
@@ -0,0 +1,12 @@
1
+ import { CSS_VALUE_CHECK, cssValueStatus, validateRuleDeclarations } from "./value-validation.js";
2
+ import { CSS_SYNTAX_CHECK, cssSyntaxStatus } from "./syntax-validation.js";
3
+ import { MasterCSSToolingSession } from "./tooling-session.js";
4
+ import { MasterCSSManifest } from "@master/css-schema/manifest";
5
+ declare function createToolingSessionSync(options: {
6
+ readonly manifest: MasterCSSManifest;
7
+ /** Classes the project defines in native CSS; they report no diagnostics while they generate no rules. */
8
+ readonly nativeClassNames?: readonly string[];
9
+ }): MasterCSSToolingSession;
10
+ /** Versions from the artifact that actually executes project semantics. */
11
+ declare function getToolingBindingInfo(): import("@master/css-binding").MasterCSSBindingInfo;
12
+ export { CSS_SYNTAX_CHECK, CSS_VALUE_CHECK, type MasterCSSToolingSession, createToolingSessionSync, cssSyntaxStatus, cssValueStatus, getToolingBindingInfo, validateRuleDeclarations };
package/dist/node.js ADDED
@@ -0,0 +1,32 @@
1
+ import { bindLexerSession } from "./lexer/session.js";
2
+ import { CSS_SYNTAX_CHECK, cssSyntaxStatus } from "./syntax-validation.js";
3
+ import { CSS_VALUE_CHECK, cssValueStatus, validateRuleDeclarations } from "./value-validation.js";
4
+ import { bindLanguageSession } from "./language/session.js";
5
+ import { bindLintSession } from "./lint/binding-session.js";
6
+ import { bindSourceExtractor } from "./source/session.js";
7
+ import { bindValidatorSession } from "./validator/binding-session.js";
8
+ import { bindToolingSessionInternal } from "./tooling-session.js";
9
+ import { createToolingBindingSync } from "@master/css-binding/tooling/node";
10
+ // src/node.ts
11
+ function createToolingSessionSync(options) {
12
+ const tooling = createToolingBindingSync();
13
+ const lexer = tooling.createLexerSession();
14
+ const source = tooling.createSourceSession();
15
+ const validator = tooling.createValidatorSession(options.manifest);
16
+ const language = tooling.createLanguageSession(options.manifest);
17
+ const lint = tooling.createLintSession(options.manifest);
18
+ const lintValidator = tooling.createValidatorSession(options.manifest);
19
+ const lintLanguage = tooling.createLanguageSession(options.manifest);
20
+ return bindToolingSessionInternal({
21
+ lexer: bindLexerSession("native", lexer),
22
+ source: bindSourceExtractor("native", source),
23
+ validator: bindValidatorSession("native", validator, options.nativeClassNames),
24
+ lint: bindLintSession(lint, lintValidator, lintLanguage),
25
+ language: bindLanguageSession("native", language, options.nativeClassNames)
26
+ });
27
+ }
28
+ /** Versions from the artifact that actually executes project semantics. */
29
+ function getToolingBindingInfo() {
30
+ return createToolingBindingSync().info;
31
+ }
32
+ export { CSS_SYNTAX_CHECK, CSS_VALUE_CHECK, createToolingSessionSync, cssSyntaxStatus, cssValueStatus, getToolingBindingInfo, validateRuleDeclarations };
@@ -0,0 +1,29 @@
1
+ import { MasterCSSBindingLoadOptions, MasterCSSScannerSourceInput, MasterCSSScannerSourceOptions, MasterCSSScannerState, MasterCSSScannerUpdate } from "@master/css-binding/tooling";
2
+ import { MasterCSSManifest } from "@master/css-schema/manifest";
3
+ type BindingScannerUpdate = MasterCSSScannerUpdate;
4
+ type BindingScannerState = MasterCSSScannerState;
5
+ type ScannerSourceOptions = MasterCSSScannerSourceOptions;
6
+ type ScannerSourceInput = MasterCSSScannerSourceInput;
7
+ type BindingScannerBlocklist = string | Readonly<{
8
+ source: string;
9
+ flags: string;
10
+ }>;
11
+ interface BindingScannerSession {
12
+ readonly binding: 'native' | 'wasm';
13
+ extractCandidates(source: string, content: string, options?: ScannerSourceOptions): string[];
14
+ collectCandidates(candidates: string[]): string[];
15
+ filterCandidates(candidates: string[], blocklist: BindingScannerBlocklist[]): string[];
16
+ scanCandidates(source: string, content: string, candidates: string[], blocklist: BindingScannerBlocklist[], options?: ScannerSourceOptions): BindingScannerUpdate;
17
+ removeSource(source: string, options?: ScannerSourceOptions): BindingScannerUpdate;
18
+ reconcileSources(owner: string, inputs: readonly ScannerSourceInput[]): BindingScannerUpdate;
19
+ removeOwner(owner: string): BindingScannerUpdate;
20
+ ensureClasses(classNames: string[]): void;
21
+ registerNativeClasses(owner: string, classNames: string[]): boolean;
22
+ reset(): void;
23
+ state(): BindingScannerState;
24
+ dispose(): void;
25
+ }
26
+ declare function serializeScannerBlocklist(blocklist?: Iterable<string | RegExp>): BindingScannerBlocklist[];
27
+ declare function createNativeScannerSession(manifest: MasterCSSManifest): BindingScannerSession;
28
+ declare function createScannerSession(manifest: MasterCSSManifest, options?: MasterCSSBindingLoadOptions): Promise<BindingScannerSession>;
29
+ export { BindingScannerBlocklist, BindingScannerSession, BindingScannerState, BindingScannerUpdate, ScannerSourceInput, ScannerSourceOptions, createNativeScannerSession, createScannerSession, serializeScannerBlocklist };