@tenphi/eslint-plugin-tasty 0.3.1 → 0.4.1
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/config.js +100 -20
- package/dist/config.js.map +1 -1
- package/dist/configs.js +5 -4
- package/dist/configs.js.map +1 -1
- package/dist/constants.js +67 -2
- package/dist/constants.js.map +1 -1
- package/dist/context.js +40 -7
- package/dist/context.js.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/parsers/state-key-parser.js +486 -0
- package/dist/parsers/state-key-parser.js.map +1 -0
- package/dist/parsers/utils.js +128 -0
- package/dist/parsers/utils.js.map +1 -0
- package/dist/parsers/value-parser.js +613 -0
- package/dist/parsers/value-parser.js.map +1 -0
- package/dist/rules/consistent-token-usage.js +20 -19
- package/dist/rules/consistent-token-usage.js.map +1 -1
- package/dist/rules/known-property.js +31 -30
- package/dist/rules/known-property.js.map +1 -1
- package/dist/rules/no-duplicate-state.js +12 -11
- package/dist/rules/no-duplicate-state.js.map +1 -1
- package/dist/rules/no-important.js +12 -11
- package/dist/rules/no-important.js.map +1 -1
- package/dist/rules/no-nested-selector.js +15 -14
- package/dist/rules/no-nested-selector.js.map +1 -1
- package/dist/rules/no-nested-state-map.js +19 -18
- package/dist/rules/no-nested-state-map.js.map +1 -1
- package/dist/rules/no-raw-color-values.js +15 -14
- package/dist/rules/no-raw-color-values.js.map +1 -1
- package/dist/rules/no-runtime-styles-mutation.js +6 -5
- package/dist/rules/no-runtime-styles-mutation.js.map +1 -1
- package/dist/rules/no-unknown-state-alias.js +12 -11
- package/dist/rules/no-unknown-state-alias.js.map +1 -1
- package/dist/rules/prefer-shorthand-property.js +19 -18
- package/dist/rules/prefer-shorthand-property.js.map +1 -1
- package/dist/rules/require-default-state.js +22 -21
- package/dist/rules/require-default-state.js.map +1 -1
- package/dist/rules/static-no-dynamic-values.js +7 -6
- package/dist/rules/static-no-dynamic-values.js.map +1 -1
- package/dist/rules/static-valid-selector.js +1 -1
- package/dist/rules/valid-boolean-property.js +19 -18
- package/dist/rules/valid-boolean-property.js.map +1 -1
- package/dist/rules/valid-color-token.js +33 -21
- package/dist/rules/valid-color-token.js.map +1 -1
- package/dist/rules/valid-custom-property.js +31 -19
- package/dist/rules/valid-custom-property.js.map +1 -1
- package/dist/rules/valid-custom-unit.js +12 -16
- package/dist/rules/valid-custom-unit.js.map +1 -1
- package/dist/rules/valid-directional-modifier.js +21 -20
- package/dist/rules/valid-directional-modifier.js.map +1 -1
- package/dist/rules/valid-preset.js +5 -2
- package/dist/rules/valid-preset.js.map +1 -1
- package/dist/rules/valid-radius-shape.js +19 -18
- package/dist/rules/valid-radius-shape.js.map +1 -1
- package/dist/rules/valid-recipe.js +5 -2
- package/dist/rules/valid-recipe.js.map +1 -1
- package/dist/rules/valid-state-definition.js +70 -0
- package/dist/rules/valid-state-definition.js.map +1 -0
- package/dist/rules/valid-state-key.js +39 -102
- package/dist/rules/valid-state-key.js.map +1 -1
- package/dist/rules/valid-styles-structure.js +39 -38
- package/dist/rules/valid-styles-structure.js.map +1 -1
- package/dist/rules/valid-sub-element.js +21 -20
- package/dist/rules/valid-sub-element.js.map +1 -1
- package/dist/rules/valid-transition.js +19 -18
- package/dist/rules/valid-transition.js.map +1 -1
- package/dist/rules/valid-value.js +117 -64
- package/dist/rules/valid-value.js.map +1 -1
- package/package.json +1 -8
- package/dist/parser.js +0 -46
- package/dist/parser.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -25,6 +25,7 @@ import no_raw_color_values_default from "./rules/no-raw-color-values.js";
|
|
|
25
25
|
import no_styles_prop_default from "./rules/no-styles-prop.js";
|
|
26
26
|
import consistent_token_usage_default from "./rules/consistent-token-usage.js";
|
|
27
27
|
import no_runtime_styles_mutation_default from "./rules/no-runtime-styles-mutation.js";
|
|
28
|
+
import valid_state_definition_default from "./rules/valid-state-definition.js";
|
|
28
29
|
import { recommended, strict } from "./configs.js";
|
|
29
30
|
|
|
30
31
|
//#region src/index.ts
|
|
@@ -60,7 +61,8 @@ const plugin = {
|
|
|
60
61
|
"no-raw-color-values": no_raw_color_values_default,
|
|
61
62
|
"no-styles-prop": no_styles_prop_default,
|
|
62
63
|
"consistent-token-usage": consistent_token_usage_default,
|
|
63
|
-
"no-runtime-styles-mutation": no_runtime_styles_mutation_default
|
|
64
|
+
"no-runtime-styles-mutation": no_runtime_styles_mutation_default,
|
|
65
|
+
"valid-state-definition": valid_state_definition_default
|
|
64
66
|
},
|
|
65
67
|
configs: {
|
|
66
68
|
recommended: {
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["rules.knownProperty","rules.validValue","rules.validColorToken","rules.validCustomUnit","rules.validStateKey","rules.validStylesStructure","rules.noNestedSelector","rules.validCustomProperty","rules.validPreset","rules.validRecipe","rules.validBooleanProperty","rules.validDirectionalModifier","rules.validRadiusShape","rules.noImportant","rules.validSubElement","rules.noNestedStateMap","rules.staticNoDynamicValues","rules.staticValidSelector","rules.preferShorthandProperty","rules.validTransition","rules.requireDefaultState","rules.noDuplicateState","rules.noUnknownStateAlias","rules.noRawColorValues","rules.noStylesProp","rules.consistentTokenUsage","rules.noRuntimeStylesMutation"],"sources":["../src/index.ts"],"sourcesContent":["import type { TSESLint } from '@typescript-eslint/utils';\nimport * as rules from './rules/index.js';\nimport { recommended, strict } from './configs.js';\n\nconst ruleMap: Record<string, TSESLint.RuleModule<string, unknown[]>> = {\n 'known-property': rules.knownProperty,\n 'valid-value': rules.validValue,\n 'valid-color-token': rules.validColorToken,\n 'valid-custom-unit': rules.validCustomUnit,\n 'valid-state-key': rules.validStateKey,\n 'valid-styles-structure': rules.validStylesStructure,\n 'no-nested-selector': rules.noNestedSelector,\n 'valid-custom-property': rules.validCustomProperty,\n 'valid-preset': rules.validPreset,\n 'valid-recipe': rules.validRecipe,\n 'valid-boolean-property': rules.validBooleanProperty,\n 'valid-directional-modifier': rules.validDirectionalModifier,\n 'valid-radius-shape': rules.validRadiusShape,\n 'no-important': rules.noImportant,\n 'valid-sub-element': rules.validSubElement,\n 'no-nested-state-map': rules.noNestedStateMap,\n 'static-no-dynamic-values': rules.staticNoDynamicValues,\n 'static-valid-selector': rules.staticValidSelector,\n 'prefer-shorthand-property': rules.preferShorthandProperty,\n 'valid-transition': rules.validTransition,\n 'require-default-state': rules.requireDefaultState,\n 'no-duplicate-state': rules.noDuplicateState,\n 'no-unknown-state-alias': rules.noUnknownStateAlias,\n 'no-raw-color-values': rules.noRawColorValues,\n 'no-styles-prop': rules.noStylesProp,\n 'consistent-token-usage': rules.consistentTokenUsage,\n 'no-runtime-styles-mutation': rules.noRuntimeStylesMutation,\n};\n\nconst plugin = {\n meta: {\n name: '@tenphi/eslint-plugin-tasty',\n version: '0.1.0',\n },\n rules: ruleMap,\n configs: {\n recommended: {\n plugins: {\n get tasty() {\n return plugin;\n },\n },\n rules: recommended,\n },\n strict: {\n plugins: {\n get tasty() {\n return plugin;\n },\n },\n rules: strict,\n },\n },\n} satisfies TSESLint.FlatConfig.Plugin & {\n configs: Record<string, TSESLint.FlatConfig.Config>;\n};\n\nexport default plugin;\n\nexport { recommended, strict } from './configs.js';\nexport type { TastyValidationConfig, ResolvedConfig } from './types.js';\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","names":["rules.knownProperty","rules.validValue","rules.validColorToken","rules.validCustomUnit","rules.validStateKey","rules.validStylesStructure","rules.noNestedSelector","rules.validCustomProperty","rules.validPreset","rules.validRecipe","rules.validBooleanProperty","rules.validDirectionalModifier","rules.validRadiusShape","rules.noImportant","rules.validSubElement","rules.noNestedStateMap","rules.staticNoDynamicValues","rules.staticValidSelector","rules.preferShorthandProperty","rules.validTransition","rules.requireDefaultState","rules.noDuplicateState","rules.noUnknownStateAlias","rules.noRawColorValues","rules.noStylesProp","rules.consistentTokenUsage","rules.noRuntimeStylesMutation","rules.validStateDefinition"],"sources":["../src/index.ts"],"sourcesContent":["import type { TSESLint } from '@typescript-eslint/utils';\nimport * as rules from './rules/index.js';\nimport { recommended, strict } from './configs.js';\n\nconst ruleMap: Record<string, TSESLint.RuleModule<string, unknown[]>> = {\n 'known-property': rules.knownProperty,\n 'valid-value': rules.validValue,\n 'valid-color-token': rules.validColorToken,\n 'valid-custom-unit': rules.validCustomUnit,\n 'valid-state-key': rules.validStateKey,\n 'valid-styles-structure': rules.validStylesStructure,\n 'no-nested-selector': rules.noNestedSelector,\n 'valid-custom-property': rules.validCustomProperty,\n 'valid-preset': rules.validPreset,\n 'valid-recipe': rules.validRecipe,\n 'valid-boolean-property': rules.validBooleanProperty,\n 'valid-directional-modifier': rules.validDirectionalModifier,\n 'valid-radius-shape': rules.validRadiusShape,\n 'no-important': rules.noImportant,\n 'valid-sub-element': rules.validSubElement,\n 'no-nested-state-map': rules.noNestedStateMap,\n 'static-no-dynamic-values': rules.staticNoDynamicValues,\n 'static-valid-selector': rules.staticValidSelector,\n 'prefer-shorthand-property': rules.preferShorthandProperty,\n 'valid-transition': rules.validTransition,\n 'require-default-state': rules.requireDefaultState,\n 'no-duplicate-state': rules.noDuplicateState,\n 'no-unknown-state-alias': rules.noUnknownStateAlias,\n 'no-raw-color-values': rules.noRawColorValues,\n 'no-styles-prop': rules.noStylesProp,\n 'consistent-token-usage': rules.consistentTokenUsage,\n 'no-runtime-styles-mutation': rules.noRuntimeStylesMutation,\n 'valid-state-definition': rules.validStateDefinition,\n};\n\nconst plugin = {\n meta: {\n name: '@tenphi/eslint-plugin-tasty',\n version: '0.1.0',\n },\n rules: ruleMap,\n configs: {\n recommended: {\n plugins: {\n get tasty() {\n return plugin;\n },\n },\n rules: recommended,\n },\n strict: {\n plugins: {\n get tasty() {\n return plugin;\n },\n },\n rules: strict,\n },\n },\n} satisfies TSESLint.FlatConfig.Plugin & {\n configs: Record<string, TSESLint.FlatConfig.Config>;\n};\n\nexport default plugin;\n\nexport { recommended, strict } from './configs.js';\nexport type { TastyValidationConfig, ResolvedConfig } from './types.js';\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmCA,MAAM,SAAS;CACb,MAAM;EACJ,MAAM;EACN,SAAS;EACV;CACD,OApCsE;EACtE,kBAAkBA;EAClB,eAAeC;EACf,qBAAqBC;EACrB,qBAAqBC;EACrB,mBAAmBC;EACnB,0BAA0BC;EAC1B,sBAAsBC;EACtB,yBAAyBC;EACzB,gBAAgBC;EAChB,gBAAgBC;EAChB,0BAA0BC;EAC1B,8BAA8BC;EAC9B,sBAAsBC;EACtB,gBAAgBC;EAChB,qBAAqBC;EACrB,uBAAuBC;EACvB,4BAA4BC;EAC5B,yBAAyBC;EACzB,6BAA6BC;EAC7B,oBAAoBC;EACpB,yBAAyBC;EACzB,sBAAsBC;EACtB,0BAA0BC;EAC1B,uBAAuBC;EACvB,kBAAkBC;EAClB,0BAA0BC;EAC1B,8BAA8BC;EAC9B,0BAA0BC;EAC3B;CAQC,SAAS;EACP,aAAa;GACX,SAAS,EACP,IAAI,QAAQ;AACV,WAAO;MAEV;GACD,OAAO;GACR;EACD,QAAQ;GACN,SAAS,EACP,IAAI,QAAQ;AACV,WAAO;MAEV;GACD,OAAO;GACR;EACF;CACF"}
|
|
@@ -0,0 +1,486 @@
|
|
|
1
|
+
import { KNOWN_PSEUDO_CLASSES } from "../constants.js";
|
|
2
|
+
|
|
3
|
+
//#region src/parsers/state-key-parser.ts
|
|
4
|
+
/**
|
|
5
|
+
* Pattern for tokenizing state notation.
|
|
6
|
+
* Matches operators, parentheses, @-prefixed states, value mods, boolean mods,
|
|
7
|
+
* pseudo-classes with functions (including :is/:has/:not/:where with nesting),
|
|
8
|
+
* class selectors, and attribute selectors.
|
|
9
|
+
*/
|
|
10
|
+
const STATE_TOKEN_PATTERN = /([&|!^])|([()])|(@media:[a-z]+)|(@media\([^)]*\))|(@supports\([^()]*(?:\([^)]*\))?[^)]*\))|(@root\([^)]*\))|(@parent\([^)]*\))|(@own\([^)]*\))|(@\([^()]*(?:\([^)]*\))?[^)]*\))|(@starting)|(@[A-Za-z][A-Za-z0-9-]*)|([a-z][a-z0-9-]*(?:\^=|\$=|\*=|=)(?:"[^"]*"|'[^']*'|[^\s&|!^()]+))|([a-z][a-z0-9-]+)|(:(?:is|has|not|where)\([^()]*(?:\([^()]*(?:\([^)]*\))?[^)]*\))*[^)]*\))|(:[-a-z][a-z0-9-]*(?:\([^)]+\))?)|(\.[a-z][a-z0-9-]+)|(\[[^\]]+\])/gi;
|
|
11
|
+
function tokenize(stateKey) {
|
|
12
|
+
const tokens = [];
|
|
13
|
+
const errors = [];
|
|
14
|
+
const normalized = replaceCommasOutsideParens(stateKey);
|
|
15
|
+
const covered = /* @__PURE__ */ new Set();
|
|
16
|
+
STATE_TOKEN_PATTERN.lastIndex = 0;
|
|
17
|
+
let match;
|
|
18
|
+
while ((match = STATE_TOKEN_PATTERN.exec(normalized)) !== null) {
|
|
19
|
+
const fullMatch = match[0];
|
|
20
|
+
const offset = match.index;
|
|
21
|
+
for (let i = offset; i < offset + fullMatch.length; i++) covered.add(i);
|
|
22
|
+
if (match[1]) switch (fullMatch) {
|
|
23
|
+
case "&":
|
|
24
|
+
tokens.push({
|
|
25
|
+
type: "AND",
|
|
26
|
+
value: "&",
|
|
27
|
+
offset,
|
|
28
|
+
length: 1
|
|
29
|
+
});
|
|
30
|
+
break;
|
|
31
|
+
case "|":
|
|
32
|
+
tokens.push({
|
|
33
|
+
type: "OR",
|
|
34
|
+
value: "|",
|
|
35
|
+
offset,
|
|
36
|
+
length: 1
|
|
37
|
+
});
|
|
38
|
+
break;
|
|
39
|
+
case "!":
|
|
40
|
+
tokens.push({
|
|
41
|
+
type: "NOT",
|
|
42
|
+
value: "!",
|
|
43
|
+
offset,
|
|
44
|
+
length: 1
|
|
45
|
+
});
|
|
46
|
+
break;
|
|
47
|
+
case "^":
|
|
48
|
+
tokens.push({
|
|
49
|
+
type: "XOR",
|
|
50
|
+
value: "^",
|
|
51
|
+
offset,
|
|
52
|
+
length: 1
|
|
53
|
+
});
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
else if (match[2]) if (fullMatch === "(") tokens.push({
|
|
57
|
+
type: "LPAREN",
|
|
58
|
+
value: "(",
|
|
59
|
+
offset,
|
|
60
|
+
length: 1
|
|
61
|
+
});
|
|
62
|
+
else tokens.push({
|
|
63
|
+
type: "RPAREN",
|
|
64
|
+
value: ")",
|
|
65
|
+
offset,
|
|
66
|
+
length: 1
|
|
67
|
+
});
|
|
68
|
+
else tokens.push({
|
|
69
|
+
type: "STATE",
|
|
70
|
+
value: fullMatch,
|
|
71
|
+
offset,
|
|
72
|
+
length: fullMatch.length
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
const uncovered = [];
|
|
76
|
+
for (let i = 0; i < stateKey.length; i++) {
|
|
77
|
+
const ch = stateKey[i];
|
|
78
|
+
if (ch === " " || ch === " " || ch === ",") continue;
|
|
79
|
+
if (!covered.has(i)) uncovered.push({
|
|
80
|
+
ch,
|
|
81
|
+
pos: i
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
if (uncovered.length > 0) {
|
|
85
|
+
const chars = [...new Set(uncovered.map((u) => u.ch))].join("");
|
|
86
|
+
errors.push({
|
|
87
|
+
message: `Unrecognized characters '${chars}' in state key '${stateKey}'.`,
|
|
88
|
+
offset: uncovered[0].pos,
|
|
89
|
+
length: 1
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
return {
|
|
93
|
+
tokens,
|
|
94
|
+
errors
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
function replaceCommasOutsideParens(str) {
|
|
98
|
+
let result = "";
|
|
99
|
+
let depth = 0;
|
|
100
|
+
for (const char of str) if (char === "(") {
|
|
101
|
+
depth++;
|
|
102
|
+
result += char;
|
|
103
|
+
} else if (char === ")") {
|
|
104
|
+
depth--;
|
|
105
|
+
result += char;
|
|
106
|
+
} else if (char === "," && depth === 0) result += "|";
|
|
107
|
+
else result += char;
|
|
108
|
+
return result;
|
|
109
|
+
}
|
|
110
|
+
const MAX_XOR_CHAIN_LENGTH = 4;
|
|
111
|
+
const DIMENSION_SHORTHANDS = new Set([
|
|
112
|
+
"w",
|
|
113
|
+
"h",
|
|
114
|
+
"is",
|
|
115
|
+
"bs"
|
|
116
|
+
]);
|
|
117
|
+
const DIMENSION_FULL = new Set([
|
|
118
|
+
"width",
|
|
119
|
+
"height",
|
|
120
|
+
"inline-size",
|
|
121
|
+
"block-size"
|
|
122
|
+
]);
|
|
123
|
+
var StateKeyValidator = class {
|
|
124
|
+
errors = [];
|
|
125
|
+
hasOwn = false;
|
|
126
|
+
referencedAliases = [];
|
|
127
|
+
tokens;
|
|
128
|
+
pos = 0;
|
|
129
|
+
opts;
|
|
130
|
+
insideOwn = false;
|
|
131
|
+
constructor(tokens, tokenErrors, opts) {
|
|
132
|
+
this.tokens = tokens;
|
|
133
|
+
this.errors = [...tokenErrors];
|
|
134
|
+
this.opts = opts;
|
|
135
|
+
}
|
|
136
|
+
validate() {
|
|
137
|
+
if (this.tokens.length > 0) this.parseExpression();
|
|
138
|
+
return {
|
|
139
|
+
errors: this.errors,
|
|
140
|
+
hasOwn: this.hasOwn,
|
|
141
|
+
referencedAliases: this.referencedAliases
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
current() {
|
|
145
|
+
return this.tokens[this.pos];
|
|
146
|
+
}
|
|
147
|
+
advance() {
|
|
148
|
+
return this.tokens[this.pos++];
|
|
149
|
+
}
|
|
150
|
+
match(type) {
|
|
151
|
+
if (this.current()?.type === type) {
|
|
152
|
+
this.advance();
|
|
153
|
+
return true;
|
|
154
|
+
}
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
parseExpression() {
|
|
158
|
+
this.parseAnd();
|
|
159
|
+
}
|
|
160
|
+
parseAnd() {
|
|
161
|
+
this.parseOr();
|
|
162
|
+
while (this.current()?.type === "AND") {
|
|
163
|
+
this.advance();
|
|
164
|
+
if (!this.current() || this.current()?.type === "AND") {
|
|
165
|
+
const prev = this.tokens[this.pos - 1];
|
|
166
|
+
this.errors.push({
|
|
167
|
+
message: "Expected expression after '&' operator.",
|
|
168
|
+
offset: prev.offset,
|
|
169
|
+
length: prev.length
|
|
170
|
+
});
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
this.parseOr();
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
parseOr() {
|
|
177
|
+
this.parseXor();
|
|
178
|
+
while (this.current()?.type === "OR") {
|
|
179
|
+
this.advance();
|
|
180
|
+
if (!this.current() || this.current()?.type === "OR") {
|
|
181
|
+
const prev = this.tokens[this.pos - 1];
|
|
182
|
+
this.errors.push({
|
|
183
|
+
message: "Expected expression after '|' operator.",
|
|
184
|
+
offset: prev.offset,
|
|
185
|
+
length: prev.length
|
|
186
|
+
});
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
this.parseXor();
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
parseXor() {
|
|
193
|
+
this.parseUnary();
|
|
194
|
+
let operandCount = 1;
|
|
195
|
+
while (this.current()?.type === "XOR") {
|
|
196
|
+
this.advance();
|
|
197
|
+
operandCount++;
|
|
198
|
+
if (operandCount > MAX_XOR_CHAIN_LENGTH) {
|
|
199
|
+
const prev = this.tokens[this.pos - 1];
|
|
200
|
+
this.errors.push({
|
|
201
|
+
message: `XOR chain with ${operandCount} operands produces ${Math.pow(2, operandCount - 1)} OR branches. Consider breaking into smaller expressions.`,
|
|
202
|
+
offset: prev.offset,
|
|
203
|
+
length: prev.length
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
this.parseUnary();
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
parseUnary() {
|
|
210
|
+
if (this.match("NOT")) {
|
|
211
|
+
if (!this.current() || this.current()?.type === "AND" || this.current()?.type === "OR" || this.current()?.type === "XOR") {
|
|
212
|
+
const prev = this.tokens[this.pos - 1];
|
|
213
|
+
this.errors.push({
|
|
214
|
+
message: "Expected expression after '!' operator.",
|
|
215
|
+
offset: prev.offset,
|
|
216
|
+
length: prev.length
|
|
217
|
+
});
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
this.parseUnary();
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
this.parsePrimary();
|
|
224
|
+
}
|
|
225
|
+
parsePrimary() {
|
|
226
|
+
if (this.match("LPAREN")) {
|
|
227
|
+
this.parseExpression();
|
|
228
|
+
if (!this.match("RPAREN")) this.errors.push({
|
|
229
|
+
message: "Missing closing ')' in state expression.",
|
|
230
|
+
offset: this.tokens[this.pos - 1]?.offset ?? 0,
|
|
231
|
+
length: 1
|
|
232
|
+
});
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
const token = this.current();
|
|
236
|
+
if (token?.type === "STATE") {
|
|
237
|
+
this.advance();
|
|
238
|
+
this.validateStateToken(token);
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
if (token) {
|
|
242
|
+
this.errors.push({
|
|
243
|
+
message: `Unexpected token '${token.value}'.`,
|
|
244
|
+
offset: token.offset,
|
|
245
|
+
length: token.length
|
|
246
|
+
});
|
|
247
|
+
this.advance();
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
validateStateToken(token) {
|
|
251
|
+
const value = token.value;
|
|
252
|
+
if (value === "@starting") return;
|
|
253
|
+
if (value.startsWith("@media:")) {
|
|
254
|
+
const mediaType = value.slice(7);
|
|
255
|
+
if (!new Set([
|
|
256
|
+
"print",
|
|
257
|
+
"screen",
|
|
258
|
+
"all",
|
|
259
|
+
"speech"
|
|
260
|
+
]).has(mediaType)) this.errors.push({
|
|
261
|
+
message: `Unknown media type '${mediaType}'. Valid: print, screen, all, speech.`,
|
|
262
|
+
offset: token.offset,
|
|
263
|
+
length: token.length
|
|
264
|
+
});
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
if (value.startsWith("@media(")) {
|
|
268
|
+
this.validateMediaQuery(value, token);
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
if (value.startsWith("@supports(")) {
|
|
272
|
+
this.validateSupportsQuery(value, token);
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
if (value.startsWith("@root(")) {
|
|
276
|
+
this.validateInnerStateExpression(value, 6, token);
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
if (value.startsWith("@parent(")) {
|
|
280
|
+
this.validateParentState(value, token);
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
if (value.startsWith("@own(")) {
|
|
284
|
+
this.hasOwn = true;
|
|
285
|
+
if (this.insideOwn) {
|
|
286
|
+
this.errors.push({
|
|
287
|
+
message: "Nested @own() is not allowed.",
|
|
288
|
+
offset: token.offset,
|
|
289
|
+
length: token.length
|
|
290
|
+
});
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
const prevInsideOwn = this.insideOwn;
|
|
294
|
+
this.insideOwn = true;
|
|
295
|
+
this.validateInnerStateExpression(value, 5, token);
|
|
296
|
+
this.insideOwn = prevInsideOwn;
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
if (value.startsWith("@(")) {
|
|
300
|
+
this.validateContainerQuery(value, token);
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
if (value.startsWith("@") && /^@[A-Za-z][A-Za-z0-9-]*$/.test(value)) {
|
|
304
|
+
this.referencedAliases.push(value);
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
if (value.startsWith(":")) {
|
|
308
|
+
this.validatePseudoClass(value, token);
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
if (value.startsWith(".")) return;
|
|
312
|
+
if (value.startsWith("[")) {
|
|
313
|
+
this.validateAttributeSelector(value, token);
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
if (value.includes("=")) {
|
|
317
|
+
this.validateValueModifier(value, token);
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
if (/^[a-z][a-z0-9-]+$/i.test(value)) return;
|
|
321
|
+
this.errors.push({
|
|
322
|
+
message: `Unrecognized state token '${value}'.`,
|
|
323
|
+
offset: token.offset,
|
|
324
|
+
length: token.length
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
validateMediaQuery(raw, token) {
|
|
328
|
+
const content = raw.slice(7, -1);
|
|
329
|
+
if (!content.trim()) {
|
|
330
|
+
this.errors.push({
|
|
331
|
+
message: "Empty @media() query.",
|
|
332
|
+
offset: token.offset,
|
|
333
|
+
length: token.length
|
|
334
|
+
});
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
const expanded = expandDimensionShorthands(content.trim());
|
|
338
|
+
if (expanded.includes(":") && !expanded.includes("<") && !expanded.includes(">") && !expanded.includes("=")) return;
|
|
339
|
+
if (!expanded.includes("<") && !expanded.includes(">") && !expanded.includes("=")) return;
|
|
340
|
+
this.validateDimensionCondition(expanded, token);
|
|
341
|
+
}
|
|
342
|
+
validateDimensionCondition(condition, token) {
|
|
343
|
+
const rangeMatch = condition.match(/^(.+?)\s*(<=|<)\s*(\S+)\s*(<=|<)\s*(.+)$/);
|
|
344
|
+
if (rangeMatch) {
|
|
345
|
+
const dim = rangeMatch[3];
|
|
346
|
+
if (!DIMENSION_FULL.has(dim) && !DIMENSION_SHORTHANDS.has(dim)) this.errors.push({
|
|
347
|
+
message: `Unknown dimension '${dim}' in media/container query. Valid: width, height, inline-size, block-size (or w, h, is, bs).`,
|
|
348
|
+
offset: token.offset,
|
|
349
|
+
length: token.length
|
|
350
|
+
});
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
const simpleMatch = condition.match(/^(\S+)\s*(<=|>=|<|>|=)\s*(.+)$/);
|
|
354
|
+
if (simpleMatch) {
|
|
355
|
+
const dim = simpleMatch[1];
|
|
356
|
+
if (DIMENSION_FULL.has(dim) || DIMENSION_SHORTHANDS.has(dim)) return;
|
|
357
|
+
}
|
|
358
|
+
const reversedMatch = condition.match(/^(.+?)\s*(<=|>=|<|>|=)\s*(\S+)$/);
|
|
359
|
+
if (reversedMatch) {
|
|
360
|
+
const dim = reversedMatch[3];
|
|
361
|
+
if (DIMENSION_FULL.has(dim) || DIMENSION_SHORTHANDS.has(dim)) return;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
validateSupportsQuery(raw, token) {
|
|
365
|
+
if (!raw.slice(10, -1).trim()) this.errors.push({
|
|
366
|
+
message: "Empty @supports() query.",
|
|
367
|
+
offset: token.offset,
|
|
368
|
+
length: token.length
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
validateInnerStateExpression(raw, prefixLen, _token) {
|
|
372
|
+
const content = raw.slice(prefixLen, -1);
|
|
373
|
+
if (!content.trim()) return;
|
|
374
|
+
const innerResult = parseStateKey(content, this.opts);
|
|
375
|
+
this.errors.push(...innerResult.errors);
|
|
376
|
+
if (innerResult.hasOwn) this.hasOwn = true;
|
|
377
|
+
this.referencedAliases.push(...innerResult.referencedAliases);
|
|
378
|
+
}
|
|
379
|
+
validateParentState(raw, token) {
|
|
380
|
+
const content = raw.slice(8, -1);
|
|
381
|
+
if (!content.trim()) {
|
|
382
|
+
this.errors.push({
|
|
383
|
+
message: "Empty @parent() state.",
|
|
384
|
+
offset: token.offset,
|
|
385
|
+
length: token.length
|
|
386
|
+
});
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
let condition = content.trim();
|
|
390
|
+
const lastCommaIdx = condition.lastIndexOf(",");
|
|
391
|
+
if (lastCommaIdx !== -1) {
|
|
392
|
+
if (condition.slice(lastCommaIdx + 1).trim() === ">") condition = condition.slice(0, lastCommaIdx).trim();
|
|
393
|
+
}
|
|
394
|
+
const innerResult = parseStateKey(condition, this.opts);
|
|
395
|
+
this.errors.push(...innerResult.errors);
|
|
396
|
+
this.referencedAliases.push(...innerResult.referencedAliases);
|
|
397
|
+
}
|
|
398
|
+
validateContainerQuery(raw, token) {
|
|
399
|
+
const content = raw.slice(2, -1);
|
|
400
|
+
if (!content.trim()) {
|
|
401
|
+
this.errors.push({
|
|
402
|
+
message: "Empty container query.",
|
|
403
|
+
offset: token.offset,
|
|
404
|
+
length: token.length
|
|
405
|
+
});
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
const commaIdx = findTopLevelComma(content);
|
|
409
|
+
let condition;
|
|
410
|
+
if (commaIdx !== -1) condition = content.slice(commaIdx + 1).trim();
|
|
411
|
+
else condition = content.trim();
|
|
412
|
+
if (condition.startsWith("$")) return;
|
|
413
|
+
if (/^[a-zA-Z][\w-]*\s*\(/.test(condition)) return;
|
|
414
|
+
const expanded = expandDimensionShorthands(condition);
|
|
415
|
+
if (expanded.includes("<") || expanded.includes(">") || expanded.includes("=")) this.validateDimensionCondition(expanded, token);
|
|
416
|
+
}
|
|
417
|
+
validatePseudoClass(value, token) {
|
|
418
|
+
if (/^:(is|has|not|where)\(/.exec(value)) return;
|
|
419
|
+
const funcMatch = /^(:[a-z-]+)\(/.exec(value);
|
|
420
|
+
if (funcMatch) {
|
|
421
|
+
const baseName = funcMatch[1];
|
|
422
|
+
if (!KNOWN_PSEUDO_CLASSES.has(baseName)) this.errors.push({
|
|
423
|
+
message: `Unknown pseudo-class '${baseName}'.`,
|
|
424
|
+
offset: token.offset,
|
|
425
|
+
length: token.length
|
|
426
|
+
});
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
if (!KNOWN_PSEUDO_CLASSES.has(value)) this.errors.push({
|
|
430
|
+
message: `Unknown pseudo-class '${value}'.`,
|
|
431
|
+
offset: token.offset,
|
|
432
|
+
length: token.length
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
validateAttributeSelector(value, token) {
|
|
436
|
+
if (!value.startsWith("[") || !value.endsWith("]")) this.errors.push({
|
|
437
|
+
message: `Malformed attribute selector '${value}'.`,
|
|
438
|
+
offset: token.offset,
|
|
439
|
+
length: token.length
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
validateValueModifier(value, token) {
|
|
443
|
+
if (!value.match(/^([a-z][a-z0-9-]*)(\^=|\$=|\*=|=)(.+)$/i)) this.errors.push({
|
|
444
|
+
message: `Invalid value modifier syntax '${value}'.`,
|
|
445
|
+
offset: token.offset,
|
|
446
|
+
length: token.length
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
};
|
|
450
|
+
function expandDimensionShorthands(condition) {
|
|
451
|
+
return condition.replace(/\bw\b/g, "width").replace(/\bh\b/g, "height").replace(/\bis\b/g, "inline-size").replace(/\bbs\b/g, "block-size");
|
|
452
|
+
}
|
|
453
|
+
function findTopLevelComma(str) {
|
|
454
|
+
let depth = 0;
|
|
455
|
+
for (let i = 0; i < str.length; i++) {
|
|
456
|
+
const ch = str[i];
|
|
457
|
+
if (ch === "(") depth++;
|
|
458
|
+
else if (ch === ")") depth = Math.max(0, depth - 1);
|
|
459
|
+
else if (ch === "," && depth === 0) return i;
|
|
460
|
+
}
|
|
461
|
+
return -1;
|
|
462
|
+
}
|
|
463
|
+
/**
|
|
464
|
+
* Validate a state key string and return detailed errors.
|
|
465
|
+
*/
|
|
466
|
+
function parseStateKey(stateKey, opts = {}) {
|
|
467
|
+
if (!stateKey || !stateKey.trim()) return {
|
|
468
|
+
errors: [],
|
|
469
|
+
hasOwn: false,
|
|
470
|
+
referencedAliases: []
|
|
471
|
+
};
|
|
472
|
+
const { tokens, errors: tokenErrors } = tokenize(stateKey.trim());
|
|
473
|
+
return new StateKeyValidator(tokens, tokenErrors, opts).validate();
|
|
474
|
+
}
|
|
475
|
+
/**
|
|
476
|
+
* Validate a state definition value (the RHS of a state alias).
|
|
477
|
+
* State definitions should be valid state expressions like
|
|
478
|
+
* '@media(w < 768px)', '@root(theme=dark)', etc.
|
|
479
|
+
*/
|
|
480
|
+
function validateStateDefinition(value, opts = {}) {
|
|
481
|
+
return parseStateKey(value, opts);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
//#endregion
|
|
485
|
+
export { parseStateKey, validateStateDefinition };
|
|
486
|
+
//# sourceMappingURL=state-key-parser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state-key-parser.js","names":[],"sources":["../../src/parsers/state-key-parser.ts"],"sourcesContent":["import { BUILT_IN_STATE_PREFIXES, KNOWN_PSEUDO_CLASSES } from '../constants.js';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface StateKeyError {\n message: string;\n offset: number;\n length: number;\n}\n\nexport interface StateKeyResult {\n errors: StateKeyError[];\n hasOwn: boolean;\n referencedAliases: string[];\n}\n\nexport interface StateKeyParserOptions {\n knownAliases?: string[];\n}\n\n// ============================================================================\n// Tokenizer\n// ============================================================================\n\ntype TokenType = 'AND' | 'OR' | 'NOT' | 'XOR' | 'LPAREN' | 'RPAREN' | 'STATE';\n\ninterface Token {\n type: TokenType;\n value: string;\n offset: number;\n length: number;\n}\n\n/**\n * Pattern for tokenizing state notation.\n * Matches operators, parentheses, @-prefixed states, value mods, boolean mods,\n * pseudo-classes with functions (including :is/:has/:not/:where with nesting),\n * class selectors, and attribute selectors.\n */\nconst STATE_TOKEN_PATTERN =\n /([&|!^])|([()])|(@media:[a-z]+)|(@media\\([^)]*\\))|(@supports\\([^()]*(?:\\([^)]*\\))?[^)]*\\))|(@root\\([^)]*\\))|(@parent\\([^)]*\\))|(@own\\([^)]*\\))|(@\\([^()]*(?:\\([^)]*\\))?[^)]*\\))|(@starting)|(@[A-Za-z][A-Za-z0-9-]*)|([a-z][a-z0-9-]*(?:\\^=|\\$=|\\*=|=)(?:\"[^\"]*\"|'[^']*'|[^\\s&|!^()]+))|([a-z][a-z0-9-]+)|(:(?:is|has|not|where)\\([^()]*(?:\\([^()]*(?:\\([^)]*\\))?[^)]*\\))*[^)]*\\))|(:[-a-z][a-z0-9-]*(?:\\([^)]+\\))?)|(\\.[a-z][a-z0-9-]+)|(\\[[^\\]]+\\])/gi;\n\nfunction tokenize(stateKey: string): { tokens: Token[]; errors: StateKeyError[] } {\n const tokens: Token[] = [];\n const errors: StateKeyError[] = [];\n\n // Replace commas with | outside of parentheses\n const normalized = replaceCommasOutsideParens(stateKey);\n\n const covered = new Set<number>();\n\n STATE_TOKEN_PATTERN.lastIndex = 0;\n let match: RegExpExecArray | null;\n while ((match = STATE_TOKEN_PATTERN.exec(normalized)) !== null) {\n const fullMatch = match[0];\n const offset = match.index;\n\n for (let i = offset; i < offset + fullMatch.length; i++) {\n covered.add(i);\n }\n\n if (match[1]) {\n switch (fullMatch) {\n case '&':\n tokens.push({ type: 'AND', value: '&', offset, length: 1 });\n break;\n case '|':\n tokens.push({ type: 'OR', value: '|', offset, length: 1 });\n break;\n case '!':\n tokens.push({ type: 'NOT', value: '!', offset, length: 1 });\n break;\n case '^':\n tokens.push({ type: 'XOR', value: '^', offset, length: 1 });\n break;\n }\n } else if (match[2]) {\n if (fullMatch === '(') {\n tokens.push({ type: 'LPAREN', value: '(', offset, length: 1 });\n } else {\n tokens.push({ type: 'RPAREN', value: ')', offset, length: 1 });\n }\n } else {\n tokens.push({\n type: 'STATE',\n value: fullMatch,\n offset,\n length: fullMatch.length,\n });\n }\n }\n\n // Check for uncovered characters (unrecognized tokens)\n const uncovered: { ch: string; pos: number }[] = [];\n for (let i = 0; i < stateKey.length; i++) {\n const ch = stateKey[i];\n if (ch === ' ' || ch === '\\t' || ch === ',') continue;\n if (!covered.has(i)) {\n uncovered.push({ ch, pos: i });\n }\n }\n\n if (uncovered.length > 0) {\n const chars = [...new Set(uncovered.map((u) => u.ch))].join('');\n errors.push({\n message: `Unrecognized characters '${chars}' in state key '${stateKey}'.`,\n offset: uncovered[0].pos,\n length: 1,\n });\n }\n\n return { tokens, errors };\n}\n\nfunction replaceCommasOutsideParens(str: string): string {\n let result = '';\n let depth = 0;\n\n for (const char of str) {\n if (char === '(') {\n depth++;\n result += char;\n } else if (char === ')') {\n depth--;\n result += char;\n } else if (char === ',' && depth === 0) {\n result += '|';\n } else {\n result += char;\n }\n }\n\n return result;\n}\n\n// ============================================================================\n// Validator\n// ============================================================================\n\nconst MAX_XOR_CHAIN_LENGTH = 4;\n\nconst DIMENSION_SHORTHANDS = new Set(['w', 'h', 'is', 'bs']);\nconst DIMENSION_FULL = new Set([\n 'width',\n 'height',\n 'inline-size',\n 'block-size',\n]);\n\nclass StateKeyValidator {\n private errors: StateKeyError[] = [];\n private hasOwn = false;\n private referencedAliases: string[] = [];\n private tokens: Token[];\n private pos = 0;\n private opts: StateKeyParserOptions;\n private insideOwn = false;\n\n constructor(\n tokens: Token[],\n tokenErrors: StateKeyError[],\n opts: StateKeyParserOptions,\n ) {\n this.tokens = tokens;\n this.errors = [...tokenErrors];\n this.opts = opts;\n }\n\n validate(): StateKeyResult {\n if (this.tokens.length > 0) {\n this.parseExpression();\n }\n\n return {\n errors: this.errors,\n hasOwn: this.hasOwn,\n referencedAliases: this.referencedAliases,\n };\n }\n\n private current(): Token | undefined {\n return this.tokens[this.pos];\n }\n\n private advance(): Token | undefined {\n return this.tokens[this.pos++];\n }\n\n private match(type: TokenType): boolean {\n if (this.current()?.type === type) {\n this.advance();\n return true;\n }\n return false;\n }\n\n private parseExpression(): void {\n this.parseAnd();\n }\n\n private parseAnd(): void {\n this.parseOr();\n while (this.current()?.type === 'AND') {\n this.advance();\n if (!this.current() || this.current()?.type === 'AND') {\n const prev = this.tokens[this.pos - 1];\n this.errors.push({\n message: \"Expected expression after '&' operator.\",\n offset: prev.offset,\n length: prev.length,\n });\n return;\n }\n this.parseOr();\n }\n }\n\n private parseOr(): void {\n this.parseXor();\n while (this.current()?.type === 'OR') {\n this.advance();\n if (!this.current() || this.current()?.type === 'OR') {\n const prev = this.tokens[this.pos - 1];\n this.errors.push({\n message: \"Expected expression after '|' operator.\",\n offset: prev.offset,\n length: prev.length,\n });\n return;\n }\n this.parseXor();\n }\n }\n\n private parseXor(): void {\n this.parseUnary();\n let operandCount = 1;\n\n while (this.current()?.type === 'XOR') {\n this.advance();\n operandCount++;\n if (operandCount > MAX_XOR_CHAIN_LENGTH) {\n const prev = this.tokens[this.pos - 1];\n this.errors.push({\n message: `XOR chain with ${operandCount} operands produces ${Math.pow(2, operandCount - 1)} OR branches. Consider breaking into smaller expressions.`,\n offset: prev.offset,\n length: prev.length,\n });\n }\n this.parseUnary();\n }\n }\n\n private parseUnary(): void {\n if (this.match('NOT')) {\n if (\n !this.current() ||\n this.current()?.type === 'AND' ||\n this.current()?.type === 'OR' ||\n this.current()?.type === 'XOR'\n ) {\n const prev = this.tokens[this.pos - 1];\n this.errors.push({\n message: \"Expected expression after '!' operator.\",\n offset: prev.offset,\n length: prev.length,\n });\n return;\n }\n this.parseUnary();\n return;\n }\n this.parsePrimary();\n }\n\n private parsePrimary(): void {\n if (this.match('LPAREN')) {\n this.parseExpression();\n if (!this.match('RPAREN')) {\n this.errors.push({\n message: \"Missing closing ')' in state expression.\",\n offset: this.tokens[this.pos - 1]?.offset ?? 0,\n length: 1,\n });\n }\n return;\n }\n\n const token = this.current();\n if (token?.type === 'STATE') {\n this.advance();\n this.validateStateToken(token);\n return;\n }\n\n // Unexpected token or end\n if (token) {\n this.errors.push({\n message: `Unexpected token '${token.value}'.`,\n offset: token.offset,\n length: token.length,\n });\n this.advance();\n }\n }\n\n private validateStateToken(token: Token): void {\n const value = token.value;\n\n // @starting\n if (value === '@starting') return;\n\n // @media:type\n if (value.startsWith('@media:')) {\n const mediaType = value.slice(7);\n const validTypes = new Set(['print', 'screen', 'all', 'speech']);\n if (!validTypes.has(mediaType)) {\n this.errors.push({\n message: `Unknown media type '${mediaType}'. Valid: print, screen, all, speech.`,\n offset: token.offset,\n length: token.length,\n });\n }\n return;\n }\n\n // @media(...)\n if (value.startsWith('@media(')) {\n this.validateMediaQuery(value, token);\n return;\n }\n\n // @supports(...)\n if (value.startsWith('@supports(')) {\n this.validateSupportsQuery(value, token);\n return;\n }\n\n // @root(...)\n if (value.startsWith('@root(')) {\n this.validateInnerStateExpression(value, 6, token);\n return;\n }\n\n // @parent(...)\n if (value.startsWith('@parent(')) {\n this.validateParentState(value, token);\n return;\n }\n\n // @own(...)\n if (value.startsWith('@own(')) {\n this.hasOwn = true;\n if (this.insideOwn) {\n this.errors.push({\n message: 'Nested @own() is not allowed.',\n offset: token.offset,\n length: token.length,\n });\n return;\n }\n const prevInsideOwn = this.insideOwn;\n this.insideOwn = true;\n this.validateInnerStateExpression(value, 5, token);\n this.insideOwn = prevInsideOwn;\n return;\n }\n\n // @(...) container query\n if (value.startsWith('@(')) {\n this.validateContainerQuery(value, token);\n return;\n }\n\n // @alias predefined state\n if (value.startsWith('@') && /^@[A-Za-z][A-Za-z0-9-]*$/.test(value)) {\n this.referencedAliases.push(value);\n return;\n }\n\n // Pseudo-class/pseudo-element\n if (value.startsWith(':')) {\n this.validatePseudoClass(value, token);\n return;\n }\n\n // Class selector\n if (value.startsWith('.')) return;\n\n // Attribute selector\n if (value.startsWith('[')) {\n this.validateAttributeSelector(value, token);\n return;\n }\n\n // Value modifier (e.g., theme=danger)\n if (value.includes('=')) {\n this.validateValueModifier(value, token);\n return;\n }\n\n // Boolean modifier (e.g., hovered, disabled)\n if (/^[a-z][a-z0-9-]+$/i.test(value)) return;\n\n this.errors.push({\n message: `Unrecognized state token '${value}'.`,\n offset: token.offset,\n length: token.length,\n });\n }\n\n private validateMediaQuery(raw: string, token: Token): void {\n const content = raw.slice(7, -1);\n if (!content.trim()) {\n this.errors.push({\n message: 'Empty @media() query.',\n offset: token.offset,\n length: token.length,\n });\n return;\n }\n\n // Expand dimension shorthands for validation\n const expanded = expandDimensionShorthands(content.trim());\n\n // Feature query (contains ':' but no comparison operators)\n if (\n expanded.includes(':') &&\n !expanded.includes('<') &&\n !expanded.includes('>') &&\n !expanded.includes('=')\n ) {\n return;\n }\n\n // Boolean feature query (no comparison operators)\n if (\n !expanded.includes('<') &&\n !expanded.includes('>') &&\n !expanded.includes('=')\n ) {\n return;\n }\n\n // Dimension query — validate dimension names\n this.validateDimensionCondition(expanded, token);\n }\n\n private validateDimensionCondition(condition: string, token: Token): void {\n // Range syntax: 600px <= width < 1200px\n const rangeMatch = condition.match(\n /^(.+?)\\s*(<=|<)\\s*(\\S+)\\s*(<=|<)\\s*(.+)$/,\n );\n if (rangeMatch) {\n const dim = rangeMatch[3];\n if (!DIMENSION_FULL.has(dim) && !DIMENSION_SHORTHANDS.has(dim)) {\n this.errors.push({\n message: `Unknown dimension '${dim}' in media/container query. Valid: width, height, inline-size, block-size (or w, h, is, bs).`,\n offset: token.offset,\n length: token.length,\n });\n }\n return;\n }\n\n // Simple comparison: width < 768px\n const simpleMatch = condition.match(\n /^(\\S+)\\s*(<=|>=|<|>|=)\\s*(.+)$/,\n );\n if (simpleMatch) {\n const dim = simpleMatch[1];\n if (\n DIMENSION_FULL.has(dim) ||\n DIMENSION_SHORTHANDS.has(dim)\n ) {\n return;\n }\n }\n\n // Reversed: 768px > width\n const reversedMatch = condition.match(\n /^(.+?)\\s*(<=|>=|<|>|=)\\s*(\\S+)$/,\n );\n if (reversedMatch) {\n const dim = reversedMatch[3];\n if (\n DIMENSION_FULL.has(dim) ||\n DIMENSION_SHORTHANDS.has(dim)\n ) {\n return;\n }\n }\n }\n\n private validateSupportsQuery(raw: string, token: Token): void {\n const content = raw.slice(10, -1);\n if (!content.trim()) {\n this.errors.push({\n message: 'Empty @supports() query.',\n offset: token.offset,\n length: token.length,\n });\n }\n }\n\n private validateInnerStateExpression(\n raw: string,\n prefixLen: number,\n _token: Token,\n ): void {\n const content = raw.slice(prefixLen, -1);\n if (!content.trim()) return;\n\n const innerResult = parseStateKey(content, this.opts);\n this.errors.push(...innerResult.errors);\n if (innerResult.hasOwn) this.hasOwn = true;\n this.referencedAliases.push(...innerResult.referencedAliases);\n }\n\n private validateParentState(raw: string, token: Token): void {\n const content = raw.slice(8, -1);\n if (!content.trim()) {\n this.errors.push({\n message: 'Empty @parent() state.',\n offset: token.offset,\n length: token.length,\n });\n return;\n }\n\n let condition = content.trim();\n\n // Check for direct parent combinator: @parent(hovered, >)\n const lastCommaIdx = condition.lastIndexOf(',');\n if (lastCommaIdx !== -1) {\n const afterComma = condition.slice(lastCommaIdx + 1).trim();\n if (afterComma === '>') {\n condition = condition.slice(0, lastCommaIdx).trim();\n }\n }\n\n const innerResult = parseStateKey(condition, this.opts);\n this.errors.push(...innerResult.errors);\n this.referencedAliases.push(...innerResult.referencedAliases);\n }\n\n private validateContainerQuery(raw: string, token: Token): void {\n const content = raw.slice(2, -1);\n if (!content.trim()) {\n this.errors.push({\n message: 'Empty container query.',\n offset: token.offset,\n length: token.length,\n });\n return;\n }\n\n // Named container: @(layout, w < 600px)\n const commaIdx = findTopLevelComma(content);\n let condition: string;\n\n if (commaIdx !== -1) {\n condition = content.slice(commaIdx + 1).trim();\n } else {\n condition = content.trim();\n }\n\n // Style query: @($variant=primary) — skip\n if (condition.startsWith('$')) return;\n\n // Function-like: scroll-state(...) — skip\n if (/^[a-zA-Z][\\w-]*\\s*\\(/.test(condition)) return;\n\n // Dimension query\n const expanded = expandDimensionShorthands(condition);\n if (\n expanded.includes('<') ||\n expanded.includes('>') ||\n expanded.includes('=')\n ) {\n this.validateDimensionCondition(expanded, token);\n }\n }\n\n private validatePseudoClass(value: string, token: Token): void {\n // :is(), :has(), :not(), :where() — structural pseudo-classes\n const enhancedMatch = /^:(is|has|not|where)\\(/.exec(value);\n if (enhancedMatch) return;\n\n // Functional pseudo-classes like :nth-child(2n+1)\n const funcMatch = /^(:[a-z-]+)\\(/.exec(value);\n if (funcMatch) {\n const baseName = funcMatch[1];\n if (!KNOWN_PSEUDO_CLASSES.has(baseName)) {\n this.errors.push({\n message: `Unknown pseudo-class '${baseName}'.`,\n offset: token.offset,\n length: token.length,\n });\n }\n return;\n }\n\n // Simple pseudo-class: :hover, :focus, etc.\n if (!KNOWN_PSEUDO_CLASSES.has(value)) {\n this.errors.push({\n message: `Unknown pseudo-class '${value}'.`,\n offset: token.offset,\n length: token.length,\n });\n }\n }\n\n private validateAttributeSelector(value: string, token: Token): void {\n if (!value.startsWith('[') || !value.endsWith(']')) {\n this.errors.push({\n message: `Malformed attribute selector '${value}'.`,\n offset: token.offset,\n length: token.length,\n });\n }\n }\n\n private validateValueModifier(value: string, token: Token): void {\n const opMatch = value.match(/^([a-z][a-z0-9-]*)(\\^=|\\$=|\\*=|=)(.+)$/i);\n if (!opMatch) {\n this.errors.push({\n message: `Invalid value modifier syntax '${value}'.`,\n offset: token.offset,\n length: token.length,\n });\n }\n }\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\nfunction expandDimensionShorthands(condition: string): string {\n return condition\n .replace(/\\bw\\b/g, 'width')\n .replace(/\\bh\\b/g, 'height')\n .replace(/\\bis\\b/g, 'inline-size')\n .replace(/\\bbs\\b/g, 'block-size');\n}\n\nfunction findTopLevelComma(str: string): number {\n let depth = 0;\n for (let i = 0; i < str.length; i++) {\n const ch = str[i];\n if (ch === '(') depth++;\n else if (ch === ')') depth = Math.max(0, depth - 1);\n else if (ch === ',' && depth === 0) return i;\n }\n return -1;\n}\n\n// ============================================================================\n// Public API\n// ============================================================================\n\n/**\n * Validate a state key string and return detailed errors.\n */\nexport function parseStateKey(\n stateKey: string,\n opts: StateKeyParserOptions = {},\n): StateKeyResult {\n if (!stateKey || !stateKey.trim()) {\n return { errors: [], hasOwn: false, referencedAliases: [] };\n }\n\n const { tokens, errors: tokenErrors } = tokenize(stateKey.trim());\n const validator = new StateKeyValidator(tokens, tokenErrors, opts);\n return validator.validate();\n}\n\n/**\n * Validate a state definition value (the RHS of a state alias).\n * State definitions should be valid state expressions like\n * '@media(w < 768px)', '@root(theme=dark)', etc.\n */\nexport function validateStateDefinition(\n value: string,\n opts: StateKeyParserOptions = {},\n): StateKeyResult {\n return parseStateKey(value, opts);\n}\n"],"mappings":"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"}
|