@tbela99/css-parser
Version:
CSS parser for node and the browser
1,259 lines (1,258 loc) • 62.6 kB
JavaScript
import { webkitPseudoAliasMap, isIdentStart, isIdent, mathFuncs, isColor, isHexColor, isPseudo, pseudoElements, isAtKeyword, isFunction, isNumber, isPercentage, isFlex, isDimension, parseDimension, isHash, mediaTypes } from '../syntax/syntax.js';
import './utils/config.js';
import { EnumToken, funcLike, ValidationLevel } from '../ast/types.js';
import { minify, definedPropertySettings, combinators } from '../ast/minify.js';
import { walkValues, walk } from '../ast/walk.js';
import { expand } from '../ast/expand.js';
import { parseDeclarationNode } from './utils/declaration.js';
import { renderToken } from '../renderer/render.js';
import { COLORS_NAMES, systemColors, deprecatedSystemColors } from '../renderer/color/utils/constants.js';
import { tokenize } from './tokenize.js';
import '../validation/config.js';
import '../validation/parser/types.js';
import '../validation/parser/parse.js';
import { validateSelector } from '../validation/selector.js';
import { validateAtRule } from '../validation/atrule.js';
import '../validation/syntaxes/complex-selector.js';
const urlTokenMatcher = /^(["']?)[a-zA-Z0-9_/.-][a-zA-Z0-9_/:.#?-]+(\1)$/;
const trimWhiteSpace = [EnumToken.CommentTokenType, EnumToken.GtTokenType, EnumToken.GteTokenType, EnumToken.LtTokenType, EnumToken.LteTokenType, EnumToken.ColumnCombinatorTokenType];
const BadTokensTypes = [
EnumToken.BadCommentTokenType,
EnumToken.BadCdoTokenType,
EnumToken.BadUrlTokenType,
EnumToken.BadStringTokenType
];
const enumTokenHints = new Set([
EnumToken.WhitespaceTokenType, EnumToken.SemiColonTokenType, EnumToken.ColonTokenType, EnumToken.BlockStartTokenType,
EnumToken.BlockStartTokenType, EnumToken.AttrStartTokenType, EnumToken.AttrEndTokenType, EnumToken.StartParensTokenType, EnumToken.EndParensTokenType,
EnumToken.CommaTokenType, EnumToken.GtTokenType, EnumToken.LtTokenType, EnumToken.GteTokenType, EnumToken.LteTokenType, EnumToken.CommaTokenType,
EnumToken.StartMatchTokenType, EnumToken.EndMatchTokenType, EnumToken.IncludeMatchTokenType, EnumToken.DashMatchTokenType, EnumToken.ContainMatchTokenType,
EnumToken.EOFTokenType
]);
function reject(reason) {
throw new Error(reason ?? 'Parsing aborted');
}
/**
* parse css string
* @param iterator
* @param options
*/
async function doParse(iterator, options = {}) {
if (options.signal != null) {
options.signal.addEventListener('abort', reject);
}
options = {
src: '',
sourcemap: false,
minify: true,
pass: 1,
parseColor: true,
nestingRules: false,
resolveImport: false,
resolveUrls: false,
removeCharset: true,
removeEmpty: true,
removeDuplicateDeclarations: true,
computeShorthand: true,
computeCalcExpression: true,
inlineCssVariables: false,
setParent: true,
removePrefix: false,
validation: true,
lenient: true,
...options
};
if (options.expandNestingRules) {
options.nestingRules = false;
}
if (options.resolveImport) {
options.resolveUrls = true;
}
const startTime = performance.now();
const errors = [];
const src = options.src;
const stack = [];
const stats = {
bytesIn: 0,
importedBytesIn: 0,
parse: `0ms`,
minify: `0ms`,
total: `0ms`
};
let ast = {
typ: EnumToken.StyleSheetNodeType,
chi: []
};
let tokens = [];
let map = new Map;
let context = ast;
if (options.sourcemap) {
ast.loc = {
sta: {
ind: 0,
lin: 1,
col: 1
},
src: ''
};
}
const iter = tokenize(iterator);
let item;
const rawTokens = [];
while (item = iter.next().value) {
stats.bytesIn = item.bytesIn;
rawTokens.push(item);
// doParse error
if (item.hint != null && BadTokensTypes.includes(item.hint)) {
// bad token
continue;
}
if (item.hint != EnumToken.EOFTokenType) {
tokens.push(item);
}
if (item.token == ';' || item.token == '{') {
let node = await parseNode(tokens, context, stats, options, errors, src, map, rawTokens);
rawTokens.length = 0;
if (node != null) {
// @ts-ignore
stack.push(node);
// @ts-ignore
context = node;
}
else if (item.token == '{') {
let inBlock = 1;
do {
item = iter.next().value;
if (item == null) {
break;
}
if (item.token == '{') {
inBlock++;
}
else if (item.token == '}') {
inBlock--;
}
} while (inBlock != 0);
}
tokens = [];
map = new Map;
}
else if (item.token == '}') {
await parseNode(tokens, context, stats, options, errors, src, map, rawTokens);
rawTokens.length = 0;
const previousNode = stack.pop();
// @ts-ignore
context = stack[stack.length - 1] ?? ast;
// @ts-ignore
if (previousNode != null && previousNode.typ == EnumToken.InvalidRuleTokenType) {
// @ts-ignore
const index = context.chi.findIndex(node => node == previousNode);
if (index > -1) {
// @ts-ignore
context.chi.splice(index, 1);
}
}
// @ts-ignore
if (options.removeEmpty && previousNode != null && previousNode.chi.length == 0 && context.chi[context.chi.length - 1] == previousNode) {
// @ts-ignore
context.chi.pop();
}
tokens = [];
map = new Map;
}
}
if (tokens.length > 0) {
await parseNode(tokens, context, stats, options, errors, src, map, rawTokens);
rawTokens.length = 0;
if (context != null && context.typ == EnumToken.InvalidRuleTokenType) {
const index = context.chi.findIndex((node) => node == context);
if (index > -1) {
context.chi.splice(index, 1);
}
}
}
while (stack.length > 0 && context != ast) {
const previousNode = stack.pop();
// @ts-ignore
context = stack[stack.length - 1] ?? ast;
// remove empty nodes
// @ts-ignore
if (options.removeEmpty && previousNode != null && previousNode.chi.length == 0 && context.chi[context.chi.length - 1] == previousNode) {
// @ts-ignore
context.chi.pop();
continue;
}
break;
}
const endParseTime = performance.now();
if (options.expandNestingRules) {
ast = expand(ast);
}
if (options.visitor != null) {
for (const result of walk(ast)) {
if (result.node.typ == EnumToken.DeclarationNodeType &&
// @ts-ignore
(typeof options.visitor.Declaration == 'function' || options.visitor.Declaration?.[result.node.nam] != null)) {
const callable = typeof options.visitor.Declaration == 'function' ? options.visitor.Declaration : options.visitor.Declaration[result.node.nam];
const results = await callable(result.node);
if (results == null || (Array.isArray(results) && results.length == 0)) {
continue;
}
// @ts-ignore
result.parent.chi.splice(result.parent.chi.indexOf(result.node), 1, ...(Array.isArray(results) ? results : [results]));
}
else if (options.visitor.Rule != null && result.node.typ == EnumToken.RuleNodeType) {
const results = await options.visitor.Rule(result.node);
if (results == null || (Array.isArray(results) && results.length == 0)) {
continue;
}
// @ts-ignore
result.parent.chi.splice(result.parent.chi.indexOf(result.node), 1, ...(Array.isArray(results) ? results : [results]));
}
else if (options.visitor.AtRule != null &&
result.node.typ == EnumToken.AtRuleNodeType &&
// @ts-ignore
(typeof options.visitor.AtRule == 'function' || options.visitor.AtRule?.[result.node.nam] != null)) {
const callable = typeof options.visitor.AtRule == 'function' ? options.visitor.AtRule : options.visitor.AtRule[result.node.nam];
const results = await callable(result.node);
if (results == null || (Array.isArray(results) && results.length == 0)) {
continue;
}
// @ts-ignore
result.parent.chi.splice(result.parent.chi.indexOf(result.node), 1, ...(Array.isArray(results) ? results : [results]));
}
}
}
if (options.minify) {
if (ast.chi.length > 0) {
let passes = options.pass ?? 1;
while (passes--) {
minify(ast, options, true, errors, false);
}
}
}
const endTime = performance.now();
if (options.signal != null) {
options.signal.removeEventListener('abort', reject);
}
stats.bytesIn += stats.importedBytesIn;
return {
ast,
errors,
stats: {
...stats,
parse: `${(endParseTime - startTime).toFixed(2)}ms`,
minify: `${(endTime - endParseTime).toFixed(2)}ms`,
total: `${(endTime - startTime).toFixed(2)}ms`
}
};
}
function getLastNode(context) {
let i = context.chi.length;
while (i--) {
if ([EnumToken.CommentTokenType, EnumToken.CDOCOMMTokenType, EnumToken.WhitespaceTokenType].includes(context.chi[i].typ)) {
continue;
}
return context.chi[i];
}
return null;
}
async function parseNode(results, context, stats, options, errors, src, map, rawTokens) {
let tokens = [];
for (const t of results) {
const node = getTokenType(t.token, t.hint);
map.set(node, t.position);
tokens.push(node);
}
let i;
let loc;
for (i = 0; i < tokens.length; i++) {
if (tokens[i].typ == EnumToken.CommentTokenType || tokens[i].typ == EnumToken.CDOCOMMTokenType) {
const position = map.get(tokens[i]);
if (tokens[i].typ == EnumToken.CDOCOMMTokenType && context.typ != EnumToken.StyleSheetNodeType) {
errors.push({
action: 'drop',
message: `CDOCOMM not allowed here ${JSON.stringify(tokens[i], null, 1)}`,
location: { src, ...position }
});
continue;
}
loc = {
sta: position,
src
};
// @ts-ignore
context.chi.push(tokens[i]);
if (options.sourcemap) {
tokens[i].loc = loc;
}
}
else if (tokens[i].typ != EnumToken.WhitespaceTokenType) {
break;
}
}
tokens = tokens.slice(i);
if (tokens.length == 0) {
return null;
}
let delim = tokens.at(-1);
if (delim.typ == EnumToken.SemiColonTokenType || delim.typ == EnumToken.BlockStartTokenType || delim.typ == EnumToken.BlockEndTokenType) {
tokens.pop();
}
else {
delim = { typ: EnumToken.SemiColonTokenType };
}
// @ts-ignore
while ([EnumToken.WhitespaceTokenType, EnumToken.BadStringTokenType, EnumToken.BadCommentTokenType].includes(tokens.at(-1)?.typ)) {
tokens.pop();
}
if (tokens.length == 0) {
return null;
}
if (tokens[0]?.typ == EnumToken.AtRuleTokenType) {
const atRule = tokens.shift();
const position = map.get(atRule);
// @ts-ignore
while ([EnumToken.WhitespaceTokenType].includes(tokens[0]?.typ)) {
tokens.shift();
}
if (atRule.val == 'import') {
// only @charset and @layer are accepted before @import
// @ts-ignore
if (context.chi.length > 0) {
// @ts-ignore
let i = context.chi.length;
while (i--) {
// @ts-ignore
const type = context.chi[i].typ;
if (type == EnumToken.CommentNodeType) {
continue;
}
if (type != EnumToken.AtRuleNodeType) {
// @ts-ignore
if (!(type == EnumToken.InvalidAtRuleTokenType &&
// @ts-ignore
['charset', 'layer', 'import'].includes(context.chi[i].nam))) {
errors.push({ action: 'drop', message: 'invalid @import', location: { src, ...position } });
return null;
}
}
// @ts-ignore
const name = context.chi[i].nam;
if (name != 'charset' && name != 'import' && name != 'layer') {
errors.push({ action: 'drop', message: 'invalid @import', location: { src, ...position } });
return null;
}
break;
}
}
// @ts-ignore
if (tokens[0]?.typ != EnumToken.StringTokenType && tokens[0]?.typ != EnumToken.UrlFunctionTokenType) {
errors.push({
action: 'drop',
message: 'doParse: invalid @import',
location: { src, ...position }
});
return null;
}
// @ts-ignore
if (tokens[0].typ == EnumToken.UrlFunctionTokenType && tokens[1]?.typ != EnumToken.UrlTokenTokenType && tokens[1]?.typ != EnumToken.StringTokenType) {
errors.push({
action: 'drop',
message: 'doParse: invalid @import',
location: { src, ...position }
});
return null;
}
}
if (atRule.val == 'import') {
// @ts-ignore
if (tokens[0].typ == EnumToken.UrlFunctionTokenType) {
if (tokens[1].typ == EnumToken.UrlTokenTokenType || tokens[1].typ == EnumToken.StringTokenType) {
tokens.shift();
if (tokens[0]?.typ == EnumToken.UrlTokenTokenType) {
// @ts-ignore
tokens[0].typ = EnumToken.StringTokenType;
// @ts-ignore
tokens[0].val = `"${tokens[0].val}"`;
}
// @ts-ignore
while (tokens[1]?.typ == EnumToken.WhitespaceTokenType || tokens[1]?.typ == EnumToken.CommentTokenType) {
tokens.splice(1, 1);
}
// @ts-ignore
if (tokens[1]?.typ == EnumToken.EndParensTokenType) {
tokens.splice(1, 1);
}
}
}
// @ts-ignore
if (tokens[0].typ == EnumToken.StringTokenType) {
if (options.resolveImport) {
const url = tokens[0].val.slice(1, -1);
try {
// @ts-ignore
const root = await options.load(url, options.src).then((src) => {
return doParse(src, Object.assign({}, options, {
minify: false,
setParent: false,
// @ts-ignore
src: options.resolve(url, options.src).absolute
}));
});
stats.importedBytesIn += root.stats.bytesIn;
if (root.ast.chi.length > 0) {
// @todo - filter charset, layer and scope
// @ts-ignore
context.chi.push(...root.ast.chi);
}
if (root.errors.length > 0) {
errors.push(...root.errors);
}
return null;
}
catch (error) {
// @ts-ignore
errors.push({ action: 'ignore', message: 'doParse: ' + error.message, error });
}
}
}
}
// https://www.w3.org/TR/css-nesting-1/#conditionals
// allowed nesting at-rules
// there must be a top level rule in the stack
if (atRule.val == 'charset') {
let spaces = 0;
// https://developer.mozilla.org/en-US/docs/Web/CSS/@charset
for (let k = 1; k < rawTokens.length; k++) {
if (rawTokens[k].hint == EnumToken.WhitespaceTokenType) {
spaces += rawTokens[k].len;
continue;
}
if (rawTokens[k].hint == EnumToken.CommentTokenType) {
continue;
}
if (rawTokens[k].hint == EnumToken.CDOCOMMTokenType) {
continue;
}
if (spaces > 1) {
errors.push({
action: 'drop',
message: '@charset must have only one space',
// @ts-ignore
location: { src, ...(map.get(atRule) ?? position) }
});
return null;
}
if (rawTokens[k].hint != EnumToken.StringTokenType || rawTokens[k].token[0] != '"') {
errors.push({
action: 'drop',
message: '@charset expects a "<charset>"',
// @ts-ignore
location: { src, ...(map.get(atRule) ?? position) }
});
return null;
}
break;
}
if (options.removeCharset) {
return null;
}
}
const t = parseAtRulePrelude(parseTokens(tokens, { minify: options.minify }), atRule);
const raw = t.reduce((acc, curr) => {
acc.push(renderToken(curr, { removeComments: true }));
return acc;
}, []);
const node = {
typ: EnumToken.AtRuleNodeType,
nam: renderToken(atRule, { removeComments: true }),
// tokens: t,
val: raw.join('')
};
Object.defineProperties(node, {
tokens: { ...definedPropertySettings, enumerable: false, value: tokens.slice() },
raw: { ...definedPropertySettings, value: raw }
});
if (delim.typ == EnumToken.BlockStartTokenType) {
node.chi = [];
}
loc = {
sta: position,
src
};
if (options.sourcemap) {
node.loc = loc;
}
if (options.validation) {
let isValid = true;
if (node.nam == 'else') {
const prev = getLastNode(context);
if (prev != null && prev.typ == EnumToken.AtRuleNodeType && ['when', 'else'].includes(prev.nam)) {
if (prev.nam == 'else') {
isValid = Array.isArray(prev.tokens) && prev.tokens.length > 0;
}
}
else {
isValid = false;
}
}
const valid = isValid ? validateAtRule(node, options, context) : {
valid: ValidationLevel.Drop,
node,
syntax: '@' + node.nam,
error: '@' + node.nam + ' not allowed here'};
if (valid.valid == ValidationLevel.Drop) {
errors.push({
action: 'drop',
message: valid.error + ' - "' + tokens.reduce((acc, curr) => acc + renderToken(curr, { minify: false }), '') + '"',
// @ts-ignore
location: { src, ...(map.get(valid.node) ?? position) }
});
// @ts-ignore
node.typ = EnumToken.InvalidAtRuleTokenType;
}
else {
node.val = node.tokens.reduce((acc, curr) => acc + renderToken(curr, { minify: false, removeComments: true }), '');
}
}
// @ts-ignore
context.chi.push(node);
Object.defineProperty(node, 'parent', { ...definedPropertySettings, value: context });
return delim.typ == EnumToken.BlockStartTokenType ? node : null;
}
else {
// rule
if (delim.typ == EnumToken.BlockStartTokenType) {
const position = map.get(tokens[0]);
const uniq = new Map;
parseTokens(tokens, { minify: true }).reduce((acc, curr, index, array) => {
if (curr.typ == EnumToken.CommentTokenType) {
return acc;
}
if (curr.typ == EnumToken.WhitespaceTokenType) {
if (trimWhiteSpace.includes(array[index - 1]?.typ) ||
trimWhiteSpace.includes(array[index + 1]?.typ) ||
combinators.includes(array[index - 1]?.val) ||
combinators.includes(array[index + 1]?.val)) {
return acc;
}
}
let t = renderToken(curr, { minify: false });
if (t == ',') {
acc.push([]);
// uniqTokens.push([]);
}
else {
acc[acc.length - 1].push(t);
// uniqTokens[uniqTokens.length - 1].push(curr);
}
return acc;
}, [[]]).reduce((acc, curr) => {
let i = 0;
for (; i < curr.length; i++) {
if (i + 1 < curr.length && curr[i] == '*') {
if (curr[i] == '*') {
let index = curr[i + 1] == ' ' ? 2 : 1;
if (!['>', '~', '+'].includes(curr[index])) {
curr.splice(i, index);
}
}
}
}
acc.set(curr.join(''), curr);
return acc;
}, uniq);
const ruleType = context.typ == EnumToken.AtRuleNodeType && context.nam == 'keyframes' ? EnumToken.KeyFrameRuleNodeType : EnumToken.RuleNodeType;
if (ruleType == EnumToken.RuleNodeType) {
parseSelector(tokens);
if (options.validation) {
// @ts-ignore
const valid = validateSelector(tokens, options, context);
if (valid.valid != ValidationLevel.Valid) {
const node = {
typ: EnumToken.InvalidRuleTokenType,
// @ts-ignore
sel: tokens.reduce((acc, curr) => acc + renderToken(curr, { minify: false }), ''),
chi: []
};
errors.push({
action: 'drop',
message: valid.error + ' - "' + tokens.reduce((acc, curr) => acc + renderToken(curr, { minify: false }), '') + '"',
// @ts-ignore
location: { src, ...(map.get(valid.node) ?? position) }
});
// @ts-ignore
context.chi.push(node);
Object.defineProperty(node, 'parent', { ...definedPropertySettings, value: context });
return node;
}
}
}
const node = {
typ: ruleType,
sel: [...uniq.keys()].join(','),
chi: []
};
Object.defineProperty(node, 'tokens', {
...definedPropertySettings,
enumerable: false,
value: tokens.slice()
});
let raw = [...uniq.values()];
Object.defineProperty(node, 'raw', {
enumerable: false,
configurable: true,
writable: true,
value: raw
});
loc = {
sta: position,
src
};
if (options.sourcemap) {
node.loc = loc;
}
// @ts-ignore
context.chi.push(node);
Object.defineProperty(node, 'parent', { ...definedPropertySettings, value: context });
return node;
}
else {
let name = null;
let value = null;
for (let i = 0; i < tokens.length; i++) {
if (tokens[i].typ == EnumToken.CommentTokenType) {
continue;
}
if (name == null && [EnumToken.IdenTokenType, EnumToken.DashedIdenTokenType].includes(tokens[i].typ)) {
name = tokens.slice(0, i + 1);
}
else if (name != null && funcLike.concat([
EnumToken.LiteralTokenType,
EnumToken.IdenTokenType, EnumToken.DashedIdenTokenType,
EnumToken.PseudoClassTokenType, EnumToken.PseudoClassFuncTokenType
]).includes(tokens[i].typ)) {
if (tokens[i].val.charAt(0) == ':') {
Object.assign(tokens[i], getTokenType(tokens[i].val.slice(1)));
}
if ('chi' in tokens[i]) {
tokens[i].typ = EnumToken.FunctionTokenType;
}
value = parseTokens(tokens.slice(i), {
parseColor: options.parseColor,
src: options.src,
resolveUrls: options.resolveUrls,
resolve: options.resolve,
cwd: options.cwd
});
break;
}
if (tokens[i].typ == EnumToken.ColonTokenType) {
name = tokens.slice(0, i);
value = parseTokens(tokens.slice(i + 1), {
parseColor: options.parseColor,
src: options.src,
resolveUrls: options.resolveUrls,
resolve: options.resolve,
cwd: options.cwd
});
break;
}
}
if (name == null) {
name = tokens;
}
const position = map.get(name[0]);
if (name.length > 0) {
for (let i = 1; i < name.length; i++) {
if (name[i].typ != EnumToken.WhitespaceTokenType && name[i].typ != EnumToken.CommentTokenType) {
errors.push({
action: 'drop',
message: 'doParse: invalid declaration',
location: { src, ...position }
});
return null;
}
}
}
if (value == null || value.length == 0) {
errors.push({
action: 'drop',
message: 'doParse: invalid declaration',
location: { src, ...position }
});
return null;
}
const node = {
typ: EnumToken.DeclarationNodeType,
// @ts-ignore
nam: renderToken(name.shift(), { removeComments: true }),
// @ts-ignore
val: value
};
const result = parseDeclarationNode(node, errors, src, position);
if (result != null) {
// if (options.validation) {
//
// const valid: ValidationResult = validateDeclaration(result, options, context);
//
// // console.error({valid});
//
// if (valid.valid == ValidationLevel.Drop) {
//
// errors.push({
// action: 'drop',
// message: valid.error + ' - "' + tokens.reduce((acc, curr) => acc + renderToken(curr, {minify: false}), '') + '"',
// // @ts-ignore
// location: {src, ...(map.get(valid.node) ?? position)}
// });
//
// return null;
// }
// }
// @ts-ignore
context.chi.push(result);
Object.defineProperty(result, 'parent', { ...definedPropertySettings, value: context });
}
return null;
}
}
}
/**
* parse at-rule prelude
* @param tokens
* @param atRule
*/
function parseAtRulePrelude(tokens, atRule) {
// @ts-ignore
for (const { value, parent } of walkValues(tokens, null, null, true)) {
if (value.typ == EnumToken.CommentTokenType ||
value.typ == EnumToken.WhitespaceTokenType ||
value.typ == EnumToken.CommaTokenType) {
continue;
}
if (atRule.val == 'page' && value.typ == EnumToken.PseudoClassTokenType) {
if ([':left', ':right', ':first', ':blank'].includes(value.val)) {
// @ts-ignore
value.typ = EnumToken.PseudoPageTokenType;
}
}
if (atRule.val == 'layer') {
if (parent == null && value.typ == EnumToken.LiteralTokenType) {
if (value.val.charAt(0) == '.') {
if (isIdent(value.val.slice(1))) {
// @ts-ignore
value.typ = EnumToken.ClassSelectorTokenType;
}
}
}
}
if (value.typ == EnumToken.IdenTokenType) {
if (parent == null && mediaTypes.some((t) => {
if (value.val.localeCompare(t, 'en', { sensitivity: 'base' }) == 0) {
// @ts-ignore
value.typ = EnumToken.MediaFeatureTokenType;
return true;
}
return false;
})) {
continue;
}
if (value.typ == EnumToken.IdenTokenType && 'and'.localeCompare(value.val, 'en', { sensitivity: 'base' }) == 0) {
// @ts-ignore
value.typ = EnumToken.MediaFeatureAndTokenType;
continue;
}
if (value.typ == EnumToken.IdenTokenType && 'or'.localeCompare(value.val, 'en', { sensitivity: 'base' }) == 0) {
// @ts-ignore
value.typ = EnumToken.MediaFeatureOrTokenType;
continue;
}
if (value.typ == EnumToken.IdenTokenType &&
['not', 'only'].some((t) => t.localeCompare(value.val, 'en', { sensitivity: 'base' }) == 0)) {
// @ts-ignore
const array = parent?.chi ?? tokens;
const startIndex = array.indexOf(value);
let index = startIndex + 1;
if (index == 0) {
continue;
}
while (index < array.length && [EnumToken.CommentTokenType, EnumToken.WhitespaceTokenType].includes(array[index].typ)) {
index++;
}
if (array[index] == null || array[index].typ == EnumToken.CommaTokenType) {
continue;
}
Object.assign(array[startIndex], {
typ: value.val.toLowerCase() == 'not' ? EnumToken.MediaFeatureNotTokenType : EnumToken.MediaFeatureOnlyTokenType,
val: array[index]
});
array.splice(startIndex + 1, index - startIndex);
continue;
}
}
if (value.typ == EnumToken.ParensTokenType || (value.typ == EnumToken.FunctionTokenType && ['media', 'supports', 'style', 'scroll-state'].includes(value.val))) {
// @todo parse range and declarations
// parseDeclaration(parent.chi);
let i;
let nameIndex = -1;
let valueIndex = -1;
const dashedIdent = value.typ == EnumToken.FunctionTokenType && value.val == 'style';
for (let i = 0; i < value.chi.length; i++) {
if (value.chi[i].typ == EnumToken.CommentTokenType || value.chi[i].typ == EnumToken.WhitespaceTokenType) {
continue;
}
if ((dashedIdent && value.chi[i].typ == EnumToken.DashedIdenTokenType) || value.chi[i].typ == EnumToken.IdenTokenType || value.chi[i].typ == EnumToken.FunctionTokenType || value.chi[i].typ == EnumToken.ColorTokenType) {
nameIndex = i;
}
break;
}
if (nameIndex == -1) {
continue;
}
for (let i = nameIndex + 1; i < value.chi.length; i++) {
if (value.chi[i].typ == EnumToken.CommentTokenType || value.chi[i].typ == EnumToken.WhitespaceTokenType) {
continue;
}
valueIndex = i;
break;
}
if (valueIndex == -1) {
// @ts-ignore
// value.chi[nameIndex].typ = EnumToken.MediaFeatureTokenType;
continue;
// return tokens;
}
for (i = nameIndex + 1; i < value.chi.length; i++) {
if ([
EnumToken.GtTokenType, EnumToken.LtTokenType,
EnumToken.GteTokenType, EnumToken.LteTokenType,
EnumToken.ColonTokenType
].includes(value.chi[valueIndex].typ)) {
const val = value.chi.splice(valueIndex, 1)[0];
const node = value.chi.splice(nameIndex, 1)[0];
// 'background'
// @ts-ignore
if (node.typ == EnumToken.ColorTokenType && node.kin == 'dpsys') {
// @ts-ignore
delete node.kin;
node.typ = EnumToken.IdenTokenType;
}
while (value.chi[0]?.typ == EnumToken.WhitespaceTokenType) {
value.chi.shift();
}
const t = [{
typ: EnumToken.MediaQueryConditionTokenType,
l: node,
op: { typ: val.typ },
r: value.chi.slice()
}];
value.chi.length = 0;
value.chi.push(...t);
}
}
}
}
return tokens;
}
/**
* parse selector
* @param tokens
*/
function parseSelector(tokens) {
for (const { value, previousValue, nextValue, parent } of walkValues(tokens)) {
if (value.typ == EnumToken.CommentTokenType ||
value.typ == EnumToken.WhitespaceTokenType ||
value.typ == EnumToken.CommaTokenType ||
value.typ == EnumToken.IdenTokenType ||
value.typ == EnumToken.HashTokenType) {
continue;
}
if (parent == null) {
if (value.typ == EnumToken.GtTokenType) {
// @ts-ignore
value.typ = EnumToken.ChildCombinatorTokenType;
}
// @ts-ignore
else if (value.typ == EnumToken.WhitespaceTokenType) {
if (nextValue != null && nextValue.typ == EnumToken.LiteralTokenType) {
if (['>', '+', '~'].includes(nextValue.val)) {
switch (value.val) {
case '>':
// @ts-ignore
nextValue.typ = EnumToken.ChildCombinatorTokenType;
break;
case '+':
// @ts-ignore
nextValue.typ = EnumToken.NextSiblingCombinatorTokenType;
break;
case '~':
// @ts-ignore
nextValue.typ = EnumToken.SubsequentSiblingCombinatorTokenType;
break;
}
// @ts-ignore
delete nextValue.val;
continue;
}
}
if (previousValue != null && [
EnumToken.ChildCombinatorTokenType,
EnumToken.DescendantCombinatorTokenType,
EnumToken.NextSiblingCombinatorTokenType,
EnumToken.SubsequentSiblingCombinatorTokenType,
EnumToken.ColumnCombinatorTokenType,
EnumToken.NameSpaceAttributeTokenType,
EnumToken.CommaTokenType
].includes(previousValue.typ)) {
continue;
}
// @ts-ignore
value.typ = EnumToken.DescendantCombinatorTokenType;
}
else if (value.typ == EnumToken.LiteralTokenType) {
if (value.val.charAt(0) == '&') {
// @ts-ignore
value.typ = EnumToken.NestingSelectorTokenType;
// @ts-ignore
delete value.val;
}
else if (value.val.charAt(0) == '.') {
if (!isIdent(value.val.slice(1))) {
// @ts-ignore
value.typ = EnumToken.InvalidClassSelectorTokenType;
}
else {
// @ts-ignore
value.typ = EnumToken.ClassSelectorTokenType;
}
}
// @ts-ignore
if (value.typ == EnumToken.DelimTokenType) {
// @ts-ignore
value.typ = EnumToken.NextSiblingCombinatorTokenType;
}
else if (['*', '>', '+', '~'].includes(value.val)) {
switch (value.val) {
case '*':
// @ts-ignore
value.typ = EnumToken.UniversalSelectorTokenType;
break;
case '>':
// @ts-ignore
value.typ = EnumToken.ChildCombinatorTokenType;
break;
case '+':
// @ts-ignore
value.typ = EnumToken.NextSiblingCombinatorTokenType;
break;
case '~':
// @ts-ignore
value.typ = EnumToken.SubsequentSiblingCombinatorTokenType;
break;
}
// @ts-ignore
// @ts-ignore
delete value.val;
}
}
else if (value.typ == EnumToken.ColorTokenType) {
if (value.kin == 'lit' || value.kin == 'hex' || value.kin == 'sys' || value.kin == 'dpsys') {
if (value.kin == 'hex') {
if (!isIdent(value.val.slice(1))) {
continue;
}
// @ts-ignore
value.typ = EnumToken.HashTokenType;
}
else {
// @ts-ignore
value.typ = EnumToken.IdenTokenType;
}
// @ts-ignore
delete value.kin;
}
}
}
}
let i = 0;
const combinators = [
EnumToken.ChildCombinatorTokenType,
EnumToken.NextSiblingCombinatorTokenType,
EnumToken.SubsequentSiblingCombinatorTokenType
];
for (; i < tokens.length; i++) {
if (combinators.includes(tokens[i].typ)) {
if (i + 1 < tokens.length && [EnumToken.WhitespaceTokenType, EnumToken.DescendantCombinatorTokenType].includes(tokens[i + 1].typ)) {
tokens.splice(i + 1, 1);
}
if (i > 0 && [EnumToken.WhitespaceTokenType, EnumToken.DescendantCombinatorTokenType].includes(tokens[i - 1].typ)) {
tokens.splice(i - 1, 1);
i--;
continue;
}
}
if (tokens[i].typ == EnumToken.WhitespaceTokenType) {
tokens[i].typ = EnumToken.DescendantCombinatorTokenType;
}
}
return tokens;
}
// export async function parseDeclarations(src: string, options: ParserOptions = {}): Promise<AstDeclaration[]> {
//
// return doParse(`.x{${src}`, options).then((result: ParseResult) => <AstDeclaration[]>(<AstRule>result.ast.chi[0]).chi.filter(t => t.typ == EnumToken.DeclarationNodeType));
// }
/**
* parse string
* @param src
* @param options
*/
function parseString(src, options = { location: false }) {
return parseTokens([...tokenize(src)].map(t => {
const token = getTokenType(t.token, t.hint);
if (options.location) {
Object.assign(token, { loc: t.position });
}
return token;
}));
}
function getTokenType(val, hint) {
if (hint != null) {
return enumTokenHints.has(hint) ? { typ: hint } : { typ: hint, val };
}
if (val == ' ') {
return { typ: EnumToken.WhitespaceTokenType };
}
if (val == ';') {
return { typ: EnumToken.SemiColonTokenType };
}
if (val == '{') {
return { typ: EnumToken.BlockStartTokenType };
}
if (val == '}') {
return { typ: EnumToken.BlockEndTokenType };
}
if (val == '[') {
return { typ: EnumToken.AttrStartTokenType };
}
if (val == ']') {
return { typ: EnumToken.AttrEndTokenType };
}
if (val == ':') {
return { typ: EnumToken.ColonTokenType };
}
if (val == ')') {
return { typ: EnumToken.EndParensTokenType };
}
if (val == '(') {
return { typ: EnumToken.StartParensTokenType };
}
if (val == '=') {
return { typ: EnumToken.DelimTokenType };
}
if (val == ';') {
return { typ: EnumToken.SemiColonTokenType };
}
if (val == ',') {
return { typ: EnumToken.CommaTokenType };
}
if (val == '<') {
return { typ: EnumToken.LtTokenType };
}
if (val == '>') {
return { typ: EnumToken.GtTokenType };
}
if (isPseudo(val)) {
return val.endsWith('(') ? {
typ: EnumToken.PseudoClassFuncTokenType,
val: val.slice(0, -1),
chi: []
}
: (
// https://www.w3.org/TR/selectors-4/#single-colon-pseudos
val.startsWith('::') || pseudoElements.includes(val) ? {
typ: EnumToken.PseudoElementTokenType,
val
} :
{
typ: EnumToken.PseudoClassTokenType,
val
});
}
if (isAtKeyword(val)) {
return {
typ: EnumToken.AtRuleTokenType,
val: val.slice(1)
};
}
if (isFunction(val)) {
val = val.slice(0, -1);
if (val == 'url') {
return {
typ: EnumToken.UrlFunctionTokenType,
val,
chi: []
};
}
if (['linear-gradient', 'radial-gradient', 'repeating-linear-gradient', 'repeating-radial-gradient', 'conic-gradient', 'image', 'image-set', 'element', 'cross-fade', 'paint'].includes(val)) {
return {
typ: EnumToken.ImageFunctionTokenType,
val,
chi: []
};
}
if (['ease', 'ease-in', 'ease-out', 'ease-in-out', 'linear', 'step-start', 'step-end', 'steps', 'cubic-bezier'].includes(val)) {
return {
typ: EnumToken.TimingFunctionTokenType,
val,
chi: []
};
}
if (['view', 'scroll'].includes(val)) {
return {
typ: EnumToken.TimelineFunctionTokenType,
val,
chi: []
};
}
return {
typ: EnumToken.FunctionTokenType,
val,
chi: []
};
}
if (isNumber(val)) {
return {
typ: EnumToken.NumberTokenType,
val
};
}
if (isPercentage(val)) {
return {
typ: EnumToken.PercentageTokenType,
val: val.slice(0, -1)
};
}
if (isFlex(val)) {
return {
typ: EnumToken.FlexTokenType,
val: val.slice(0, -2)
};
}
if (isDimension(val)) {
return parseDimension(val);
}
const v = val.toLowerCase();
if (v == 'currentcolor' || v == 'transparent' || v in COLORS_NAMES) {
return {
typ: EnumToken.ColorTokenType,
val: v,
kin: 'lit'
};
}
if (isIdent(val)) {
if (systemColors.has(val.toLowerCase())) {
return {
typ: EnumToken.ColorTokenType,
val,
kin: 'sys'
};
}
if (deprecatedSystemColors.has(val.toLowerCase())) {
return {
typ: EnumToken.ColorTokenType,
val,
kin: 'dpsys'
};
}
return {
typ: val.startsWith('--') ? EnumToken.DashedIdenTokenType : EnumToken.IdenTokenType,
val
};
}
if (val.charAt(0) == '#' && isHexColor(val)) {
return {
typ: EnumToken.ColorTokenType,
val,
kin: 'hex'
};
}
if (val.charAt(0) == '#' && isHash(val)) {
return {
typ: EnumToken.HashTokenType,
val
};
}
if ('"\''.includes(val.charAt(0))) {
return {
typ: EnumToken.UnclosedStringTokenType,
val
};
}
return {
typ: EnumToken.LiteralTokenType,
val
};
}
/**
* parse token list
* @param tokens
* @param options
*/
function parseTokens(tokens, options = {}) {
for (let i = 0; i < tokens.length; i++) {
const t = tokens[i];
if (t.typ == EnumToken.PseudoClassFuncTokenType) {
if (t.val.slice(1) in webkitPseudoAliasMap) {
t.val = ':' + webkitPseudoAliasMap[t.val.slice(1)];
}
}
else if (t.typ == EnumToken.PseudoClassTokenType) {
if (t.val.slice(1) in webkitPseudoAliasMap) {
t.val = ':' + webkitPseudoAliasMap[t.val.slice(1)];
}
}
if (t.typ == EnumToken.WhitespaceTokenType && ((i == 0 ||
i + 1 == tokens.length ||
[EnumToken.CommaTokenType, EnumToken.GteTokenType, EnumToken.LteTokenType, EnumToken.ColumnCombinatorTokenType].includes(tokens[i + 1].typ)) ||
(i > 0 && trimWhiteSpace.includes(tokens[i - 1].typ)))) {
tokens.splice(i--, 1);
continue;
}
if (t.typ == EnumToken.ColonTokenType) {
const typ = tokens[i + 1]?.typ;
if (typ != null) {
if (typ == EnumToken.FunctionTokenType) {
tokens[i + 1].val = ':' + (tokens[i + 1].val in webkitPseudoAliasMap ? webkitPseudoAliasMap[tokens[i + 1].val] : tokens[i + 1].val);
tokens[i + 1].typ = EnumToken.PseudoClassFuncTokenType;
}
else if (typ == EnumToken.IdenTokenType) {
if (tokens[i + 1].val in webkitPseudoAliasMap) {
tokens[i + 1].val = webkitPseudoAliasMap[tokens[i + 1].val];
}
tokens[i + 1].val = ':' + tokens[i + 1].val;
tokens[i + 1].typ = EnumToken.PseudoClassTokenType;
}
if (typ == EnumToken.FunctionTokenType || typ == EnumToken.IdenTokenType) {
tokens.splice(i, 1);
i--;
}
}
continue;
}
if (t.typ == EnumToken.AttrStartTokenType) {
let k = i;
let inAttr = 1;
while (++k < tokens.length) {
if (tokens[k].typ == EnumToken.AttrEndTokenType) {
inAttr--;
}
else if (tokens[k].typ == EnumToken.AttrStartTokenType) {
inAttr++;
}
if (inAttr == 0) {
break;
}
}
const