UNPKG

@tbela99/css-parser

Version:

CSS parser, minifier and validator for node and the browser

460 lines (457 loc) 23.9 kB
import { EnumToken } from '../../ast/types.js'; import { pseudoElements, LOC, tokensfuncDefMap } from '../../syntax/constants.js'; import { getSyntaxConfig, getParsedSyntax } from '../../validation/config.js'; import { trimArray, matchAllSyntaxes, createValidationContext } from '../../validation/match.js'; import { ValidationSyntaxGroupEnum } from '../../validation/parser/typedef.js'; import { parseDeclaration } from './declaration.js'; function parseAtRuleSupportSyntax(stream, context, options = {}) { const tokens = []; const stack = []; let i = 0; let success = true; let expectAndOr = false; let scope = new Set(); let val; const errors = []; const scopes = [scope]; const trimWhiteSpace = new Set([ EnumToken.GtTokenType, EnumToken.ChildCombinatorTokenType, EnumToken.NextSiblingCombinatorTokenType, EnumToken.SubsequentSiblingCombinatorTokenType, EnumToken.ColumnCombinatorTokenType, EnumToken.UniversalSelectorTokenType, ]); // while ( // i < stream.length && // (stream[i]?.typ === EnumToken.WhitespaceTokenType || stream[i]?.typ === EnumToken.CommentTokenType) // ) { // tokens.push(stream[i]); // i++; // } // if ( // !tokensfuncDefMap.has(stream[i]?.typ) && // stream[i]?.typ !== EnumToken.StartParensTokenType && // !(stream[i]?.typ === EnumToken.IdenTokenType && "not" === (stream[i] as IdentToken)?.val.toLowerCase()) // ) { // return { // success: false, // errors: [ // { // action: "drop", // node: stream[i], // message: `expecting '<supports-condition>' at ${stream[i]?.[LOC]?.src}:${stream[i]?.[LOC]?.sta.lin}:${stream[i]?.[LOC]?.sta.col}`, // }, // ], // }; // } for (; i < stream.length; i++) { tokens.push(stream[i]); if (stream[i].typ == EnumToken.ColonTokenType) { if (stream[i + 1]?.typ == EnumToken.IdenTokenType) { const val = stream[i + 1].val; Object.assign(stream[i], { typ: pseudoElements.includes(val) ? EnumToken.PseudoElementTokenType : EnumToken.PseudoClassTokenType, val: ":" + val, }); stream[i][LOC].end = stream[i + 1][LOC].end; stream.splice(i + 1, 1); continue; } if (stream[i + 1]?.typ == EnumToken.FunctionTokenDefType) { val = ":" + stream[i + 1].val; Object.assign(stream[i], { typ: val + "()" in getSyntaxConfig().selectors ? EnumToken.PseudoClassFunctionTokenDefType : stream[i + 1].typ, val, }); stack.push(stream[i]); stream[i][LOC].end = stream[i + 1][LOC].end; stream.splice(i + 1, 1); continue; } } // if (stream[i].typ == EnumToken.DoubleColonTokenType) { // val = ":" + (stream[i + 1] as IdentToken).val; // if (stream[i + 1]?.typ == EnumToken.IdenTokenType) { // Object.assign(stream[i], { // typ: EnumToken.PseudoClassTokenType, // val: (pseudoElements.includes(val) ? "" : ":") + val, // }); // stream[i][LOC]!.end = stream[i + 1]![LOC]!.end; // stream.splice(i + 1, 1); // continue; // } // if (stream[i + 1]?.typ == EnumToken.FunctionTokenDefType) { // val = "::" + (stream[i + 1] as IdentToken).val; // Object.assign(stream[i], { // typ: // val + "()" in getSyntaxConfig().selectors // ? EnumToken.PseudoClassFunctionTokenDefType // : EnumToken.FunctionTokenDefType, // val, // }); // stack.push(stream[i]); // stream[i][LOC]!.end = stream[i + 1]![LOC]!.end; // stream.splice(i + 1, 1); // continue; // } // } if (trimWhiteSpace.has(stream[i].typ)) { if (tokens.at(-2)?.typ === EnumToken.WhitespaceTokenType) { tokens.splice(tokens.length - 2, 1); } if (stream[i + 1]?.typ === EnumToken.WhitespaceTokenType) { i++; continue; } } if (expectAndOr) { let k = i; while (k < stream.length && (stream[k]?.typ === EnumToken.WhitespaceTokenType || stream[k]?.typ === EnumToken.CommentTokenType)) { tokens.push(stream[k]); k++; } // if (k < stream.length) { // if ( // stream[k].typ !== EnumToken.EndParensTokenType && // !( // stream[k].typ === EnumToken.IdenTokenType && // ("and" === (stream[k] as IdentToken)?.val.toLowerCase() || // "or" === (stream[k] as IdentToken)?.val.toLowerCase()) // ) // ) { // return { // success: false, // errors: [ // { // action: "drop", // node: stream[k], // message: `expecting 'and' or 'or' at ${stream[k]?.[LOC]?.src}:${stream[k]?.[LOC]?.sta.lin}:${stream[k]?.[LOC]?.sta.col}`, // }, // ], // }; // } // } expectAndOr = false; } if (stream[i].typ === EnumToken.StartParensTokenType || tokensfuncDefMap.has(stream[i].typ)) { scopes.push((scope = new Set())); stack.push(stream[i]); continue; } switch (stream[i].typ) { case EnumToken.EndParensTokenType: { // if (stack.length === 0) { // return { // success: false, // errors: [ // { // action: "drop", // node: stream[i], // message: `unmatched ')' at ${stream[i]?.[LOC]?.src}:${stream[i]?.[LOC]?.sta.lin}:${stream[i]?.[LOC]?.sta.col}`, // }, // ], // }; // } if (stack[stack.length - 1].typ === EnumToken.ColonTokenType) { // if (tokensfuncDefMap.has(stack[stack.length - 2]?.typ)) { // const index: number = tokens.indexOf(stack[stack.length - 1]); // // expecting ident or dashed ident // if ( // tokens[index + 1]?.typ == EnumToken.IdenTokenType || // tokens[index + 1]?.typ == EnumToken.DashedIdenTokenType // ) { // const val: string = (tokens[index + 1] as IdentToken | DashedIdentToken).val; // Object.assign(tokens[index], { // typ: pseudoElements.includes(val) // ? EnumToken.PseudoElementTokenType // : EnumToken.PseudoClassTokenType, // val: ":" + val, // }); // tokens[index][LOC]!.end = tokens[index + 1][LOC]!.end; // tokens.splice(index + 1, 1); // stack.pop(); // const index2: number = tokens.indexOf(stack[stack.length - 1]); // const result = matchAllSyntaxes( // ( // getParsedSyntax( // ValidationSyntaxGroupEnum.Syntaxes, // (tokens[index2] as FunctionToken).val + "()", // ) as ValidationFunctionToken[] // )[0]?.chi as ValidationToken[], // createValidationContext(tokens.slice(index2 + 1, -1)), // options, // ); // if (!result.success) { // return { // success: false, // errors: result.errors, // }; // } // tokens.pop(); // Object.assign(tokens[index2], { // typ: tokensfuncDefMap.get(tokens[index2].typ)!, // chi: tokens.splice(index2 + 1, tokens.length - index2 - 1), // }); // tokens[index2][LOC]!.end = stream[i][LOC]!.end; // stack.pop(); // break; // } // } // if (stack[stack.length - 2]?.typ !== EnumToken.StartParensTokenType) { // return { // success: false, // errors: [ // { // action: "drop", // node: stream[i], // message: `unmatched ')' at ${stream[i]?.[LOC]?.src}:${stream[i]?.[LOC]?.sta.lin}:${stream[i]?.[LOC]?.sta.col}`, // }, // ], // }; // } // match declaration const index = tokens.indexOf(stack[stack.length - 2]); const slice = trimArray(tokens.splice(index + 1, tokens.length - index - 2)); const declaration = parseDeclaration(slice, context, { ...options, validation: false }, errors); // if (declaration.typ !== EnumToken.DeclarationNodeType) { // return { // success: false, // errors: [ // { // action: "drop", // node: declaration, // message: `invalid declaration at ${declaration?.[LOC]?.src}:${declaration?.[LOC]?.sta.lin}:${declaration?.[LOC]?.sta.col}`, // }, // ], // }; // } tokens.splice(index + 1, 0, declaration); stack.pop(); } // match <support-condition-name> // @supports (--condition-name) {} if (stack.at(-1)?.typ === EnumToken.StartParensTokenType) { const index = tokens.indexOf(stack.at(-1)); const slice = trimArray(tokens.splice(index + 1, tokens.length - index - 2)); slice.filter((token) => { return (token.typ !== EnumToken.WhitespaceTokenType && token.typ !== EnumToken.CommentTokenType); }); // if ( // filtered.length !== 1 || // !( // trimTokenSpace.has(filtered[0].typ) || // tokensfuncDefMap.has(filtered[0].typ) || // tokensfuncSet.has(filtered[0].typ) // ) // ) { // const token = filtered[0] ?? slice[0] ?? stream[i]; // return { // success: false, // errors: [ // { // action: "drop", // node: token, // message: `expecting '<${filtered[0]?.typ === EnumToken.IdenTokenType ? "supports-condition-name" : "supports-condition"}>' at ${token?.[LOC]?.src}:${token?.[LOC]?.sta.lin}:${token?.[LOC]?.sta.col}`, // }, // ], // }; // } tokens[index] = { typ: EnumToken.ParensTokenType, chi: slice, [LOC]: { ...stack.at(-1)[LOC], end: { ...stream[i]?.[LOC]?.end } }, }; stack.pop(); tokens.pop(); scopes.pop(); scope = scopes[scopes.length - 1]; expectAndOr = true; } else if (tokensfuncDefMap.has(stack.at(-1)?.typ)) { const index = tokens.indexOf(stack.at(-1)); tokens[index] = { typ: tokensfuncDefMap.get(stack.at(-1)?.typ), val: stack.at(-1).val, chi: trimArray(tokens.splice(index + 1, tokens.length - index - 2)), [LOC]: { ...stack.at(-1)[LOC], end: { ...stream[i]?.[LOC]?.end } }, }; if (tokens[index].typ === EnumToken.PseudoClassFuncTokenType) { // not a declaration const result = matchAllSyntaxes(getParsedSyntax(ValidationSyntaxGroupEnum.Selectors, tokens[index].val + "()")?.[0]?.chi, createValidationContext(tokens[index].chi), options); if (!result.success) { return { success: false, errors: result.errors, }; } stack.pop(); tokens.pop(); break; } // let k = stack.length - 1; while (tokensfuncDefMap.has(stack[k]?.typ)) { k--; } // if (stack[k]?.typ !== EnumToken.ColonTokenType) { // if ( // tokens[index].typ !== EnumToken.SupportsFunctionTokenType && // !equalsIgnoreCase("env", (tokens[index] as FunctionToken).val) // ) { // errors.push({ // action: "ignore", // node: tokens[index], // message: `expecting <supports-selector-fn>, <supports-env-fn>, <font-tech()>, <font-format()>, <at-rule()> or <named-feature()> at ${tokens[index]?.[LOC]?.src}:${tokens[index]?.[LOC]?.sta.lin}:${tokens[index]?.[LOC]?.sta.col}`, // }); // } else { // // not a declaration // const result = matchAllSyntaxes( // ( // getParsedSyntax( // ValidationSyntaxGroupEnum.Syntaxes, // (tokens[index] as FunctionToken).val + "()", // ) as ValidationFunctionToken[] // )?.[0]?.chi as ValidationToken[], // createValidationContext((tokens[index] as FunctionToken).chi as Token[]), // options, // ); // if (!result.valid) { // errors.push(...result.errors, { // action: "ignore", // node: tokens[index], // message: `missing syntax for function '${(tokens[index] as FunctionToken).val}()' at ${tokens[index]?.[LOC]?.src}:${tokens[index]?.[LOC]?.sta.lin}:${tokens[index]?.[LOC]?.sta.col}`, // }); // } else if (!result.success) { // return { // success: false, // errors: result.errors, // }; // } // } // } stack.pop(); tokens.pop(); scopes.pop(); scope = scopes[scopes.length - 1]; } if (stack.at(-1)?.typ === EnumToken.NotTokenType) { const index = tokens.indexOf(stack.at(-1)); tokens[index] = { typ: EnumToken.SupportsQueryUnaryConditionTokenType, l: stack.at(-1), r: trimArray(tokens.splice(index + 1, i - index - 1)), [LOC]: { ...stack.at(-1)[LOC], end: { ...stream[i]?.[LOC]?.end } }, }; stack.pop(); } if (stack.at(-1)?.typ === EnumToken.AndTokenType || stack.at(-1)?.typ === EnumToken.OrTokenType) { // if (stack.length > 1 && stack.at(-2)?.typ !== EnumToken.StartParensTokenType) { // return { // success: false, // errors: [ // { // action: "drop", // node: stack.at(-2), // message: `expecting '(' at ${stack.at(-2)?.[LOC]?.src}:${stack.at(-2)?.[LOC]?.sta.lin}:${stack.at(-2)?.[LOC]?.sta.col}`, // }, // ], // }; // } const index = tokens.indexOf(stack.at(-1)); const index2 = stack.length > 1 ? tokens.indexOf(stack.at(-2)) + 1 : 0; const left = trimArray(tokens.slice(index2, index)); left.find((t) => t.typ === EnumToken.SupportsQueryUnaryConditionTokenType && t.l.typ === EnumToken.NotTokenType); // if (notToken != null) { // return { // success: false, // errors: [ // { // action: "drop", // node: stack.at(-1), // message: `unexpected token after 'not' expression at ${stack.at(-1)?.[LOC]?.src}:${stack.at(-1)?.[LOC]?.sta.lin}:${stack.at(-1)?.[LOC]?.sta.col}`, // }, // ], // }; // } tokens[index2] = { typ: EnumToken.SupportsQueryConditionTokenType, op: stack.at(-1), l: left, r: trimArray(tokens.slice(index + 1)), [LOC]: { ...stack.at(-1)[LOC], end: { ...stream[i]?.[LOC]?.end } }, }; tokens.length = index2 + 1; stack.pop(); } } break; case EnumToken.ColonTokenType: stack.push(stream[i]); break; case EnumToken.IdenTokenType: { const val = stream[i].val.toLowerCase(); if ("not" === val) { stack.push(stream[i]); Object.assign(stream[i], { typ: EnumToken.NotTokenType }); break; } if ("and" === val || "or" === val) { // if (scope.has("or" === val ? EnumToken.AndTokenType : EnumToken.OrTokenType)) { // return { // success: false, // errors: [ // { // action: "drop", // message: `mixing <and> and <or> at the same level is not allowed at ${stream[i]?.[LOC]?.src}:${stream[i]?.[LOC]?.sta.lin}:${stream[i]?.[LOC]?.sta.col}`, // }, // ], // }; // } if ("or" === val && scopes.length === 1) { return { success: false, errors: [ { action: "drop", message: `<or> is not allowed outside of a parenthesis at ${stream[i]?.[LOC]?.src}:${stream[i]?.[LOC]?.sta.lin}:${stream[i]?.[LOC]?.sta.col}`, }, ], }; } Object.assign(stream[i], { typ: "or" === val ? EnumToken.OrTokenType : EnumToken.AndTokenType, }); scope.add(stream[i].typ); stack.push(stream[i]); break; } } break; } } // if (stack.length > 0) { // return { // success: false, // errors: [ // { // action: "drop", // node: stack.at(-1), // message: `unmatched token '${renderValue(stack.at(-1) as Token)}' at ${stack.at(-1)![LOC]!.src}:${stack.at(-1)![LOC]!.sta.lin}:${stack.at(-1)![LOC]!.sta.col}`, // }, // ], // }; // } stream.length = 0; stream.push(...trimArray(tokens)); return { success, errors }; } export { parseAtRuleSupportSyntax };