UNPKG

@tbela99/css-parser

Version:

CSS parser, minifier and validator for node and the browser

361 lines (358 loc) 18 kB
import { EnumToken } from '../../ast/types.js'; import { trimArray } from '../../validation/match.js'; import { tokensfuncDefMap, LOC } from '../../syntax/constants.js'; import { parseMediaqueryList } from './at-rule-media.js'; import { parseAtRuleSupportSyntax } from './at-rule-support.js'; function matchAtRuleWhenElseSyntax(stream, context, options = {}) { const tokens = []; const stack = []; let i = 0; let success = true; let expectAndOr = false; let scope = new Set(); const errors = []; // while ( // i < stream.length && // (stream[i]?.typ === EnumToken.WhitespaceTokenType || stream[i]?.typ === EnumToken.CommentTokenType) // ) { // tokens.push(stream[i]); // i++; // } // if (i >= stream.length) { // success = "else" === context.nam; // return { // success, // errors: success // ? [] // : [ // { // action: "drop", // node: stream[i], // location: stream[i]?.[LOC], // message: `expecting '<boolean-condition>' at ${stream[i]?.[LOC]?.src}:${stream[i]?.[LOC]?.sta.lin}:${stream[i]?.[LOC]?.sta.col}`, // }, // ], // }; // } // if ( // stream[i]?.typ !== EnumToken.WhenElseFunctionTokenDefType && // stream[i]?.typ !== EnumToken.SupportsFunctionTokenDefType && // stream[i]?.typ !== EnumToken.StartParensTokenType && // "val" in stream[i] && // !( // stream[i]?.typ === EnumToken.IdenTokenType && // (equalsIgnoreCase((stream[i] as IdentToken).val, "not") || // equalsIgnoreCase((stream[i] as IdentToken).val, "only")) // ) // ) { // return { // success: false, // errors: [ // { // action: "drop", // node: stream[i], // location: stream[i]?.[LOC], // message: `expecting '<boolean-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.StartParensTokenType) { // stack.push(stream[i]); // scopes.push((scope = new Set())); // 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 && // (equalsIgnoreCase((stream[k] as IdentToken).val, "and") || // equalsIgnoreCase((stream[k] as IdentToken).val, "or")) // ) // ) { // 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}`, // location: stream[k][LOC], // }, // ], // }; // } // } expectAndOr = false; } 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.at(-1)?.typ === EnumToken.StartParensTokenType) { // const index: number = tokens.indexOf(stack.at(-1)!); // const slice: Token[] = trimArray(tokens.splice(index + 1, tokens.length - index - 2)); // const filtered: Token[] = slice.filter((token) => { // return ( // token.typ !== EnumToken.WhitespaceTokenType && token.typ !== EnumToken.CommentTokenType // ); // }); // if ( // filtered.length !== 1 || // !( // validChildTypes.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, // location: token[LOC], // message: `expecting '<boolean-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 } } as Location, // }; // stack.pop(); // tokens.pop(); // scopes.pop(); // scope = scopes[scopes.length - 1]; // expectAndOr = true; // } // if (stack.at(-1)?.typ === EnumToken.NotTokenType || stack.at(-1)?.typ === EnumToken.OnlyTokenType) { // const index: number = 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 } }, // } as SupportsQueryUnaryConditionToken; // 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: number = tokens.indexOf(stack.at(-1)!); // const index2: number = stack.length > 1 ? tokens.indexOf(stack.at(-2)!) + 1 : 0; // const left: Token[] = trimArray(tokens.slice(index2, index)); // const notToken = left.find( // (t) => // t.typ === EnumToken.SupportsQueryUnaryConditionTokenType && // (t as SupportsQueryUnaryConditionToken).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 } }, // } as SupportsQueryConditionToken; // tokens.length = index2 + 1; // stack.pop(); // } // } // break; case EnumToken.IdenTokenType: { const val = stream[i].val.toLowerCase(); // if ("not" === val || "only" === val) { // stack.push(stream[i]); // Object.assign(stream[i], { // typ: "only" === val ? EnumToken.OnlyTokenType : 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; case EnumToken.SupportsFunctionTokenDefType: case EnumToken.WhenElseFunctionTokenDefType: { let matchCount = 1; let j = i + 1; for (; j < stream.length; j++) { if (stream[j].typ === EnumToken.EndParensTokenType) { matchCount--; if (matchCount === 0) { break; } } else if (stream[j].typ === EnumToken.StartParensTokenType || tokensfuncDefMap.has(stream[j].typ)) { matchCount++; } } const slice = stream.slice(i, j + 1); const funcName = stream[i].val.toLowerCase(); const tokenList = [ { typ: EnumToken.StartParensTokenType, [LOC]: { ...stream[i][LOC], end: { ...stream[j]?.[LOC]?.end } }, }, // @ts-expect-error ].concat(slice.slice(1)); if ("media" === funcName) { const result = parseMediaqueryList(tokenList, options); if (!result.success) { return result; } } else if ("supports" === funcName) { const result = parseAtRuleSupportSyntax(tokenList, context, options); if (!result.success) { return result; } } else if (stream[i].typ === EnumToken.SupportsFunctionTokenDefType) { tokenList.splice(0, tokenList.length, ...slice); const result = parseAtRuleSupportSyntax(stream.slice(i, j + 1), context, options); if (!result.success) { return result; } } // else { // errors.push({ // action: "ignore", // message: `unknown <boolean-condition> function '${funcName}' at ${stream[i]?.[LOC]?.src}:${stream[i]?.[LOC]?.sta.lin}:${stream[i]?.[LOC]?.sta.col}`, // node: stream[i], // location: stream[i][LOC], // }); // } stream[i][LOC] = { ...stream[i][LOC], end: { ...stream[j]?.[LOC]?.end } }; Object.assign(stream[i], { typ: tokensfuncDefMap.get(stream[i].typ), chi: stream[i].typ === EnumToken.SupportsFunctionTokenDefType ? trimArray(slice.slice(1, -1)) : tokenList[0].chi, }); // if (stack.at(-1)?.typ === EnumToken.NotTokenType || stack.at(-1)?.typ === EnumToken.OnlyTokenType) { // const index: number = tokens.indexOf(stack.at(-1)!); // tokens[index] = { // typ: EnumToken.WhenElseUnaryConditionTokenType, // l: stack.at(-1)!, // r: trimArray(tokens.slice(index + 1)), // [LOC]: { ...stack.at(-1)![LOC], end: { ...stream[i]?.[LOC]?.end } }, // } as WhenElseUnaryConditionToken; // tokens.length = index + 1; // stack.pop(); // } if (stack.at(-1)?.typ === EnumToken.AndTokenType || stack.at(-1)?.typ === EnumToken.OrTokenType) { const index = tokens.indexOf(stack.at(-1)); const index2 = stack.length > 1 ? tokens.indexOf(stack.at(-2)) + 1 : 0; tokens[index2] = { typ: EnumToken.WhenElseQueryConditionTokenType, op: stack.at(-1), l: trimArray(tokens.slice(index2, index)), r: trimArray(tokens.slice(index + 1)), [LOC]: { ...stack.at(-1)[LOC], end: { ...stream[i]?.[LOC]?.end } }, }; tokens.length = index2 + 1; stack.pop(); } i = j; expectAndOr = true; } 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 { matchAtRuleWhenElseSyntax };