UNPKG

@tbela99/css-parser

Version:

CSS parser for node and the browser

294 lines (291 loc) 10.8 kB
import { ValidationLevel, EnumToken } from '../../ast/types.js'; import '../../ast/minify.js'; import '../../ast/walk.js'; import { parseSelector } from '../../parser/parse.js'; import { colorFontTech, fontFeaturesTech, fontFormat } from '../../syntax/syntax.js'; import '../../parser/utils/config.js'; import '../../renderer/color/utils/constants.js'; import '../../renderer/sourcemap/lib/encode.js'; import { consumeWhitespace } from '../utils/whitespace.js'; import { splitTokenList } from '../utils/list.js'; import { validateComplexSelector } from '../syntaxes/complex-selector.js'; function validateAtRuleSupports(atRule, options, root) { // media-query-list if (!Array.isArray(atRule.tokens) || atRule.tokens.length == 0) { // @ts-ignore return { valid: ValidationLevel.Drop, matches: [], node: atRule, syntax: '@' + atRule.nam, error: 'expected supports query list', tokens: [] }; } const result = validateAtRuleSupportsConditions(atRule, atRule.tokens); if (result) { if (result.node == null) { result.node = atRule; } return result; } if (!('chi' in atRule)) { // @ts-ignore return { valid: ValidationLevel.Drop, matches: [], node: atRule, syntax: '@' + atRule.nam, error: 'expected at-rule body', tokens: [] }; } // @ts-ignore return { valid: ValidationLevel.Valid, matches: [], node: atRule, syntax: '@' + atRule.nam, error: '', tokens: [] }; } function validateAtRuleSupportsConditions(atRule, tokenList) { let result = null; for (const tokens of splitTokenList(tokenList)) { if (tokens.length == 0) { // @ts-ignore return { valid: ValidationLevel.Drop, matches: [], node: tokens[0] ?? atRule, syntax: '@' + atRule.nam, error: 'unexpected token', tokens: [] }; } let previousToken = null; while (tokens.length > 0) { result = validateSupportCondition(atRule, tokens[0]); // supports-condition if (result.valid == ValidationLevel.Valid) { previousToken = tokens[0]; tokens.shift(); } else { result = validateSupportFeature(tokens[0]); if ( /*result == null || */result.valid == ValidationLevel.Valid) { previousToken = tokens[0]; tokens.shift(); } else { if (tokens[0].typ == EnumToken.ParensTokenType) { result = validateAtRuleSupportsConditions(atRule, tokens[0].chi); if ( /* result == null || */result.valid == ValidationLevel.Valid) { previousToken = tokens[0]; tokens.shift(); // continue; } else { return result; } } else { return result; } // if (result!= null && result.valid == ValidationLevel.Drop) { // // return { // valid: ValidationLevel.Drop, // matches: [], // node: tokens[0] ?? atRule, // syntax: '@' + atRule.nam, // // @ts-ignore // error: result.error as string ?? 'unexpected token', // tokens: [] // }; // } } } if (tokens.length == 0) { break; } if (!consumeWhitespace(tokens)) { if (previousToken?.typ != EnumToken.ParensTokenType) { // @ts-ignore return { valid: ValidationLevel.Drop, matches: [], node: tokens[0] ?? previousToken ?? atRule, syntax: '@' + atRule.nam, error: 'expected whitespace', tokens: [] }; } } if (![EnumToken.MediaFeatureOrTokenType, EnumToken.MediaFeatureAndTokenType].includes(tokens[0].typ)) { // @ts-ignore return { valid: ValidationLevel.Drop, matches: [], node: tokens[0] ?? atRule, syntax: '@' + atRule.nam, error: 'expected and/or', tokens: [] }; } if (tokens.length == 1) { // @ts-ignore return { valid: ValidationLevel.Drop, matches: [], node: tokens[0] ?? atRule, syntax: '@' + atRule.nam, error: 'expected supports-condition', tokens: [] }; } tokens.shift(); if (!consumeWhitespace(tokens)) { // @ts-ignore return { valid: ValidationLevel.Drop, matches: [], node: tokens[0] ?? atRule, syntax: '@' + atRule.nam, error: 'expected whitespace', tokens: [] }; } } } return { valid: ValidationLevel.Valid, matches: [], node: atRule, syntax: '@' + atRule.nam, error: '', tokens: [] }; } function validateSupportCondition(atRule, token) { if (token.typ == EnumToken.MediaFeatureNotTokenType) { return validateSupportCondition(atRule, token.val); } if (token.typ != EnumToken.ParensTokenType && !(['when', 'else'].includes(atRule.nam) && token.typ == EnumToken.FunctionTokenType && ['supports', 'font-format', 'font-tech'].includes(token.val))) { // @ts-ignore return { valid: ValidationLevel.Drop, matches: [], node: token, syntax: '@' + atRule.nam, error: 'expected supports condition-in-parens', tokens: [] }; } const chi = token.chi.filter((t) => t.typ != EnumToken.CommentTokenType && t.typ != EnumToken.WhitespaceTokenType); if (chi.length != 1) { return validateAtRuleSupportsConditions(atRule, token.chi); } if (chi[0].typ == EnumToken.IdenTokenType) { // @ts-ignore return { valid: ValidationLevel.Valid, matches: [], node: null, syntax: '@' + atRule.nam, error: '', tokens: [] }; } if (chi[0].typ == EnumToken.MediaFeatureNotTokenType) { return validateSupportCondition(atRule, chi[0].val); } if (chi[0].typ == EnumToken.MediaQueryConditionTokenType) { // @ts-ignore return chi[0].l.typ == EnumToken.IdenTokenType && chi[0].op.typ == EnumToken.ColonTokenType ? { valid: ValidationLevel.Valid, matches: [], node: null, syntax: 'supports-condition', error: '', tokens: [] } : { valid: ValidationLevel.Drop, matches: [], node: token, syntax: 'supports-condition', error: 'expected supports condition-in-parens', tokens: [] }; } // @ts-ignore return { valid: ValidationLevel.Drop, matches: [], node: token, syntax: 'supports-condition', error: 'expected supports condition-in-parens', tokens: [] }; } function validateSupportFeature(token) { if (token.typ == EnumToken.FunctionTokenType) { if (token.val.localeCompare('selector', undefined, { sensitivity: 'base' }) == 0) { return validateComplexSelector(parseSelector(token.chi)); } if (token.val.localeCompare('font-tech', undefined, { sensitivity: 'base' }) == 0) { const chi = token.chi.filter((t) => ![EnumToken.WhitespaceTokenType, EnumToken.CommentTokenType].includes(t.typ)); // @ts-ignore return chi.length == 1 && chi[0].typ == EnumToken.IdenTokenType && colorFontTech.concat(fontFeaturesTech).some((t) => t.localeCompare(chi[0].val, undefined, { sensitivity: 'base' }) == 0) ? { valid: ValidationLevel.Valid, matches: [], node: token, syntax: 'font-tech', error: '', tokens: [] } : { valid: ValidationLevel.Drop, matches: [], node: token, syntax: 'font-tech', error: 'expected font-tech', tokens: [] }; } if (token.val.localeCompare('font-format', undefined, { sensitivity: 'base' }) == 0) { const chi = token.chi.filter((t) => ![EnumToken.WhitespaceTokenType, EnumToken.CommentTokenType].includes(t.typ)); // @ts-ignore return chi.length == 1 && chi[0].typ == EnumToken.IdenTokenType && fontFormat.some((t) => t.localeCompare(chi[0].val, undefined, { sensitivity: 'base' }) == 0) ? { valid: ValidationLevel.Valid, matches: [], node: token, syntax: 'font-format', error: '', tokens: [] } : { valid: ValidationLevel.Drop, matches: [], node: token, syntax: 'font-format', error: 'expected font-format', tokens: [] }; } } // @ts-ignore return { valid: ValidationLevel.Drop, matches: [], node: token, syntax: '@supports', error: 'expected feature', tokens: [] }; } export { validateAtRuleSupports, validateAtRuleSupportsConditions, validateSupportCondition };