@tenphi/eslint-plugin-tasty 0.6.2 → 0.6.4

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.
@@ -7,7 +7,7 @@ import { KNOWN_PSEUDO_CLASSES } from "../constants.js";
7
7
  * pseudo-classes with functions (including :is/:has/:not/:where with nesting),
8
8
  * class selectors, and attribute selectors.
9
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;
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
11
  function tokenize(stateKey) {
12
12
  const tokens = [];
13
13
  const errors = [];
@@ -1 +1 @@
1
- {"version":3,"file":"state-key-parser.js","names":[],"sources":["../../src/parsers/state-key-parser.ts"],"sourcesContent":["import { 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): {\n tokens: Token[];\n errors: StateKeyError[];\n} {\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(/^(\\S+)\\s*(<=|>=|<|>|=)\\s*(.+)$/);\n if (simpleMatch) {\n const dim = simpleMatch[1];\n if (DIMENSION_FULL.has(dim) || DIMENSION_SHORTHANDS.has(dim)) {\n return;\n }\n }\n\n // Reversed: 768px > width\n const reversedMatch = condition.match(/^(.+?)\\s*(<=|>=|<|>|=)\\s*(\\S+)$/);\n if (reversedMatch) {\n const dim = reversedMatch[3];\n if (DIMENSION_FULL.has(dim) || DIMENSION_SHORTHANDS.has(dim)) {\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":";;;;;;;;;AAyCA,MAAM,sBACJ;AAEF,SAAS,SAAS,UAGhB;CACA,MAAM,SAAkB,EAAE;CAC1B,MAAM,SAA0B,EAAE;CAGlC,MAAM,aAAa,2BAA2B,SAAS;CAEvD,MAAM,0BAAU,IAAI,KAAa;AAEjC,qBAAoB,YAAY;CAChC,IAAI;AACJ,SAAQ,QAAQ,oBAAoB,KAAK,WAAW,MAAM,MAAM;EAC9D,MAAM,YAAY,MAAM;EACxB,MAAM,SAAS,MAAM;AAErB,OAAK,IAAI,IAAI,QAAQ,IAAI,SAAS,UAAU,QAAQ,IAClD,SAAQ,IAAI,EAAE;AAGhB,MAAI,MAAM,GACR,SAAQ,WAAR;GACE,KAAK;AACH,WAAO,KAAK;KAAE,MAAM;KAAO,OAAO;KAAK;KAAQ,QAAQ;KAAG,CAAC;AAC3D;GACF,KAAK;AACH,WAAO,KAAK;KAAE,MAAM;KAAM,OAAO;KAAK;KAAQ,QAAQ;KAAG,CAAC;AAC1D;GACF,KAAK;AACH,WAAO,KAAK;KAAE,MAAM;KAAO,OAAO;KAAK;KAAQ,QAAQ;KAAG,CAAC;AAC3D;GACF,KAAK;AACH,WAAO,KAAK;KAAE,MAAM;KAAO,OAAO;KAAK;KAAQ,QAAQ;KAAG,CAAC;AAC3D;;WAEK,MAAM,GACf,KAAI,cAAc,IAChB,QAAO,KAAK;GAAE,MAAM;GAAU,OAAO;GAAK;GAAQ,QAAQ;GAAG,CAAC;MAE9D,QAAO,KAAK;GAAE,MAAM;GAAU,OAAO;GAAK;GAAQ,QAAQ;GAAG,CAAC;MAGhE,QAAO,KAAK;GACV,MAAM;GACN,OAAO;GACP;GACA,QAAQ,UAAU;GACnB,CAAC;;CAKN,MAAM,YAA2C,EAAE;AACnD,MAAK,IAAI,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK;EACxC,MAAM,KAAK,SAAS;AACpB,MAAI,OAAO,OAAO,OAAO,OAAQ,OAAO,IAAK;AAC7C,MAAI,CAAC,QAAQ,IAAI,EAAE,CACjB,WAAU,KAAK;GAAE;GAAI,KAAK;GAAG,CAAC;;AAIlC,KAAI,UAAU,SAAS,GAAG;EACxB,MAAM,QAAQ,CAAC,GAAG,IAAI,IAAI,UAAU,KAAK,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,KAAK,GAAG;AAC/D,SAAO,KAAK;GACV,SAAS,4BAA4B,MAAM,kBAAkB,SAAS;GACtE,QAAQ,UAAU,GAAG;GACrB,QAAQ;GACT,CAAC;;AAGJ,QAAO;EAAE;EAAQ;EAAQ;;AAG3B,SAAS,2BAA2B,KAAqB;CACvD,IAAI,SAAS;CACb,IAAI,QAAQ;AAEZ,MAAK,MAAM,QAAQ,IACjB,KAAI,SAAS,KAAK;AAChB;AACA,YAAU;YACD,SAAS,KAAK;AACvB;AACA,YAAU;YACD,SAAS,OAAO,UAAU,EACnC,WAAU;KAEV,WAAU;AAId,QAAO;;AAOT,MAAM,uBAAuB;AAE7B,MAAM,uBAAuB,IAAI,IAAI;CAAC;CAAK;CAAK;CAAM;CAAK,CAAC;AAC5D,MAAM,iBAAiB,IAAI,IAAI;CAC7B;CACA;CACA;CACA;CACD,CAAC;AAEF,IAAM,oBAAN,MAAwB;CACtB,AAAQ,SAA0B,EAAE;CACpC,AAAQ,SAAS;CACjB,AAAQ,oBAA8B,EAAE;CACxC,AAAQ;CACR,AAAQ,MAAM;CACd,AAAQ;CACR,AAAQ,YAAY;CAEpB,YACE,QACA,aACA,MACA;AACA,OAAK,SAAS;AACd,OAAK,SAAS,CAAC,GAAG,YAAY;AAC9B,OAAK,OAAO;;CAGd,WAA2B;AACzB,MAAI,KAAK,OAAO,SAAS,EACvB,MAAK,iBAAiB;AAGxB,SAAO;GACL,QAAQ,KAAK;GACb,QAAQ,KAAK;GACb,mBAAmB,KAAK;GACzB;;CAGH,AAAQ,UAA6B;AACnC,SAAO,KAAK,OAAO,KAAK;;CAG1B,AAAQ,UAA6B;AACnC,SAAO,KAAK,OAAO,KAAK;;CAG1B,AAAQ,MAAM,MAA0B;AACtC,MAAI,KAAK,SAAS,EAAE,SAAS,MAAM;AACjC,QAAK,SAAS;AACd,UAAO;;AAET,SAAO;;CAGT,AAAQ,kBAAwB;AAC9B,OAAK,UAAU;;CAGjB,AAAQ,WAAiB;AACvB,OAAK,SAAS;AACd,SAAO,KAAK,SAAS,EAAE,SAAS,OAAO;AACrC,QAAK,SAAS;AACd,OAAI,CAAC,KAAK,SAAS,IAAI,KAAK,SAAS,EAAE,SAAS,OAAO;IACrD,MAAM,OAAO,KAAK,OAAO,KAAK,MAAM;AACpC,SAAK,OAAO,KAAK;KACf,SAAS;KACT,QAAQ,KAAK;KACb,QAAQ,KAAK;KACd,CAAC;AACF;;AAEF,QAAK,SAAS;;;CAIlB,AAAQ,UAAgB;AACtB,OAAK,UAAU;AACf,SAAO,KAAK,SAAS,EAAE,SAAS,MAAM;AACpC,QAAK,SAAS;AACd,OAAI,CAAC,KAAK,SAAS,IAAI,KAAK,SAAS,EAAE,SAAS,MAAM;IACpD,MAAM,OAAO,KAAK,OAAO,KAAK,MAAM;AACpC,SAAK,OAAO,KAAK;KACf,SAAS;KACT,QAAQ,KAAK;KACb,QAAQ,KAAK;KACd,CAAC;AACF;;AAEF,QAAK,UAAU;;;CAInB,AAAQ,WAAiB;AACvB,OAAK,YAAY;EACjB,IAAI,eAAe;AAEnB,SAAO,KAAK,SAAS,EAAE,SAAS,OAAO;AACrC,QAAK,SAAS;AACd;AACA,OAAI,eAAe,sBAAsB;IACvC,MAAM,OAAO,KAAK,OAAO,KAAK,MAAM;AACpC,SAAK,OAAO,KAAK;KACf,SAAS,kBAAkB,aAAa,qBAAqB,KAAK,IAAI,GAAG,eAAe,EAAE,CAAC;KAC3F,QAAQ,KAAK;KACb,QAAQ,KAAK;KACd,CAAC;;AAEJ,QAAK,YAAY;;;CAIrB,AAAQ,aAAmB;AACzB,MAAI,KAAK,MAAM,MAAM,EAAE;AACrB,OACE,CAAC,KAAK,SAAS,IACf,KAAK,SAAS,EAAE,SAAS,SACzB,KAAK,SAAS,EAAE,SAAS,QACzB,KAAK,SAAS,EAAE,SAAS,OACzB;IACA,MAAM,OAAO,KAAK,OAAO,KAAK,MAAM;AACpC,SAAK,OAAO,KAAK;KACf,SAAS;KACT,QAAQ,KAAK;KACb,QAAQ,KAAK;KACd,CAAC;AACF;;AAEF,QAAK,YAAY;AACjB;;AAEF,OAAK,cAAc;;CAGrB,AAAQ,eAAqB;AAC3B,MAAI,KAAK,MAAM,SAAS,EAAE;AACxB,QAAK,iBAAiB;AACtB,OAAI,CAAC,KAAK,MAAM,SAAS,CACvB,MAAK,OAAO,KAAK;IACf,SAAS;IACT,QAAQ,KAAK,OAAO,KAAK,MAAM,IAAI,UAAU;IAC7C,QAAQ;IACT,CAAC;AAEJ;;EAGF,MAAM,QAAQ,KAAK,SAAS;AAC5B,MAAI,OAAO,SAAS,SAAS;AAC3B,QAAK,SAAS;AACd,QAAK,mBAAmB,MAAM;AAC9B;;AAIF,MAAI,OAAO;AACT,QAAK,OAAO,KAAK;IACf,SAAS,qBAAqB,MAAM,MAAM;IAC1C,QAAQ,MAAM;IACd,QAAQ,MAAM;IACf,CAAC;AACF,QAAK,SAAS;;;CAIlB,AAAQ,mBAAmB,OAAoB;EAC7C,MAAM,QAAQ,MAAM;AAGpB,MAAI,UAAU,YAAa;AAG3B,MAAI,MAAM,WAAW,UAAU,EAAE;GAC/B,MAAM,YAAY,MAAM,MAAM,EAAE;AAEhC,OAAI,CADe,IAAI,IAAI;IAAC;IAAS;IAAU;IAAO;IAAS,CAAC,CAChD,IAAI,UAAU,CAC5B,MAAK,OAAO,KAAK;IACf,SAAS,uBAAuB,UAAU;IAC1C,QAAQ,MAAM;IACd,QAAQ,MAAM;IACf,CAAC;AAEJ;;AAIF,MAAI,MAAM,WAAW,UAAU,EAAE;AAC/B,QAAK,mBAAmB,OAAO,MAAM;AACrC;;AAIF,MAAI,MAAM,WAAW,aAAa,EAAE;AAClC,QAAK,sBAAsB,OAAO,MAAM;AACxC;;AAIF,MAAI,MAAM,WAAW,SAAS,EAAE;AAC9B,QAAK,6BAA6B,OAAO,GAAG,MAAM;AAClD;;AAIF,MAAI,MAAM,WAAW,WAAW,EAAE;AAChC,QAAK,oBAAoB,OAAO,MAAM;AACtC;;AAIF,MAAI,MAAM,WAAW,QAAQ,EAAE;AAC7B,QAAK,SAAS;AACd,OAAI,KAAK,WAAW;AAClB,SAAK,OAAO,KAAK;KACf,SAAS;KACT,QAAQ,MAAM;KACd,QAAQ,MAAM;KACf,CAAC;AACF;;GAEF,MAAM,gBAAgB,KAAK;AAC3B,QAAK,YAAY;AACjB,QAAK,6BAA6B,OAAO,GAAG,MAAM;AAClD,QAAK,YAAY;AACjB;;AAIF,MAAI,MAAM,WAAW,KAAK,EAAE;AAC1B,QAAK,uBAAuB,OAAO,MAAM;AACzC;;AAIF,MAAI,MAAM,WAAW,IAAI,IAAI,2BAA2B,KAAK,MAAM,EAAE;AACnE,QAAK,kBAAkB,KAAK,MAAM;AAClC;;AAIF,MAAI,MAAM,WAAW,IAAI,EAAE;AACzB,QAAK,oBAAoB,OAAO,MAAM;AACtC;;AAIF,MAAI,MAAM,WAAW,IAAI,CAAE;AAG3B,MAAI,MAAM,WAAW,IAAI,EAAE;AACzB,QAAK,0BAA0B,OAAO,MAAM;AAC5C;;AAIF,MAAI,MAAM,SAAS,IAAI,EAAE;AACvB,QAAK,sBAAsB,OAAO,MAAM;AACxC;;AAIF,MAAI,qBAAqB,KAAK,MAAM,CAAE;AAEtC,OAAK,OAAO,KAAK;GACf,SAAS,6BAA6B,MAAM;GAC5C,QAAQ,MAAM;GACd,QAAQ,MAAM;GACf,CAAC;;CAGJ,AAAQ,mBAAmB,KAAa,OAAoB;EAC1D,MAAM,UAAU,IAAI,MAAM,GAAG,GAAG;AAChC,MAAI,CAAC,QAAQ,MAAM,EAAE;AACnB,QAAK,OAAO,KAAK;IACf,SAAS;IACT,QAAQ,MAAM;IACd,QAAQ,MAAM;IACf,CAAC;AACF;;EAIF,MAAM,WAAW,0BAA0B,QAAQ,MAAM,CAAC;AAG1D,MACE,SAAS,SAAS,IAAI,IACtB,CAAC,SAAS,SAAS,IAAI,IACvB,CAAC,SAAS,SAAS,IAAI,IACvB,CAAC,SAAS,SAAS,IAAI,CAEvB;AAIF,MACE,CAAC,SAAS,SAAS,IAAI,IACvB,CAAC,SAAS,SAAS,IAAI,IACvB,CAAC,SAAS,SAAS,IAAI,CAEvB;AAIF,OAAK,2BAA2B,UAAU,MAAM;;CAGlD,AAAQ,2BAA2B,WAAmB,OAAoB;EAExE,MAAM,aAAa,UAAU,MAC3B,2CACD;AACD,MAAI,YAAY;GACd,MAAM,MAAM,WAAW;AACvB,OAAI,CAAC,eAAe,IAAI,IAAI,IAAI,CAAC,qBAAqB,IAAI,IAAI,CAC5D,MAAK,OAAO,KAAK;IACf,SAAS,sBAAsB,IAAI;IACnC,QAAQ,MAAM;IACd,QAAQ,MAAM;IACf,CAAC;AAEJ;;EAIF,MAAM,cAAc,UAAU,MAAM,iCAAiC;AACrE,MAAI,aAAa;GACf,MAAM,MAAM,YAAY;AACxB,OAAI,eAAe,IAAI,IAAI,IAAI,qBAAqB,IAAI,IAAI,CAC1D;;EAKJ,MAAM,gBAAgB,UAAU,MAAM,kCAAkC;AACxE,MAAI,eAAe;GACjB,MAAM,MAAM,cAAc;AAC1B,OAAI,eAAe,IAAI,IAAI,IAAI,qBAAqB,IAAI,IAAI,CAC1D;;;CAKN,AAAQ,sBAAsB,KAAa,OAAoB;AAE7D,MAAI,CADY,IAAI,MAAM,IAAI,GAAG,CACpB,MAAM,CACjB,MAAK,OAAO,KAAK;GACf,SAAS;GACT,QAAQ,MAAM;GACd,QAAQ,MAAM;GACf,CAAC;;CAIN,AAAQ,6BACN,KACA,WACA,QACM;EACN,MAAM,UAAU,IAAI,MAAM,WAAW,GAAG;AACxC,MAAI,CAAC,QAAQ,MAAM,CAAE;EAErB,MAAM,cAAc,cAAc,SAAS,KAAK,KAAK;AACrD,OAAK,OAAO,KAAK,GAAG,YAAY,OAAO;AACvC,MAAI,YAAY,OAAQ,MAAK,SAAS;AACtC,OAAK,kBAAkB,KAAK,GAAG,YAAY,kBAAkB;;CAG/D,AAAQ,oBAAoB,KAAa,OAAoB;EAC3D,MAAM,UAAU,IAAI,MAAM,GAAG,GAAG;AAChC,MAAI,CAAC,QAAQ,MAAM,EAAE;AACnB,QAAK,OAAO,KAAK;IACf,SAAS;IACT,QAAQ,MAAM;IACd,QAAQ,MAAM;IACf,CAAC;AACF;;EAGF,IAAI,YAAY,QAAQ,MAAM;EAG9B,MAAM,eAAe,UAAU,YAAY,IAAI;AAC/C,MAAI,iBAAiB,IAEnB;OADmB,UAAU,MAAM,eAAe,EAAE,CAAC,MAAM,KACxC,IACjB,aAAY,UAAU,MAAM,GAAG,aAAa,CAAC,MAAM;;EAIvD,MAAM,cAAc,cAAc,WAAW,KAAK,KAAK;AACvD,OAAK,OAAO,KAAK,GAAG,YAAY,OAAO;AACvC,OAAK,kBAAkB,KAAK,GAAG,YAAY,kBAAkB;;CAG/D,AAAQ,uBAAuB,KAAa,OAAoB;EAC9D,MAAM,UAAU,IAAI,MAAM,GAAG,GAAG;AAChC,MAAI,CAAC,QAAQ,MAAM,EAAE;AACnB,QAAK,OAAO,KAAK;IACf,SAAS;IACT,QAAQ,MAAM;IACd,QAAQ,MAAM;IACf,CAAC;AACF;;EAIF,MAAM,WAAW,kBAAkB,QAAQ;EAC3C,IAAI;AAEJ,MAAI,aAAa,GACf,aAAY,QAAQ,MAAM,WAAW,EAAE,CAAC,MAAM;MAE9C,aAAY,QAAQ,MAAM;AAI5B,MAAI,UAAU,WAAW,IAAI,CAAE;AAG/B,MAAI,uBAAuB,KAAK,UAAU,CAAE;EAG5C,MAAM,WAAW,0BAA0B,UAAU;AACrD,MACE,SAAS,SAAS,IAAI,IACtB,SAAS,SAAS,IAAI,IACtB,SAAS,SAAS,IAAI,CAEtB,MAAK,2BAA2B,UAAU,MAAM;;CAIpD,AAAQ,oBAAoB,OAAe,OAAoB;AAG7D,MADsB,yBAAyB,KAAK,MAAM,CACvC;EAGnB,MAAM,YAAY,gBAAgB,KAAK,MAAM;AAC7C,MAAI,WAAW;GACb,MAAM,WAAW,UAAU;AAC3B,OAAI,CAAC,qBAAqB,IAAI,SAAS,CACrC,MAAK,OAAO,KAAK;IACf,SAAS,yBAAyB,SAAS;IAC3C,QAAQ,MAAM;IACd,QAAQ,MAAM;IACf,CAAC;AAEJ;;AAIF,MAAI,CAAC,qBAAqB,IAAI,MAAM,CAClC,MAAK,OAAO,KAAK;GACf,SAAS,yBAAyB,MAAM;GACxC,QAAQ,MAAM;GACd,QAAQ,MAAM;GACf,CAAC;;CAIN,AAAQ,0BAA0B,OAAe,OAAoB;AACnE,MAAI,CAAC,MAAM,WAAW,IAAI,IAAI,CAAC,MAAM,SAAS,IAAI,CAChD,MAAK,OAAO,KAAK;GACf,SAAS,iCAAiC,MAAM;GAChD,QAAQ,MAAM;GACd,QAAQ,MAAM;GACf,CAAC;;CAIN,AAAQ,sBAAsB,OAAe,OAAoB;AAE/D,MAAI,CADY,MAAM,MAAM,0CAA0C,CAEpE,MAAK,OAAO,KAAK;GACf,SAAS,kCAAkC,MAAM;GACjD,QAAQ,MAAM;GACd,QAAQ,MAAM;GACf,CAAC;;;AASR,SAAS,0BAA0B,WAA2B;AAC5D,QAAO,UACJ,QAAQ,UAAU,QAAQ,CAC1B,QAAQ,UAAU,SAAS,CAC3B,QAAQ,WAAW,cAAc,CACjC,QAAQ,WAAW,aAAa;;AAGrC,SAAS,kBAAkB,KAAqB;CAC9C,IAAI,QAAQ;AACZ,MAAK,IAAI,IAAI,GAAG,IAAI,IAAI,QAAQ,KAAK;EACnC,MAAM,KAAK,IAAI;AACf,MAAI,OAAO,IAAK;WACP,OAAO,IAAK,SAAQ,KAAK,IAAI,GAAG,QAAQ,EAAE;WAC1C,OAAO,OAAO,UAAU,EAAG,QAAO;;AAE7C,QAAO;;;;;AAUT,SAAgB,cACd,UACA,OAA8B,EAAE,EAChB;AAChB,KAAI,CAAC,YAAY,CAAC,SAAS,MAAM,CAC/B,QAAO;EAAE,QAAQ,EAAE;EAAE,QAAQ;EAAO,mBAAmB,EAAE;EAAE;CAG7D,MAAM,EAAE,QAAQ,QAAQ,gBAAgB,SAAS,SAAS,MAAM,CAAC;AAEjE,QADkB,IAAI,kBAAkB,QAAQ,aAAa,KAAK,CACjD,UAAU;;;;;;;AAQ7B,SAAgB,wBACd,OACA,OAA8B,EAAE,EAChB;AAChB,QAAO,cAAc,OAAO,KAAK"}
1
+ {"version":3,"file":"state-key-parser.js","names":[],"sources":["../../src/parsers/state-key-parser.ts"],"sourcesContent":["import { 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): {\n tokens: Token[];\n errors: StateKeyError[];\n} {\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(/^(\\S+)\\s*(<=|>=|<|>|=)\\s*(.+)$/);\n if (simpleMatch) {\n const dim = simpleMatch[1];\n if (DIMENSION_FULL.has(dim) || DIMENSION_SHORTHANDS.has(dim)) {\n return;\n }\n }\n\n // Reversed: 768px > width\n const reversedMatch = condition.match(/^(.+?)\\s*(<=|>=|<|>|=)\\s*(\\S+)$/);\n if (reversedMatch) {\n const dim = reversedMatch[3];\n if (DIMENSION_FULL.has(dim) || DIMENSION_SHORTHANDS.has(dim)) {\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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"}
@@ -32,18 +32,22 @@ var valid_preset_default = createRule({
32
32
  const modSegment = segments[1]?.trim();
33
33
  if (!nameSegment) return;
34
34
  if (CSS_GLOBAL_KEYWORDS.has(nameSegment)) return;
35
- if (!PRESET_MODIFIERS.has(nameSegment)) {
36
- if (ctx.config.presets.length > 0 && !ctx.config.presets.includes(nameSegment)) context.report({
35
+ const nameTokens = nameSegment.split(/\s+/).filter(Boolean);
36
+ if (!(nameTokens.length > 0 && nameTokens.every((t) => PRESET_MODIFIERS.has(t)))) {
37
+ const presetName = nameTokens[0];
38
+ if (presetName && ctx.config.presets.length > 0 && !ctx.config.presets.includes(presetName)) context.report({
37
39
  node,
38
40
  messageId: "unknownPreset",
39
- data: { name: nameSegment }
41
+ data: { name: presetName }
42
+ });
43
+ }
44
+ if (modSegment) {
45
+ for (const mod of modSegment.split(/\s+/).filter(Boolean)) if (!PRESET_MODIFIERS.has(mod)) context.report({
46
+ node,
47
+ messageId: "unknownModifier",
48
+ data: { modifier: mod }
40
49
  });
41
50
  }
42
- if (modSegment && !PRESET_MODIFIERS.has(modSegment)) context.report({
43
- node,
44
- messageId: "unknownModifier",
45
- data: { modifier: modSegment }
46
- });
47
51
  }
48
52
  function checkObject(node) {
49
53
  if (!ctx.isStyleObject(node)) return;
@@ -1 +1 @@
1
- {"version":3,"file":"valid-preset.js","names":[],"sources":["../../src/rules/valid-preset.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport { createRule } from '../create-rule.js';\nimport { TastyContext, styleObjectListeners } from '../context.js';\nimport { getKeyName, getStringValue } from '../utils.js';\nimport { PRESET_MODIFIERS } from '../constants.js';\n\ntype MessageIds = 'unknownPreset' | 'unknownModifier';\n\nexport default createRule<[], MessageIds>({\n name: 'valid-preset',\n meta: {\n type: 'problem',\n docs: {\n description: 'Validate preset property values against config',\n },\n messages: {\n unknownPreset: \"Unknown preset '{{name}}'.\",\n unknownModifier: \"Unknown preset modifier '{{modifier}}'.\",\n },\n schema: [],\n },\n defaultOptions: [],\n create(context) {\n const ctx = new TastyContext(context);\n\n const CSS_GLOBAL_KEYWORDS = new Set([\n 'inherit',\n 'initial',\n 'unset',\n 'revert',\n 'revert-layer',\n ]);\n\n function checkPresetValue(value: string, node: TSESTree.Node): void {\n const segments = value.trim().split(/\\s*\\/\\s*/);\n if (segments.length === 0) return;\n\n const nameSegment = segments[0].trim();\n const modSegment = segments[1]?.trim();\n\n if (!nameSegment) return;\n if (CSS_GLOBAL_KEYWORDS.has(nameSegment)) return;\n\n // Mod-only shorthand: preset=\"bold\" (name defaults to inherit)\n if (!PRESET_MODIFIERS.has(nameSegment)) {\n if (\n ctx.config.presets.length > 0 &&\n !ctx.config.presets.includes(nameSegment)\n ) {\n context.report({\n node,\n messageId: 'unknownPreset',\n data: { name: nameSegment },\n });\n }\n }\n\n if (modSegment && !PRESET_MODIFIERS.has(modSegment)) {\n context.report({\n node,\n messageId: 'unknownModifier',\n data: { modifier: modSegment },\n });\n }\n }\n\n function checkObject(node: TSESTree.ObjectExpression): void {\n if (!ctx.isStyleObject(node)) return;\n\n for (const prop of node.properties) {\n if (prop.type !== 'Property' || prop.computed) continue;\n\n const key = getKeyName(prop.key);\n if (key !== 'preset') continue;\n\n const str = getStringValue(prop.value);\n if (str) {\n checkPresetValue(str, prop.value);\n continue;\n }\n\n if (prop.value.type === 'Literal' && prop.value.value === true) {\n continue;\n }\n\n if (prop.value.type === 'ObjectExpression') {\n for (const stateProp of prop.value.properties) {\n if (stateProp.type !== 'Property') continue;\n const stateStr = getStringValue(stateProp.value);\n if (stateStr) {\n checkPresetValue(stateStr, stateProp.value);\n }\n }\n }\n }\n }\n\n function handleStyleObject(node: TSESTree.ObjectExpression) {\n checkObject(node);\n }\n\n return {\n ImportDeclaration(node) {\n ctx.trackImport(node);\n },\n\n ...styleObjectListeners(handleStyleObject),\n };\n },\n});\n"],"mappings":";;;;;;AAQA,2BAAe,WAA2B;CACxC,MAAM;CACN,MAAM;EACJ,MAAM;EACN,MAAM,EACJ,aAAa,kDACd;EACD,UAAU;GACR,eAAe;GACf,iBAAiB;GAClB;EACD,QAAQ,EAAE;EACX;CACD,gBAAgB,EAAE;CAClB,OAAO,SAAS;EACd,MAAM,MAAM,IAAI,aAAa,QAAQ;EAErC,MAAM,sBAAsB,IAAI,IAAI;GAClC;GACA;GACA;GACA;GACA;GACD,CAAC;EAEF,SAAS,iBAAiB,OAAe,MAA2B;GAClE,MAAM,WAAW,MAAM,MAAM,CAAC,MAAM,WAAW;AAC/C,OAAI,SAAS,WAAW,EAAG;GAE3B,MAAM,cAAc,SAAS,GAAG,MAAM;GACtC,MAAM,aAAa,SAAS,IAAI,MAAM;AAEtC,OAAI,CAAC,YAAa;AAClB,OAAI,oBAAoB,IAAI,YAAY,CAAE;AAG1C,OAAI,CAAC,iBAAiB,IAAI,YAAY,EACpC;QACE,IAAI,OAAO,QAAQ,SAAS,KAC5B,CAAC,IAAI,OAAO,QAAQ,SAAS,YAAY,CAEzC,SAAQ,OAAO;KACb;KACA,WAAW;KACX,MAAM,EAAE,MAAM,aAAa;KAC5B,CAAC;;AAIN,OAAI,cAAc,CAAC,iBAAiB,IAAI,WAAW,CACjD,SAAQ,OAAO;IACb;IACA,WAAW;IACX,MAAM,EAAE,UAAU,YAAY;IAC/B,CAAC;;EAIN,SAAS,YAAY,MAAuC;AAC1D,OAAI,CAAC,IAAI,cAAc,KAAK,CAAE;AAE9B,QAAK,MAAM,QAAQ,KAAK,YAAY;AAClC,QAAI,KAAK,SAAS,cAAc,KAAK,SAAU;AAG/C,QADY,WAAW,KAAK,IAAI,KACpB,SAAU;IAEtB,MAAM,MAAM,eAAe,KAAK,MAAM;AACtC,QAAI,KAAK;AACP,sBAAiB,KAAK,KAAK,MAAM;AACjC;;AAGF,QAAI,KAAK,MAAM,SAAS,aAAa,KAAK,MAAM,UAAU,KACxD;AAGF,QAAI,KAAK,MAAM,SAAS,mBACtB,MAAK,MAAM,aAAa,KAAK,MAAM,YAAY;AAC7C,SAAI,UAAU,SAAS,WAAY;KACnC,MAAM,WAAW,eAAe,UAAU,MAAM;AAChD,SAAI,SACF,kBAAiB,UAAU,UAAU,MAAM;;;;EAOrD,SAAS,kBAAkB,MAAiC;AAC1D,eAAY,KAAK;;AAGnB,SAAO;GACL,kBAAkB,MAAM;AACtB,QAAI,YAAY,KAAK;;GAGvB,GAAG,qBAAqB,kBAAkB;GAC3C;;CAEJ,CAAC"}
1
+ {"version":3,"file":"valid-preset.js","names":[],"sources":["../../src/rules/valid-preset.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport { createRule } from '../create-rule.js';\nimport { TastyContext, styleObjectListeners } from '../context.js';\nimport { getKeyName, getStringValue } from '../utils.js';\nimport { PRESET_MODIFIERS } from '../constants.js';\n\ntype MessageIds = 'unknownPreset' | 'unknownModifier';\n\nexport default createRule<[], MessageIds>({\n name: 'valid-preset',\n meta: {\n type: 'problem',\n docs: {\n description: 'Validate preset property values against config',\n },\n messages: {\n unknownPreset: \"Unknown preset '{{name}}'.\",\n unknownModifier: \"Unknown preset modifier '{{modifier}}'.\",\n },\n schema: [],\n },\n defaultOptions: [],\n create(context) {\n const ctx = new TastyContext(context);\n\n const CSS_GLOBAL_KEYWORDS = new Set([\n 'inherit',\n 'initial',\n 'unset',\n 'revert',\n 'revert-layer',\n ]);\n\n function checkPresetValue(value: string, node: TSESTree.Node): void {\n const segments = value.trim().split(/\\s*\\/\\s*/);\n if (segments.length === 0) return;\n\n const nameSegment = segments[0].trim();\n const modSegment = segments[1]?.trim();\n\n if (!nameSegment) return;\n if (CSS_GLOBAL_KEYWORDS.has(nameSegment)) return;\n\n const nameTokens = nameSegment.split(/\\s+/).filter(Boolean);\n // Mod-only shorthand: every token in the name segment is a recognized\n // modifier (e.g. preset=\"bold\", preset=\"bold italic\").\n const isModOnlyShorthand =\n nameTokens.length > 0 &&\n nameTokens.every((t) => PRESET_MODIFIERS.has(t));\n\n if (!isModOnlyShorthand) {\n const presetName = nameTokens[0];\n if (\n presetName &&\n ctx.config.presets.length > 0 &&\n !ctx.config.presets.includes(presetName)\n ) {\n context.report({\n node,\n messageId: 'unknownPreset',\n data: { name: presetName },\n });\n }\n }\n\n if (modSegment) {\n for (const mod of modSegment.split(/\\s+/).filter(Boolean)) {\n if (!PRESET_MODIFIERS.has(mod)) {\n context.report({\n node,\n messageId: 'unknownModifier',\n data: { modifier: mod },\n });\n }\n }\n }\n }\n\n function checkObject(node: TSESTree.ObjectExpression): void {\n if (!ctx.isStyleObject(node)) return;\n\n for (const prop of node.properties) {\n if (prop.type !== 'Property' || prop.computed) continue;\n\n const key = getKeyName(prop.key);\n if (key !== 'preset') continue;\n\n const str = getStringValue(prop.value);\n if (str) {\n checkPresetValue(str, prop.value);\n continue;\n }\n\n if (prop.value.type === 'Literal' && prop.value.value === true) {\n continue;\n }\n\n if (prop.value.type === 'ObjectExpression') {\n for (const stateProp of prop.value.properties) {\n if (stateProp.type !== 'Property') continue;\n const stateStr = getStringValue(stateProp.value);\n if (stateStr) {\n checkPresetValue(stateStr, stateProp.value);\n }\n }\n }\n }\n }\n\n function handleStyleObject(node: TSESTree.ObjectExpression) {\n checkObject(node);\n }\n\n return {\n ImportDeclaration(node) {\n ctx.trackImport(node);\n },\n\n ...styleObjectListeners(handleStyleObject),\n };\n },\n});\n"],"mappings":";;;;;;AAQA,2BAAe,WAA2B;CACxC,MAAM;CACN,MAAM;EACJ,MAAM;EACN,MAAM,EACJ,aAAa,kDACd;EACD,UAAU;GACR,eAAe;GACf,iBAAiB;GAClB;EACD,QAAQ,EAAE;EACX;CACD,gBAAgB,EAAE;CAClB,OAAO,SAAS;EACd,MAAM,MAAM,IAAI,aAAa,QAAQ;EAErC,MAAM,sBAAsB,IAAI,IAAI;GAClC;GACA;GACA;GACA;GACA;GACD,CAAC;EAEF,SAAS,iBAAiB,OAAe,MAA2B;GAClE,MAAM,WAAW,MAAM,MAAM,CAAC,MAAM,WAAW;AAC/C,OAAI,SAAS,WAAW,EAAG;GAE3B,MAAM,cAAc,SAAS,GAAG,MAAM;GACtC,MAAM,aAAa,SAAS,IAAI,MAAM;AAEtC,OAAI,CAAC,YAAa;AAClB,OAAI,oBAAoB,IAAI,YAAY,CAAE;GAE1C,MAAM,aAAa,YAAY,MAAM,MAAM,CAAC,OAAO,QAAQ;AAO3D,OAAI,EAHF,WAAW,SAAS,KACpB,WAAW,OAAO,MAAM,iBAAiB,IAAI,EAAE,CAAC,GAEzB;IACvB,MAAM,aAAa,WAAW;AAC9B,QACE,cACA,IAAI,OAAO,QAAQ,SAAS,KAC5B,CAAC,IAAI,OAAO,QAAQ,SAAS,WAAW,CAExC,SAAQ,OAAO;KACb;KACA,WAAW;KACX,MAAM,EAAE,MAAM,YAAY;KAC3B,CAAC;;AAIN,OAAI,YACF;SAAK,MAAM,OAAO,WAAW,MAAM,MAAM,CAAC,OAAO,QAAQ,CACvD,KAAI,CAAC,iBAAiB,IAAI,IAAI,CAC5B,SAAQ,OAAO;KACb;KACA,WAAW;KACX,MAAM,EAAE,UAAU,KAAK;KACxB,CAAC;;;EAMV,SAAS,YAAY,MAAuC;AAC1D,OAAI,CAAC,IAAI,cAAc,KAAK,CAAE;AAE9B,QAAK,MAAM,QAAQ,KAAK,YAAY;AAClC,QAAI,KAAK,SAAS,cAAc,KAAK,SAAU;AAG/C,QADY,WAAW,KAAK,IAAI,KACpB,SAAU;IAEtB,MAAM,MAAM,eAAe,KAAK,MAAM;AACtC,QAAI,KAAK;AACP,sBAAiB,KAAK,KAAK,MAAM;AACjC;;AAGF,QAAI,KAAK,MAAM,SAAS,aAAa,KAAK,MAAM,UAAU,KACxD;AAGF,QAAI,KAAK,MAAM,SAAS,mBACtB,MAAK,MAAM,aAAa,KAAK,MAAM,YAAY;AAC7C,SAAI,UAAU,SAAS,WAAY;KACnC,MAAM,WAAW,eAAe,UAAU,MAAM;AAChD,SAAI,SACF,kBAAiB,UAAU,UAAU,MAAM;;;;EAOrD,SAAS,kBAAkB,MAAiC;AAC1D,eAAY,KAAK;;AAGnB,SAAO;GACL,kBAAkB,MAAM;AACtB,QAAI,YAAY,KAAK;;GAGvB,GAAG,qBAAqB,kBAAkB;GAC3C;;CAEJ,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tenphi/eslint-plugin-tasty",
3
- "version": "0.6.2",
3
+ "version": "0.6.4",
4
4
  "description": "ESLint plugin for validating tasty() and tastyStatic() style objects",
5
5
  "type": "module",
6
6
  "exports": {