UNPKG

@tbela99/css-parser

Version:

CSS parser for node and the browser

1,220 lines (1,219 loc) 62.2 kB
import { ValidationTokenEnum, specialValues } from './parser/types.js'; import { renderSyntax } from './parser/parse.js'; import { ValidationLevel, EnumToken, funcLike } from '../ast/types.js'; import '../ast/minify.js'; import '../ast/walk.js'; import '../parser/parse.js'; import { isLength } from '../syntax/syntax.js'; import '../parser/utils/config.js'; import { renderToken } from '../renderer/render.js'; import { getSyntaxConfig, getParsedSyntax } from './config.js'; import { validateSelector } from './selector.js'; import './syntaxes/complex-selector.js'; import { validateImage } from './syntaxes/image.js'; const config = getSyntaxConfig(); function consumeToken(tokens) { tokens.shift(); } function consumeSyntax(syntaxes) { syntaxes.shift(); } function splice(tokens, matches) { if (matches.length == 0) { return tokens; } // @ts-ignore const index = tokens.indexOf(matches.at(-1)); if (index > -1) { tokens.splice(0, index + 1); } return tokens; } function validateSyntax(syntaxes, tokens, root, options, context = { level: 0 }) { console.error(JSON.stringify({ syntax: syntaxes?.reduce?.((acc, curr) => acc + renderSyntax(curr), ''), // syntaxes, tokens: tokens.reduce((acc, curr) => acc + renderToken(curr), ''), s: new Error('bar').stack }, null, 1)); if (syntaxes == null) { // @ts-ignore return { valid: ValidationLevel.Drop, matches: [], node: tokens[0] ?? null, syntax: null, error: 'no matching syntaxes found', tokens }; } let token = null; let syntax; let result = null; let validSyntax = false; let matched = false; const matches = []; tokens = tokens.slice(); syntaxes = syntaxes.slice(); tokens = tokens.slice(); if (context.cache == null) { context.cache = new WeakMap; } if (context.tokens == null) { context.tokens = tokens.slice(); } context = { ...context }; main: while (tokens.length > 0) { if (syntaxes.length == 0) { break; } token = tokens[0]; syntax = syntaxes[0]; // @ts-ignore context.position = context.tokens.indexOf(token); const cached = context.cache.get(token)?.get(syntax.text) ?? null; if (cached != null) { if (cached.error.length > 0) { return { ...cached, tokens, node: cached.valid == ValidationLevel.Valid ? null : token }; } syntaxes.shift(); tokens.shift(); continue; } if (token.typ == EnumToken.DescendantCombinatorTokenType) { tokens.shift(); if (syntax.typ == ValidationTokenEnum.Whitespace) { syntaxes.shift(); } continue; } else if (syntax.typ == ValidationTokenEnum.Whitespace) { syntaxes.shift(); if (token.typ == EnumToken.WhitespaceTokenType) { tokens.shift(); } continue; } else if (syntax.typ == ValidationTokenEnum.Block && EnumToken.AtRuleTokenType == token.typ && ('chi' in token)) { syntaxes.shift(); tokens.shift(); // @ts-ignore matches.push(token); continue; } if (syntax.isOptional) { if (!context.cache.has(token)) { context.cache.set(token, new Map); } if (context.cache.get(token).has(syntax.text)) { result = context.cache.get(token).get(syntax.text); return { ...result, tokens, node: result.valid == ValidationLevel.Valid ? null : token }; } // @ts-ignore const { isOptional, ...c } = syntax; // @ts-ignore let result2; // @ts-ignore result2 = validateSyntax([c], tokens, root, options, context); if (result2.valid == ValidationLevel.Valid && result2.matches.length > 0) { tokens = result2.tokens; // splice(tokens, result2.matches); // tokens = result2.tokens; // @ts-ignore matches.push(...result2.matches); matched = true; result = result2; } else { syntaxes.shift(); continue; } syntaxes.shift(); if (syntaxes.length == 0) { // @ts-ignore return { valid: ValidationLevel.Valid, matches: result2.matches, node: result2.node, syntax: result2.syntax, error: result2.error, tokens }; } continue; } if (syntax.isList) { let index = -1; // @ts-ignore let { isList, ...c } = syntax; // const c: ValidationToken = {...syntaxes, isList: false} as ValidationToken; let result2 = null; validSyntax = false; do { for (let i = index + 1; i < tokens.length; i++) { if (tokens[i].typ == EnumToken.CommaTokenType) { index = i; break; } } if (tokens[index + 1]?.typ == EnumToken.CommaTokenType) { return { valid: ValidationLevel.Drop, matches, node: tokens[0], syntax, error: 'unexpected token', tokens }; } if (index == -1) { index = tokens.length; } if (index == 0) { break; } // @ts-ignore result2 = validateSyntax([c], tokens.slice(0, index), root, options, context); matched = result2.valid == ValidationLevel.Valid && result2.matches.length > 0; if (matched) { const l = tokens.length; validSyntax = true; // @ts-ignore // matches.push(...result2.matches); // splice(tokens, result2.matches); if (tokens.length == 1 && tokens[0].typ == EnumToken.CommaTokenType) { return { valid: ValidationLevel.Drop, matches, node: tokens[0], syntax, error: 'unexpected token', tokens }; } tokens = tokens.slice(index); result = result2; // @ts-ignore matches.push(...result2.matches); if (result.tokens.length > 0) { if (index == -1) { tokens = result.tokens; } else { tokens = tokens.slice(index - result.tokens.length); } } else if (index > 0) { tokens = tokens.slice(index); } index = -1; if (l == tokens.length) { break; } } else { break; } } while (tokens.length > 0); // if (level == 0) { // } if (!matched) { return { valid: ValidationLevel.Drop, // @ts-ignore matches: [...new Set(matches)], node: token, syntax, error: 'unexpected token', tokens }; } syntaxes.shift(); continue; } if (syntax.isRepeatable) { // @ts-ignore let { isRepeatable, ...c } = syntax; let result2 = null; validSyntax = false; let l = tokens.length; let tok = null; do { // @ts-ignore result2 = validateSyntax([c], tokens, root, options, context); if (result2.matches.length == 0 && result2.error.length > 0) { syntaxes.shift(); break main; } if (result2.valid == ValidationLevel.Valid) { tokens = result2.tokens; // @ts-ignore matches.push(...result2.matches); result = result2; if (l == tokens.length) { if (tok == tokens[0]) { break; } if (result2.matches.length == 0 && tokens.length > 0) { tokens = result2.tokens; tok = tokens[0]; continue; } break; } if (matches.length == 0) { tokens = result2.tokens; } l = tokens.length; continue; } break; } while (result2.valid == ValidationLevel.Valid && tokens.length > 0); // if (lastResult != null) { // // splice(tokens, lastResult.matches); // // tokens = lastResult.tokens; // } syntaxes.shift(); continue; } // at least one match if (syntax.isRepeatableGroup) { validSyntax = false; let count = 0; let l = tokens.length; let result2 = null; do { // @ts-ignore const { isRepeatableGroup, ...c } = syntax; // @ts-ignore result2 = validateSyntax([c], tokens, root, options, context); if (result2.valid == ValidationLevel.Drop || result2.matches.length == 0) { if (count > 0) { syntaxes.shift(); // if (result2.matches.length == 0) { tokens = result2.tokens; // break main; if (syntaxes.length == 0) { return result2; } break main; } return result2; } if (result2.valid == ValidationLevel.Valid && result2.matches.length > 0) { count++; // lastResult = result; validSyntax = true; tokens = result2.tokens; // splice(tokens, result2.matches); // tokens = result2.tokens; // @ts-ignore matches.push(...result2.matches); result = result2; if (l == tokens.length) { break; } l = tokens.length; } else { break; } } while (tokens.length > 0 && result.valid == ValidationLevel.Valid); // if (lastResult != null) { // // splice(tokens, lastResult.matches); // // tokens = lastResult.tokens; // } // at least one match is expected if (!validSyntax /* || result.matches.length == 0 */) { // @ts-ignore return { valid: ValidationLevel.Drop, node: token, tokens, syntax, error: 'unexpected token', matches: [] }; } syntaxes.shift(); continue; } if (syntax.atLeastOnce) { const { atLeastOnce, ...c } = syntax; result = validateSyntax([c], tokens, root, options, context); if (result.valid == ValidationLevel.Drop) { return result; } splice(tokens, result.matches); // tokens = result.tokens; // @ts-ignore matches.push(...result.matches); let l = tokens.length; let r = validateSyntax([c], tokens, root, options, context); while (r.valid == ValidationLevel.Valid) { splice(tokens, r.matches); // tokens = r.tokens; r = validateSyntax([c], tokens, root, options, context); if (l == tokens.length) { break; } if (r.valid == ValidationLevel.Valid && r.matches.length > 0) { // @ts-ignore matches.push(...result.matches); } l = tokens.length; } syntaxes.shift(); continue; } // @ts-ignore if (syntax.occurence != null) { // @ts-ignore const { occurence, ...c } = syntax; // && syntaxes.occurence.max != null // consume all tokens let match = 1; // @ts-ignore result = validateSyntax([c], tokens, root, options, context); if (result.valid == ValidationLevel.Drop) { return result; } if (result.matches.length == 0) { syntaxes.shift(); continue; } // splice(tokens, result.matches); // tokens = result.tokens; // @ts-ignore matches.push(...result.matches); matched = true; tokens = result.tokens; while (occurence.max == null || match < occurence.max) { // trim whitespace if (tokens[0]?.typ == EnumToken.WhitespaceTokenType) { tokens.shift(); } // @ts-ignore let r = validateSyntax([c], tokens, root, options, context); if (r.valid != ValidationLevel.Valid || r.matches.length == 0) { break; } result = r; // splice(tokens, r.matches); // tokens = r.tokens; // @ts-ignore matches.push(...result.matches); match++; tokens = r.tokens; result = r; if (tokens.length == 0 || (occurence.max != null && match >= occurence.max)) { break; } // @ts-ignore // r = validateSyntax([c], tokens, root, options, context); } syntaxes.shift(); continue; } // @ts-ignore if (syntax.typ == ValidationTokenEnum.Whitespace) { if (token.typ == EnumToken.WhitespaceTokenType) { tokens.shift(); } syntaxes.shift(); continue; } // @ts-ignore if (token.val != null && specialValues.includes(token.val)) { matched = true; result = { valid: ValidationLevel.Valid, matches: [token], node: null, syntax, error: '', tokens }; // @ts-ignore matches.push(...result.matches); } else { result = doValidateSyntax(syntax, token, tokens, root, options, context); matched = result.valid == ValidationLevel.Valid && result.matches.length > 0; if (matched) { // splice(tokens, result.matches); tokens = result.tokens; // @ts-ignore matches.push(...result.matches); } } if (result.valid == ValidationLevel.Drop) { // @ts-ignore return { ...result, matches, tokens, node: result.valid == ValidationLevel.Valid ? null : token }; } consumeSyntax(syntaxes); if (tokens.length == 0) { return result; } } if (result?.valid == ValidationLevel.Valid) { // splice(tokens, result.matches); tokens = result.tokens; // @ts-ignore matches.push(...result.matches); } if ( /* result == null && */tokens.length == 0 && syntaxes.length > 0) { validSyntax = isOptionalSyntax(syntaxes); } if (result == null) { result = { valid: validSyntax ? ValidationLevel.Valid : ValidationLevel.Drop, matches, node: validSyntax ? null : tokens[0] ?? null, // @ts-ignore syntax, error: validSyntax ? '' : 'unexpected token', tokens }; } if (token != null) { if (!context.cache.has(token)) { context.cache.set(token, new Map); } context.cache.get(token).set(syntax.text, result); } if (result != null) { // @ts-ignore return { ...result, matches: [...(new Set(matches))] }; } return result; } function isOptionalSyntax(syntaxes) { return syntaxes.length > 0 && syntaxes.every(t => t.typ == ValidationTokenEnum.Whitespace || t.isOptional || t.isRepeatable || (t.typ == ValidationTokenEnum.PropertyType && isOptionalSyntax(getParsedSyntax("syntaxes" /* ValidationSyntaxGroupEnum.Syntaxes */, t.val) ?? getParsedSyntax("declarations" /* ValidationSyntaxGroupEnum.Declarations */, t.val) ?? []))); } function doValidateSyntax(syntax, token, tokens, root, options, context) { let valid = false; let result; let children; let queue; let matches; let child; let astNodes = new Set; if (token.typ == EnumToken.NestingSelectorTokenType && syntax.typ == 2) { valid = root != null && 'relative-selector' == syntax.val; return { valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, matches: valid ? [token] : [], node: valid ? null : token, syntax, error: valid ? '' : 'unexpected token', tokens }; } switch (syntax.typ) { case ValidationTokenEnum.Comma: valid = token.typ === EnumToken.CommaTokenType; // @ts-ignore result = { valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, matches: valid ? [token] : [], node: valid ? null : token, syntax, error: valid ? '' : 'unexpected token', tokens }; break; case ValidationTokenEnum.AtRule: if (token.typ != EnumToken.AtRuleNodeType) { // @ts-ignore return { valid: ValidationLevel.Drop, matches: [], node: token, syntax, error: 'expecting at-rule', tokens }; } if (token.nam != syntax.val) { // @ts-ignore return { valid: ValidationLevel.Drop, matches: [], node: token, syntax, error: `expecting '@${syntax.val}' but found '@${token.nam}'`, tokens }; } if (root == null) { return { valid: ValidationLevel.Valid, matches: [token], node: null, syntax, error: '', tokens }; } if (root.typ != EnumToken.AtRuleNodeType) { // @ts-ignore return { valid: ValidationLevel.Drop, matches: [], node: token, syntax, error: 'not allowed here', tokens }; } if (!('chi' in token)) { // @ts-ignore return { valid: ValidationLevel.Drop, matches: [], node: token, syntax, error: '@at-rule must have children', tokens }; } // @ts-ignore result = { valid: ValidationLevel.Valid, matches: [token], node: null, syntax, error: '', tokens }; break; case ValidationTokenEnum.AtRuleDefinition: if (token.typ != EnumToken.AtRuleNodeType) { // @ts-ignore return { valid: ValidationLevel.Drop, matches: [], node: token, syntax, error: 'expecting at-rule', tokens }; } if ('chi' in syntax && !('chi' in token)) { // @ts-ignore return { valid: ValidationLevel.Drop, matches: [], node: token, syntax, error: '@at-rule must have children', tokens }; } if ('chi' in token && !('chi' in token)) { // @ts-ignore return { valid: ValidationLevel.Drop, matches: [], node: token, syntax, error: 'children not allowed here', tokens }; } const s = getParsedSyntax("atRules" /* ValidationSyntaxGroupEnum.AtRules */, '@' + token.nam); if ('prelude' in syntax) { if (!('tokens' in token)) { // @ts-ignore return { valid: ValidationLevel.Drop, matches: [], node: token, syntax, error: 'expected at-rule prelude', tokens }; } result = validateSyntax(s[0].prelude, token.tokens, root, options, { ...context, tokens: null, level: context.level + 1 }); if (result.valid == ValidationLevel.Drop) { return result; } } const hasBody = 'chi' in s[0]; if ('chi' in token) { if (!hasBody) { // @ts-ignore return { valid: ValidationLevel.Drop, matches: [], node: token, syntax, error: 'unexpected at-rule body', tokens }; } } else if (hasBody) { // @ts-ignore return { valid: ValidationLevel.Drop, matches: [], node: token, syntax, error: 'expecting at-rule body', tokens }; } break; case ValidationTokenEnum.DeclarationType: // @ts-ignore result = validateSyntax(getParsedSyntax("declarations" /* ValidationSyntaxGroupEnum.Declarations */, syntax.val), [token], root, options, context); break; case ValidationTokenEnum.Keyword: valid = (token.typ == EnumToken.IdenTokenType && token.val.localeCompare(syntax.val, 'en', { sensitivity: 'base' }) == 0) || (token.typ == EnumToken.ColorTokenType && token.kin == 'lit' && syntax.val.localeCompare(token.val, 'en', { sensitivity: 'base' }) == 0); result = { valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, matches: valid ? [token] : [], node: valid ? null : token, syntax, error: valid ? '' : 'unexpected token', tokens }; break; case ValidationTokenEnum.SemiColon: valid = root == null || [EnumToken.RuleNodeType, EnumToken.AtRuleNodeType, EnumToken.StyleSheetNodeType].includes(root.typ); result = { valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, matches: valid ? [token] : [], node: valid ? null : token, syntax, error: valid ? '' : 'unexpected token', tokens }; break; case ValidationTokenEnum.Separator: valid = token.typ == EnumToken.LiteralTokenType && token.val != '/'; result = { valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, matches: valid ? [token] : [], node: valid ? null : token, syntax, error: valid ? '' : 'unexpected token', tokens }; break; case ValidationTokenEnum.PropertyType: // if ('image' == syntax.val) { valid = token.typ == EnumToken.UrlFunctionTokenType || token.typ == EnumToken.ImageFunctionTokenType; if (!valid) { return { valid: ValidationLevel.Drop, matches: [], node: token, syntax, error: 'unexpected <image>', tokens }; } result = validateImage(token); } else if (['media-feature', 'mf-plain'].includes(syntax.val)) { valid = token.typ == EnumToken.DeclarationNodeType; result = { valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, matches: valid ? [token] : [], node: valid ? null : token, syntax, error: valid ? '' : 'unexpected token', tokens }; } else if (syntax.val == 'pseudo-page') { valid = token.typ == EnumToken.PseudoClassTokenType && [':left', ':right', ':first', ':blank'].includes(token.val); result = { valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, matches: valid ? [token] : [], node: valid ? null : token, syntax, error: valid ? '' : 'unexpected token', tokens }; } else if (syntax.val == 'page-body') { if (token.typ == EnumToken.DeclarationNodeType) { valid = true; // @ts-ignore result = { valid: ValidationLevel.Valid, matches: [token], node: null, syntax, error: '', tokens }; while (tokens.length > 0 && [EnumToken.DeclarationNodeType].includes(tokens[0].typ)) { // @ts-ignore result.matches.push(tokens.shift()); } } else if (token.typ == EnumToken.AtRuleNodeType) { result = validateSyntax(getParsedSyntax("syntaxes" /* ValidationSyntaxGroupEnum.Syntaxes */, 'page-margin-box-type'), [token], root, options, context); } } else if (syntax.val == 'group-rule-body') { valid = [EnumToken.AtRuleNodeType, EnumToken.RuleNodeType].includes(token.typ); if (!valid && token.typ == EnumToken.DeclarationNodeType && root?.typ == EnumToken.AtRuleNodeType && root.nam == 'media') { // allowed only if nested rule let parent = root; while (parent != null) { if (parent.typ == EnumToken.RuleNodeType) { valid = true; break; } // @ts-ignore parent = parent.parent; } } // @ts-ignore result = { valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, matches: [], node: valid ? null : token, syntax, error: valid ? '' : 'token is not allowed as a child', tokens }; if (!valid) { return result; } } // else if ('type-selector' == syntax.val) { valid = (token.typ == EnumToken.UniversalSelectorTokenType) || token.typ == EnumToken.IdenTokenType || (token.typ == EnumToken.NameSpaceAttributeTokenType && (token.l == null || token.l.typ == EnumToken.IdenTokenType || (token.l.typ == EnumToken.LiteralTokenType && token.l.val == '*')) && token.r.typ == EnumToken.IdenTokenType); result = { valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, matches: valid ? [token] : [], node: token, syntax, error: valid ? '' : 'unexpected token', tokens }; } else if ('wq-name' == syntax.val) { valid = token.typ == EnumToken.IdenTokenType || (token.typ == EnumToken.NameSpaceAttributeTokenType && (token.l == null || token.l.typ == EnumToken.IdenTokenType || (token.l.typ == EnumToken.LiteralTokenType && token.l.val == '*')) && token.r.typ == EnumToken.IdenTokenType); result = { valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, matches: valid ? [token] : [], node: token, syntax, error: valid ? '' : 'unexpected token', tokens }; } else if (EnumToken.UniversalSelectorTokenType == token.typ && 'subclass-selector' == syntax.val) { valid = true; result = { valid: ValidationLevel.Valid, matches: [token], node: null, syntax, error: '', tokens }; } else if ('attribute-selector' == syntax.val) { valid = token.typ == EnumToken.AttrTokenType && token.chi.length > 0; if (valid) { const children = token.chi.filter(t => t.typ != EnumToken.WhitespaceTokenType && t.typ != EnumToken.CommaTokenType); valid = children.length == 1 && [ EnumToken.IdenTokenType, EnumToken.NameSpaceAttributeTokenType, EnumToken.MatchExpressionTokenType ].includes(children[0].typ); if (valid && children[0].typ == EnumToken.MatchExpressionTokenType) { const t = children[0]; valid = [ EnumToken.IdenTokenType, EnumToken.NameSpaceAttributeTokenType ].includes(t.l.typ) && (t.op == null || ([ EnumToken.DelimTokenType, EnumToken.DashMatchTokenType, EnumToken.StartMatchTokenType, EnumToken.ContainMatchTokenType, EnumToken.EndMatchTokenType, EnumToken.IncludeMatchTokenType ].includes(t.op.typ) && t.r != null && [ EnumToken.StringTokenType, EnumToken.IdenTokenType ].includes(t.r.typ))); if (valid && t.attr != null) { const s = getParsedSyntax("syntaxes" /* ValidationSyntaxGroupEnum.Syntaxes */, 'attr-modifier')[0]; valid = s.chi.some((l) => l.some((r) => r.val == t.attr)); } } } result = { valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, matches: valid ? [token] : [], node: valid ? null : token, syntax, error: valid ? '' : 'unexpected token', tokens }; if (!valid) { return result; } } else if ('combinator' == syntax.val) { valid = [ EnumToken.DescendantCombinatorTokenType, EnumToken.SubsequentSiblingCombinatorTokenType, EnumToken.NextSiblingCombinatorTokenType, EnumToken.ChildCombinatorTokenType, EnumToken.ColumnCombinatorTokenType ].includes(token.typ); if (valid) { // @ts-ignore const position = context.tokens.indexOf(token); if (root == null) { valid = position > 0 && context.tokens[position - 1]?.typ != EnumToken.CommaTokenType; } } result = { valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, matches: valid ? [token] : [], node: valid ? null : token, syntax, error: valid ? '' : 'unexpected token', tokens }; if (!valid) { return result; } } else if ('ident-token' == syntax.val) { valid = token.typ == EnumToken.IdenTokenType; result = { valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, matches: valid ? [token] : [], node: valid ? null : token, syntax, error: valid ? '' : 'unexpected token', tokens }; } else if ('hex-color' == syntax.val) { valid = token.typ == EnumToken.ColorTokenType && token.kin == 'hex'; result = { valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, matches: valid ? [token] : [], node: valid ? null : token, syntax, error: valid ? '' : 'unexpected token', tokens }; } else if ('resolution' == syntax.val) { valid = token.typ == EnumToken.ResolutionTokenType; result = { valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, matches: valid ? [token] : [], node: valid ? null : token, syntax, error: valid ? '' : 'unexpected token', tokens }; } else if ('angle' == syntax.val) { valid = token.typ == EnumToken.AngleTokenType || (token.typ == EnumToken.NumberTokenType && token.val == '0'); result = { valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, matches: valid ? [token] : [], node: valid ? null : token, syntax, error: valid ? '' : 'unexpected token', tokens }; } else if ('time' == syntax.val) { valid = token.typ == EnumToken.TimingFunctionTokenType; result = { valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, matches: valid ? [token] : [], node: valid ? null : token, syntax, error: valid ? '' : 'unexpected token', tokens }; } else if ('ident' == syntax.val) { valid = token.typ == EnumToken.IdenTokenType; result = { valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, matches: valid ? [token] : [], node: valid ? null : token, syntax, error: valid ? '' : 'unexpected token', tokens }; } else if (['id-selector', 'hash-token'].includes(syntax.val)) { valid = token.typ == EnumToken.HashTokenType; result = { valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, matches: valid ? [token] : [], node: valid ? null : token, syntax, error: valid ? '' : 'unexpected token', tokens }; } else if (['integer', 'number'].includes(syntax.val)) { // valid = token.typ == EnumToken.NumberTokenType; valid = token.typ == EnumToken.NumberTokenType && ('integer' != syntax.val || Number.isInteger(+token.val)); if (valid && 'range' in syntax) { const value = Number(token.val); const range = syntax.range; valid = value >= range[0] && (range[1] == null || value <= range[1]); } result = { valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, matches: valid ? [token] : [], node: valid ? null : token, syntax, error: valid ? '' : 'unexpected token', tokens }; } else if ('length' == syntax.val) { valid = isLength(token) || (token.typ == EnumToken.NumberTokenType && token.val == '0'); // @ts-ignore result = { valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, matches: valid ? [token] : [], node: valid ? null : token, syntax, error: valid ? '' : 'unexpected token', tokens }; } else if ('percentage' == syntax.val) { valid = token.typ == EnumToken.PercentageTokenType || (token.typ == EnumToken.NumberTokenType && token.val == '0'); result = { valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, matches: valid ? [token] : [], node: valid ? null : token, syntax, error: valid ? '' : 'unexpected token', tokens }; } else if ('dashed-ident' == syntax.val) { valid = token.typ == EnumToken.DashedIdenTokenType; result = { valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, matches: valid ? [token] : [], node: valid ? null : token, syntax, error: valid ? '' : 'unexpected token', tokens }; } else if ('custom-ident' == syntax.val) { valid = token.typ == EnumToken.DashedIdenTokenType || token.typ == EnumToken.IdenTokenType; result = { valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, matches: valid ? [token] : [], node: valid ? null : token, syntax, error: valid ? '' : 'unexpected token', tokens }; } else if ('custom-property-name' == syntax.val) { valid = token.typ == EnumToken.DashedIdenTokenType; result = { valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, matches: valid ? [token] : [], node: valid ? null : token, syntax, error: valid ? '' : 'unexpected token', tokens }; } else if ('string' == syntax.val) { valid = token.typ == EnumToken.StringTokenType; result = { valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, matches: valid ? [token] : [], node: valid ? null : token, syntax, error: valid ? '' : 'unexpected token', tokens }; } else if ('declaration-value' == syntax.val) { valid = token.typ != EnumToken.LiteralTokenType; result = { valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, matches: valid ? [token] : [], node: valid ? null : token, syntax, error: valid ? '' : 'unexpected token', tokens }; } else if ('url' == syntax.val) { valid = token.typ == EnumToken.UrlFunctionTokenType || token.typ == EnumToken.StringTokenType; result = { valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, matches: valid ? [token] : [], node: valid ? null : token, syntax, error: valid ? '' : 'unexpected token', tokens }; } else if ('declaration' == syntax.val) { valid = token.typ == EnumToken.DeclarationNodeType && (token.nam.startsWith(('--')) || token.nam in config.declarations || token.nam in config.syntaxes); if (!valid) { // @ts-ignore result = { valid: ValidationLevel.Drop, matches: [], node: token, syntax, error: 'unexpected token', tokens }; } else if (token.nam.startsWith(('--'))) { result = { valid: ValidationLevel.Valid, matches: [token], node: null, syntax, error: '', tokens }; } else { result = validateSyntax(getParsedSyntax("declarations" /* ValidationSyntaxGroupEnum.Declarations */, token.nam) ?? getParsedSyntax("syntaxes" /* ValidationSyntaxGroupEnum.Syntaxes */, token.nam), token.val, token, options, { ...context, tokens: null, level: 0 }); if (result.valid == ValidationLevel.Valid && result.error.length == 0) { tokens = result.tokens; } } } else if ('class-selector' == syntax.val) { valid = EnumToken.ClassSelectorTokenType == token.typ || EnumToken.UniversalSelectorTokenType == token.typ; result = { valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, matches: valid ? [token] : [], node: token, syntax, error: valid ? '' : 'unexpected token', tokens }; } // else if ('complex-selector' == (syntaxes as ValidationPropertyToken).val) { // // result = validateSyntax(getParsedSyntax(ValidationSyntaxGroupEnum.Syntaxes, (syntaxes as ValidationPropertyToken).val) as ValidationToken[], tokens, root as AstNode, options, context); // // } else if (['pseudo-element-selector', 'pseudo-class-selector'].includes(syntax.val)) { valid = false; if (token.typ == EnumToken.PseudoClassTokenType) { let val = token.val; if (val == ':before' || val == ':after') { val = ':' + val; } valid = val in config.selectors; if (!valid && val.match(/^:?:-/) != null) { const match = token.val.match(/^(:?:)(-[^-]+-)(.*)$/); if (match != null) { valid = true; } } result = { valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, matches: valid ? [token] : [], node: valid ? null : token, syntax, error: valid ? '' : 'invalid pseudo class', tokens }; } else if (token.typ == EnumToken.PseudoClassFuncTokenType) { let key = token.val in config.selectors ? token.val : token.val + '()'; valid = key in config.selectors; if (!valid && token.val.match(/^:?:-/)) { const match = token.val.match(/^(:?:)(-[^-]+-)(.*)$/); if (match != null) { key = match[1] + match[3] in config.selectors ? match[1] + match[3] : match[1] + match[3] + '()'; valid = key in config.selectors; } } const s = getParsedSyntax("selectors" /* ValidationSyntaxGroupEnum.Selectors */, key); if (s != null) { const r = s[0]; if (r.typ != ValidationTokenEnum.PseudoClassFunctionToken) { valid = false; } else { result = validateSyntax(s[0].chi, token.chi, root, options, { ...context, tokens: null, level: context.level + 1 }); break; } } } result = { valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, matches: valid ? [token] : [], node: token, syntax, error: valid ? '' : 'unexpected token', tokens }; } // <relative-selector-list> // <complex-selector-list> else if ('relative-selector' == syntax.val) { if (tokens.length == 1 && token.typ == EnumToken.NestingSelectorTokenType) { return { valid: ValidationLevel.Valid, matches: [token], node: token, syntax, error: '', tokens }; } result = validateSyntax(getParsedSyntax("syntaxes" /* ValidationSyntaxGroupEnum.Syntaxes */, syntax.val), token.typ == EnumToken.NestingSelectorTokenType ? tokens.slice(1) : tokens, root, options, context); } // <relative-selector-list> // <complex-selector-list> else if (['forgiving-selector-list', 'forgiving-relative-selector-list'].includes(syntax.val)) { // @ts-ignore result