Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@tokey/css-selector-parser

Package Overview
Dependencies
Maintainers
3
Versions
8
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@tokey/css-selector-parser - npm Package Compare versions

Comparing version 0.5.1 to 0.6.0

3

dist/ast-tools/compound.d.ts

@@ -1,4 +0,5 @@

import type { SelectorList, Selector, ImmutableSelector, ImmutableSelectorList } from "../ast-types";
import type { SelectorList, Selector, ImmutableSelector, ImmutableSelectorList } from '../ast-types';
export interface GroupCompoundOptions {
splitPseudoElements?: boolean;
deep?: boolean;
}

@@ -5,0 +6,0 @@ export declare function groupCompoundSelectors<AST extends Selector>(input: AST, options?: GroupCompoundOptions): Selector;

@@ -14,2 +14,13 @@ "use strict";

// second level: (parents.length === 1)
if ((options === null || options === void 0 ? void 0 : options.deep) && `nodes` in node) {
// compound nested selectors
/* This `nodes` type is hard since it's internal we use any[] here. sorry */
const nodes = [];
for (const nested of node.nodes) {
nodes.push(nested.type === `selector`
? groupCompoundSelectors(nested, options)
: nested);
}
node = { ...node, nodes };
}
context.handleNode(node);

@@ -24,3 +35,3 @@ // don't go deeper - shallow group

exports.groupCompoundSelectors = groupCompoundSelectors;
function createCompoundContext({ splitPseudoElements = true, } = {}) {
function createCompoundContext({ splitPseudoElements = true } = {}) {
const output = [];

@@ -71,4 +82,3 @@ let lastSelector;

if (lastCompoundInitialPart) {
lastCompound.invalid =
node.type === `universal` || node.type === `type`;
lastCompound.invalid = node.type === `universal` || node.type === `type`;
}

@@ -75,0 +85,0 @@ lastCompoundInitialPart = node;

@@ -1,2 +0,2 @@

import type { ImmutableSelectorNode } from "../ast-types";
import type { ImmutableSelectorNode } from '../ast-types';
export declare type Specificity = [

@@ -3,0 +3,0 @@ inlineLevel: number,

@@ -43,6 +43,6 @@ "use strict";

},
"nth-child": pseudoClassPlusMostSpecificInnerSelector,
"nth-last-child": pseudoClassPlusMostSpecificInnerSelector,
"nth-of-type": pseudoClassPlusMostSpecificInnerSelector,
"nth-last-of-type": pseudoClassPlusMostSpecificInnerSelector,
'nth-child': pseudoClassPlusMostSpecificInnerSelector,
'nth-last-child': pseudoClassPlusMostSpecificInnerSelector,
'nth-of-type': pseudoClassPlusMostSpecificInnerSelector,
'nth-last-of-type': pseudoClassPlusMostSpecificInnerSelector,
};

@@ -49,0 +49,0 @@ function pseudoClassPlusMostSpecificInnerSelector(node, result) {

@@ -1,5 +0,5 @@

import type { SelectorNode, SelectorList, ImmutableSelectorNode, ImmutableSelectorList } from "../ast-types";
import type { SelectorNode, SelectorList, ImmutableSelectorNode, ImmutableSelectorList } from '../ast-types';
export interface WalkOptions {
visitList?: SelectorNode["type"][];
ignoreList?: SelectorNode["type"][];
visitList?: SelectorNode['type'][];
ignoreList?: SelectorNode['type'][];
}

@@ -6,0 +6,0 @@ export declare type WalkVisitor<AST extends SelectorNode | ImmutableSelectorNode> = (node: AST, index: number, nodes: AST[], parents: AST[]) => number | undefined | void;

@@ -8,3 +8,5 @@ "use strict";

// set initial top nodes to traverse
const toVisit = Array.isArray(topNode) ? [...topNode] : [topNode];
const toVisit = Array.isArray(topNode)
? [...topNode]
: [topNode];
// initiate context

@@ -71,5 +73,3 @@ const context = createWalkContext(topNode);

const currentParent = currentParents[currentParents.length - 1];
context.nodesInSelector = currentParent
? currentParent.nodes
: topNode;
context.nodesInSelector = currentParent ? currentParent.nodes : topNode;
},

@@ -76,0 +76,0 @@ next() {

@@ -1,4 +0,4 @@

import type { Immutable } from "./types";
import type { Token } from "@tokey/core";
export interface Selector extends Omit<Token<"selector">, "value"> {
import type { Immutable } from './types';
import type { Token } from '@tokey/core';
export interface Selector extends Omit<Token<'selector'>, 'value'> {
nodes: SelectorNodes;

@@ -9,7 +9,7 @@ before: string;

export declare type NthSelectorList = [Nth, ...SelectorList];
export interface PseudoClass extends Token<"pseudo_class"> {
export interface PseudoClass extends Token<'pseudo_class'> {
nodes?: SelectorList | NthSelectorList;
colonComments: Comment[];
}
export interface PseudoElement extends Token<"pseudo_element"> {
export interface PseudoElement extends Token<'pseudo_element'> {
nodes?: SelectorList;

@@ -21,10 +21,10 @@ colonComments: {

}
export interface Class extends Token<"class"> {
export interface Class extends Token<'class'> {
nodes?: SelectorList;
dotComments: SelectorNodes;
}
export interface Id extends Token<"id"> {
export interface Id extends Token<'id'> {
nodes?: SelectorList;
}
export interface Attribute extends Token<"attribute"> {
export interface Attribute extends Token<'attribute'> {
nodes?: SelectorList;

@@ -36,14 +36,14 @@ }

afterComments: Comment[];
invalid?: "namespace" | "target" | "namespace,target" | "";
invalid?: 'namespace' | 'target' | 'namespace,target' | '';
}
export interface Type extends Token<"type"> {
export interface Type extends Token<'type'> {
namespace?: Namespace;
nodes?: SelectorList;
}
export interface Universal extends Token<"universal"> {
export interface Universal extends Token<'universal'> {
namespace?: Namespace;
nodes?: SelectorList;
}
export interface Combinator extends Token<"combinator"> {
combinator: "space" | "+" | "~" | ">";
export interface Combinator extends Token<'combinator'> {
combinator: 'space' | '+' | '~' | '>';
before: string;

@@ -53,8 +53,8 @@ after: string;

}
export interface Nesting extends Token<"nesting"> {
value: "&";
export interface Nesting extends Token<'nesting'> {
value: '&';
nodes?: SelectorList;
}
export declare type Invalid = Token<"invalid">;
export interface Comment extends Token<"comment"> {
export declare type Invalid = Token<'invalid'>;
export interface Comment extends Token<'comment'> {
before: string;

@@ -64,4 +64,4 @@ after: string;

export declare type CommentWithNoSpacing = Comment & {
before: "";
after: "";
before: '';
after: '';
};

@@ -73,8 +73,8 @@ export interface NthBase<PART extends string> extends Token<PART> {

}
export declare type NthStep = NthBase<"nth_step">;
export declare type NthOffset = NthBase<"nth_offset">;
export declare type NthDash = NthBase<"nth_dash">;
export declare type NthOf = NthBase<"nth_of">;
export declare type NthStep = NthBase<'nth_step'>;
export declare type NthOffset = NthBase<'nth_offset'>;
export declare type NthDash = NthBase<'nth_dash'>;
export declare type NthOf = NthBase<'nth_of'>;
export declare type NthNode = NthStep | NthOffset | NthDash | NthOf | Comment;
export interface Nth extends Omit<Token<"nth">, "value"> {
export interface Nth extends Omit<Token<'nth'>, 'value'> {
nodes: Array<NthNode>;

@@ -86,3 +86,3 @@ before: string;

export declare type SimpleSelector = Type | Universal | Attribute | Class | Id | PseudoClass;
export interface CompoundSelector extends Omit<Token<"compound_selector">, "value"> {
export interface CompoundSelector extends Omit<Token<'compound_selector'>, 'value'> {
nodes: Array<SimpleSelector | Nesting | CommentWithNoSpacing | Invalid | PseudoElement | CompoundSelector | Selector>;

@@ -89,0 +89,0 @@ before: string;

@@ -1,10 +0,10 @@

import type { CSSSelectorToken } from "./tokenizer";
import type { Combinator, Comment, Selector, Nth, NamespacedNode, SelectorList, SelectorNode } from "./ast-types";
import { Token } from "@tokey/core";
import type { CSSSelectorToken } from './tokenizer';
import type { Combinator, Comment, Selector, Nth, NamespacedNode, SelectorList, SelectorNode } from './ast-types';
import { Token } from '@tokey/core';
export declare function createEmptySelector(): Selector;
export declare function createEmptyNth(): Nth;
export declare function createCombinatorAst({ value, type, start, end, }: CSSSelectorToken & Token<"space" | "+" | ">" | "~">): Combinator;
export declare function createCommentAst({ value, start, end, }: CSSSelectorToken): Comment;
export declare function isCombinatorToken(token: CSSSelectorToken): token is Token<"space" | "+" | ">" | "~">;
export declare function isNamespacedToken(token: CSSSelectorToken): token is Token<"text" | "*">;
export declare function createCombinatorAst({ value, type, start, end, }: CSSSelectorToken & Token<'space' | '+' | '>' | '~'>): Combinator;
export declare function createCommentAst({ value, start, end }: CSSSelectorToken): Comment;
export declare function isCombinatorToken(token: CSSSelectorToken): token is Token<'space' | '+' | '>' | '~'>;
export declare function isNamespacedToken(token: CSSSelectorToken): token is Token<'text' | '*'>;
export declare function isNamespacedAst(token: SelectorNode): token is NamespacedNode;

@@ -11,0 +11,0 @@ export declare function ensureSelector(selectors: SelectorList, startToken: CSSSelectorToken): Selector;

@@ -8,7 +8,7 @@ "use strict";

return {
type: "selector",
type: 'selector',
start: -1,
end: -1,
before: "",
after: "",
before: '',
after: '',
nodes: [],

@@ -20,7 +20,7 @@ };

return {
type: "nth",
type: 'nth',
start: -1,
end: -1,
before: "",
after: "",
before: '',
after: '',
nodes: [],

@@ -43,3 +43,3 @@ };

exports.createCombinatorAst = createCombinatorAst;
function createCommentAst({ value, start, end, }) {
function createCommentAst({ value, start, end }) {
return {

@@ -57,6 +57,3 @@ type: `comment`,

function isCombinatorToken(token) {
return (token.type === "space" ||
token.type === "+" ||
token.type === ">" ||
token.type === "~");
return token.type === 'space' || token.type === '+' || token.type === '>' || token.type === '~';
}

@@ -90,5 +87,5 @@ exports.isCombinatorToken = isCombinatorToken;

// remove first space combinator and add to selector before
// (going between comment is not required for the start becuase they are taken care
// (going between comment is not required for the start because they are taken care
// of during parsing)
if ((firstNode === null || firstNode === void 0 ? void 0 : firstNode.type) === "combinator" && firstNode.combinator === "space") {
if ((firstNode === null || firstNode === void 0 ? void 0 : firstNode.type) === 'combinator' && firstNode.combinator === 'space') {
selector.nodes.shift();

@@ -98,7 +95,7 @@ selector.before += firstNode.before + firstNode.value + firstNode.after;

// remove any edge space combinators (last and between comments)
if (lastNode !== firstNode) {
if (lastNode && lastNode !== firstNode) {
let index = nodes.length - 1;
let current = lastNode;
let lastComment;
while ((current && current.type === `comment`) ||
while (current.type === `comment` ||
(current.type === `combinator` && current.combinator === `space`)) {

@@ -105,0 +102,0 @@ if (current.type === `combinator`) {

@@ -1,10 +0,10 @@

export { parseCssSelector } from "./selector-parser";
export type { ParseConfig } from "./selector-parser";
export * from "./ast-types";
export { stringifySelectorAst } from "./stringify";
export { walk } from "./ast-tools/walk";
export type { WalkOptions } from "./ast-tools/walk";
export { groupCompoundSelectors, splitCompoundSelectors, } from "./ast-tools/compound";
export { calcSpecificity, compareSpecificity } from "./ast-tools/specificity";
export type { Specificity } from "./ast-tools/specificity";
export { parseCssSelector } from './selector-parser';
export type { ParseConfig } from './selector-parser';
export * from './ast-types';
export { stringifySelectorAst } from './stringify';
export { walk } from './ast-tools/walk';
export type { WalkOptions } from './ast-tools/walk';
export { groupCompoundSelectors, splitCompoundSelectors } from './ast-tools/compound';
export { calcSpecificity, compareSpecificity } from './ast-tools/specificity';
export type { Specificity } from './ast-tools/specificity';
//# sourceMappingURL=index.d.ts.map
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {

@@ -6,0 +10,0 @@ if (k2 === undefined) k2 = k;

@@ -1,4 +0,4 @@

import type { CSSSelectorToken } from "./tokenizer";
import type { Nth } from "./ast-types";
import { Seeker } from "@tokey/core";
import type { CSSSelectorToken } from './tokenizer';
import type { Nth } from './ast-types';
import { Seeker } from '@tokey/core';
export declare class NthParser {

@@ -37,3 +37,3 @@ private selectorNode;

static nthStartExp: RegExp;
state: "step" | `dash` | `offset` | `of` | `selector`;
state: 'step' | `dash` | `offset` | `of` | `selector`;
private standaloneDash;

@@ -40,0 +40,0 @@ private ast;

@@ -26,5 +26,3 @@ "use strict";

// pickup 1 or more tokens for `5n` / `+5n` / `+5n-4` / `5`
const nextToken = type === `+` && this.s.peek().type === `text`
? this.s.next()
: undefined;
const nextToken = type === `+` && this.s.peek().type === `text` ? this.s.next() : undefined;
this.breakFirstChunk({

@@ -39,5 +37,3 @@ type: `text`,

case `dash`: {
const nextToken = type === `+` && this.s.peek().type === `text`
? this.s.next()
: undefined;
const nextToken = type === `+` && this.s.peek().type === `text` ? this.s.next() : undefined;
this.pushDash({

@@ -52,5 +48,3 @@ type: `text`,

case `offset`: {
const nextToken = type === `+` && this.s.peek().type === `text`
? this.s.next()
: undefined;
const nextToken = type === `+` && this.s.peek().type === `text` ? this.s.next() : undefined;
this.pushOffset({

@@ -114,5 +108,3 @@ type: `text`,

const offset = matchValidStart[2];
if (!offset &&
!step.match(/[nN]+$/) &&
step.match(NthParser.validOffset)) {
if (!offset && !step.match(/[nN]+$/) && step.match(NthParser.validOffset)) {
// no `n` - just offset

@@ -169,4 +161,3 @@ this.pushOffset(token);

};
isInvalid =
isInvalid !== undefined ? isInvalid : !value.match(NthParser.validStep);
isInvalid = isInvalid !== undefined ? isInvalid : !value.match(NthParser.validStep);
if (isInvalid) {

@@ -173,0 +164,0 @@ stepNode.invalid = true;

@@ -1,2 +0,2 @@

import type { SelectorList } from "./ast-types";
import type { SelectorList } from './ast-types';
export interface ParseConfig {

@@ -3,0 +3,0 @@ offset: number;

@@ -20,8 +20,8 @@ "use strict";

const ast = currentSelector.nodes;
if (token.type === ".") {
const comments = s.takeMany("multi-comment").map(helpers_1.createCommentAst);
const name = s.take("text");
if (token.type === '.') {
const comments = s.takeMany('multi-comment').map(helpers_1.createCommentAst);
const name = s.take('text');
ast.push({
type: "class",
value: (_a = name === null || name === void 0 ? void 0 : name.value) !== null && _a !== void 0 ? _a : "",
type: 'class',
value: (_a = name === null || name === void 0 ? void 0 : name.value) !== null && _a !== void 0 ? _a : '',
start: token.start,

@@ -32,12 +32,12 @@ end: (_d = (_b = name === null || name === void 0 ? void 0 : name.end) !== null && _b !== void 0 ? _b : (_c = (0, core_1.last)(comments)) === null || _c === void 0 ? void 0 : _c.end) !== null && _d !== void 0 ? _d : token.end,

}
else if (token.type === ":") {
const firstComments = s.takeMany("multi-comment").map(helpers_1.createCommentAst);
const type = s.take(":") || token;
else if (token.type === ':') {
const firstComments = s.takeMany('multi-comment').map(helpers_1.createCommentAst);
const type = s.take(':') || token;
const isClass = token === type;
if (isClass) {
const name = s.take("text");
const name = s.take('text');
const endToken = name || (0, core_1.last)(firstComments) || type;
ast.push({
type: "pseudo_class",
value: (_e = name === null || name === void 0 ? void 0 : name.value) !== null && _e !== void 0 ? _e : "",
type: 'pseudo_class',
value: (_e = name === null || name === void 0 ? void 0 : name.value) !== null && _e !== void 0 ? _e : '',
start: token.start,

@@ -49,8 +49,8 @@ end: (_f = name === null || name === void 0 ? void 0 : name.end) !== null && _f !== void 0 ? _f : endToken.end,

else {
const secondComments = s.takeMany("multi-comment").map(helpers_1.createCommentAst);
const name = s.take("text");
const secondComments = s.takeMany('multi-comment').map(helpers_1.createCommentAst);
const name = s.take('text');
const endToken = name || (0, core_1.last)(secondComments) || type;
ast.push({
type: "pseudo_element",
value: (_g = name === null || name === void 0 ? void 0 : name.value) !== null && _g !== void 0 ? _g : "",
type: 'pseudo_element',
value: (_g = name === null || name === void 0 ? void 0 : name.value) !== null && _g !== void 0 ? _g : '',
start: token.start,

@@ -62,12 +62,12 @@ end: (_h = name === null || name === void 0 ? void 0 : name.end) !== null && _h !== void 0 ? _h : endToken.end,

}
else if (token.type === "[") {
else if (token.type === '[') {
const block = s.run((token, ast) => {
ast.push(token);
return token.type !== "]";
return token.type !== ']';
}, [token], source);
const closed = ((_j = (0, core_1.last)(block)) === null || _j === void 0 ? void 0 : _j.type) === "]";
const closed = ((_j = (0, core_1.last)(block)) === null || _j === void 0 ? void 0 : _j.type) === ']';
if (closed) {
ast.push({
type: "attribute",
value: block.length > 2 ? (0, core_1.getText)(block, 1, block.length - 1, source) : "",
type: 'attribute',
value: block.length > 2 ? (0, core_1.getText)(block, 1, block.length - 1, source) : '',
start: token.start,

@@ -79,3 +79,3 @@ end: (_l = (_k = (0, core_1.last)(block)) === null || _k === void 0 ? void 0 : _k.end) !== null && _l !== void 0 ? _l : token.end,

ast.push({
type: "invalid",
type: 'invalid',
value: (0, core_1.getText)(block, undefined, undefined, source),

@@ -118,4 +118,3 @@ start: token.start,

}
else if (lastAst === lastCombinatorAst &&
lastAst.combinator === "space") {
else if (lastAst === lastCombinatorAst && lastAst.combinator === 'space') {
// combine next combinator into previous (space)

@@ -125,5 +124,3 @@ const nextCombinator = (0, helpers_1.createCombinatorAst)(next);

lastCombinatorAst.before +=
lastCombinatorAst.after +
lastCombinatorAst.value +
nextCombinator.before;
lastCombinatorAst.after + lastCombinatorAst.value + nextCombinator.before;
lastCombinatorAst.after = nextCombinator.after;

@@ -184,5 +181,5 @@ lastCombinatorAst.value = nextCombinator.value;

}
else if (token.type === "text") {
else if (token.type === 'text') {
ast.push({
type: "type",
type: 'type',
value: token.value,

@@ -193,7 +190,7 @@ start: token.start,

}
else if (token.type === "#") {
t = s.take("text");
else if (token.type === '#') {
t = s.take('text');
ast.push({
type: "id",
value: (_p = t === null || t === void 0 ? void 0 : t.value) !== null && _p !== void 0 ? _p : "",
type: 'id',
value: (_p = t === null || t === void 0 ? void 0 : t.value) !== null && _p !== void 0 ? _p : '',
start: token.start,

@@ -203,6 +200,6 @@ end: (_q = t === null || t === void 0 ? void 0 : t.end) !== null && _q !== void 0 ? _q : token.end,

}
else if (token.type === "*") {
else if (token.type === '*') {
ast.push({
type: "universal",
value: "*",
type: 'universal',
value: '*',
start: token.start,

@@ -212,3 +209,3 @@ end: token.end,

}
else if (token.type === "|") {
else if (token.type === '|') {
// search backwards compatible namespace in ast

@@ -292,5 +289,3 @@ let prevAst;

beforeComments: validNamespace ? beforeComments : [],
afterComments: validTarget
? potentialAfterComments.map(helpers_1.createCommentAst)
: [],
afterComments: validTarget ? potentialAfterComments.map(helpers_1.createCommentAst) : [],
};

@@ -308,3 +303,3 @@ nsAst.value = (target === null || target === void 0 ? void 0 : target.value) || ``;

}
else if (token.type === "(") {
else if (token.type === '(') {
const prev = (0, core_1.last)(ast);

@@ -342,3 +337,3 @@ const res = [];

var _a, _b;
if (token.type === ")") {
if (token.type === ')') {
const currentSelector = (0, core_1.last)(selectors);

@@ -355,13 +350,13 @@ if (currentSelector) {

if (!prev ||
"nodes" in prev ||
prev.type === "invalid" ||
prev.type === "combinator" ||
prev.type === "comment" ||
prev.type === "nth_step" ||
prev.type === "nth_dash" ||
prev.type === "nth_offset" ||
prev.type === "nth_of" ||
ended.type !== ")") {
'nodes' in prev ||
prev.type === 'invalid' ||
prev.type === 'combinator' ||
prev.type === 'comment' ||
prev.type === 'nth_step' ||
prev.type === 'nth_dash' ||
prev.type === 'nth_offset' ||
prev.type === 'nth_of' ||
ended.type !== ')') {
ast.push({
type: "invalid",
type: 'invalid',
value: (0, core_1.getText)([token, ended], undefined, undefined, source),

@@ -373,4 +368,4 @@ start: token.start,

else {
if (res.length) {
const lastSelector = (0, core_1.last)(res);
const lastSelector = (0, core_1.last)(res);
if (lastSelector) {
(0, helpers_1.trimCombinators)(lastSelector);

@@ -385,3 +380,4 @@ }

}
else if (token.type === ",") {
else if (token.type === ',') {
// we ensure at least one selector present
const selector = (0, core_1.last)(selectors);

@@ -400,6 +396,6 @@ selector.end = token.start;

}
else if (token.type === "&") {
else if (token.type === '&') {
ast.push({
type: "nesting",
value: "&",
type: 'nesting',
value: '&',
start: token.start,

@@ -411,3 +407,3 @@ end: token.end,

ast.push({
type: "invalid",
type: 'invalid',
value: token.value,

@@ -419,4 +415,3 @@ start: token.start,

if (s.done()) {
currentSelector.end =
(_u = (_t = (0, core_1.last)(currentSelector.nodes)) === null || _t === void 0 ? void 0 : _t.end) !== null && _u !== void 0 ? _u : currentSelector.start;
currentSelector.end = (_u = (_t = (0, core_1.last)(currentSelector.nodes)) === null || _t === void 0 ? void 0 : _t.end) !== null && _u !== void 0 ? _u : currentSelector.start;
(0, helpers_1.trimCombinators)(currentSelector);

@@ -423,0 +418,0 @@ }

@@ -1,3 +0,3 @@

import type { ImmutableSelectorNode, ImmutableSelectorList, ImmutableNthSelectorList } from "./ast-types";
import type { ImmutableSelectorNode, ImmutableSelectorList, ImmutableNthSelectorList } from './ast-types';
export declare function stringifySelectorAst(value: ImmutableSelectorNode | ImmutableSelectorList | ImmutableNthSelectorList): string;
//# sourceMappingURL=stringify.d.ts.map

@@ -6,3 +6,3 @@ "use strict";

function stringifySelectorAst(value) {
return "length" in value ? stringifySelectors(value) : stringifyNode(value);
return 'length' in value ? stringifySelectors(value) : stringifyNode(value);
}

@@ -12,17 +12,17 @@ exports.stringifySelectorAst = stringifySelectorAst;

id: (node) => `#${node.value}${stringifyNested(node)}`,
class: (node) => `.${node.dotComments.map(stringifyNode).join("")}${node.value}${stringifyNested(node)}`,
class: (node) => `.${node.dotComments.map(stringifyNode).join('')}${node.value}${stringifyNested(node)}`,
type: (node) => `${stringifyNamespace(node)}${node.value}${stringifyNested(node)}`,
combinator: (node) => `${node.before}${node.value}${node.after}`,
attribute: (node) => `[${node.value}]${stringifyNested(node)}`,
pseudo_class: (node) => `:${node.colonComments.map(stringifyNode).join("")}${node.value}${stringifyNested(node)}`,
pseudo_element: (node) => `:${node.colonComments.first
pseudo_class: (node) => `:${node.colonComments.map(stringifyNode).join('')}${node.value}${stringifyNested(node)}`,
pseudo_element: (node) => `:${node.colonComments.first.map(stringifyNode).join('')}:${node.colonComments.second
.map(stringifyNode)
.join("")}:${node.colonComments.second.map(stringifyNode).join("")}${node.value}${stringifyNested(node)}`,
.join('')}${node.value}${stringifyNested(node)}`,
comment: ({ before, value, after }) => `${before}${value}${after}`,
universal: (node) => `${stringifyNamespace(node)}${node.value}${stringifyNested(node)}`,
nesting: (node) => `${node.value}${stringifyNested(node)}`,
selector: (node) => `${node.before}${node.nodes.map(stringifyNode).join("")}${node.after}`,
compound_selector: (node) => `${node.before}${node.nodes.map(stringifyNode).join("")}${node.after}`,
selector: (node) => `${node.before}${node.nodes.map(stringifyNode).join('')}${node.after}`,
compound_selector: (node) => `${node.before}${node.nodes.map(stringifyNode).join('')}${node.after}`,
invalid: (node) => node.value,
nth: (node) => `${node.before}${node.nodes.map(stringifyNode).join("")}${node.after}`,
nth: (node) => `${node.before}${node.nodes.map(stringifyNode).join('')}${node.after}`,
nth_step: ({ before, value, after }) => `${before}${value}${after}`,

@@ -35,3 +35,3 @@ nth_dash: ({ before, value, after }) => `${before}${value}${after}`,

var _a, _b;
return (_b = (_a = printers[node.type]) === null || _a === void 0 ? void 0 : _a.call(printers, node)) !== null && _b !== void 0 ? _b : "";
return (_b = (_a = printers[node.type]) === null || _a === void 0 ? void 0 : _a.call(printers, node)) !== null && _b !== void 0 ? _b : '';
}

@@ -47,6 +47,5 @@ function stringifySelectors(selectors) {

var _a;
if ("nodes" in node) {
if ('nodes' in node) {
if ((_a = node.nodes) === null || _a === void 0 ? void 0 : _a.length) {
if (node.type === `pseudo_class` &&
nth_parser_1.NthParser.isNthPseudoClass(node.value)) {
if (node.type === `pseudo_class` && nth_parser_1.NthParser.isNthPseudoClass(node.value)) {
const [nthNode, ...selectors] = node.nodes;

@@ -63,3 +62,3 @@ return `(${stringifyNode(nthNode)}${stringifySelectors(selectors)})`;

}
return "";
return '';
}

@@ -66,0 +65,0 @@ function stringifyNamespace({ namespace }) {

@@ -1,3 +0,3 @@

import type { Token, Descriptors } from "@tokey/core";
declare type Delimiters = "[" | "]" | "(" | ")" | "," | "*" | "|" | ":" | "." | "#" | ">" | "~" | "+" | "{" | "}" | "&";
import type { Token, Descriptors } from '@tokey/core';
declare type Delimiters = '[' | ']' | '(' | ')' | ',' | '*' | '|' | ':' | '.' | '#' | '>' | '~' | '+' | '{' | '}' | '&';
export declare type CSSSelectorToken = Token<Descriptors | Delimiters>;

@@ -4,0 +4,0 @@ export declare function tokenizeSelector(source: string, options?: {

@@ -24,19 +24,19 @@ "use strict";

exports.tokenizeSelector = tokenizeSelector;
const isDelimiter = (char, previousChar) => previousChar !== "\\" &&
(char === "[" ||
char === "]" ||
char === "(" ||
char === ")" ||
char === "," ||
char === "*" ||
char === "|" ||
char === ":" ||
char === "." ||
char === "#" ||
char === ">" ||
char === "~" ||
char === "+" ||
char === "{" ||
char === "}" ||
char === "&");
const isDelimiter = (char, previousChar) => previousChar !== '\\' &&
(char === '[' ||
char === ']' ||
char === '(' ||
char === ')' ||
char === ',' ||
char === '*' ||
char === '|' ||
char === ':' ||
char === '.' ||
char === '#' ||
char === '>' ||
char === '~' ||
char === '+' ||
char === '{' ||
char === '}' ||
char === '&');
//# sourceMappingURL=tokenizer.js.map
{
"name": "@tokey/css-selector-parser",
"description": "selector parser for css",
"version": "0.5.1",
"version": "0.6.0",
"main": "dist/index.js",

@@ -11,3 +11,3 @@ "types": "dist/index.d.ts",

"dependencies": {
"@tokey/core": "^1.2.1"
"@tokey/core": "^1.3.0"
},

@@ -31,4 +31,3 @@ "keywords": [

"repository": "https://github.com/wixplosives/tokey/packages/css-selector-parser",
"prettier": {},
"sideEffects": false
}

@@ -298,3 +298,3 @@ # @tokey/css-selector-parser

const specificity = parseCssSelector(`span.x.y#z`);
const specificity = calcSpecificity(parseCssSelector(`span.x.y#z`));
// [0, 1, 2, 1]

@@ -332,2 +332,2 @@ ```

`:nth-child`, `:nth-last-child`, `:nth-of-type` and `:nth-last-of-type` are a set of special cases where `An+B of` syntax is expected.
`:nth-child`, `:nth-last-child`, `:nth-of-type` and `:nth-last-of-type` are a set of special cases where `An+B of` syntax is expected.
import type {
SelectorNode,
SelectorList,
Selector,
CompoundSelector,
Comment,
CommentWithNoSpacing,
ImmutableSelector,
ImmutableSelectorList,
ImmutableSelectorNode,
} from "../ast-types";
import { walk } from "./walk";
SelectorNode,
SelectorList,
Selector,
CompoundSelector,
Comment,
CommentWithNoSpacing,
ImmutableSelector,
ImmutableSelectorList,
ImmutableSelectorNode,
} from '../ast-types';
import { walk } from './walk';
export interface GroupCompoundOptions {
splitPseudoElements?: boolean;
splitPseudoElements?: boolean;
deep?: boolean;
}
export function groupCompoundSelectors<AST extends Selector>(
input: AST,
options?: GroupCompoundOptions
input: AST,
options?: GroupCompoundOptions
): Selector;
export function groupCompoundSelectors<AST extends ImmutableSelector>(
input: AST,
options?: GroupCompoundOptions
input: AST,
options?: GroupCompoundOptions
): ImmutableSelector;
export function groupCompoundSelectors<AST extends SelectorList>(
input: AST,
options?: GroupCompoundOptions
input: AST,
options?: GroupCompoundOptions
): SelectorList;
export function groupCompoundSelectors<AST extends ImmutableSelectorList>(
input: AST,
options?: GroupCompoundOptions
input: AST,
options?: GroupCompoundOptions
): ImmutableSelectorList;
export function groupCompoundSelectors<
AST extends
| Selector
| ImmutableSelector
| SelectorList
| ImmutableSelectorList
AST extends Selector | ImmutableSelector | SelectorList | ImmutableSelectorList
>(
input: AST,
options?: GroupCompoundOptions
input: AST,
options?: GroupCompoundOptions
): Selector | ImmutableSelector | SelectorList | ImmutableSelectorList {
const context = createCompoundContext(options);
walk(input, (node, _index, _nodes, parents) => {
if (parents.length === 0 && node.type === `selector`) {
// first level: create top level selector
context.addSelector(node);
} else {
// second level: (parents.length === 1)
context.handleNode(node);
// don't go deeper - shallow group
return walk.skipNested;
}
return;
});
return `length` in input ? context.output : context.output[0];
const context = createCompoundContext(options);
walk(input, (node, _index, _nodes, parents) => {
if (parents.length === 0 && node.type === `selector`) {
// first level: create top level selector
context.addSelector(node);
} else {
// second level: (parents.length === 1)
if (options?.deep && `nodes` in node) {
// compound nested selectors
/* This `nodes` type is hard since it's internal we use any[] here. sorry */
const nodes: any[] = [];
for (const nested of node.nodes!) {
nodes.push(
nested.type === `selector`
? groupCompoundSelectors(nested, options)
: nested
);
}
node = { ...node, nodes };
}
context.handleNode(node);
// don't go deeper - shallow group
return walk.skipNested;
}
return;
});
return `length` in input ? context.output : context.output[0];
}
type CompoundSelectorPart = CompoundSelector["nodes"][number];
function createCompoundContext({
splitPseudoElements = true,
}: GroupCompoundOptions = {}) {
const output: SelectorList = [];
let lastSelector: Selector;
let lastCompound: CompoundSelector | undefined;
let lastCompoundInitialPart: CompoundSelectorPart | undefined;
const handleNode = (node: SelectorNode | ImmutableSelectorNode) => {
if (node.type === `pseudo_element` && splitPseudoElements === true) {
lastCompound = undefined;
}
if (node.type === `combinator`) {
lastSelector.nodes.push(node);
lastCompound = undefined;
} else if (node.type === `comment` && !isCommentWithNoSpacing(node)) {
// comment that breaks compound
lastSelector.nodes.push(node);
lastCompound = undefined;
} else if (
node.type === `type` ||
node.type === `universal` ||
node.type === `class` ||
node.type === `id` ||
node.type === `attribute` ||
node.type === `nesting` ||
node.type === `pseudo_class` ||
node.type === `pseudo_element` ||
node.type === `invalid` ||
node.type === `comment` /*no spacing*/
) {
// part of compound
if (!lastCompound) {
// add new compound selector
lastCompoundInitialPart = undefined;
lastCompound = {
type: `compound_selector`,
start: node.start,
end: node.end,
before: ``,
after: ``,
nodes: [],
invalid: false,
};
lastSelector.nodes.push(lastCompound);
}
if (!lastCompound.invalid && node.type !== `comment`) {
// validate compound parts after initial
if (lastCompoundInitialPart) {
lastCompound.invalid =
node.type === `universal` || node.type === `type`;
type CompoundSelectorPart = CompoundSelector['nodes'][number];
function createCompoundContext({ splitPseudoElements = true }: GroupCompoundOptions = {}) {
const output: SelectorList = [];
let lastSelector: Selector;
let lastCompound: CompoundSelector | undefined;
let lastCompoundInitialPart: CompoundSelectorPart | undefined;
const handleNode = (node: SelectorNode | ImmutableSelectorNode) => {
if (node.type === `pseudo_element` && splitPseudoElements === true) {
lastCompound = undefined;
}
lastCompoundInitialPart = node as CompoundSelectorPart;
}
lastCompound.nodes.push(node as CompoundSelectorPart);
lastCompound.end = node.end;
} else if (node.type === `selector` || node.type === `compound_selector`) {
// spread
for (const innerNode of node.nodes) {
handleNode(innerNode);
}
} else {
// handle out of context nodes
lastSelector.nodes.push(node as SelectorNode);
lastCompound = undefined;
}
};
return {
addSelector(node: Selector | ImmutableSelector) {
lastSelector = {
type: `selector`,
start: node.start,
end: node.end,
before: `before` in node ? node.before : ``,
after: `after` in node ? node.after : ``,
nodes: [],
};
output.push(lastSelector);
lastCompound = undefined;
},
handleNode,
output,
};
if (node.type === `combinator`) {
lastSelector.nodes.push(node);
lastCompound = undefined;
} else if (node.type === `comment` && !isCommentWithNoSpacing(node)) {
// comment that breaks compound
lastSelector.nodes.push(node);
lastCompound = undefined;
} else if (
node.type === `type` ||
node.type === `universal` ||
node.type === `class` ||
node.type === `id` ||
node.type === `attribute` ||
node.type === `nesting` ||
node.type === `pseudo_class` ||
node.type === `pseudo_element` ||
node.type === `invalid` ||
node.type === `comment` /*no spacing*/
) {
// part of compound
if (!lastCompound) {
// add new compound selector
lastCompoundInitialPart = undefined;
lastCompound = {
type: `compound_selector`,
start: node.start,
end: node.end,
before: ``,
after: ``,
nodes: [],
invalid: false,
};
lastSelector.nodes.push(lastCompound);
}
if (!lastCompound.invalid && node.type !== `comment`) {
// validate compound parts after initial
if (lastCompoundInitialPart) {
lastCompound.invalid = node.type === `universal` || node.type === `type`;
}
lastCompoundInitialPart = node as CompoundSelectorPart;
}
lastCompound.nodes.push(node as CompoundSelectorPart);
lastCompound.end = node.end;
} else if (node.type === `selector` || node.type === `compound_selector`) {
// spread
for (const innerNode of node.nodes) {
handleNode(innerNode);
}
} else {
// handle out of context nodes
lastSelector.nodes.push(node as SelectorNode);
lastCompound = undefined;
}
};
return {
addSelector(node: Selector | ImmutableSelector) {
lastSelector = {
type: `selector`,
start: node.start,
end: node.end,
before: `before` in node ? node.before : ``,
after: `after` in node ? node.after : ``,
nodes: [],
};
output.push(lastSelector);
lastCompound = undefined;
},
handleNode,
output,
};
}
export function splitCompoundSelectors<AST extends Selector>(
input: AST
): Selector;
export function splitCompoundSelectors<AST extends Selector>(input: AST): Selector;
export function splitCompoundSelectors<AST extends ImmutableSelector>(
input: AST
input: AST
): ImmutableSelector;
export function splitCompoundSelectors<AST extends SelectorList>(
input: AST
): SelectorList;
export function splitCompoundSelectors<AST extends SelectorList>(input: AST): SelectorList;
export function splitCompoundSelectors<AST extends ImmutableSelectorList>(
input: AST
input: AST
): ImmutableSelectorList;
export function splitCompoundSelectors<AST extends SelectorList | Selector>(
input: AST
input: AST
): SelectorList | Selector | ImmutableSelector | ImmutableSelectorList {
const inputSelectors: SelectorList = Array.isArray(input) ? input : [input];
const output: SelectorList = [];
for (const inputSelector of inputSelectors) {
const outputSelector: Selector = {
...inputSelector,
nodes: [],
};
for (const node of inputSelector.nodes) {
if (node.type === `compound_selector`) {
outputSelector.nodes.push(...node.nodes);
} else {
outputSelector.nodes.push(node);
}
const inputSelectors: SelectorList = Array.isArray(input) ? input : [input];
const output: SelectorList = [];
for (const inputSelector of inputSelectors) {
const outputSelector: Selector = {
...inputSelector,
nodes: [],
};
for (const node of inputSelector.nodes) {
if (node.type === `compound_selector`) {
outputSelector.nodes.push(...node.nodes);
} else {
outputSelector.nodes.push(node);
}
}
output.push(outputSelector);
}
output.push(outputSelector);
}
return `length` in input ? output : output[0];
return `length` in input ? output : output[0];
}
function isCommentWithNoSpacing(node: Comment): node is CommentWithNoSpacing {
return node.type === `comment` && node.before === `` && node.after === ``;
return node.type === `comment` && node.before === `` && node.after === ``;
}

@@ -1,82 +0,70 @@

import type {
ImmutableSelectorNode,
ImmutablePseudoClass,
SelectorNode,
} from "../ast-types";
import { walk } from "./walk";
import type { ImmutableSelectorNode, ImmutablePseudoClass, SelectorNode } from '../ast-types';
import { walk } from './walk';
export type Specificity = [
inlineLevel: number,
idLevel: number,
classOrAttributeLevel: number,
typeOrElementLevel: number
inlineLevel: number,
idLevel: number,
classOrAttributeLevel: number,
typeOrElementLevel: number
];
export function calcSpecificity(ast: ImmutableSelectorNode): Specificity {
const result: Specificity = [0, 0, 0, 0];
// ToDo: remove casting once immutable walk is supported
walk(ast as SelectorNode, (node) => {
switch (node.type) {
case `type`:
case `pseudo_element`:
result[3]++;
break;
case `class`:
case `attribute`:
result[2]++;
break;
case `pseudo_class`:
if (customPseudoClass[node.value]) {
customPseudoClass[node.value](node, result);
return walk.skipNested;
const result: Specificity = [0, 0, 0, 0];
// ToDo: remove casting once immutable walk is supported
walk(ast as SelectorNode, (node) => {
switch (node.type) {
case `type`:
case `pseudo_element`:
result[3]++;
break;
case `class`:
case `attribute`:
result[2]++;
break;
case `pseudo_class`:
if (customPseudoClass[node.value]) {
customPseudoClass[node.value](node, result);
return walk.skipNested;
}
result[2]++;
break;
case `id`:
result[1]++;
break;
}
result[2]++;
break;
case `id`:
result[1]++;
break;
}
return node.type !== `selector` && node.type !== `compound_selector`
? walk.skipNested
: undefined;
});
return result;
return node.type !== `selector` && node.type !== `compound_selector`
? walk.skipNested
: undefined;
});
return result;
}
const customPseudoClass: Record<
string,
(node: ImmutablePseudoClass, result: Specificity) => void
> = {
not: mostSpecificInnerSelector,
is: mostSpecificInnerSelector,
has: mostSpecificInnerSelector,
where: () => {
/* no specificity*/
},
"nth-child": pseudoClassPlusMostSpecificInnerSelector,
"nth-last-child": pseudoClassPlusMostSpecificInnerSelector,
"nth-of-type": pseudoClassPlusMostSpecificInnerSelector,
"nth-last-of-type": pseudoClassPlusMostSpecificInnerSelector,
};
const customPseudoClass: Record<string, (node: ImmutablePseudoClass, result: Specificity) => void> =
{
not: mostSpecificInnerSelector,
is: mostSpecificInnerSelector,
has: mostSpecificInnerSelector,
where: () => {
/* no specificity*/
},
'nth-child': pseudoClassPlusMostSpecificInnerSelector,
'nth-last-child': pseudoClassPlusMostSpecificInnerSelector,
'nth-of-type': pseudoClassPlusMostSpecificInnerSelector,
'nth-last-of-type': pseudoClassPlusMostSpecificInnerSelector,
};
function pseudoClassPlusMostSpecificInnerSelector(
node: ImmutablePseudoClass,
result: Specificity
) {
result[2]++;
mostSpecificInnerSelector(node, result);
function pseudoClassPlusMostSpecificInnerSelector(node: ImmutablePseudoClass, result: Specificity) {
result[2]++;
mostSpecificInnerSelector(node, result);
}
function mostSpecificInnerSelector(
node: ImmutablePseudoClass,
result: Specificity
) {
if (node.nodes?.length) {
let highest: Specificity = [0, 0, 0, 0];
for (const selector of node.nodes) {
const currentSpecificity = calcSpecificity(selector);
if (!highest || compareSpecificity(currentSpecificity, highest) === 1) {
highest = currentSpecificity;
}
function mostSpecificInnerSelector(node: ImmutablePseudoClass, result: Specificity) {
if (node.nodes?.length) {
let highest: Specificity = [0, 0, 0, 0];
for (const selector of node.nodes) {
const currentSpecificity = calcSpecificity(selector);
if (!highest || compareSpecificity(currentSpecificity, highest) === 1) {
highest = currentSpecificity;
}
}
addSpecificity(result, highest);
}
addSpecificity(result, highest);
}
}

@@ -90,11 +78,11 @@ /**

export function compareSpecificity(a: Specificity, b: Specificity): -1 | 0 | 1 {
for (let i = 0; i < 4; ++i) {
const specificityDiff = a[i] - b[i];
if (specificityDiff > 0) {
return 1;
} else if (specificityDiff < 0) {
return -1;
for (let i = 0; i < 4; ++i) {
const specificityDiff = a[i] - b[i];
if (specificityDiff > 0) {
return 1;
} else if (specificityDiff < 0) {
return -1;
}
}
}
return 0;
return 0;
}

@@ -108,5 +96,5 @@

function addSpecificity(to: Specificity, from: Specificity) {
for (let i = 0; i < 4; ++i) {
to[i] += from[i];
}
for (let i = 0; i < 4; ++i) {
to[i] += from[i];
}
}
import type {
SelectorNode,
SelectorList,
FunctionalSelector,
ImmutableSelectorNode,
ImmutableSelectorList,
} from "../ast-types";
SelectorNode,
SelectorList,
FunctionalSelector,
ImmutableSelectorNode,
ImmutableSelectorList,
} from '../ast-types';
export interface WalkOptions {
visitList?: SelectorNode["type"][];
ignoreList?: SelectorNode["type"][];
visitList?: SelectorNode['type'][];
ignoreList?: SelectorNode['type'][];
}
export type WalkVisitor<AST extends SelectorNode | ImmutableSelectorNode> = (
node: AST,
index: number,
nodes: AST[],
parents: AST[]
node: AST,
index: number,
nodes: AST[],
parents: AST[]
) => number | undefined | void;

@@ -33,128 +33,119 @@ const nestEnd = Symbol(`nest-end`);

export function walk<AST extends SelectorNode | SelectorList>(
topNode: AST,
visit: WalkVisitor<SelectorNode>,
options?: WalkOptions
topNode: AST,
visit: WalkVisitor<SelectorNode>,
options?: WalkOptions
): void;
export function walk<AST extends ImmutableSelectorNode | ImmutableSelectorList>(
topNode: AST,
visit: WalkVisitor<ImmutableSelectorNode>,
options?: WalkOptions
topNode: AST,
visit: WalkVisitor<ImmutableSelectorNode>,
options?: WalkOptions
): void;
export function walk<
AST extends
| SelectorNode
| SelectorList
| ImmutableSelectorNode
| ImmutableSelectorList
AST extends SelectorNode | SelectorList | ImmutableSelectorNode | ImmutableSelectorList
>(
topNode: AST,
visit: WalkVisitor<SelectorNode> | WalkVisitor<ImmutableSelectorNode>,
options: WalkOptions = {}
topNode: AST,
visit: WalkVisitor<SelectorNode> | WalkVisitor<ImmutableSelectorNode>,
options: WalkOptions = {}
): void {
// set initial top nodes to traverse
const toVisit: Array<SelectorNode | ImmutableSelectorNode | typeof nestEnd> =
Array.isArray(topNode) ? [...topNode] : [topNode];
// initiate context
const context = createWalkContext(topNode);
// iterate nodes
while (toVisit.length) {
const current = toVisit.shift()!;
if (current === nestEnd) {
// end of nested level
context.up();
continue;
} else if (
(!options.ignoreList || !options.ignoreList.includes(current.type)) &&
(!options.visitList || options.visitList.includes(current.type))
) {
// visit node
let skipAmount =
(visit(
current as SelectorNode,
context.indexInSelector,
context.nodesInSelector as SelectorNode[],
context.parents as SelectorNode[]
) as number) ?? -1;
// point to next selector node
context.next();
// set initial top nodes to traverse
const toVisit: Array<SelectorNode | ImmutableSelectorNode | typeof nestEnd> = Array.isArray(
topNode
)
? [...topNode]
: [topNode];
// initiate context
const context = createWalkContext(topNode);
// iterate nodes
while (toVisit.length) {
const current = toVisit.shift()!;
if (current === nestEnd) {
// end of nested level
context.up();
continue;
} else if (
(!options.ignoreList || !options.ignoreList.includes(current.type)) &&
(!options.visitList || options.visitList.includes(current.type))
) {
// visit node
let skipAmount =
(visit(
current as SelectorNode,
context.indexInSelector,
context.nodesInSelector as SelectorNode[],
context.parents as SelectorNode[]
) as number) ?? -1;
// point to next selector node
context.next();
// check if to skip nested or current/following selectors
if (skipAmount === Infinity) {
// stop all: fast bail out
return;
} else if (skipAmount >= 0) {
// skip levels
while (skipAmount > 0 && toVisit.length) {
const next = toVisit.shift()!;
if (next === nestEnd) {
skipAmount--;
context.up();
}
// check if to skip nested or current/following selectors
if (skipAmount === Infinity) {
// stop all: fast bail out
return;
} else if (skipAmount >= 0) {
// skip levels
while (skipAmount > 0 && toVisit.length) {
const next = toVisit.shift()!;
if (next === nestEnd) {
skipAmount--;
context.up();
}
}
continue;
}
} else {
// point to next selector node
context.next();
}
continue;
}
} else {
// point to next selector node
context.next();
// add nested nodes
if (isWithNodes(current)) {
context.insertNested(current);
toVisit.unshift(...current.nodes, nestEnd);
}
}
// add nested nodes
if (isWithNodes(current)) {
context.insertNested(current);
toVisit.unshift(...current.nodes, nestEnd);
}
}
}
interface WalkContext<AST> {
parents: AST[];
indexInSelector: number;
nodesInSelector: ReadonlyArray<AST>;
up(): void;
next(): void;
insertNested(node: ContainerWithNodes): void;
parents: AST[];
indexInSelector: number;
nodesInSelector: ReadonlyArray<AST>;
up(): void;
next(): void;
insertNested(node: ContainerWithNodes): void;
}
function createWalkContext(topNode: SelectorNode | SelectorList): WalkContext<SelectorNode>;
function createWalkContext(
topNode: SelectorNode | SelectorList
): WalkContext<SelectorNode>;
function createWalkContext(
topNode: ImmutableSelectorNode | ImmutableSelectorList
topNode: ImmutableSelectorNode | ImmutableSelectorList
): WalkContext<ImmutableSelectorNode>;
function createWalkContext(
topNode:
| SelectorNode
| SelectorList
| ImmutableSelectorNode
| ImmutableSelectorList
topNode: SelectorNode | SelectorList | ImmutableSelectorNode | ImmutableSelectorList
) {
const prevIndex: number[] = [];
const prevParents: ImmutableSelectorNode[][] = [[]];
const context: WalkContext<SelectorNode | ImmutableSelectorNode> = {
parents: [],
indexInSelector: 0,
nodesInSelector: Array.isArray(topNode)
? topNode
: `nodes` in topNode
? topNode.nodes!
: ([topNode] as SelectorNode[]),
up() {
context.parents.pop();
context.indexInSelector = prevIndex.shift()!;
const currentParents = context.parents;
const currentParent = currentParents[currentParents.length - 1];
context.nodesInSelector = currentParent
? (currentParent as any).nodes
: topNode;
},
next() {
context.indexInSelector++;
},
insertNested(node) {
context.parents = [...context.parents, node];
prevParents.push(context.parents);
prevIndex.unshift(context.indexInSelector);
context.indexInSelector = 0;
context.nodesInSelector = node.nodes;
},
};
return context;
const prevIndex: number[] = [];
const prevParents: ImmutableSelectorNode[][] = [[]];
const context: WalkContext<SelectorNode | ImmutableSelectorNode> = {
parents: [],
indexInSelector: 0,
nodesInSelector: Array.isArray(topNode)
? topNode
: `nodes` in topNode
? topNode.nodes!
: ([topNode] as SelectorNode[]),
up() {
context.parents.pop();
context.indexInSelector = prevIndex.shift()!;
const currentParents = context.parents;
const currentParent = currentParents[currentParents.length - 1];
context.nodesInSelector = currentParent ? (currentParent as any).nodes : topNode;
},
next() {
context.indexInSelector++;
},
insertNested(node) {
context.parents = [...context.parents, node];
prevParents.push(context.parents);
prevIndex.unshift(context.indexInSelector);
context.indexInSelector = 0;
context.nodesInSelector = node.nodes;
},
};
return context;
}

@@ -168,3 +159,3 @@

function isWithNodes(node: any): node is ContainerWithNodes {
return node && `nodes` in node;
return node && `nodes` in node;
}

@@ -1,8 +0,8 @@

import type { Immutable } from "./types";
import type { Token } from "@tokey/core";
import type { Immutable } from './types';
import type { Token } from '@tokey/core';
export interface Selector extends Omit<Token<"selector">, "value"> {
nodes: SelectorNodes;
before: string;
after: string;
export interface Selector extends Omit<Token<'selector'>, 'value'> {
nodes: SelectorNodes;
before: string;
after: string;
}

@@ -13,80 +13,80 @@

// ToDo: try type NthSelectorList only for the specific set of types
export interface PseudoClass extends Token<"pseudo_class"> {
nodes?: SelectorList | NthSelectorList;
colonComments: Comment[];
export interface PseudoClass extends Token<'pseudo_class'> {
nodes?: SelectorList | NthSelectorList;
colonComments: Comment[];
}
export interface PseudoElement extends Token<"pseudo_element"> {
nodes?: SelectorList;
colonComments: { first: Comment[]; second: Comment[] };
export interface PseudoElement extends Token<'pseudo_element'> {
nodes?: SelectorList;
colonComments: { first: Comment[]; second: Comment[] };
}
export interface Class extends Token<"class"> {
nodes?: SelectorList;
dotComments: SelectorNodes;
export interface Class extends Token<'class'> {
nodes?: SelectorList;
dotComments: SelectorNodes;
}
export interface Id extends Token<"id"> {
nodes?: SelectorList;
export interface Id extends Token<'id'> {
nodes?: SelectorList;
}
export interface Attribute extends Token<"attribute"> {
// left: string;
// right: string;
// op: "" | "=" | "~=" | "|=" | "^=" | "$=" | "*=";
// quotes: "'" | '"' | "";
nodes?: SelectorList;
export interface Attribute extends Token<'attribute'> {
// left: string;
// right: string;
// op: "" | "=" | "~=" | "|=" | "^=" | "$=" | "*=";
// quotes: "'" | '"' | "";
nodes?: SelectorList;
}
export interface Namespace {
value: string;
beforeComments: Comment[];
afterComments: Comment[];
invalid?: "namespace" | "target" | "namespace,target" | "";
value: string;
beforeComments: Comment[];
afterComments: Comment[];
invalid?: 'namespace' | 'target' | 'namespace,target' | '';
}
export interface Type extends Token<"type"> {
namespace?: Namespace;
nodes?: SelectorList;
export interface Type extends Token<'type'> {
namespace?: Namespace;
nodes?: SelectorList;
}
export interface Universal extends Token<"universal"> {
namespace?: Namespace;
nodes?: SelectorList;
export interface Universal extends Token<'universal'> {
namespace?: Namespace;
nodes?: SelectorList;
}
export interface Combinator extends Token<"combinator"> {
combinator: "space" | "+" | "~" | ">";
before: string;
after: string;
invalid: boolean;
export interface Combinator extends Token<'combinator'> {
combinator: 'space' | '+' | '~' | '>';
before: string;
after: string;
invalid: boolean;
}
export interface Nesting extends Token<"nesting"> {
value: "&";
nodes?: SelectorList;
export interface Nesting extends Token<'nesting'> {
value: '&';
nodes?: SelectorList;
}
export type Invalid = Token<"invalid">;
export interface Comment extends Token<"comment"> {
before: string;
after: string;
export type Invalid = Token<'invalid'>;
export interface Comment extends Token<'comment'> {
before: string;
after: string;
}
export type CommentWithNoSpacing = Comment & { before: ""; after: "" };
export type CommentWithNoSpacing = Comment & { before: ''; after: '' };
export interface NthBase<PART extends string> extends Token<PART> {
before: string;
after: string;
invalid?: boolean;
before: string;
after: string;
invalid?: boolean;
}
export type NthStep = NthBase<"nth_step">;
export type NthOffset = NthBase<"nth_offset">;
export type NthDash = NthBase<"nth_dash">;
export type NthOf = NthBase<"nth_of">;
export type NthStep = NthBase<'nth_step'>;
export type NthOffset = NthBase<'nth_offset'>;
export type NthDash = NthBase<'nth_dash'>;
export type NthOf = NthBase<'nth_of'>;
export type NthNode = NthStep | NthOffset | NthDash | NthOf | Comment;
export interface Nth extends Omit<Token<"nth">, "value"> {
nodes: Array<NthNode>;
before: string;
after: string;
// invalid?: boolean;
export interface Nth extends Omit<Token<'nth'>, 'value'> {
nodes: Array<NthNode>;
before: string;
after: string;
// invalid?: boolean;
}

@@ -96,48 +96,41 @@

export type SimpleSelector =
| Type
| Universal
| Attribute
| Class
| Id
| PseudoClass;
export type SimpleSelector = Type | Universal | Attribute | Class | Id | PseudoClass;
export interface CompoundSelector
extends Omit<Token<"compound_selector">, "value"> {
nodes: Array<
| SimpleSelector
// non standard
| Nesting
| CommentWithNoSpacing
| Invalid
| PseudoElement
| CompoundSelector
| Selector
>;
before: string;
after: string;
invalid: boolean;
export interface CompoundSelector extends Omit<Token<'compound_selector'>, 'value'> {
nodes: Array<
| SimpleSelector
// non standard
| Nesting
| CommentWithNoSpacing
| Invalid
| PseudoElement
| CompoundSelector
| Selector
>;
before: string;
after: string;
invalid: boolean;
}
export type FunctionalSelector =
| NamespacedNode
| Attribute
| Id
| Class
| PseudoClass
| PseudoElement
| Nesting;
| NamespacedNode
| Attribute
| Id
| Class
| PseudoClass
| PseudoElement
| Nesting;
export type SelectorNode =
| FunctionalSelector
| Selector
| CompoundSelector
| Combinator
| Comment
| Invalid
| Nth
| NthStep
| NthDash
| NthOffset
| NthOf;
| FunctionalSelector
| Selector
| CompoundSelector
| Combinator
| Comment
| Invalid
| Nth
| NthStep
| NthDash
| NthOffset
| NthOf;
export type SelectorNodes = SelectorNode[];

@@ -144,0 +137,0 @@ export type SelectorList = Selector[];

@@ -1,12 +0,12 @@

import type { CSSSelectorToken } from "./tokenizer";
import type { CSSSelectorToken } from './tokenizer';
import type {
Combinator,
Comment,
Selector,
Nth,
NamespacedNode,
SelectorList,
SelectorNode,
} from "./ast-types";
import { Token, last } from "@tokey/core";
Combinator,
Comment,
Selector,
Nth,
NamespacedNode,
SelectorList,
SelectorNode,
} from './ast-types';
import { Token, last } from '@tokey/core';

@@ -16,54 +16,50 @@ // create ast nodes

export function createEmptySelector(): Selector {
return {
type: "selector",
start: -1,
end: -1,
before: "",
after: "",
nodes: [],
};
return {
type: 'selector',
start: -1,
end: -1,
before: '',
after: '',
nodes: [],
};
}
export function createEmptyNth(): Nth {
return {
type: "nth",
start: -1,
end: -1,
before: "",
after: "",
nodes: [],
};
return {
type: 'nth',
start: -1,
end: -1,
before: '',
after: '',
nodes: [],
};
}
export function createCombinatorAst({
value,
type,
start,
end,
}: CSSSelectorToken & Token<"space" | "+" | ">" | "~">): Combinator {
return {
type: `combinator`,
combinator: type,
value: type === `space` ? ` ` : value,
value,
type,
start,
end,
before: ``,
after: type === `space` ? value.slice(1) : ``,
invalid: false,
};
}: CSSSelectorToken & Token<'space' | '+' | '>' | '~'>): Combinator {
return {
type: `combinator`,
combinator: type,
value: type === `space` ? ` ` : value,
start,
end,
before: ``,
after: type === `space` ? value.slice(1) : ``,
invalid: false,
};
}
export function createCommentAst({
value,
start,
end,
}: CSSSelectorToken): Comment {
return {
type: `comment`,
value,
start,
end,
before: ``,
after: ``,
};
export function createCommentAst({ value, start, end }: CSSSelectorToken): Comment {
return {
type: `comment`,
value,
start,
end,
before: ``,
after: ``,
};
}

@@ -74,19 +70,12 @@

export function isCombinatorToken(
token: CSSSelectorToken
): token is Token<"space" | "+" | ">" | "~"> {
return (
token.type === "space" ||
token.type === "+" ||
token.type === ">" ||
token.type === "~"
);
token: CSSSelectorToken
): token is Token<'space' | '+' | '>' | '~'> {
return token.type === 'space' || token.type === '+' || token.type === '>' || token.type === '~';
}
export function isNamespacedToken(
token: CSSSelectorToken
): token is Token<"text" | "*"> {
return token.type === `*` || token.type === `text`;
export function isNamespacedToken(token: CSSSelectorToken): token is Token<'text' | '*'> {
return token.type === `*` || token.type === `text`;
}
export function isNamespacedAst(token: SelectorNode): token is NamespacedNode {
return token.type === `universal` || token.type === `type`;
return token.type === `universal` || token.type === `type`;
}

@@ -96,54 +85,51 @@

export function ensureSelector(
selectors: SelectorList,
startToken: CSSSelectorToken
) {
let lastSelector = last(selectors);
if (!lastSelector) {
lastSelector = createEmptySelector();
lastSelector.start = startToken.start;
selectors.push(lastSelector);
}
return lastSelector;
export function ensureSelector(selectors: SelectorList, startToken: CSSSelectorToken) {
let lastSelector = last(selectors);
if (!lastSelector) {
lastSelector = createEmptySelector();
lastSelector.start = startToken.start;
selectors.push(lastSelector);
}
return lastSelector;
}
export function trimCombinators(selector: Selector) {
// costly way to turn combinators to before and after.
// this can be inlined in the handle token process
const nodes = selector.nodes;
const firstNode = nodes[0];
const lastNode = last(nodes);
// remove first space combinator and add to selector before
// (going between comment is not required for the start becuase they are taken care
// of during parsing)
if (firstNode?.type === "combinator" && firstNode.combinator === "space") {
selector.nodes.shift();
selector.before += firstNode.before + firstNode.value + firstNode.after;
}
// remove any edge space combinators (last and between comments)
if (lastNode !== firstNode) {
let index = nodes.length - 1;
let current = lastNode;
let lastComment: Comment | undefined;
while (
(current && current.type === `comment`) ||
(current.type === `combinator` && current.combinator === `space`)
) {
if (current.type === `combinator`) {
if (!lastComment) {
// attach space to end of selector
selector.nodes.pop();
selector.after += current.before + current.value + current.after;
} else {
// attach space to start of comment
selector.nodes.splice(index, 1);
lastComment.before += current.before + current.value + current.after;
lastComment.start = current.start;
// costly way to turn combinators to before and after.
// this can be inlined in the handle token process
const nodes = selector.nodes;
const firstNode = nodes[0];
const lastNode = last(nodes);
// remove first space combinator and add to selector before
// (going between comment is not required for the start because they are taken care
// of during parsing)
if (firstNode?.type === 'combinator' && firstNode.combinator === 'space') {
selector.nodes.shift();
selector.before += firstNode.before + firstNode.value + firstNode.after;
}
// remove any edge space combinators (last and between comments)
if (lastNode && lastNode !== firstNode) {
let index = nodes.length - 1;
let current = lastNode;
let lastComment: Comment | undefined;
while (
current.type === `comment` ||
(current.type === `combinator` && current.combinator === `space`)
) {
if (current.type === `combinator`) {
if (!lastComment) {
// attach space to end of selector
selector.nodes.pop();
selector.after += current.before + current.value + current.after;
} else {
// attach space to start of comment
selector.nodes.splice(index, 1);
lastComment.before += current.before + current.value + current.after;
lastComment.start = current.start;
}
} else {
lastComment = current;
}
current = nodes[--index];
}
} else {
lastComment = current;
}
current = nodes[--index];
}
}
}

@@ -1,12 +0,9 @@

export { parseCssSelector } from "./selector-parser";
export type { ParseConfig } from "./selector-parser";
export * from "./ast-types";
export { stringifySelectorAst } from "./stringify";
export { walk } from "./ast-tools/walk";
export type { WalkOptions } from "./ast-tools/walk";
export {
groupCompoundSelectors,
splitCompoundSelectors,
} from "./ast-tools/compound";
export { calcSpecificity, compareSpecificity } from "./ast-tools/specificity";
export type { Specificity } from "./ast-tools/specificity";
export { parseCssSelector } from './selector-parser';
export type { ParseConfig } from './selector-parser';
export * from './ast-types';
export { stringifySelectorAst } from './stringify';
export { walk } from './ast-tools/walk';
export type { WalkOptions } from './ast-tools/walk';
export { groupCompoundSelectors, splitCompoundSelectors } from './ast-tools/compound';
export { calcSpecificity, compareSpecificity } from './ast-tools/specificity';
export type { Specificity } from './ast-tools/specificity';

@@ -1,257 +0,246 @@

import type { CSSSelectorToken } from "./tokenizer";
import type { Nth, NthOf, NthOffset, NthStep } from "./ast-types";
import { createCommentAst } from "./helpers";
import { isComment, Seeker, last } from "@tokey/core";
import type { CSSSelectorToken } from './tokenizer';
import type { Nth, NthOf, NthOffset, NthStep } from './ast-types';
import { createCommentAst } from './helpers';
import { isComment, Seeker, last } from '@tokey/core';
export class NthParser {
static isNthPseudoClass(name: string): boolean {
return (
name === `nth-child` ||
name === `nth-last-child` ||
name === `nth-of-type` ||
name === `nth-last-of-type`
);
}
/**
* check (case insensitive) and returns 2 groups:
* 1. plus/minus sign (invalid step value)
* 2. odd/even string
* [
* `+`|`-`|undefined,
* `odd`|`even`
* ]
*/
static oddEvenStep = /([-+]?)(odd|even)/i;
/**
* check for valid step
* starts with optional minus or plus,
* ends with 0 or more digits following a `n`/`N` character
*/
static validStep = /^[-+]?\d*n$/i;
/**
* check for valid offset
* starts with optional minus or plus,
* ends with 1 or more digits
*/
static validOffset = /^[-+]?\d+$/;
/**
* check for valid start of nth expression
* and returns 2 groups:
* 1. An: optional minus or plus, 0 or more digits, `n`/`N` character
* 2. anything after that
*/
static nthStartExp = /([-+]?\d*[nN]?)(.*)/;
static isNthPseudoClass(name: string): boolean {
return (
name === `nth-child` ||
name === `nth-last-child` ||
name === `nth-of-type` ||
name === `nth-last-of-type`
);
}
/**
* check (case insensitive) and returns 2 groups:
* 1. plus/minus sign (invalid step value)
* 2. odd/even string
* [
* `+`|`-`|undefined,
* `odd`|`even`
* ]
*/
static oddEvenStep = /([-+]?)(odd|even)/i;
/**
* check for valid step
* starts with optional minus or plus,
* ends with 0 or more digits following a `n`/`N` character
*/
static validStep = /^[-+]?\d*n$/i;
/**
* check for valid offset
* starts with optional minus or plus,
* ends with 1 or more digits
*/
static validOffset = /^[-+]?\d+$/;
/**
* check for valid start of nth expression
* and returns 2 groups:
* 1. An: optional minus or plus, 0 or more digits, `n`/`N` character
* 2. anything after that
*/
static nthStartExp = /([-+]?\d*[nN]?)(.*)/;
public state: "step" | `dash` | `offset` | `of` | `selector` = `step`;
private standaloneDash = false;
private ast: Nth["nodes"];
constructor(private selectorNode: Nth, private s: Seeker<CSSSelectorToken>) {
this.ast = selectorNode.nodes;
}
public handleToken(token: CSSSelectorToken): boolean {
const type = token.type;
if (type === `text` || type === `+`) {
switch (this.state) {
case `step`: {
// pickup 1 or more tokens for `5n` / `+5n` / `+5n-4` / `5`
const nextToken =
type === `+` && this.s.peek().type === `text`
? this.s.next()
: undefined;
this.breakFirstChunk({
type: `text`,
value: token.value + (nextToken?.value || ``),
start: token.start,
end: nextToken?.end || token.end,
});
return true;
public state: 'step' | `dash` | `offset` | `of` | `selector` = `step`;
private standaloneDash = false;
private ast: Nth['nodes'];
constructor(private selectorNode: Nth, private s: Seeker<CSSSelectorToken>) {
this.ast = selectorNode.nodes;
}
public handleToken(token: CSSSelectorToken): boolean {
const type = token.type;
if (type === `text` || type === `+`) {
switch (this.state) {
case `step`: {
// pickup 1 or more tokens for `5n` / `+5n` / `+5n-4` / `5`
const nextToken =
type === `+` && this.s.peek().type === `text` ? this.s.next() : undefined;
this.breakFirstChunk({
type: `text`,
value: token.value + (nextToken?.value || ``),
start: token.start,
end: nextToken?.end || token.end,
});
return true;
}
case `dash`: {
const nextToken =
type === `+` && this.s.peek().type === `text` ? this.s.next() : undefined;
this.pushDash({
type: `text`,
value: token.value + (nextToken?.value || ``),
start: token.start,
end: nextToken?.end || token.end,
});
return true;
}
case `offset`: {
const nextToken =
type === `+` && this.s.peek().type === `text` ? this.s.next() : undefined;
this.pushOffset({
type: `text`,
value: token.value + (nextToken?.value || ``),
start: token.start,
end: nextToken?.end || token.end,
});
return true;
}
case `of`: {
this.pushOf(token);
return false;
}
}
} else if (type === `space`) {
// improve typing
const lastNode = last(this.ast);
if (lastNode) {
lastNode.after += token.value;
lastNode.end += token.value.length;
} else {
// add initial space to top selector
this.selectorNode.before += token.value;
}
return true;
} else if (isComment(type)) {
this.ast.push(createCommentAst(token));
return true;
}
case `dash`: {
const nextToken =
type === `+` && this.s.peek().type === `text`
? this.s.next()
: undefined;
this.pushDash({
type: `text`,
value: token.value + (nextToken?.value || ``),
start: token.start,
end: nextToken?.end || token.end,
});
return true;
// not part of `An+b of`: bail out
this.s.back();
return false;
}
/**
* first token can only be (minus contained in text):
* step: `5n`/`+5n`/`-5n`
* step & offset: `5n`/`5n-5
*/
private breakFirstChunk(token: CSSSelectorToken) {
const value = token.value;
// find odd/even
const oddEventMatch = value.match(NthParser.oddEvenStep);
if (oddEventMatch) {
const isInvalid = !!oddEventMatch[1];
this.pushStep(token, isInvalid);
return;
}
case `offset`: {
const nextToken =
type === `+` && this.s.peek().type === `text`
? this.s.next()
: undefined;
this.pushOffset({
type: `text`,
value: token.value + (nextToken?.value || ``),
// separate valid step start from rest: `-5n-4` / `-5n` / `-4` / `5n-4`
const matchValidStart = value.match(NthParser.nthStartExp);
if (!matchValidStart) {
// invalid step
this.pushStep(token);
} else {
const step = matchValidStart[1];
const offset = matchValidStart[2];
if (!offset && !step.match(/[nN]+$/) && step.match(NthParser.validOffset)) {
// no `n` - just offset
this.pushOffset(token);
} else if (offset === `-`) {
// connected dash: `5n-`
this.pushStep({
type: `text`,
value: step,
start: token.start,
end: token.start + step.length,
});
this.pushDash({
type: `text`,
value: `-`,
start: token.end - 1,
end: token.end,
});
} else if (offset && !offset.match(/-\d+/)) {
// invalid step: `-3x`
this.pushStep(token);
} else {
// step with potential minus offset: `5n-4`
this.pushStep({
type: `text`,
value: step,
start: token.start,
end: token.start + step.length,
});
if (offset) {
this.pushOffset({
type: `text`,
value: offset,
start: token.end - offset.length,
end: token.end,
});
}
}
}
}
private pushStep(token: CSSSelectorToken, isInvalid?: boolean) {
const value = token.value;
const stepNode: NthStep = {
type: `nth_step`,
value,
before: ``,
after: ``,
start: token.start,
end: nextToken?.end || token.end,
});
return true;
end: token.end,
};
isInvalid = isInvalid !== undefined ? isInvalid : !value.match(NthParser.validStep);
if (isInvalid) {
stepNode.invalid = true;
}
case `of`: {
this.pushOf(token);
return false;
this.state = `dash`;
this.ast.push(stepNode);
}
private pushDash(token: CSSSelectorToken) {
const value = token.value;
if (value === `+` || value === `-`) {
this.ast.push({
type: `nth_dash`,
value: token.value,
start: token.start,
end: token.end,
before: ``,
after: ``,
});
this.standaloneDash = true;
this.state = `offset`;
} else {
this.pushOffset(token);
}
}
} else if (type === `space`) {
// improve typing
const lastNode = last(this.ast);
if (lastNode) {
lastNode.after += token.value;
lastNode.end += token.value.length;
} else {
// add initial space to top selector
this.selectorNode.before += token.value;
}
return true;
} else if (isComment(type)) {
this.ast.push(createCommentAst(token));
return true;
}
// not part of `An+b of`: bail out
this.s.back();
return false;
}
/**
* first token can only be (minus contained in text):
* step: `5n`/`+5n`/`-5n`
* step & offset: `5n`/`5n-5
*/
private breakFirstChunk(token: CSSSelectorToken) {
const value = token.value;
// find odd/even
const oddEventMatch = value.match(NthParser.oddEvenStep);
if (oddEventMatch) {
const isInvalid = !!oddEventMatch[1];
this.pushStep(token, isInvalid);
return;
private pushOffset(token: CSSSelectorToken) {
if (token.value === `of`) {
this.pushOf(token);
} else {
const value = token.value;
const offsetNode: NthOffset = {
type: `nth_offset`,
value,
before: ``,
after: ``,
start: token.start,
end: token.end,
};
if (
!value.match(NthParser.validOffset) ||
(this.standaloneDash && value.match(/^[-+]/))
) {
offsetNode.invalid = true;
}
this.state = `of`;
this.ast.push(offsetNode);
}
}
// separate valid step start from rest: `-5n-4` / `-5n` / `-4` / `5n-4`
const matchValidStart = value.match(NthParser.nthStartExp);
if (!matchValidStart) {
// invalid step
this.pushStep(token);
} else {
const step = matchValidStart[1];
const offset = matchValidStart[2];
if (
!offset &&
!step.match(/[nN]+$/) &&
step.match(NthParser.validOffset)
) {
// no `n` - just offset
this.pushOffset(token);
} else if (offset === `-`) {
// connected dash: `5n-`
this.pushStep({
type: `text`,
value: step,
start: token.start,
end: token.start + step.length,
});
this.pushDash({
type: `text`,
value: `-`,
start: token.end - 1,
end: token.end,
});
} else if (offset && !offset.match(/-\d+/)) {
// invalid step: `-3x`
this.pushStep(token);
} else {
// step with potential minus offset: `5n-4`
this.pushStep({
type: `text`,
value: step,
start: token.start,
end: token.start + step.length,
});
if (offset) {
this.pushOffset({
type: `text`,
value: offset,
start: token.end - offset.length,
private pushOf(token: CSSSelectorToken) {
const ofNode: NthOf = {
type: `nth_of`,
value: token.value,
before: ``,
after: ``,
start: token.start,
end: token.end,
});
};
if (token.value !== `of`) {
ofNode.invalid = true;
}
}
this.ast.push(ofNode);
this.state = `selector`;
}
}
private pushStep(token: CSSSelectorToken, isInvalid?: boolean) {
const value = token.value;
const stepNode: NthStep = {
type: `nth_step`,
value,
before: ``,
after: ``,
start: token.start,
end: token.end,
};
isInvalid =
isInvalid !== undefined ? isInvalid : !value.match(NthParser.validStep);
if (isInvalid) {
stepNode.invalid = true;
}
this.state = `dash`;
this.ast.push(stepNode);
}
private pushDash(token: CSSSelectorToken) {
const value = token.value;
if (value === `+` || value === `-`) {
this.ast.push({
type: `nth_dash`,
value: token.value,
start: token.start,
end: token.end,
before: ``,
after: ``,
});
this.standaloneDash = true;
this.state = `offset`;
} else {
this.pushOffset(token);
}
}
private pushOffset(token: CSSSelectorToken) {
if (token.value === `of`) {
this.pushOf(token);
} else {
const value = token.value;
const offsetNode: NthOffset = {
type: `nth_offset`,
value,
before: ``,
after: ``,
start: token.start,
end: token.end,
};
if (
!value.match(NthParser.validOffset) ||
(this.standaloneDash && value.match(/^[-+]/))
) {
offsetNode.invalid = true;
}
this.state = `of`;
this.ast.push(offsetNode);
}
}
private pushOf(token: CSSSelectorToken) {
const ofNode: NthOf = {
type: `nth_of`,
value: token.value,
before: ``,
after: ``,
start: token.start,
end: token.end,
};
if (token.value !== `of`) {
ofNode.invalid = true;
}
this.ast.push(ofNode);
this.state = `selector`;
}
}

@@ -1,426 +0,418 @@

import { tokenizeSelector, CSSSelectorToken } from "./tokenizer";
import { NthParser } from "./nth-parser";
import { tokenizeSelector, CSSSelectorToken } from './tokenizer';
import { NthParser } from './nth-parser';
import type {
Namespace,
Combinator,
Comment,
NamespacedNode,
Selector,
SelectorList,
SelectorNode,
} from "./ast-types";
Namespace,
Combinator,
Comment,
NamespacedNode,
Selector,
SelectorList,
SelectorNode,
} from './ast-types';
import {
createCombinatorAst,
createCommentAst,
createEmptySelector,
createEmptyNth,
isCombinatorToken,
isNamespacedAst,
isNamespacedToken,
ensureSelector,
trimCombinators,
} from "./helpers";
import { isComment, getText, Seeker, last } from "@tokey/core";
createCombinatorAst,
createCommentAst,
createEmptySelector,
createEmptyNth,
isCombinatorToken,
isNamespacedAst,
isNamespacedToken,
ensureSelector,
trimCombinators,
} from './helpers';
import { isComment, getText, Seeker, last } from '@tokey/core';
export interface ParseConfig {
offset: number;
offset: number;
}
export function parseCssSelector(source: string, options: Partial<ParseConfig> = {}) {
return parseTokens(source, tokenizeSelector(source, options));
return parseTokens(source, tokenizeSelector(source, options));
}
function parseTokens(source: string, tokens: CSSSelectorToken[]): SelectorList {
return new Seeker(tokens).run<SelectorList>(handleToken, [], source);
return new Seeker(tokens).run<SelectorList>(handleToken, [], source);
}
function handleToken(
token: CSSSelectorToken,
selectors: SelectorList,
source: string,
s: Seeker<CSSSelectorToken>
token: CSSSelectorToken,
selectors: SelectorList,
source: string,
s: Seeker<CSSSelectorToken>
): void {
let t;
const currentSelector = ensureSelector(selectors, token);
const ast = currentSelector.nodes;
if (token.type === ".") {
const comments = s.takeMany("multi-comment").map(createCommentAst);
const name = s.take("text");
ast.push({
type: "class",
value: name?.value ?? "",
start: token.start,
end: name?.end ?? last(comments)?.end ?? token.end,
dotComments: comments,
});
} else if (token.type === ":") {
const firstComments = s.takeMany("multi-comment").map(createCommentAst);
const type = s.take(":") || token;
const isClass = token === type;
let t;
const currentSelector = ensureSelector(selectors, token);
const ast = currentSelector.nodes;
if (token.type === '.') {
const comments = s.takeMany('multi-comment').map(createCommentAst);
const name = s.take('text');
ast.push({
type: 'class',
value: name?.value ?? '',
start: token.start,
end: name?.end ?? last(comments)?.end ?? token.end,
dotComments: comments,
});
} else if (token.type === ':') {
const firstComments = s.takeMany('multi-comment').map(createCommentAst);
const type = s.take(':') || token;
const isClass = token === type;
if (isClass) {
const name = s.take("text");
const endToken = name || last(firstComments) || type;
ast.push({
type: "pseudo_class",
value: name?.value ?? "",
start: token.start,
end: name?.end ?? endToken.end,
colonComments: firstComments,
});
} else {
const secondComments = s.takeMany("multi-comment").map(createCommentAst);
const name = s.take("text");
const endToken = name || last(secondComments) || type;
if (isClass) {
const name = s.take('text');
const endToken = name || last(firstComments) || type;
ast.push({
type: 'pseudo_class',
value: name?.value ?? '',
start: token.start,
end: name?.end ?? endToken.end,
colonComments: firstComments,
});
} else {
const secondComments = s.takeMany('multi-comment').map(createCommentAst);
const name = s.take('text');
const endToken = name || last(secondComments) || type;
ast.push({
type: "pseudo_element",
value: name?.value ?? "",
start: token.start,
end: name?.end ?? endToken.end,
colonComments: { first: firstComments, second: secondComments },
});
}
} else if (token.type === "[") {
const block = s.run(
(token, ast) => {
ast.push(token);
return token.type !== "]";
},
[token],
source
);
const closed = last(block)?.type === "]";
if (closed) {
ast.push({
type: "attribute",
value:
block.length > 2 ? getText(block, 1, block.length - 1, source) : "",
start: token.start,
end: last(block)?.end ?? token.end,
});
} else {
ast.push({
type: "invalid",
value: getText(block, undefined, undefined, source),
start: token.start,
end: last(block)?.end ?? token.end,
});
}
} else if (isCombinatorToken(token)) {
let lastCombinatorAst = createCombinatorAst(token);
let lastAst: Combinator | Comment = lastCombinatorAst;
// insert token as a combinator
ast.push(lastCombinatorAst);
// save the insertion point of the first combinator in case it's a space
// that might be considered a normal space later and will need to be changed.
let initialSpaceCombIndex: number =
lastCombinatorAst.combinator === `space` ? ast.length - 1 : -1;
/**
* take next spaces/combinators/comments:
* - combinator/space token:
* - spaces: merge to previous ast node before them
* - previous ast equal to space combinator
* - turn previous ast to the next combinator type
* - merge spaces between them
* - cancel initial space tracking - must be merged with other non space combinator or already canceled
* - initial ast is space (must be comments following it)
* - initial space is first in selector: merge initial ast into the selector before
* - otherwise merge initial ast the comment following it
* - insert an invalid combinator
* - comment token: insert to ast
*/
//
let next = s.next();
while (next) {
if (isCombinatorToken(next)) {
if (next.type === `space`) {
// add space to the last ast node
lastAst.after += next.value;
lastAst.end = next.end;
} else if (
lastAst === lastCombinatorAst &&
lastAst.combinator === "space"
) {
// combine next combinator into previous (space)
const nextCombinator = createCombinatorAst(next);
lastCombinatorAst.combinator = nextCombinator.combinator;
lastCombinatorAst.before +=
lastCombinatorAst.after +
lastCombinatorAst.value +
nextCombinator.before;
lastCombinatorAst.after = nextCombinator.after;
lastCombinatorAst.value = nextCombinator.value;
lastCombinatorAst.end = nextCombinator.end;
// reset initial space
initialSpaceCombIndex = -1;
} else if (initialSpaceCombIndex !== -1) {
// merge initial space combinator (classified as combinator before a comment)
const initialSpace = ast[initialSpaceCombIndex] as Combinator;
const spaceValue =
initialSpace.before + initialSpace.value + initialSpace.after;
if (initialSpaceCombIndex === 0) {
// merge to beginning of selector
currentSelector.before += spaceValue;
} else {
// merge to the next comment
const nodeAfterInitial = ast[initialSpaceCombIndex + 1];
if (nodeAfterInitial?.type === `comment`) {
nodeAfterInitial.before += spaceValue;
nodeAfterInitial.start = initialSpace.start;
ast.push({
type: 'pseudo_element',
value: name?.value ?? '',
start: token.start,
end: name?.end ?? endToken.end,
colonComments: { first: firstComments, second: secondComments },
});
}
} else if (token.type === '[') {
const block = s.run(
(token, ast) => {
ast.push(token);
return token.type !== ']';
},
[token],
source
);
const closed = last(block)?.type === ']';
if (closed) {
ast.push({
type: 'attribute',
value: block.length > 2 ? getText(block, 1, block.length - 1, source) : '',
start: token.start,
end: last(block)?.end ?? token.end,
});
} else {
ast.push({
type: 'invalid',
value: getText(block, undefined, undefined, source),
start: token.start,
end: last(block)?.end ?? token.end,
});
}
} else if (isCombinatorToken(token)) {
let lastCombinatorAst = createCombinatorAst(token);
let lastAst: Combinator | Comment = lastCombinatorAst;
// insert token as a combinator
ast.push(lastCombinatorAst);
// save the insertion point of the first combinator in case it's a space
// that might be considered a normal space later and will need to be changed.
let initialSpaceCombIndex: number =
lastCombinatorAst.combinator === `space` ? ast.length - 1 : -1;
/**
* take next spaces/combinators/comments:
* - combinator/space token:
* - spaces: merge to previous ast node before them
* - previous ast equal to space combinator
* - turn previous ast to the next combinator type
* - merge spaces between them
* - cancel initial space tracking - must be merged with other non space combinator or already canceled
* - initial ast is space (must be comments following it)
* - initial space is first in selector: merge initial ast into the selector before
* - otherwise merge initial ast the comment following it
* - insert an invalid combinator
* - comment token: insert to ast
*/
//
let next = s.next();
while (next) {
if (isCombinatorToken(next)) {
if (next.type === `space`) {
// add space to the last ast node
lastAst.after += next.value;
lastAst.end = next.end;
} else if (lastAst === lastCombinatorAst && lastAst.combinator === 'space') {
// combine next combinator into previous (space)
const nextCombinator = createCombinatorAst(next);
lastCombinatorAst.combinator = nextCombinator.combinator;
lastCombinatorAst.before +=
lastCombinatorAst.after + lastCombinatorAst.value + nextCombinator.before;
lastCombinatorAst.after = nextCombinator.after;
lastCombinatorAst.value = nextCombinator.value;
lastCombinatorAst.end = nextCombinator.end;
// reset initial space
initialSpaceCombIndex = -1;
} else if (initialSpaceCombIndex !== -1) {
// merge initial space combinator (classified as combinator before a comment)
const initialSpace = ast[initialSpaceCombIndex] as Combinator;
const spaceValue =
initialSpace.before + initialSpace.value + initialSpace.after;
if (initialSpaceCombIndex === 0) {
// merge to beginning of selector
currentSelector.before += spaceValue;
} else {
// merge to the next comment
const nodeAfterInitial = ast[initialSpaceCombIndex + 1];
if (nodeAfterInitial?.type === `comment`) {
nodeAfterInitial.before += spaceValue;
nodeAfterInitial.start = initialSpace.start;
} else {
// shouldn't happen as initial space is considered as a combinator
// only when a comment is following it and before
}
}
ast.splice(initialSpaceCombIndex, 1);
initialSpaceCombIndex = -1;
// add combinator
lastCombinatorAst = createCombinatorAst(next);
lastAst = lastCombinatorAst;
ast.push(lastCombinatorAst);
} else {
// add invalid combinator
lastCombinatorAst = createCombinatorAst(next);
lastCombinatorAst.invalid = true;
lastAst = lastCombinatorAst;
ast.push(lastCombinatorAst);
}
} else if (isComment(next.type)) {
lastAst = createCommentAst(next);
ast.push(lastAst);
} else {
// shouldn't happen as initial space is considered as a combinator
// only when a comment is following it and before
break;
}
}
ast.splice(initialSpaceCombIndex, 1);
initialSpaceCombIndex = -1;
// add combinator
lastCombinatorAst = createCombinatorAst(next);
lastAst = lastCombinatorAst;
ast.push(lastCombinatorAst);
next = s.next();
}
// put back any unrelated token
if (next && !isCombinatorToken(next)) {
s.back();
}
} else if (token.type === 'text') {
ast.push({
type: 'type',
value: token.value,
start: token.start,
end: token.end,
});
} else if (token.type === '#') {
t = s.take('text');
ast.push({
type: 'id',
value: t?.value ?? '',
start: token.start,
end: t?.end ?? token.end,
});
} else if (token.type === '*') {
ast.push({
type: 'universal',
value: '*',
start: token.start,
end: token.end,
});
} else if (token.type === '|') {
// search backwards compatible namespace in ast
let prevAst: NamespacedNode | undefined;
let prevInvalidAst: SelectorNode | undefined;
const beforeComments: Comment[] = [];
for (let i = ast.length - 1; i >= 0; --i) {
const current = ast[i];
if (isNamespacedAst(current)) {
if (current.namespace) {
// already namespaced
prevInvalidAst = current;
} else {
// merge with previous
prevAst = current;
}
break;
} else if (
current.type === `comment` &&
current.before === `` &&
current.after === ``
) {
beforeComments.unshift(current);
} else {
prevInvalidAst = current;
break;
}
}
// search forward target token
let target: CSSSelectorToken | undefined;
let searchIndex = 1;
const potentialAfterComments: CSSSelectorToken[] = [];
// eslint-disable-next-line no-constant-condition
while (true) {
const nextToken = s.peek(searchIndex);
if (isComment(nextToken.type)) {
potentialAfterComments.push(nextToken);
} else if (isNamespacedToken(nextToken)) {
target = nextToken;
break;
} else {
// space or end of tokens
break;
}
searchIndex++;
}
// create/update ast
const validNamespace = !prevInvalidAst;
const validTarget = !!target;
const type = target?.type === `*` ? `universal` : `type`;
let invalid: NonNullable<Namespace['invalid']> = ``;
// remove before/after pipe comments
if (validNamespace) {
ast.splice(ast.length - beforeComments.length, beforeComments.length);
} else {
// add invalid combinator
lastCombinatorAst = createCombinatorAst(next);
lastCombinatorAst.invalid = true;
lastAst = lastCombinatorAst;
ast.push(lastCombinatorAst);
invalid = `namespace`;
}
} else if (isComment(next.type)) {
lastAst = createCommentAst(next);
ast.push(lastAst);
} else {
break;
}
next = s.next();
}
// put back any unrelated token
if (next && !isCombinatorToken(next)) {
s.back();
}
} else if (token.type === "text") {
ast.push({
type: "type",
value: token.value,
start: token.start,
end: token.end,
});
} else if (token.type === "#") {
t = s.take("text");
ast.push({
type: "id",
value: t?.value ?? "",
start: token.start,
end: t?.end ?? token.end,
});
} else if (token.type === "*") {
ast.push({
type: "universal",
value: "*",
start: token.start,
end: token.end,
});
} else if (token.type === "|") {
// search backwards compatible namespace in ast
let prevAst: NamespacedNode | undefined;
let prevInvalidAst: SelectorNode | undefined;
const beforeComments: Comment[] = [];
for (let i = ast.length - 1; i >= 0; --i) {
const current = ast[i];
if (isNamespacedAst(current)) {
if (current.namespace) {
// already namespaced
prevInvalidAst = current;
if (validTarget) {
potentialAfterComments.forEach(() => s.next());
s.next();
} else {
// merge with previous
prevAst = current;
invalid = invalid ? `namespace,target` : `target`;
}
break;
} else if (
current.type === `comment` &&
current.before === `` &&
current.after === ``
) {
beforeComments.unshift(current);
} else {
prevInvalidAst = current;
break;
}
}
// search forward target token
let target: CSSSelectorToken | undefined;
let searchIndex = 1;
const potentialAfterComments: CSSSelectorToken[] = [];
// eslint-disable-next-line no-constant-condition
while (true) {
const nextToken = s.peek(searchIndex);
if (isComment(nextToken.type)) {
potentialAfterComments.push(nextToken);
} else if (isNamespacedToken(nextToken)) {
target = nextToken;
break;
} else {
// space or end of tokens
break;
}
searchIndex++;
}
// create/update ast
const validNamespace = !prevInvalidAst;
const validTarget = !!target;
const type = target?.type === `*` ? `universal` : `type`;
let invalid: NonNullable<Namespace["invalid"]> = ``;
// remove before/after pipe comments
if (validNamespace) {
ast.splice(ast.length - beforeComments.length, beforeComments.length);
} else {
invalid = `namespace`;
}
if (validTarget) {
potentialAfterComments.forEach(() => s.next());
s.next();
} else {
invalid = invalid ? `namespace,target` : `target`;
}
// create new ast or modify the prev
const nsAst: NamespacedNode =
prevAst ||
({
type,
value: ``,
start: token.start,
end: target?.end || token.end,
} as NamespacedNode);
nsAst.type = type;
nsAst.namespace = {
value: prevAst?.value || ``,
beforeComments: validNamespace ? beforeComments : [],
afterComments: validTarget
? potentialAfterComments.map(createCommentAst)
: [],
};
nsAst.value = target?.value || ``;
nsAst.end = target?.end || token.end;
// set invalid
if (invalid) {
nsAst.namespace.invalid = invalid;
}
// add ast if not modified
if (!prevAst) {
ast.push(nsAst);
}
} else if (token.type === "(") {
const prev = last(ast);
const res: SelectorList = [];
// handle nth selector
if (
prev &&
prev.type === `pseudo_class` &&
NthParser.isNthPseudoClass(prev.value) &&
s.peek().type !== `)`
) {
// collect "An+B of" expression
const nthSelector = createEmptyNth();
nthSelector.start = s.peek().start;
res.push(nthSelector as unknown as Selector);
const nthParser = new NthParser(nthSelector, s);
s.run(
(token) => {
if (nthParser.state === `selector`) {
// got to selector, push back and stop
s.back();
return false;
}
return nthParser.handleToken(token);
},
nthSelector,
source
);
// setup next selector
if (s.peek().type !== `)`) {
nthSelector.end = last(nthSelector.nodes)?.end || nthSelector.start;
// add "of" selector
// create new ast or modify the prev
const nsAst: NamespacedNode =
prevAst ||
({
type,
value: ``,
start: token.start,
end: target?.end || token.end,
} as NamespacedNode);
nsAst.type = type;
nsAst.namespace = {
value: prevAst?.value || ``,
beforeComments: validNamespace ? beforeComments : [],
afterComments: validTarget ? potentialAfterComments.map(createCommentAst) : [],
};
nsAst.value = target?.value || ``;
nsAst.end = target?.end || token.end;
// set invalid
if (invalid) {
nsAst.namespace.invalid = invalid;
}
// add ast if not modified
if (!prevAst) {
ast.push(nsAst);
}
} else if (token.type === '(') {
const prev = last(ast);
const res: SelectorList = [];
// handle nth selector
if (
prev &&
prev.type === `pseudo_class` &&
NthParser.isNthPseudoClass(prev.value) &&
s.peek().type !== `)`
) {
// collect "An+B of" expression
const nthSelector = createEmptyNth();
nthSelector.start = s.peek().start;
res.push(nthSelector as unknown as Selector);
const nthParser = new NthParser(nthSelector, s);
s.run(
(token) => {
if (nthParser.state === `selector`) {
// got to selector, push back and stop
s.back();
return false;
}
return nthParser.handleToken(token);
},
nthSelector,
source
);
// setup next selector
if (s.peek().type !== `)`) {
nthSelector.end = last(nthSelector.nodes)?.end || nthSelector.start;
// add "of" selector
const newSelector = createEmptySelector();
newSelector.start = nthSelector.end;
res.push(newSelector);
}
}
// get all tokens until closed
s.run(
(token, selectors) => {
if (token.type === ')') {
const currentSelector = last(selectors);
if (currentSelector) {
currentSelector.end =
last(currentSelector.nodes)?.end ?? currentSelector.start;
}
return false;
}
return handleToken(token, selectors, source, s);
},
res,
source
);
const ended = s.peek(0);
if (
!prev ||
'nodes' in prev ||
prev.type === 'invalid' ||
prev.type === 'combinator' ||
prev.type === 'comment' ||
prev.type === 'nth_step' ||
prev.type === 'nth_dash' ||
prev.type === 'nth_offset' ||
prev.type === 'nth_of' ||
ended.type !== ')'
) {
ast.push({
type: 'invalid',
value: getText([token, ended], undefined, undefined, source),
start: token.start,
end: ended?.end ?? s.peekBack().end,
});
} else {
const lastSelector = last(res);
if (lastSelector) {
trimCombinators(lastSelector);
}
prev.nodes = res;
prev.end = ended.end;
}
} else if (isComment(token.type)) {
ast.push(createCommentAst(token));
} else if (token.type === ',') {
// we ensure at least one selector present
const selector = last(selectors)!;
selector.end = token.start;
trimCombinators(selector);
const newSelector = createEmptySelector();
newSelector.start = nthSelector.end;
res.push(newSelector);
}
}
// get all tokens until closed
s.run(
(token, selectors) => {
if (token.type === ")") {
const currentSelector = last(selectors);
if (currentSelector) {
currentSelector.end =
last(currentSelector.nodes)?.end ?? currentSelector.start;
}
return false;
if (s.done()) {
newSelector.start = token.end;
newSelector.end = token.end;
} else {
newSelector.start = s.peek().start;
}
return handleToken(token, selectors, source, s);
},
res,
source
);
const ended = s.peek(0);
if (
!prev ||
"nodes" in prev ||
prev.type === "invalid" ||
prev.type === "combinator" ||
prev.type === "comment" ||
prev.type === "nth_step" ||
prev.type === "nth_dash" ||
prev.type === "nth_offset" ||
prev.type === "nth_of" ||
ended.type !== ")"
) {
ast.push({
type: "invalid",
value: getText([token, ended], undefined, undefined, source),
start: token.start,
end: ended?.end ?? s.peekBack().end,
});
selectors.push(newSelector);
} else if (token.type === '&') {
ast.push({
type: 'nesting',
value: '&',
start: token.start,
end: token.end,
});
} else {
if (res.length) {
const lastSelector = last(res);
trimCombinators(lastSelector);
}
prev.nodes = res;
prev.end = ended.end;
ast.push({
type: 'invalid',
value: token.value,
start: token.start,
end: token.end,
});
}
} else if (isComment(token.type)) {
ast.push(createCommentAst(token));
} else if (token.type === ",") {
const selector = last(selectors);
selector.end = token.start;
trimCombinators(selector);
const newSelector = createEmptySelector();
if (s.done()) {
newSelector.start = token.end;
newSelector.end = token.end;
} else {
newSelector.start = s.peek().start;
currentSelector.end = last(currentSelector.nodes)?.end ?? currentSelector.start;
trimCombinators(currentSelector);
}
selectors.push(newSelector);
} else if (token.type === "&") {
ast.push({
type: "nesting",
value: "&",
start: token.start,
end: token.end,
});
} else {
ast.push({
type: "invalid",
value: token.value,
start: token.start,
end: token.end,
});
}
if (s.done()) {
currentSelector.end =
last(currentSelector.nodes)?.end ?? currentSelector.start;
trimCombinators(currentSelector);
}
}

@@ -1,131 +0,110 @@

import { NthParser } from "./nth-parser";
import { NthParser } from './nth-parser';
import type {
ImmutableId,
ImmutableAttribute,
ImmutableClass,
ImmutableCombinator,
ImmutableComment,
ImmutableType,
ImmutableInvalid,
ImmutableNth,
ImmutableNthDash,
ImmutableNthOf,
ImmutableNthOffset,
ImmutableNthStep,
ImmutablePseudoClass,
ImmutablePseudoElement,
ImmutableSelector,
ImmutableCompoundSelector,
ImmutableUniversal,
ImmutableNesting,
ImmutableSelectorNode,
ImmutableSelectorList,
ImmutableNthSelectorList,
ImmutableFunctionalSelector,
ImmutableNamespacedNode,
} from "./ast-types";
ImmutableId,
ImmutableAttribute,
ImmutableClass,
ImmutableCombinator,
ImmutableComment,
ImmutableType,
ImmutableInvalid,
ImmutableNth,
ImmutableNthDash,
ImmutableNthOf,
ImmutableNthOffset,
ImmutableNthStep,
ImmutablePseudoClass,
ImmutablePseudoElement,
ImmutableSelector,
ImmutableCompoundSelector,
ImmutableUniversal,
ImmutableNesting,
ImmutableSelectorNode,
ImmutableSelectorList,
ImmutableNthSelectorList,
ImmutableFunctionalSelector,
ImmutableNamespacedNode,
} from './ast-types';
export function stringifySelectorAst(
value:
| ImmutableSelectorNode
| ImmutableSelectorList
| ImmutableNthSelectorList
value: ImmutableSelectorNode | ImmutableSelectorList | ImmutableNthSelectorList
): string {
return "length" in value ? stringifySelectors(value) : stringifyNode(value);
return 'length' in value ? stringifySelectors(value) : stringifyNode(value);
}
type Printers = {
[K in ImmutableSelectorNode as K["type"]]: (node: K) => string;
[K in ImmutableSelectorNode as K['type']]: (node: K) => string;
};
const printers: Printers = {
id: (node: ImmutableId) => `#${node.value}${stringifyNested(node)}`,
class: (node: ImmutableClass) =>
`.${node.dotComments.map(stringifyNode).join("")}${
node.value
}${stringifyNested(node)}`,
type: (node: ImmutableType) =>
`${stringifyNamespace(node)}${node.value}${stringifyNested(node)}`,
combinator: (node: ImmutableCombinator) =>
`${node.before}${node.value}${node.after}`,
attribute: (node: ImmutableAttribute) =>
`[${node.value}]${stringifyNested(node)}`,
pseudo_class: (node: ImmutablePseudoClass) =>
`:${node.colonComments.map(stringifyNode).join("")}${
node.value
}${stringifyNested(node)}`,
pseudo_element: (node: ImmutablePseudoElement) =>
`:${node.colonComments.first
.map(stringifyNode)
.join("")}:${node.colonComments.second.map(stringifyNode).join("")}${
node.value
}${stringifyNested(node)}`,
comment: ({ before, value, after }: ImmutableComment) =>
`${before}${value}${after}`,
universal: (node: ImmutableUniversal) =>
`${stringifyNamespace(node)}${node.value}${stringifyNested(node)}`,
nesting: (node: ImmutableNesting) => `${node.value}${stringifyNested(node)}`,
selector: (node: ImmutableSelector) =>
`${node.before}${node.nodes.map(stringifyNode).join("")}${node.after}`,
compound_selector: (node: ImmutableCompoundSelector) =>
`${node.before}${node.nodes.map(stringifyNode).join("")}${node.after}`,
invalid: (node: ImmutableInvalid) => node.value,
nth: (node: ImmutableNth) =>
`${node.before}${node.nodes.map(stringifyNode).join("")}${node.after}`,
nth_step: ({ before, value, after }: ImmutableNthStep) =>
`${before}${value}${after}`,
nth_dash: ({ before, value, after }: ImmutableNthDash) =>
`${before}${value}${after}`,
nth_offset: ({ before, value, after }: ImmutableNthOffset) =>
`${before}${value}${after}`,
nth_of: ({ before, value, after }: ImmutableNthOf) =>
`${before}${value}${after}`,
id: (node: ImmutableId) => `#${node.value}${stringifyNested(node)}`,
class: (node: ImmutableClass) =>
`.${node.dotComments.map(stringifyNode).join('')}${node.value}${stringifyNested(node)}`,
type: (node: ImmutableType) =>
`${stringifyNamespace(node)}${node.value}${stringifyNested(node)}`,
combinator: (node: ImmutableCombinator) => `${node.before}${node.value}${node.after}`,
attribute: (node: ImmutableAttribute) => `[${node.value}]${stringifyNested(node)}`,
pseudo_class: (node: ImmutablePseudoClass) =>
`:${node.colonComments.map(stringifyNode).join('')}${node.value}${stringifyNested(node)}`,
pseudo_element: (node: ImmutablePseudoElement) =>
`:${node.colonComments.first.map(stringifyNode).join('')}:${node.colonComments.second
.map(stringifyNode)
.join('')}${node.value}${stringifyNested(node)}`,
comment: ({ before, value, after }: ImmutableComment) => `${before}${value}${after}`,
universal: (node: ImmutableUniversal) =>
`${stringifyNamespace(node)}${node.value}${stringifyNested(node)}`,
nesting: (node: ImmutableNesting) => `${node.value}${stringifyNested(node)}`,
selector: (node: ImmutableSelector) =>
`${node.before}${node.nodes.map(stringifyNode).join('')}${node.after}`,
compound_selector: (node: ImmutableCompoundSelector) =>
`${node.before}${node.nodes.map(stringifyNode).join('')}${node.after}`,
invalid: (node: ImmutableInvalid) => node.value,
nth: (node: ImmutableNth) =>
`${node.before}${node.nodes.map(stringifyNode).join('')}${node.after}`,
nth_step: ({ before, value, after }: ImmutableNthStep) => `${before}${value}${after}`,
nth_dash: ({ before, value, after }: ImmutableNthDash) => `${before}${value}${after}`,
nth_offset: ({ before, value, after }: ImmutableNthOffset) => `${before}${value}${after}`,
nth_of: ({ before, value, after }: ImmutableNthOf) => `${before}${value}${after}`,
};
function stringifyNode(node: ImmutableSelectorNode): string {
return printers[node.type]?.(node as never) ?? "";
return printers[node.type]?.(node as never) ?? '';
}
function stringifySelectors(
selectors: ReadonlyArray<ImmutableSelector | ImmutableNth>
) {
const result: string[] = [];
for (const node of selectors) {
result.push(stringifyNode(node));
}
return result.join(`,`);
function stringifySelectors(selectors: ReadonlyArray<ImmutableSelector | ImmutableNth>) {
const result: string[] = [];
for (const node of selectors) {
result.push(stringifyNode(node));
}
return result.join(`,`);
}
function stringifyNested(node: ImmutableFunctionalSelector): string {
if ("nodes" in node) {
if (node.nodes?.length) {
if (
node.type === `pseudo_class` &&
NthParser.isNthPseudoClass(node.value)
) {
const [nthNode, ...selectors] = node.nodes;
return `(${stringifyNode(nthNode)}${stringifySelectors(selectors)})`;
} else {
return `(${stringifySelectors(node.nodes)})`;
}
} else {
return `()`;
if ('nodes' in node) {
if (node.nodes?.length) {
if (node.type === `pseudo_class` && NthParser.isNthPseudoClass(node.value)) {
const [nthNode, ...selectors] = node.nodes;
return `(${stringifyNode(nthNode)}${stringifySelectors(selectors)})`;
} else {
return `(${stringifySelectors(node.nodes)})`;
}
} else {
return `()`;
}
}
}
return "";
return '';
}
function stringifyNamespace({ namespace }: ImmutableNamespacedNode): string {
let ns = ``;
if (namespace) {
ns += namespace.value;
for (const comment of namespace.beforeComments) {
ns += printers.comment(comment);
let ns = ``;
if (namespace) {
ns += namespace.value;
for (const comment of namespace.beforeComments) {
ns += printers.comment(comment);
}
ns += `|`;
for (const comment of namespace.afterComments) {
ns += printers.comment(comment);
}
}
ns += `|`;
for (const comment of namespace.afterComments) {
ns += printers.comment(comment);
}
}
return ns;
return ns;
}
import {
tokenize,
isStringDelimiter,
isWhitespace,
createToken,
getJSCommentStartType,
getMultilineCommentStartType,
isCommentEnd,
getUnclosedComment,
} from "@tokey/core";
import type { Token, Descriptors } from "@tokey/core";
tokenize,
isStringDelimiter,
isWhitespace,
createToken,
getJSCommentStartType,
getMultilineCommentStartType,
isCommentEnd,
getUnclosedComment,
} from '@tokey/core';
import type { Token, Descriptors } from '@tokey/core';
type Delimiters =
| "["
| "]"
| "("
| ")"
| ","
| "*"
| "|"
| ":"
| "."
| "#"
| ">"
| "~"
| "+"
| "{"
| "}"
| "&";
| '['
| ']'
| '('
| ')'
| ','
| '*'
| '|'
| ':'
| '.'
| '#'
| '>'
| '~'
| '+'
| '{'
| '}'
| '&';
export type CSSSelectorToken = Token<Descriptors | Delimiters>;
export function tokenizeSelector(
source: string,
options: { offset?: number } = {}
) {
const parseLineComments = false; // why would that be a choice?
return tokenize<CSSSelectorToken>(source, {
isDelimiter,
isStringDelimiter(char: string, previousChar: string) {
return previousChar !== `\\` && isStringDelimiter(char);
},
isWhitespace,
shouldAddToken: () => true,
createToken,
getCommentStartType: parseLineComments
? getJSCommentStartType
: getMultilineCommentStartType,
isCommentEnd,
getUnclosedComment,
offset: options.offset,
});
export function tokenizeSelector(source: string, options: { offset?: number } = {}) {
const parseLineComments = false; // why would that be a choice?
return tokenize<CSSSelectorToken>(source, {
isDelimiter,
isStringDelimiter(char: string, previousChar: string) {
return previousChar !== `\\` && isStringDelimiter(char);
},
isWhitespace,
shouldAddToken: () => true,
createToken,
getCommentStartType: parseLineComments
? getJSCommentStartType
: getMultilineCommentStartType,
isCommentEnd,
getUnclosedComment,
offset: options.offset,
});
}
const isDelimiter = (char: string, previousChar: string) =>
previousChar !== "\\" &&
(char === "[" ||
char === "]" ||
char === "(" ||
char === ")" ||
char === "," ||
char === "*" ||
char === "|" ||
char === ":" ||
char === "." ||
char === "#" ||
char === ">" ||
char === "~" ||
char === "+" ||
char === "{" ||
char === "}" ||
char === "&");
previousChar !== '\\' &&
(char === '[' ||
char === ']' ||
char === '(' ||
char === ')' ||
char === ',' ||
char === '*' ||
char === '|' ||
char === ':' ||
char === '.' ||
char === '#' ||
char === '>' ||
char === '~' ||
char === '+' ||
char === '{' ||
char === '}' ||
char === '&');
/**
* convert object|array into a replica deep readonly type
*/
export type Immutable<T> = T extends Function
? T
: T extends object
? ImmutableMap<T>
: T;
export type Immutable<T> = T extends Function ? T : T extends object ? ImmutableMap<T> : T;
export type ImmutableMap<T> = {
readonly [P in keyof T]: Immutable<T[P]>;
readonly [P in keyof T]: Immutable<T[P]>;
};

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc