UNPKG

@tbela99/css-parser

Version:

CSS parser, minifier and validator for node and the browser

1,164 lines (1,162 loc) 127 kB
import { getParsedSyntax, getSyntaxConfig } from './config.js'; import { EnumToken } from '../ast/types.js'; import { ValidationSyntaxGroupEnum, ValidationTokenEnum, MediaFeatureType } from './parser/typedef.js'; import { LOC, tokensfuncDefMap, tokensfuncSet, funcLike, mFLT, mFGT } from '../syntax/constants.js'; import { isColor } from '../syntax/syntax.js'; import { equalsIgnoreCase } from '../parser/utils/text.js'; import { cloneNode } from '../ast/clone.js'; import { parseTokens } from '../parser/parse.js'; const config = getSyntaxConfig(); // @ts-expect-error const allValues = config.declarations.all.syntax.split(/[\s|]+/g); const funcTypes = [ ...tokensfuncDefMap.values(), EnumToken.FunctionTokenType, EnumToken.PseudoClassFuncTokenType, ]; function trimArray(tokens) { while (tokens[0]?.typ === EnumToken.WhitespaceTokenType) { tokens.shift(); } while (tokens[tokens.length - 1]?.typ === EnumToken.WhitespaceTokenType) { tokens.pop(); } return tokens; } /** * * @param featureName * @returns */ function getMFInfo(featureName) { // @ts-expect-error return config.mediaFeatures[featureName.toLowerCase()]; } /** * * @param featureName * @param tokens * @returns object with: * - valid: boolean. true the media feaure is known or is a custom property. false otherwise * - success: boolean. validation result */ function isMFValue(featureName, tokens, isMFRange) { // mf-value: <number> | <dimension> | <ident> | <ratio> tokens = tokens.filter((token) => token.typ !== EnumToken.WhitespaceTokenType && token.typ !== EnumToken.CommentTokenType); // if (tokens.length === 0) { // return { valid: true, success: false }; // } // https://www.w3.org/TR/mediaqueries-5/#:~:text=Attempting%20to%20evaluate%20a%20min%2Fmax%20prefixed // https://www.w3.org/TR/mediaqueries-5/#custom-mq if (featureName.startsWith("--") || (isMFRange && /^((min)|(max))-/g.test(featureName))) { return { valid: true, success: false, isValueAllowed: false }; } featureName = featureName.toLowerCase(); if (!(featureName in config.mediaFeatures)) { return { valid: false, success: false }; } // @ts-expect-error const mediaFeature = config.mediaFeatures[featureName]; // if ( // tokens.length === 1 && // tokens[0].typ === EnumToken.MathFunctionTokenType && // (tokens[0] as FunctionToken).val === "calc" // ) { // // todo: check calc tokens are of compatible types : resolution, ratio, length, integer, number? // // https://github.com/web-platform-tests/wpt/blob/master/css/mediaqueries/mq-calc-sign-function-003.html // return { // valid: true, // success: // mediaFeature.type !== MediaFeatureType.KeywordType && mediaFeature.type !== MediaFeatureType.StringType, // }; // } switch (mediaFeature.type) { case MediaFeatureType.BooleanType: return { valid: true, success: tokens.length === 1 && tokens[0].typ === EnumToken.NumberTokenType && (tokens[0].val === 0 || tokens[0].val === 1), }; case MediaFeatureType.KeywordType: return { valid: true, success: tokens.length === 1 && tokens[0].typ === EnumToken.IdenTokenType && mediaFeature.values.includes(tokens[0].val.toLowerCase()), }; case MediaFeatureType.LengthType: return { valid: true, success: tokens.length === 1 && tokens[0].typ == EnumToken.LengthTokenType }; case MediaFeatureType.IntergerType: return { valid: true, success: tokens.length === 1 && tokens[0].typ == EnumToken.NumberTokenType && !tokens[0].val.toString().includes("."), }; case MediaFeatureType.NumberType: return { valid: true, success: tokens.length === 1 && tokens[0].typ == EnumToken.NumberTokenType && typeof tokens[0].val === "number", }; case MediaFeatureType.StringType: return { valid: true, success: tokens.length === 1 && tokens[0].typ == EnumToken.StringTokenType }; case MediaFeatureType.ResolutionType: return { valid: true, success: tokens.length === 1 && tokens[0].typ == EnumToken.ResolutionTokenType }; case MediaFeatureType.RatioType: return { valid: true, success: (tokens.length == 1 && tokens[0].typ == EnumToken.NumberTokenType && tokens[0].val.typ == EnumToken.FractionTokenType) || (tokens.length === 3 && tokens[0].typ == EnumToken.NumberTokenType && typeof tokens[0].val === "number" && tokens[1].typ == EnumToken.LiteralTokenType && tokens[1].val === "/" && tokens[2].typ == EnumToken.NumberTokenType && typeof tokens[2].val === "number"), }; default: console.debug("Unknown media feature type " + mediaFeature.type); } return { valid: true, success: true, }; } // export function isStyleRangeValue(tokens: Token[]): { success: boolean; errors: ErrorDescription[] } { // const filtered: Token[] = tokens.filter( // (token) => token.typ !== EnumToken.WhitespaceTokenType && token.typ !== EnumToken.CommentTokenType, // ); // const result = isDeclarationValue(tokens); // if (result.success) { // result.success = filtered.length > 0; // } // return result; // } function createValidationContext(tokens) { tokens = trimArray(tokens.filter((t) => t.typ !== EnumToken.CommentTokenType)); if (tokens.at(-1)?.typ === EnumToken.ImportantTokenType) { tokens.pop(); trimArray(tokens); } const token = { tokens, index: -1, current() { if (this.index < 0) { return null; } // while ( // this.tokens[this.index]?.typ == EnumToken.WhitespaceTokenType || // this.tokens[this.index]?.typ == EnumToken.CommentTokenType || // this.tokens[this.index]?.typ == EnumToken.CDOCOMMTokenType // ) { // this.index++; // } return this.tokens[this.index]; }, peek(offset = 0) { let index = this.index; let token = this.tokens[++index]; // if (!skipWhitespace) { // while (offset >= 0 && index < this.tokens.length) { // while (token != null && token.typ === EnumToken.CommentTokenType) { // token = this.tokens[++index]; // } // if (offset === 0 || token == null) { // return token; // } // offset--; // token = this.tokens[++index]; // } // return token; // } while (offset >= 0 && index < this.tokens.length) { while (token?.typ == EnumToken.WhitespaceTokenType || token?.typ == EnumToken.CommentTokenType || token?.typ == EnumToken.CDOCOMMTokenType) { token = this.tokens[++index]; } if (token == null || offset === 0) { return token; } offset--; token = this.tokens[++index]; } return token; }, /** * * @param stopCondition * @param matchCount * @returns */ peekRange(open = EnumToken.StartParensTokenType, close = EnumToken.EndParensTokenType, counter = 0) { let index = this.index; let token = this.tokens[index]; // track balanced parens let matchCount = 0; const tokens = []; while (index + 1 < this.tokens.length && this.tokens[index + 1]?.typ === EnumToken.WhitespaceTokenType) { index++; } while (index + 1 < this.tokens.length) { token = this.tokens[++index]; if (token?.typ === EnumToken.StartParensTokenType || tokensfuncDefMap.has(token?.typ)) { matchCount++; } else if (token?.typ === EnumToken.EndParensTokenType) { matchCount--; } if (matchCount >= 0) { tokens.push(token); } if (matchCount === 0) { if (close !== EnumToken.EndParensTokenType && token?.typ === close) { counter--; } // else if (open !== EnumToken.StartParensTokenType && token?.typ === open) { // counter++; // } } if (matchCount <= 0 && counter <= 0) { break; } } // if (tokens[0]?.typ === EnumToken.WhitespaceTokenType) { // tokens.shift(); // } return tokens; }, // 2 split(split = EnumToken.CommaTokenType) { let index = this.index; let token = this.tokens[index]; // track balanced parens let matchCount = 0; const tokens = [[]]; while (index + 1 < this.tokens.length) { token = this.tokens[++index]; if (token?.typ === EnumToken.StartParensTokenType || tokensfuncDefMap.has(token?.typ)) { matchCount++; } else if (token?.typ === EnumToken.EndParensTokenType) { matchCount--; } if (matchCount === 0 && token.typ === split) { tokens.at(-1).push(token); tokens.push([]); } else { tokens.at(-1).push(token); } } return tokens; }, getRemainingTokens() { return this.tokens.slice(this.index + 1); }, // last() { // let index: number = this.tokens.length - 1; // let token: Token = this.tokens[index]; // while ( // (this.index >= 0 && token?.typ === EnumToken.WhitespaceTokenType) || // token?.typ === EnumToken.CommentTokenType || // token?.typ === EnumToken.CDOCOMMTokenType || // token?.typ === EnumToken.InvalidCommentTokenType || // token?.typ === EnumToken.BadCommentTokenType || // token?.typ === EnumToken.BadStringTokenType // ) { // token = this.tokens[--index]; // if (token == null) { // break; // } // } // return token; // }, end() { this.index = this.tokens.length + 1; return this; }, next() { let token = this.tokens[++this.index]; while (token?.typ === EnumToken.WhitespaceTokenType || token?.typ === EnumToken.CommentTokenType || token?.typ === EnumToken.CDOCOMMTokenType || token?.typ === EnumToken.InvalidCommentTokenType || token?.typ === EnumToken.BadCommentTokenType || token?.typ === EnumToken.BadStringTokenType) { token = this.tokens[++this.index]; } return token; }, slice() { return { ...token, index: this.index == -1 ? -1 : 0, tokens: this.index == -1 ? this.tokens.slice() : this.tokens.slice(this.index), }; }, update(token) { const index = this.tokens.indexOf(token); if (index != -1) { this.index = index; } return this; }, done() { if (this.index + 1 < this.tokens.length) { let index = this.index + 1; while (index < this.tokens.length) { if (this.tokens[index].typ === EnumToken.WhitespaceTokenType || this.tokens[index].typ === EnumToken.CommentTokenType) { index++; } else { return false; } } } return true; }, }; return token; } function matchSelectorSyntax(stream, errors, options, nested = true) { const stack = []; const tokens = []; const nodes = [ EnumToken.CommaTokenType, EnumToken.ColumnCombinatorTokenType, EnumToken.ChildCombinatorTokenType, EnumToken.NextSiblingCombinatorTokenType, EnumToken.SubsequentSiblingCombinatorTokenType, ]; const trimWhitespaceBefore = nodes.concat(EnumToken.DelimTokenType, EnumToken.DashMatchTokenType, EnumToken.IncludeMatchTokenType, EnumToken.ContainMatchTokenType, EnumToken.StartMatchTokenType, EnumToken.EndMatchTokenType, EnumToken.AttrEndTokenType); const trimWhitespaceAfter = nodes.concat(EnumToken.DelimTokenType, EnumToken.DashMatchTokenType, EnumToken.IncludeMatchTokenType, EnumToken.ContainMatchTokenType, EnumToken.StartMatchTokenType, EnumToken.EndMatchTokenType, EnumToken.AttrStartTokenType); const enumMap = new Map([ [EnumToken.Tilda, EnumToken.SubsequentSiblingCombinatorTokenType], [EnumToken.GtTokenType, EnumToken.ChildCombinatorTokenType], ]); let token; let i = 0; let success = true; while (i < stream.length && (stream[i].typ === EnumToken.WhitespaceTokenType || stream[i].typ === EnumToken.CommentTokenType)) { i++; } if (i < stream.length) { switch (stream[i].typ) { case EnumToken.Plus: case EnumToken.Tilda: case EnumToken.GtTokenType: case EnumToken.ColumnCombinatorTokenType: case EnumToken.ChildCombinatorTokenType: case EnumToken.NextSiblingCombinatorTokenType: case EnumToken.SubsequentSiblingCombinatorTokenType: if (!nested) { return { success: false, errors: [ { action: "drop", message: `Unexpected token ${EnumToken[stream[i].typ]} at ${stream[i][LOC].src}:${stream[i][LOC].sta.lin}:${stream[i][LOC].sta.col}`, node: stream[i], location: stream[i][LOC], }, ], }; } } } for (; i < stream.length; i++) { token = stream[i]; // if (token.typ === EnumToken.EOF) { // break; // } if (token.typ === EnumToken.Star) { token.typ = EnumToken.UniversalSelectorTokenType; } tokens.push(token); if (tokensfuncDefMap.has(token.typ)) { if (stack.length > 0 && nodes.includes(stack.at(-1).typ)) { stack.pop(); } stack.push(token); continue; } if (stack.length > 0 && nodes.includes(stack.at(-1).typ) && !nodes.includes(token.typ) && token.typ !== EnumToken.WhitespaceTokenType && token.typ !== EnumToken.CommentTokenType && token.typ !== EnumToken.CDOCOMMTokenType) { stack.pop(); } // if (token.typ === EnumToken.LiteralTokenType && "+" === (token as LiteralToken).val) { // Object.assign(token, { typ: EnumToken.NextSiblingCombinatorTokenType }); // continue; // } switch (token.typ) { // case EnumToken.InvalidCommentTokenType: // case EnumToken.BadCommentTokenType: // case EnumToken.BadStringTokenType: // break; case EnumToken.PseudoClassFuncTokenType: { const result = matchAllSyntaxes(getParsedSyntax(ValidationSyntaxGroupEnum.Selectors, token.val + "()")?.[0]?.chi ?? [], createValidationContext(token.chi), options); if (!result.success) { success = false; if (result.errors.length > 0) { errors.push(...result.errors); } } } break; case EnumToken.WhitespaceTokenType: case EnumToken.CommentTokenType: case EnumToken.CDOCOMMTokenType: break; case EnumToken.NestingSelectorTokenType: if (nested === false && !options.nestedRule) { return { success: false, errors: [ { action: "drop", message: `Nesting selector is not allowed at ${token[LOC].src}:${token[LOC].sta.lin}:${token[LOC].sta.col}`, node: token, location: token[LOC], }, ], }; } break; case EnumToken.Plus: Object.assign(token, { typ: EnumToken.NextSiblingCombinatorTokenType }); // if (stack.length > 0 && nodes.includes(stack.at(-1)?.typ)) { // return { // success: false, // errors: [ // { // action: "drop", // message: `Unexpected combinator ${EnumToken[token.typ]} at ${token[LOC]!.src}:${token[LOC]!.sta.lin}:${ // token[LOC]!.sta.col // }`, // node: token, // location: token[LOC], // }, // ], // }; // } stack.push(token); break; case EnumToken.Tilda: case EnumToken.GtTokenType: Object.assign(token, { typ: enumMap.get(token.typ) }); case EnumToken.ColumnCombinatorTokenType: case EnumToken.ChildCombinatorTokenType: case EnumToken.UniversalSelectorTokenType: case EnumToken.DescendantCombinatorTokenType: case EnumToken.NextSiblingCombinatorTokenType: case EnumToken.SubsequentSiblingCombinatorTokenType: // if (tokens.at(-1)?.typ === EnumToken.WhitespaceTokenType) { // tokens.pop(); // } if (stream[i + 1]?.typ === EnumToken.WhitespaceTokenType) { i++; } if (stack.length > 0 && stack.at(-1)?.typ === EnumToken.UniversalSelectorTokenType) { stack.pop(); } if (stack.length > 0 && nodes.includes(stack.at(-1)?.typ)) { return { success: false, errors: [ { action: "drop", message: `Unexpected combinator ${EnumToken[token.typ]} at ${token[LOC].src}:${token[LOC].sta.lin}:${token[LOC].sta.col}`, node: token, location: token[LOC], }, ], }; } stack.push(token); break; case EnumToken.CommaTokenType: if (stack.length > 0 && stack.at(-1).typ === EnumToken.UniversalSelectorTokenType) { stack.pop(); } // if (tokens.length === 0 || stack.at(-1)?.typ == EnumToken.CommaTokenType) { // return { // success: false, // errors: [ // { // action: "drop", // message: `Unexpected token ${EnumToken[token.typ]} at ${token[LOC]!.src}:${token[LOC]!.sta.lin}:${ // token[LOC]!.sta.col // }`, // node: token, // location: token[LOC], // }, // ], // }; // } stack.push(token); break; case EnumToken.Pipe: case EnumToken.DelimTokenType: case EnumToken.StringTokenType: case EnumToken.IncludeMatchTokenType: case EnumToken.ContainMatchTokenType: case EnumToken.StartMatchTokenType: case EnumToken.EndMatchTokenType: case EnumToken.DashMatchTokenType: // if ( // stack.at(-1)?.typ !== EnumToken.AttrStartTokenType && // !( // stack.at(-1)?.typ === EnumToken.UniversalSelectorTokenType && // stack.at(-2)?.typ === EnumToken.AttrStartTokenType // ) // ) { // return { // success: false, // errors: [ // { // action: "drop", // message: `Unexpected token ${EnumToken[token.typ]} at ${token[LOC]!.src}:${token[LOC]!.sta.lin}:${ // token[LOC]!.sta.col // }`, // node: token, // location: token[LOC], // }, // ], // }; // } break; // case EnumToken.Star: // Object.assign(token, { typ: EnumToken.UniversalSelectorTokenType }); // break; case EnumToken.IdenTokenType: case EnumToken.HashTokenType: case EnumToken.PseudoElementTokenType: case EnumToken.PseudoClassTokenType: case EnumToken.ClassSelectorTokenType: // if (stack.at(-1)?.typ === EnumToken.CommaTokenType) { // stack.pop(); // } break; case EnumToken.AttrStartTokenType: // if (stack.at(-1)?.typ === EnumToken.CommaTokenType) { // stack.pop(); // } stack.push(token); break; case EnumToken.AttrEndTokenType: if (stack.length > 0 && stack.at(-1).typ === EnumToken.UniversalSelectorTokenType) { stack.pop(); } if (stack.at(-1)?.typ !== EnumToken.AttrStartTokenType) { return { success: false, errors: [ { action: "drop", message: `Unexpected token ${EnumToken[token.typ]} at ${token[LOC].src}:${token[LOC].sta.lin}:${token[LOC].sta.col}`, node: token, location: token[LOC], }, ], }; } { const k = tokens.length - 1; let index = tokens.indexOf(stack.at(-1)); const slice = []; for (let n = index + 1; n < k; n++) { if (tokens[n].typ === EnumToken.WhitespaceTokenType || tokens[n].typ === EnumToken.CommentTokenType || tokens[n].typ === EnumToken.CDOCOMMTokenType) { continue; } slice.push(tokens[n]); } // if (slice.length === 0) { // return { // success: false, // errors: [ // { // action: "drop", // message: `Invalid selector attribute at ${token[LOC]!.src}:${token[LOC]!.sta.lin}:${ // token[LOC]!.sta.col // }`, // node: token, // location: token[LOC], // }, // ], // }; // } if (slice[1]?.typ === EnumToken.Pipe) { // if ( // slice[0].typ !== EnumToken.UniversalSelectorTokenType && // slice[0].typ !== EnumToken.Star && // slice[0].typ !== EnumToken.IdenTokenType // ) { // return { // success: false, // errors: [ // { // action: "drop", // message: `Invalid selector attribute at ${token[LOC]!.src}:${token[LOC]!.sta.lin}:${ // token[LOC]!.sta.col // }`, // node: token, // location: token[LOC], // }, // ], // }; // } slice.shift(); } if (slice[0].typ === EnumToken.Pipe) { // if (slice.length === 1) { // return { // success: false, // errors: [ // { // action: "drop", // message: `Invalid selector attribute at ${token[LOC]!.src}:${token[LOC]!.sta.lin}:${ // token[LOC]!.sta.col // }`, // node: token, // location: token[LOC], // }, // ], // }; // } // if (slice[1]?.typ !== EnumToken.IdenTokenType) { // return { // success: false, // errors: [ // { // action: "drop", // message: `Invalid selector attribute at ${token[LOC]!.src}:${token[LOC]!.sta.lin}:${ // token[LOC]!.sta.col // }`, // node: token, // location: token[LOC], // }, // ], // }; // } slice.shift(); } if (slice.length === 1) { // if (slice[0].typ !== EnumToken.IdenTokenType) { // return { // success: false, // errors: [ // { // action: "drop", // message: `Invalid selector attribute at ${token[LOC]!.src}:${token[LOC]!.sta.lin}:${ // token[LOC]!.sta.col // }`, // node: token, // location: token[LOC], // }, // ], // }; // } stack.pop(); break; } slice.shift(); // if ( // slice[0].typ != EnumToken.DelimTokenType && // slice[0].typ !== EnumToken.DashMatchTokenType && // slice[0].typ !== EnumToken.EndMatchTokenType && // slice[0].typ !== EnumToken.IncludeMatchTokenType && // slice[0].typ !== EnumToken.StartMatchTokenType && // slice[0].typ !== EnumToken.ContainMatchTokenType // ) { // return { // success: false, // errors: [ // { // action: "drop", // message: `Unexpected token ${EnumToken[slice[0].typ]} at ${slice[0][LOC]!.src}:${slice[0][LOC]!.sta.lin}:${ // slice[0][LOC]!.sta.col // }`, // node: slice[0], // location: slice[0][LOC], // }, // ], // }; // } slice.shift(); // if (slice.length === 0) { // // expect iden or string // return { // success: false, // errors: [ // { // action: "drop", // message: `Invalid selector attribute at ${slice[0][LOC]!.src}:${slice[0][LOC]!.sta.lin}:${ // slice[0][LOC]!.sta.col // }`, // node: slice[0], // location: slice[0][LOC], // }, // ], // }; // } // if (slice[0].typ !== EnumToken.IdenTokenType && slice[0].typ !== EnumToken.StringTokenType) { // return { // success: false, // errors: [ // { // action: "drop", // message: `Unexpected token ${EnumToken[slice[0].typ]} at ${slice[0][LOC]!.src}:${slice[0][LOC]!.sta.lin}:${ // slice[0][LOC]!.sta.col // }`, // node: slice[0], // location: slice[0][LOC], // }, // ], // }; // } if (slice[0]?.typ === EnumToken.StringTokenType && /^[a-zA-Z0-9_-]+$/.test(slice[0].val.slice(1, -1))) { Object.assign(slice[0], { typ: EnumToken.IdenTokenType, val: slice[0].val.slice(1, -1), }); } slice.shift(); if (slice.length === 0) { stack.pop(); break; } if (slice[0].typ !== EnumToken.IdenTokenType || (slice[0].val != "i" && slice[0].val != "s")) { return { success: false, errors: [ { action: "drop", message: `Unexpected token ${EnumToken[slice[0].typ]} at ${slice[0][LOC].src}:${slice[0][LOC].sta.lin}:${slice[0][LOC].sta.col}`, node: slice[0], location: slice[0][LOC], }, ], }; } slice.shift(); // if (slice.length > 0) { // return { // success: false, // errors: [ // { // action: "drop", // message: `Unexpected token ${EnumToken[slice[0].typ]} at ${slice[0][LOC]!.src}:${slice[0][LOC]!.sta.lin}:${ // slice[0][LOC]!.sta.col // }`, // node: slice[0], // location: slice[0][LOC], // }, // ], // }; // } stack.pop(); break; } // case EnumToken.ColonTokenType: // if (stream[i + 1]?.typ === EnumToken.IdenTokenType) { // Object.assign(token, { // typ: // (stream[i + 1] as IdentToken).val === "page" // ? EnumToken.PseudoPageTokenType // : pseudoElements.includes((token as PseudoElementToken).val) // ? EnumToken.PseudoElementTokenType // : EnumToken.PseudoClassTokenType, // val: ":" + (stream[i + 1] as IdentToken).val, // }); // token[LOC]!.end = stream[++i][LOC]!.end; // break; // } else if (stream[i + 1]?.typ === EnumToken.FunctionTokenDefType) { // Object.assign(token, { // typ: EnumToken.PseudoClassFunctionTokenDefType, // val: ":" + (stream[i + 1] as IdentToken).val, // }); // token[LOC]!.end = stream[++i][LOC]!.end; // stack.push(token); // break; // } // return { // success: false, // errors: [ // { // action: "drop", // message: `Unexpected token ${EnumToken[token.typ]} at ${token[LOC]!.src}:${token[LOC]!.sta.lin}:${ // token[LOC]!.sta.col // }`, // node: token, // location: token[LOC], // }, // ], // }; // case EnumToken.DoubleColonTokenType: // if (stream[i + 1]?.typ === EnumToken.IdenTokenType) { // Object.assign(token, { // typ: // (stream[i + 1] as IdentToken).val === "page" // ? EnumToken.PseudoPageTokenType // : EnumToken.PseudoElementTokenType, // val: "::" + (stream[i + 1] as IdentToken).val, // }); // token[LOC]!.end = stream[++i][LOC]!.end; // break; // } else if (stream[i + 1]?.typ === EnumToken.FunctionTokenDefType) { // Object.assign(token, { // typ: EnumToken.PseudoClassFunctionTokenDefType, // val: "::" + (stream[i + 1] as IdentToken).val, // }); // token[LOC]!.end = stream[++i][LOC]!.end; // stack.push(token); // break; // } // return { // success: false, // errors: [ // { // action: "drop", // message: `Unexpected token ${EnumToken[token.typ]} at ${token[LOC]!.src}:${token[LOC]!.sta.lin}:${ // token[LOC]!.sta.col // }`, // node: token, // location: token[LOC], // }, // ], // }; case EnumToken.StartParensTokenType: // if ( // tokens.at(-2)?.typ === EnumToken.PseudoClassTokenType || // tokens.at(-2)?.typ === EnumToken.PseudoElementTokenType // ) { // stack.push( // Object.assign(tokens.at(-2) as Token, { // typ: EnumToken.PseudoClassFunctionTokenDefType, // chi: [], // }), // ); // // tokens.pop(); // break; // } return { success: false, errors: [ { action: "drop", message: `Unexpected token ${EnumToken[token.typ]} at ${token[LOC].src}:${token[LOC].sta.lin}:${token[LOC].sta.col}`, node: token, location: token[LOC], }, ], }; case EnumToken.EndParensTokenType: if (stack.length > 0 && stack.at(-1)?.typ === EnumToken.UniversalSelectorTokenType) { stack.pop(); } if (stack.at(-1)?.typ === EnumToken.PseudoClassFunctionTokenDefType || stack.at(-1)?.typ === EnumToken.PseudoElementTokenType) { const token = stack.at(-1); // if (!((stack.at(-1) as PseudoClassFunctionToken).val + "()" in config.selectors)) { // return { // errors: [ // { // action: "drop", // message: `Unknown class element ${(token as PseudoElementToken).val}`, // node: token, // location: token[LOC]!, // }, // ], // success: false, // }; // } const index = tokens.indexOf(token); const result = matchAllSyntaxes(getParsedSyntax(ValidationSyntaxGroupEnum.Selectors, token.val + "()")?.[0]?.chi ?? [], createValidationContext(tokens.slice(index + 1, tokens.length - 1)), options); if (!result.success) { success = false; if (result.errors.length > 0) { errors.push(...result.errors); } } stack.pop(); break; } return { success: false, errors: [ { action: "drop", message: `Unexpected token ${EnumToken[token.typ]} at ${token[LOC].src}:${token[LOC].sta.lin}:${token[LOC].sta.col}`, node: token, location: token[LOC], }, ], }; case EnumToken.NumberTokenType: case EnumToken.LiteralTokenType: case EnumToken.DimensionTokenType: // if (stack.at(-1)?.typ === EnumToken.CommaTokenType) { // stack.pop(); // } if (stack.at(-1)?.typ === EnumToken.PseudoClassFunctionTokenDefType) { break; } default: return { success: false, errors: [ { action: "drop", message: `Unsupported selector token ${EnumToken[token.typ]} at ${token[LOC].src}:${token[LOC].sta.lin}:${token[LOC].sta.col}`, node: token, location: token[LOC], }, ], }; } if (token.typ === EnumToken.WhitespaceTokenType && trimWhitespaceAfter.includes(tokens.at(-2)?.typ) && tokens.at(-1)?.typ === EnumToken.WhitespaceTokenType) { tokens.pop(); } else if (trimWhitespaceBefore.includes(token.typ) && tokens.at(-2)?.typ === EnumToken.WhitespaceTokenType) { tokens.splice(tokens.length - 2, 1); } } if (stack.length > 0 && stack.at(-1).typ === EnumToken.UniversalSelectorTokenType) { stack.pop(); } if (stack.length > 0) { return { success: false, errors: [ { action: "drop", message: `Unmatched token ${EnumToken[stack.at(-1).typ]} at ${stack.at(-1)[LOC].src}:${stack.at(-1)[LOC].sta.lin}:${stack.at(-1)[LOC].sta.col}`, node: stack.at(-1), location: stack.at(-1)[LOC], }, ], }; } stream.length = 0; stream.push(...tokens); // if (!success && errors.length === 0) { // errors.push({ // action: "drop", // message: "Invalid selector", // node: tokens[0], // location: tokens[0][LOC]!, // }); // } return { success, errors }; } function matchAllSyntaxes(syntaxes, context, options) { const result = matchSyntax(syntaxes, context, { ...options, visited: new Map(), }); // if (result.success && !result.context.done()) { // const node = result.context.peek() as Token; // return { // ...result, // success: false, // token: context.peek(), // errors: [ // ...result.errors, // { // action: "drop", // message: `Unexpected token ${EnumToken[node?.typ]} at ${node![LOC]?.src}:${node![LOC]?.sta?.lin}:${ // node![LOC]?.sta?.col // }`, // node, // syntax: // result.syntaxToken ?? // syntaxes?.reduce?.((acc, b) => acc + renderSyntax(b), "")?.trim?.() ?? // null, // }, // ], // }; // } if (syntaxes != null && result.success && result.syntaxToken != null) { const index = syntaxes.indexOf(result.syntaxToken); if (index != -1) { for (let i = index; i < syntaxes.length; i++) { if (syntaxes[i].typ == ValidationTokenEnum.Whitespace || syntaxes[i].isOptional || syntaxes[i].isRepeatable) { continue; } // if (syntaxes[i].typ == ValidationTokenEnum.SemiColon && i === syntaxes.length - 1) { // continue; // } return { ...result, success: false, syntaxToken: syntaxes[i], }; } } } return { ...result, errors: !result.success && result.errors.length === 0 ? [ { action: "drop", message: result.errors[0]?.message || "could not match syntax", node: result.token, syntax: result.syntaxToken, location: result.token?.[LOC] ?? context.tokens.at(-1)?.[LOC], }, ] : result.errors, syntaxToken: !result.success ? result.syntaxToken : null, }; } function matchListSyntax(syntax, context, options) { const { isList, match, isOptional, ...rest } = syntax; let success = true; let result = null; let tmpResult; let count = 0; let range; success = false; do { range = context.peekRange(EnumToken.CommaTokenType, EnumToken.CommaTokenType, 1); tmpResult = matchSyntax([rest], createValidationContext(range.at(-1)?.typ === EnumToken.CommaTokenType ? range.slice(0, -1) : range), options); if (tmpResult.success) { count++; success = true; result = tmpResult; if (Number.isFinite(match?.max?.val) && count === match.max.val) { context.update(range.at(-1)?.typ === EnumToken.CommaTokenType ? range.at(-2) : range.at(-1)); break; } else { context.update(range.at(-1)); } if (context.done()) { // context.end(); break; } } } while (tmpResult.success && !context.done()); // if (result?.success && match != null) { // if (count < match.min!.val || (Number.isFinite(match.max) && count > (match.max!.val as number))) { // return { // ...result, // success: false, // errors: [ // { // action: "drop", // message: "could not match syntax", // node: context.peek(), // location: context.peek()?.[LOC]!, // }, // ], // }; // } // } return result == null ? { success: false, valid: true, context, token: context.peek(), syntaxToken: syntax, errors: [], } : { ...result, success, context, token: context.peek(), }; } function matchOccurenceSyntax(syntax, context, options) { const { match, ...rest } = syntax; let result = null; let tmpResult; let count = 0; do { tmpResult = matchSyntax([rest], context.slice(), options); if (tmpResult.success) { count++; result = tmpResult; if (tmpResult.context.done()) { context.end(); break; } context.update(tmpResult.context.current()); if (match?.max?.val != null && Number.isFinite(match?.max?.val) && count === match.max.val) { break; } } } while (tmpResult.success && !context.done()); if (result == null || (match != null && (count < match.min.val || (Number.isFinite(match.max?.val) && count > match.max.val)))) { return { success: false, errors: [ { action: "drop", message: "could not match syntax", node: context.peek(), location: context.peek()?.[LOC], }, ], syntaxToken: null, valid: true, context, token: null, }; } return result; } function matchSyntax(syntaxes, context, options) { if (syntaxes == null) { return { syntaxToken: null, token: context.peek(), success: true, valid: false, context, errors: [], }; } syntaxes = syntaxes.slice(); let i = -1; let success = false; let token = null; let result = null; let isOptional; if (context.tokens.length == 1 && context.tokens[0].typ == EnumToken.IdenTokenType && allValues.some((v) => equalsIgnoreCase(v, context.tokens[0].val))) { context.end(); return { syntaxToken: null, token: context.peek(), success: true, valid: true, context,