@tbela99/css-parser
Version:
CSS parser, minifier and validator for node and the browser
1,164 lines (1,162 loc) • 127 kB
JavaScript
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,