Port to Typescript (#144)

pull/228/head
Felix Böhm 4 years ago committed by GitHub
parent edbdbe5fd4
commit d379a7c6e5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,11 +1,5 @@
{
"extends": [
"eslint:recommended",
// "plugin:@typescript-eslint/eslint-recommended",
// "plugin:@typescript-eslint/recommended",
"prettier"
// "prettier/@typescript-eslint"
],
"extends": ["eslint:recommended", "prettier"],
"env": {
"node": true,
"es6": true
@ -16,15 +10,53 @@
"dot-notation": 2,
"no-var": 2,
"prefer-const": 2,
"prefer-arrow-callback": 2,
"prefer-arrow-callback": [2, { "allowNamedFunctions": true }],
"arrow-body-style": [2, "as-needed"],
"object-shorthand": 2,
"prefer-template": 2,
"one-var": [2, "never"],
"prefer-destructuring": [2, { "object": true }],
"@typescript-eslint/explicit-function-return-type": 0,
"@typescript-eslint/explicit-member-accessibility": 0
// "@typescript-eslint/no-use-before-define": [2, { "functions": false }]
}
"capitalized-comments": 2,
"multiline-comment-style": [2, "starred-block"],
"spaced-comment": 2,
"yoda": [2, "never"],
"curly": [2, "multi-line"],
"no-else-return": 2
},
"overrides": [
{
"files": "*.ts",
"extends": [
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended",
"prettier/@typescript-eslint"
],
"parserOptions": {
"sourceType": "module",
"project": "./tsconfig.eslint.json"
},
"rules": {
"@typescript-eslint/prefer-for-of": 0,
"@typescript-eslint/member-ordering": 0,
"@typescript-eslint/explicit-function-return-type": 0,
"@typescript-eslint/no-unused-vars": 0,
"@typescript-eslint/no-use-before-define": [
2,
{ "functions": false }
],
"@typescript-eslint/consistent-type-definitions": [
2,
"interface"
],
"@typescript-eslint/prefer-function-type": 2,
"@typescript-eslint/no-unnecessary-type-arguments": 2,
"@typescript-eslint/prefer-string-starts-ends-with": 2,
"@typescript-eslint/prefer-readonly": 2,
"@typescript-eslint/prefer-includes": 2,
"@typescript-eslint/no-unnecessary-condition": 2,
"@typescript-eslint/switch-exhaustiveness-check": 2,
"@typescript-eslint/prefer-nullish-coalescing": 2
}
}
]
}

1
.gitignore vendored

@ -1,3 +1,4 @@
.vscode/
node_modules/
coverage/
lib/

@ -0,0 +1,5 @@
{
"require": "ts-node/register",
"check-leaks": true,
"reporter": "spec"
}

@ -1,5 +1,4 @@
language: node_js
node_js:
- lts/*
script: npm run coveralls
after_success: npm run coverage

@ -62,7 +62,7 @@ const CSSselect = require("css-select");
**Note:** css-select throws errors when invalid selectors are passed to it, contrary to the behavior in browsers, which swallow them. This is done to aid with writing css selectors, but can be unexpected when processing arbitrary strings.
#### `CSSselect(query, elems, options)`
#### `CSSselect.selectAll(query, elems, options)`
Queries `elems`, returns an array containing all matches.
@ -70,7 +70,7 @@ Queries `elems`, returns an array containing all matches.
- `elems` can be either an array of elements, or a single element. If it is an element, its children will be queried.
- `options` is described below.
Aliases: `CSSselect.selectAll(query, elems)`, `CSSselect.iterate(query, elems)`.
Aliases: `default` export, `CSSselect.iterate(query, elems)`.
#### `CSSselect.compile(query)`
@ -82,7 +82,8 @@ Tests whether or not an element is matched by `query`. `query` can be either a C
#### `CSSselect.selectOne(query, elems, options)`
Arguments are the same as for `CSSselect(query, elems)`. Only returns the first match, or `null` if there was no match.
Arguments are the same as for `CSSselect.selectAll(query, elems)`.
Only returns the first match, or `null` if there was no match.
### Options
@ -101,63 +102,87 @@ getSiblings, getText, hasAttrib, removeSubsets, findAll, findOne
```
The method signature notation used below should be fairly intuitive - if not,
see the [`rtype`](https://github.com/ericelliott/rtype) or
[`TypeScript`](https://www.typescriptlang.org/) docs, as it is very similar to
both of those. You may also want to look at -[`domutils`](https://github.com/fb55/domutils) to see the default
-implementation, or at -[`css-select-browser-adapter`](https://github.com/nrkn/css-select-browser-adapter/blob/master/index.js)
-for an implementation backed by the DOM.
see the [`TypeScript`](https://www.typescriptlang.org/) docs, as it is very similar to both of those.
You may also want to look at [`domutils`](https://github.com/fb55/domutils) to see the default
implementation, or at [`css-select-browser-adapter`](https://github.com/nrkn/css-select-browser-adapter/blob/master/index.js)
for an implementation backed by the DOM.
```ts
{
// is the node a tag?
isTag: ( node:Node ) => isTag:Boolean,
// does at least one of passed element nodes pass the test predicate?
existsOne: ( test:Predicate, elems:[ElementNode] ) => existsOne:Boolean,
// get the attribute value
getAttributeValue: ( elem:ElementNode, name:String ) => value:String,
// get the node's children
getChildren: ( node:Node ) => children:[Node],
// get the name of the tag
getName: ( elem:ElementNode ) => tagName:String,
// get the parent of the node
getParent: ( node:Node ) => parentNode:Node,
/*
get the siblings of the node. Note that unlike jQuery's `siblings` method,
this is expected to include the current node as well
*/
getSiblings: ( node:Node ) => siblings:[Node],
// get the text content of the node, and its children if it has any
getText: ( node:Node ) => text:String,
// does the element have the named attribute?
hasAttrib: ( elem:ElementNode, name:String ) => hasAttrib:Boolean,
// takes an array of nodes, and removes any duplicates, as well as any nodes
// whose ancestors are also in the array
removeSubsets: ( nodes:[Node] ) => unique:[Node],
// finds all of the element nodes in the array that match the test predicate,
// as well as any of their children that match it
findAll: ( test:Predicate, nodes:[Node] ) => elems:[ElementNode],
// finds the first node in the array that matches the test predicate, or one
// of its children
findOne: ( test:Predicate, elems:[ElementNode] ) => findOne:ElementNode,
/*
The adapter can also optionally include an equals method, if your DOM
structure needs a custom equality test to compare two objects which refer
to the same underlying node. If not provided, `css-select` will fall back to
`a === b`.
*/
equals: ( a:Node, b:Node ) => Boolean
interface Adapter<Node, ElementNode extends Node> {
/**
* Is the node a tag?
*/
isTag: (node: Node) => node is ElementNode;
/**
* Does at least one of passed element nodes pass the test predicate?
*/
existsOne: (test: Predicate<ElementNode>, elems: Node[]) => boolean;
/**
* Get the attribute value.
*/
getAttributeValue: (elem: ElementNode, name: string) => string | undefined;
/**
* Get the node's children
*/
getChildren: (node: Node) => Node[];
/**
* Get the name of the tag
*/
getName: (elem: ElementNode) => string;
/**
* Get the parent of the node
*/
getParent: (node: ElementNode) => ElementNode | null;
/*
*Get the siblings of the node. Note that unlike jQuery's `siblings` method,
*this is expected to include the current node as well
*/
getSiblings: (node: Node) => Node[];
/*
* Get the text content of the node, and its children if it has any.
*/
getText: (node: Node) => string;
/**
* Does the element have the named attribute?
*/
hasAttrib: (elem: ElementNode, name: string) => boolean;
/**
* Takes an array of nodes, and removes any duplicates, as well as any
* nodes whose ancestors are also in the array.
*/
removeSubsets: (nodes: Node[]) => Node[];
/**
* Finds all of the element nodes in the array that match the test predicate,
* as well as any of their children that match it.
*/
findAll: (test: Predicate<ElementNode>, nodes: Node[]) => ElementNode[];
/**
* Finds the first node in the array that matches the test predicate, or one
* of its children.
*/
findOne: (
test: Predicate<ElementNode>,
elems: Node[]
) => ElementNode | null;
/**
* The adapter can also optionally include an equals method, if your DOM
* structure needs a custom equality test to compare two objects which refer
* to the same underlying node. If not provided, `css-select` will fall back to
* `a === b`.
*/
equals?: (a: Node, b: Node) => boolean;
}
```
@ -199,7 +224,7 @@ _As defined by CSS 4 and / or jQuery._
- `:enabled`, `:disabled`
- `:required`, `:optional`
- `:header`, `:button`, `:input`, `:text`, `:checkbox`, `:file`, `:password`, `:reset`, `:radio` etc. \*
- `:matches` \*
- `:matches`, `:is` \*
**\***: Not part of CSS3

228
index.d.ts vendored

@ -1,228 +0,0 @@
export = CSSselect;
/**
* Alias for CSSselect.selectAll(query, elems, options).
* @see [CSSselect.compile] for supported selector queries.
*/
declare function CSSselect<Node, ElementNode extends Node>(
query: CSSselect.Query,
elems: Array<ElementNode> | ElementNode,
options?: CSSselect.Options<Node, ElementNode>
): Array<ElementNode>;
declare namespace CSSselect {
type Predicate<Value> = (v: Value) => boolean;
interface Adapter<Node, ElementNode extends Node> {
/**
* is the node a tag?
*/
isTag(node: Node): node is ElementNode;
/**
* Does at least one of passed element nodes pass the test predicate?
*/
existsOne(
test: Predicate<ElementNode>,
elems: Array<ElementNode>
): boolean;
/**
* get the attribute value.
*/
getAttributeValue(elem: ElementNode, name: string): string;
/**
* get the node's children
*/
getChildren(node: Node): Array<Node>;
/**
* get the name of the tag
*/
getName(elem: ElementNode): string;
/**
* get the parent of the node
*/
getParent(node: Node): Node;
/*
Get the siblings of the node. Note that unlike jQuery's `siblings` method,
this is expected to include the current node as well
*/
getSiblings(node: Node): Array<Node>;
/*
* Get the text content of the node, and its children if it has any.
*/
getText(node: Node): string;
/**
* Does the element have the named attribute?
*/
hasAttrib(elem: ElementNode, name: string): boolean;
/**
* takes an array of nodes, and removes any duplicates, as well as any
* nodes whose ancestors are also in the array.
*/
removeSubsets(nodes: Array<Node>): Array<Node>;
/**
* finds all of the element nodes in the array that match the test predicate,
* as well as any of their children that match it.
*/
findAll(
test: Predicate<ElementNode>,
nodes: Array<Node>
): Array<ElementNode>;
/**
* finds the first node in the array that matches the test predicate, or one
* of its children.
*/
findOne(
test: Predicate<ElementNode>,
elems: Array<ElementNode>
): ElementNode | undefined;
/**
The adapter can also optionally include an equals method, if your DOM
structure needs a custom equality test to compare two objects which refer
to the same underlying node. If not provided, `css-select` will fall back to
`a === b`.
*/
equals?: (a: Node, b: Node) => boolean;
/**
* is the element in hovered state?
*/
isHovered?: (elem: ElementNode) => boolean;
/**
* is the element in visited state?
*/
isVisited?: (elem: ElementNode) => boolean;
/**
* is the element in active state?
*/
isActive?: (elem: ElementNode) => boolean;
}
// TODO default types to the domutil/httpparser2 types
interface Options<Node, ElementNode extends Node> {
/**
* When enabled, tag names will be case-sensitive. Default: false.
*/
xmlMode?: boolean;
/**
* Limits the module to only use CSS3 selectors. Default: false.
*/
strict?: boolean;
/**
* The last function in the stack, will be called with the last element
* that's looked at. Should return true.
*/
rootFunc?: (element: ElementNode) => true;
/**
* The adapter to use when interacting with the backing DOM structure. By
* default it uses domutils.
*/
adapter?: Adapter<Node, ElementNode>;
}
type CompiledQuery = (node: any) => boolean;
type Query = string | CompiledQuery;
/**
* Compiles the query, returns a function.
*
* Supported simple selectors:
* * Universal (*)
* * Tag (<tagname>)
* * Attribute ([attr=foo]), with supported comparisons:
* * [attr] (existential)
* * =
* * ~=
* * |=
* * *=
* * ^=
* * $=
* * !=
* * Can be case insensitive (E.g. [attr=foo i])
* * Pseudos:
* * :not
* * :root
* * :empty
* * :[first|last]-child[-of-type]
* * :only-of-type, :only-child
* * :nth-[last-]child[-of-type]
* * :link, :visited (the latter doesn't match any elements)
* * :checked
* * :enabled, :disabled
* * :required, :optional
* * Nonstandard Pseudos (available when strict mode is not enabled):
* * `:contains`
* * `:icontains` (case-insensitive version of :contains)
* * `:has`
* * `:parent`
* * `:selected`
* * `:header, :button, :input, :text, :checkbox, :file, :password, :reset, :radio etc.
* * :matches
*
* Supported Combinators:
*
* * Descendant (` `)
* * Child (`>`)
* * Parent (`<`) (when strict mode is not enabled)
* * Sibling (`~`)
* * Adjacent (`+`)
*/
function compile(query: string): CompiledQuery;
/**
* @template Node The generic Node type for the DOM adapter being used.
* @template ElementNode The Node type for elements for the DOM adapter being used.
* @param elems Elements to query. If it is an element, its children will be queried..
* @param query can be either a CSS selector string or a compiled query function.
* @param [options] options for querying the document.
* @see CSSselect.compile for supported selector queries.
* @returns All matching elements.
*/
function selectAll<Node, ElementNode extends Node>(
query: Query,
elems: Array<ElementNode> | ElementNode,
options?: Options<Node, ElementNode>
): Array<ElementNode>;
/**
* @template Node The generic Node type for the DOM adapter being used.
* @template ElementNode The Node type for elements for the DOM adapter being used.
* @param elems Elements to query. If it is an element, its children will be queried..
* @param query can be either a CSS selector string or a compiled query function.
* @param [options] options for querying the document.
* @see CSSselect.compile for supported selector queries.
* @returns the first match, or null if there was no match.
*/
function selectOne<Node, ElementNode extends Node>(
query: Query,
elems: Array<ElementNode> | ElementNode,
options?: Options<Node, ElementNode>
): ElementNode | null;
/**
* Tests whether or not an element is matched by query.
*
* @template Node The generic Node type for the DOM adapter being used.
* @template ElementNode The Node type for elements for the DOM adapter being used.
* @param elem The element to test if it matches the query.
* @param query can be either a CSS selector string or a compiled query function.
* @param [options] options for querying the document.
* @see CSSselect.compile for supported selector queries.
* @returns
*/
function is<Node, ElementNode extends Node>(
elem: ElementNode,
query: Query,
options?: Options<Node, ElementNode>
): boolean;
}

@ -1,100 +0,0 @@
"use strict";
module.exports = CSSselect;
const DomUtils = require("domutils");
const { falseFunc } = require("boolbase");
const compileRaw = require("./lib/compile.js");
function wrapCompile(func) {
return function addAdapter(selector, options = {}, context) {
options.adapter = options.adapter || DomUtils;
return func(selector, options, context);
};
}
const compile = wrapCompile(compileRaw);
const compileUnsafe = wrapCompile(compileRaw.compileUnsafe);
function getSelectorFunc(searchFunc) {
return function select(query, elems, options = {}) {
options.adapter = options.adapter || DomUtils;
if (typeof query !== "function") {
query = compileUnsafe(query, options, elems);
}
if (query.shouldTestNextSiblings) {
elems = appendNextSiblings(
options.context || elems,
options.adapter
);
}
if (!Array.isArray(elems)) elems = options.adapter.getChildren(elems);
else elems = options.adapter.removeSubsets(elems);
return searchFunc(query, elems, options);
};
}
function getNextSiblings(elem, adapter) {
let siblings = adapter.getSiblings(elem);
if (!Array.isArray(siblings)) return [];
siblings = siblings.slice(0);
while (siblings.shift() !== elem);
return siblings;
}
function appendNextSiblings(elems, adapter) {
// Order matters because jQuery seems to check the children before the siblings
if (!Array.isArray(elems)) elems = [elems];
const newElems = elems.slice(0);
for (let i = 0, len = elems.length; i < len; i++) {
const nextSiblings = getNextSiblings(newElems[i], adapter);
newElems.push.apply(newElems, nextSiblings);
}
return newElems;
}
const selectAll = getSelectorFunc((query, elems, options) =>
query === falseFunc || !elems || elems.length === 0
? []
: options.adapter.findAll(query, elems)
);
const selectOne = getSelectorFunc((query, elems, options) =>
query === falseFunc || !elems || elems.length === 0
? null
: options.adapter.findOne(query, elems)
);
function is(elem, query, options = {}) {
options.adapter = options.adapter || DomUtils;
return (typeof query === "function" ? query : compile(query, options))(
elem
);
}
/*
the exported interface
*/
function CSSselect(query, elems, options) {
return selectAll(query, elems, options);
}
CSSselect.compile = compile;
CSSselect.filters = compileRaw.Pseudos.filters;
CSSselect.pseudos = compileRaw.Pseudos.pseudos;
CSSselect.selectAll = selectAll;
CSSselect.selectOne = selectOne;
CSSselect.is = is;
//legacy methods (might be removed)
CSSselect.parse = compile;
CSSselect.iterate = selectAll;
//hooks
CSSselect._compileUnsafe = compileUnsafe;
CSSselect._compileToken = compileRaw.compileToken;

@ -1,214 +0,0 @@
/*
compiles a selector to an executable function
*/
module.exports = compile;
const { parse } = require("css-what");
const { trueFunc, falseFunc } = require("boolbase");
const sortRules = require("./sort.js");
const procedure = require("./procedure.json");
const Rules = require("./general.js");
const Pseudos = require("./pseudos.js");
const { filters } = Pseudos;
function compile(selector, options, context) {
const next = compileUnsafe(selector, options, context);
return wrap(next, options);
}
function wrap(next, { adapter }) {
return (elem) => adapter.isTag(elem) && next(elem);
}
function compileUnsafe(selector, options, context) {
const token = parse(selector, options);
return compileToken(token, options, context);
}
function includesScopePseudo(t) {
return (
t.type === "pseudo" &&
(t.name === "scope" ||
(Array.isArray(t.data) &&
t.data.some((data) => data.some(includesScopePseudo))))
);
}
const DESCENDANT_TOKEN = { type: "descendant" };
const FLEXIBLE_DESCENDANT_TOKEN = { type: "_flexibleDescendant" };
const SCOPE_TOKEN = { type: "pseudo", name: "scope" };
const PLACEHOLDER_ELEMENT = {};
//CSS 4 Spec (Draft): 3.3.1. Absolutizing a Scope-relative Selector
//http://www.w3.org/TR/selectors4/#absolutizing
function absolutize(token, { adapter }, context) {
//TODO better check if context is document
const hasContext =
!!context &&
!!context.length &&
context.every(
(e) => e === PLACEHOLDER_ELEMENT || !!adapter.getParent(e)
);
token.forEach((t) => {
if (t.length > 0 && isTraversal(t[0]) && t[0].type !== "descendant") {
//don't return in else branch
} else if (
hasContext &&
!(Array.isArray(t)
? t.some(includesScopePseudo)
: includesScopePseudo(t))
) {
t.unshift(DESCENDANT_TOKEN);
} else {
return;
}
t.unshift(SCOPE_TOKEN);
});
}
function compileToken(token, options, context) {
token = token.filter((t) => t.length > 0);
token.forEach(sortRules);
const isArrayContext = Array.isArray(context);
context = options.context || context;
if (context && !isArrayContext) context = [context];
absolutize(token, options, context);
let shouldTestNextSiblings = false;
const query = token
.map((rules) => {
if (rules[0] && rules[1] && rules[0].name === "scope") {
const ruleType = rules[1].type;
if (isArrayContext && ruleType === "descendant") {
rules[1] = FLEXIBLE_DESCENDANT_TOKEN;
} else if (ruleType === "adjacent" || ruleType === "sibling") {
shouldTestNextSiblings = true;
}
}
return compileRules(rules, options, context);
})
.reduce(reduceRules, falseFunc);
query.shouldTestNextSiblings = shouldTestNextSiblings;
return query;
}
function isTraversal(t) {
return procedure[t.type] < 0;
}
function compileRules(rules, options, context) {
return rules.reduce((func, rule) => {
if (func === falseFunc) return func;
if (!(rule.type in Rules)) {
throw new Error(
`Rule type ${rule.type} is not supported by css-select`
);
}
return Rules[rule.type](func, rule, options, context);
}, options.rootFunc || trueFunc);
}
function reduceRules(a, b) {
if (b === falseFunc || a === trueFunc) {
return a;
}
if (a === falseFunc || b === trueFunc) {
return b;
}
return (elem) => a(elem) || b(elem);
}
function containsTraversal(t) {
return t.some(isTraversal);
}
//:not, :has and :matches have to compile selectors
//doing this in lib/pseudos.js would lead to circular dependencies,
//so we add them here
filters.not = function (next, token, options, context) {
const opts = {
xmlMode: !!options.xmlMode,
strict: !!options.strict,
adapter: options.adapter,
};
if (opts.strict) {
if (token.length > 1 || token.some(containsTraversal)) {
throw new Error(
"complex selectors in :not aren't allowed in strict mode"
);
}
}
const func = compileToken(token, opts, context);
if (func === falseFunc) return next;
if (func === trueFunc) return falseFunc;
return (elem) => !func(elem) && next(elem);
};
filters.has = function (next, token, options) {
const { adapter } = options;
const opts = {
xmlMode: !!options.xmlMode,
strict: !!options.strict,
adapter,
};
//FIXME: Uses an array as a pointer to the current element (side effects)
const context = token.some(containsTraversal)
? [PLACEHOLDER_ELEMENT]
: null;
let func = compileToken(token, opts, context);
if (func === falseFunc) return falseFunc;
if (func === trueFunc) {
return (elem) =>
adapter.getChildren(elem).some(adapter.isTag) && next(elem);
}
func = wrap(func, options);
if (context) {
return (elem) =>
next(elem) &&
// TODO: Add note why this is done
((context[0] = elem),
adapter.existsOne(func, adapter.getChildren(elem)));
}
return (elem) =>
next(elem) && adapter.existsOne(func, adapter.getChildren(elem));
};
filters.matches = function (next, token, options, context) {
const opts = {
xmlMode: options.xmlMode,
strict: options.strict,
adapter: options.adapter,
rootFunc: next,
};
return compileToken(token, opts, context);
};
compile.compileToken = compileToken;
compile.compileUnsafe = compileUnsafe;
compile.Pseudos = Pseudos;

@ -1,104 +0,0 @@
const attributes = require("./attributes.js");
const Pseudos = require("./pseudos");
/*
all available rules
*/
module.exports = {
__proto__: null,
attribute: attributes.compile,
pseudo: Pseudos.compile,
//tags
tag(next, data, { adapter }) {
const { name } = data;
return (elem) => adapter.getName(elem) === name && next(elem);
},
//traversal
descendant(next, data, { adapter }) {
// eslint-disable-next-line no-undef
const isFalseCache =
typeof WeakSet !== "undefined" ? new WeakSet() : null;
return function descendant(elem) {
let found = false;
while (!found && (elem = adapter.getParent(elem))) {
if (!isFalseCache || !isFalseCache.has(elem)) {
found = next(elem);
if (!found && isFalseCache) {
isFalseCache.add(elem);
}
}
}
return found;
};
},
_flexibleDescendant(next, data, { adapter }) {
// Include element itself, only used while querying an array
return function descendant(elem) {
let found = next(elem);
while (!found && (elem = adapter.getParent(elem))) {
found = next(elem);
}
return found;
};
},
parent(next, data, options) {
if (options.strict) {
throw new Error("Parent selector isn't part of CSS3");
}
const { adapter } = options;
return (elem) => adapter.getChildren(elem).some(test);
function test(elem) {
return adapter.isTag(elem) && next(elem);
}
},
child(next, data, { adapter }) {
return function child(elem) {
const parent = adapter.getParent(elem);
return !!parent && next(parent);
};
},
sibling(next, data, { adapter }) {
return function sibling(elem) {
const siblings = adapter.getSiblings(elem);
for (let i = 0; i < siblings.length; i++) {
if (adapter.isTag(siblings[i])) {
if (siblings[i] === elem) break;
if (next(siblings[i])) return true;
}
}
return false;
};
},
adjacent(next, data, { adapter }) {
return function adjacent(elem) {
const siblings = adapter.getSiblings(elem);
let lastElement;
for (let i = 0; i < siblings.length; i++) {
if (adapter.isTag(siblings[i])) {
if (siblings[i] === elem) break;
lastElement = siblings[i];
}
}
return !!lastElement && next(lastElement);
};
},
universal(next) {
return next;
},
};

@ -1,11 +0,0 @@
{
"universal": 50,
"tag": 30,
"attribute": 1,
"pseudo": 0,
"descendant": -1,
"child": -1,
"parent": -1,
"sibling": -1,
"adjacent": -1
}

@ -1,426 +0,0 @@
/*
pseudo selectors
---
they are available in two forms:
* filters called when the selector
is compiled and return a function
that needs to return next()
* pseudos get called on execution
they need to return a boolean
*/
const getNCheck = require("nth-check");
const { trueFunc, falseFunc } = require("boolbase");
const attributes = require("./attributes.js");
const checkAttrib = attributes.rules.equals;
function getAttribFunc(name, value) {
const data = { name, value };
return (next, rule, options) => checkAttrib(next, data, options);
}
function getChildFunc(next, adapter) {
return (elem) => !!adapter.getParent(elem) && next(elem);
}
const filters = {
contains(next, text, { adapter }) {
return (elem) => next(elem) && adapter.getText(elem).includes(text);
},
icontains(next, text, options) {
const itext = text.toLowerCase();
const { adapter } = options;
return (elem) =>
next(elem) && adapter.getText(elem).toLowerCase().includes(itext);
},
//location specific methods
"nth-child"(next, rule, { adapter }) {
const func = getNCheck(rule);
if (func === falseFunc) return func;
if (func === trueFunc) return getChildFunc(next, adapter);
return function nthChild(elem) {
const siblings = adapter.getSiblings(elem);
let pos = 0;
for (let i = 0; i < siblings.length; i++) {
if (adapter.isTag(siblings[i])) {
if (siblings[i] === elem) break;
else pos++;
}
}
return func(pos) && next(elem);
};
},
"nth-last-child"(next, rule, { adapter }) {
const func = getNCheck(rule);
if (func === falseFunc) return func;
if (func === trueFunc) return getChildFunc(next, adapter);
return function nthLastChild(elem) {
const siblings = adapter.getSiblings(elem);
let pos = 0;
for (let i = siblings.length - 1; i >= 0; i--) {
if (adapter.isTag(siblings[i])) {
if (siblings[i] === elem) break;
else pos++;
}
}
return func(pos) && next(elem);
};
},
"nth-of-type"(next, rule, { adapter }) {
const func = getNCheck(rule);
if (func === falseFunc) return func;
if (func === trueFunc) return getChildFunc(next, adapter);
return function nthOfType(elem) {
const siblings = adapter.getSiblings(elem);
let pos = 0;
for (let i = 0; i < siblings.length; i++) {
if (adapter.isTag(siblings[i])) {
if (siblings[i] === elem) break;
if (adapter.getName(siblings[i]) === adapter.getName(elem))
pos++;
}
}
return func(pos) && next(elem);
};
},
"nth-last-of-type"(next, rule, { adapter }) {
const func = getNCheck(rule);
if (func === falseFunc) return func;
if (func === trueFunc) return getChildFunc(next, adapter);
return function nthLastOfType(elem) {
const siblings = adapter.getSiblings(elem);
let pos = 0;
for (let i = siblings.length - 1; i >= 0; i--) {
if (adapter.isTag(siblings[i])) {
if (siblings[i] === elem) break;
if (adapter.getName(siblings[i]) === adapter.getName(elem))
pos++;
}
}
return func(pos) && next(elem);
};
},
//TODO determine the actual root element
root(next, rule, { adapter }) {
return (elem) => !adapter.getParent(elem) && next(elem);
},
scope(next, rule, options, context) {
const { adapter } = options;
if (!context || context.length === 0) {
//equivalent to :root
return filters.root(next, rule, options);
}
function equals(a, b) {
if (typeof adapter.equals === "function")
return adapter.equals(a, b);
return a === b;
}
if (context.length === 1) {
//NOTE: can't be unpacked, as :has uses this for side-effects
return (elem) => equals(context[0], elem) && next(elem);
}
return (elem) => context.indexOf(elem) >= 0 && next(elem);
},
//jQuery extensions (others follow as pseudos)
checkbox: getAttribFunc("type", "checkbox"),
file: getAttribFunc("type", "file"),
password: getAttribFunc("type", "password"),
radio: getAttribFunc("type", "radio"),
reset: getAttribFunc("type", "reset"),
image: getAttribFunc("type", "image"),
submit: getAttribFunc("type", "submit"),
//dynamic state pseudos. These depend on optional Adapter methods.
hover(next, rule, { adapter }) {
if (typeof adapter.isHovered === "function") {
return (elem) => adapter.isHovered(elem) && next(elem);
}
return falseFunc;
},
visited(next, rule, { adapter }) {
if (typeof adapter.isVisited === "function") {
return (elem) => adapter.isVisited(elem) && next(elem);
}
return falseFunc;
},
active(next, rule, { adapter }) {
if (typeof adapter.isActive === "function") {
return (elem) => adapter.isActive(elem) && next(elem);
}
return falseFunc;
},
};
//helper methods
function getFirstElement(elems, adapter) {
for (let i = 0; elems && i < elems.length; i++) {
if (adapter.isTag(elems[i])) return elems[i];
}
}
//while filters are precompiled, pseudos get called when they are needed
const pseudos = {
empty(elem, adapter) {
return !adapter
.getChildren(elem)
.some((elem) => adapter.isTag(elem) || elem.type === "text");
},
"first-child"(elem, adapter) {
return getFirstElement(adapter.getSiblings(elem), adapter) === elem;
},
"last-child"(elem, adapter) {
const siblings = adapter.getSiblings(elem);
for (let i = siblings.length - 1; i >= 0; i--) {
if (siblings[i] === elem) return true;
if (adapter.isTag(siblings[i])) break;
}
return false;
},
"first-of-type"(elem, adapter) {
const siblings = adapter.getSiblings(elem);
for (let i = 0; i < siblings.length; i++) {
if (adapter.isTag(siblings[i])) {
if (siblings[i] === elem) return true;
if (adapter.getName(siblings[i]) === adapter.getName(elem))
break;
}
}
return false;
},
"last-of-type"(elem, adapter) {
const siblings = adapter.getSiblings(elem);
for (let i = siblings.length - 1; i >= 0; i--) {
if (adapter.isTag(siblings[i])) {
if (siblings[i] === elem) return true;
if (adapter.getName(siblings[i]) === adapter.getName(elem))
break;
}
}
return false;
},
"only-of-type"(elem, adapter) {
const siblings = adapter.getSiblings(elem);
for (let i = 0, j = siblings.length; i < j; i++) {
if (adapter.isTag(siblings[i])) {
if (siblings[i] === elem) continue;
if (adapter.getName(siblings[i]) === adapter.getName(elem)) {
return false;
}
}
}
return true;
},
"only-child"(elem, adapter) {
const siblings = adapter.getSiblings(elem);
for (let i = 0; i < siblings.length; i++) {
if (adapter.isTag(siblings[i]) && siblings[i] !== elem)
return false;
}
return true;
},
//:matches(a, area, link)[href]
link(elem, adapter) {
return adapter.hasAttrib(elem, "href");
},
//TODO: :any-link once the name is finalized (as an alias of :link)
//forms
//to consider: :target
//:matches([selected], select:not([multiple]):not(> option[selected]) > option:first-of-type)
selected(elem, adapter) {
if (adapter.hasAttrib(elem, "selected")) return true;
else if (adapter.getName(elem) !== "option") return false;
//the first <option> in a <select> is also selected
const parent = adapter.getParent(elem);
if (
!parent ||
adapter.getName(parent) !== "select" ||
adapter.hasAttrib(parent, "multiple")
) {
return false;
}
const siblings = adapter.getChildren(parent);
let sawElem = false;
for (let i = 0; i < siblings.length; i++) {
if (adapter.isTag(siblings[i])) {
if (siblings[i] === elem) {
sawElem = true;
} else if (!sawElem) {
return false;
} else if (adapter.hasAttrib(siblings[i], "selected")) {
return false;
}
}
}
return sawElem;
},
//https://html.spec.whatwg.org/multipage/scripting.html#disabled-elements
//:matches(
// :matches(button, input, select, textarea, menuitem, optgroup, option)[disabled],
// optgroup[disabled] > option),
// fieldset[disabled] * //TODO not child of first <legend>
//)
disabled(elem, adapter) {
return adapter.hasAttrib(elem, "disabled");
},
enabled(elem, adapter) {
return !adapter.hasAttrib(elem, "disabled");
},
//:matches(:matches(:radio, :checkbox)[checked], :selected) (TODO menuitem)
checked(elem, adapter) {
return (
adapter.hasAttrib(elem, "checked") ||
pseudos.selected(elem, adapter)
);
},
//:matches(input, select, textarea)[required]
required(elem, adapter) {
return adapter.hasAttrib(elem, "required");
},
//:matches(input, select, textarea):not([required])
optional(elem, adapter) {
return !adapter.hasAttrib(elem, "required");
},
//jQuery extensions
//:not(:empty)
parent(elem, adapter) {
return !pseudos.empty(elem, adapter);
},
//:matches(h1, h2, h3, h4, h5, h6)
header: namePseudo(["h1", "h2", "h3", "h4", "h5", "h6"]),
//:matches(button, input[type=button])
button(elem, adapter) {
const name = adapter.getName(elem);
return (
name === "button" ||
(name === "input" &&
adapter.getAttributeValue(elem, "type") === "button")
);
},
//:matches(input, textarea, select, button)
input: namePseudo(["input", "textarea", "select", "button"]),
//input:matches(:not([type!='']), [type='text' i])
text(elem, adapter) {
let attr;
return (
adapter.getName(elem) === "input" &&
(!(attr = adapter.getAttributeValue(elem, "type")) ||
attr.toLowerCase() === "text")
);
},
};
function namePseudo(names) {
if (typeof Set !== "undefined") {
// eslint-disable-next-line no-undef
const nameSet = new Set(names);
return (elem, adapter) => nameSet.has(adapter.getName(elem));
}
return (elem, adapter) => names.indexOf(adapter.getName(elem)) >= 0;
}
function verifyArgs(func, name, subselect) {
if (subselect === null) {
if (func.length > 2 && name !== "scope") {
throw new Error(`pseudo-selector :${name} requires an argument`);
}
} else {
if (func.length === 2) {
throw new Error(
`pseudo-selector :${name} doesn't have any arguments`
);
}
}
}
//FIXME this feels hacky
const re_CSS3 = /^(?:(?:nth|last|first|only)-(?:child|of-type)|root|empty|(?:en|dis)abled|checked|not)$/;
module.exports = {
compile(next, data, options, context) {
const { name } = data;
const subselect = data.data;
const { adapter } = options;
if (options.strict && !re_CSS3.test(name)) {
throw new Error(`:${name} isn't part of CSS3`);
}
if (typeof filters[name] === "function") {
return filters[name](next, subselect, options, context);
} else if (typeof pseudos[name] === "function") {
const func = pseudos[name];
verifyArgs(func, name, subselect);
if (func === falseFunc) {
return func;
}
if (next === trueFunc) {
return (elem) => func(elem, adapter, subselect);
}
return (elem) => func(elem, adapter, subselect) && next(elem);
} else {
throw new Error(`unmatched pseudo-class :${name}`);
}
},
filters,
pseudos,
};

695
package-lock.json generated

@ -1,6 +1,6 @@
{
"name": "css-select",
"version": "2.1.0",
"version": "2.0.2",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@ -40,6 +40,21 @@
"escape-string-regexp": "^1.0.5",
"supports-color": "^5.3.0"
}
},
"has-flag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
"dev": true
},
"supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"dev": true,
"requires": {
"has-flag": "^3.0.0"
}
}
}
},
@ -62,9 +77,9 @@
},
"dependencies": {
"ajv": {
"version": "6.12.4",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.4.tgz",
"integrity": "sha512-eienB2c9qVQs2KWexhkrdMLVDoIQCz5KSeLxwg9Lzk4DOfBtIK9PQwwufcsn1jjGuf9WZmqPMbGxOzfcuphJCQ==",
"version": "6.12.5",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.5.tgz",
"integrity": "sha512-lRF8RORchjpKG50/WFf8xmg7sgCLFiYNNnqdKflk63whMQcWR5ngGjiSXkL9bjxy6B2npOK2HSMN49jEBMSkag==",
"dev": true,
"requires": {
"fast-deep-equal": "^3.1.1",
@ -72,6 +87,61 @@
"json-schema-traverse": "^0.4.1",
"uri-js": "^4.2.2"
}
},
"lodash": {
"version": "4.17.20",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
"integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==",
"dev": true
}
}
},
"@jest/types": {
"version": "25.5.0",
"resolved": "https://registry.npmjs.org/@jest/types/-/types-25.5.0.tgz",
"integrity": "sha512-OXD0RgQ86Tu3MazKo8bnrkDRaDXXMGUqd+kTtLtK1Zb7CRzQcaSRPPPV37SvYTdevXEBVxe0HXylEjs8ibkmCw==",
"dev": true,
"requires": {
"@types/istanbul-lib-coverage": "^2.0.0",
"@types/istanbul-reports": "^1.1.1",
"@types/yargs": "^15.0.0",
"chalk": "^3.0.0"
},
"dependencies": {
"ansi-styles": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz",
"integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==",
"dev": true,
"requires": {
"@types/color-name": "^1.1.1",
"color-convert": "^2.0.1"
}
},
"chalk": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz",
"integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==",
"dev": true,
"requires": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
}
},
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"requires": {
"color-name": "~1.1.4"
}
},
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
}
}
},
@ -107,12 +177,68 @@
"integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==",
"dev": true
},
"@types/istanbul-lib-coverage": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz",
"integrity": "sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw==",
"dev": true
},
"@types/istanbul-lib-report": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz",
"integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==",
"dev": true,
"requires": {
"@types/istanbul-lib-coverage": "*"
}
},
"@types/istanbul-reports": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-1.1.2.tgz",
"integrity": "sha512-P/W9yOX/3oPZSpaYOCQzGqgCQRXn0FFO/V8bWrCQs+wLmvVVxk6CRBXALEvNs9OHIatlnlFokfhuDo2ug01ciw==",
"dev": true,
"requires": {
"@types/istanbul-lib-coverage": "*",
"@types/istanbul-lib-report": "*"
}
},
"@types/jest": {
"version": "26.0.14",
"resolved": "https://registry.npmjs.org/@types/jest/-/jest-26.0.14.tgz",
"integrity": "sha512-Hz5q8Vu0D288x3iWXePSn53W7hAjP0H7EQ6QvDO9c7t46mR0lNOLlfuwQ+JkVxuhygHzlzPX+0jKdA3ZgSh+Vg==",
"dev": true,
"requires": {
"jest-diff": "^25.2.1",
"pretty-format": "^25.2.1"
}
},
"@types/json-schema": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.6.tgz",
"integrity": "sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==",
"dev": true
},
"@types/node": {
"version": "14.10.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.10.2.tgz",
"integrity": "sha512-IzMhbDYCpv26pC2wboJ4MMOa9GKtjplXfcAqrMeNJpUUwpM/2ATt2w1JPUXwS6spu856TvKZL2AOmeU2rAxskw==",
"dev": true
},
"@types/yargs": {
"version": "15.0.5",
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.5.tgz",
"integrity": "sha512-Dk/IDOPtOgubt/IaevIUbTgV7doaKkoorvOyYM2CMwuDyP89bekI7H4xLIwunNYiK9jhCkmc6pUrJk3cj2AB9w==",
"dev": true,
"requires": {
"@types/yargs-parser": "*"
}
},
"@types/yargs-parser": {
"version": "15.0.0",
"resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-15.0.0.tgz",
"integrity": "sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw==",
"dev": true
},
"@typescript-eslint/eslint-plugin": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.2.0.tgz",
@ -142,6 +268,18 @@
"eslint-utils": "^2.0.0"
}
},
"@typescript-eslint/parser": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.1.1.tgz",
"integrity": "sha512-NLIhmicpKGfJbdXyQBz9j48PA6hq6e+SDOoXy7Ak6bq1ebGqbgG+fR1UIDAuay6OjQdot69c/URu2uLlsP8GQQ==",
"dev": true,
"requires": {
"@typescript-eslint/scope-manager": "4.1.1",
"@typescript-eslint/types": "4.1.1",
"@typescript-eslint/typescript-estree": "4.1.1",
"debug": "^4.1.1"
}
},
"@typescript-eslint/scope-manager": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.2.0.tgz",
@ -260,6 +398,12 @@
"picomatch": "^2.0.4"
}
},
"arg": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
"integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
"dev": true
},
"argparse": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
@ -383,6 +527,21 @@
"integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==",
"dev": true
},
"bs-logger": {
"version": "0.2.6",
"resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz",
"integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==",
"dev": true,
"requires": {
"fast-json-stable-stringify": "2.x"
}
},
"buffer-from": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
"integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==",
"dev": true
},
"callsites": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
@ -402,9 +561,9 @@
"dev": true
},
"chalk": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
"integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==",
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.0.0.tgz",
"integrity": "sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==",
"dev": true,
"requires": {
"ansi-styles": "^4.1.0",
@ -515,6 +674,12 @@
"readdirp": "~3.4.0"
}
},
"ci-info": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz",
"integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==",
"dev": true
},
"cliui": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz",
@ -532,6 +697,29 @@
"integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
"dev": true
},
"emoji-regex": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
"integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==",
"dev": true
},
"is-fullwidth-code-point": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
"integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
"dev": true
},
"string-width": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
"integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
"dev": true,
"requires": {
"emoji-regex": "^7.0.1",
"is-fullwidth-code-point": "^2.0.0",
"strip-ansi": "^5.1.0"
}
},
"strip-ansi": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
@ -666,6 +854,12 @@
"integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
"dev": true
},
"diff-sequences": {
"version": "25.2.6",
"resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-25.2.6.tgz",
"integrity": "sha512-Hq8o7+6GaZeoFjtpgvRBUknSXNeJiCx7V9Fr94ZMljNiCr9n9L8H8aJqgWOQiDDGdyn29fRNcDdRVJ5fdyihfg==",
"dev": true
},
"dir-glob": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
@ -685,9 +879,9 @@
}
},
"dom-serializer": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.0.1.tgz",
"integrity": "sha512-1Aj1Qy3YLbdslkI75QEOfdp9TkQ3o8LRISAzxOibjBs/xWwr1WxZFOQphFkZuepHFGo+kB8e5FVJSS0faAJ4Rw==",
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.1.0.tgz",
"integrity": "sha512-ox7bvGXt2n+uLWtCRLybYx60IrOlWL/aCebWJk1T0d4m3y2tzf4U3ij9wBMUb6YJZpz06HCCYuyCDveE2xXmzQ==",
"requires": {
"domelementtype": "^2.0.1",
"domhandler": "^3.0.0",
@ -785,6 +979,14 @@
"is-set": "^2.0.1",
"is-string": "^1.0.5",
"isarray": "^2.0.5"
},
"dependencies": {
"isarray": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz",
"integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==",
"dev": true
}
}
},
"es-to-primitive": {
@ -913,6 +1115,56 @@
"table": "^5.2.3",
"text-table": "^0.2.0",
"v8-compile-cache": "^2.0.3"
},
"dependencies": {
"eslint-scope": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
"integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==",
"dev": true,
"requires": {
"esrecurse": "^4.3.0",
"estraverse": "^4.1.1"
}
},
"eslint-utils": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz",
"integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==",
"dev": true,
"requires": {
"eslint-visitor-keys": "^1.1.0"
}
},
"eslint-visitor-keys": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz",
"integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==",
"dev": true
},
"esrecurse": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
"integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
"dev": true,
"requires": {
"estraverse": "^5.2.0"
},
"dependencies": {
"estraverse": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz",
"integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==",
"dev": true
}
}
},
"lodash": {
"version": "4.17.20",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
"integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==",
"dev": true
}
}
},
"eslint-config-prettier": {
@ -925,9 +1177,9 @@
}
},
"eslint-scope": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.0.tgz",
"integrity": "sha512-iiGRvtxWqgtx5m8EyQUJihBloE4EnYeGE/bz1wSPwJE6tZuJUtHlhqDM4Xj2ukE8Dyy1+HCZ4hE0fzIVMzb58w==",
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.0.0.tgz",
"integrity": "sha512-oYrhJW7S0bxAFDvWqzvMPRm6pcgcnWc4QnofCAqRTRfQC0JcwenzGglTtsLyIuuWFfkqDG9vz67cnttSd53djw==",
"dev": true,
"requires": {
"esrecurse": "^4.1.0",
@ -935,18 +1187,18 @@
}
},
"eslint-utils": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz",
"integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==",
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.0.0.tgz",
"integrity": "sha512-0HCPuJv+7Wv1bACm8y5/ECVfYdfsAm9xmVb7saeFlxjPYALefjhbYoCkBjPdPzGH8wWyTpAez82Fh3VKYEZ8OA==",
"dev": true,
"requires": {
"eslint-visitor-keys": "^1.1.0"
}
},
"eslint-visitor-keys": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz",
"integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==",
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz",
"integrity": "sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A==",
"dev": true
},
"espree": {
@ -958,6 +1210,14 @@
"acorn": "^7.4.0",
"acorn-jsx": "^5.2.0",
"eslint-visitor-keys": "^1.3.0"
},
"dependencies": {
"eslint-visitor-keys": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz",
"integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==",
"dev": true
}
}
},
"esprima": {
@ -1234,6 +1494,12 @@
}
}
},
"graceful-fs": {
"version": "4.2.4",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz",
"integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==",
"dev": true
},
"growl": {
"version": "1.10.5",
"resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz",
@ -1287,9 +1553,9 @@
}
},
"has-flag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true
},
"has-symbols": {
@ -1387,11 +1653,20 @@
"dev": true
},
"is-callable": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.0.tgz",
"integrity": "sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw==",
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.1.tgz",
"integrity": "sha512-wliAfSzx6V+6WfMOmus1xy0XvSgf/dlStkvTfq7F0g4bOIW0PSUbnyse3NhDwdyYS1ozfUtAAySqTws3z9Eqgg==",
"dev": true
},
"is-ci": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz",
"integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==",
"dev": true,
"requires": {
"ci-info": "^2.0.0"
}
},
"is-date-object": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz",
@ -1473,12 +1748,6 @@
"integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=",
"dev": true
},
"isarray": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz",
"integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==",
"dev": true
},
"isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
@ -1574,6 +1843,99 @@
"iterate-iterator": "^1.0.1"
}
},
"jest-diff": {
"version": "25.5.0",
"resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-25.5.0.tgz",
"integrity": "sha512-z1kygetuPiREYdNIumRpAHY6RXiGmp70YHptjdaxTWGmA085W3iCnXNx0DhflK3vwrKmrRWyY1wUpkPMVxMK7A==",
"dev": true,
"requires": {
"chalk": "^3.0.0",
"diff-sequences": "^25.2.6",
"jest-get-type": "^25.2.6",
"pretty-format": "^25.5.0"
},
"dependencies": {
"ansi-styles": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz",
"integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==",
"dev": true,
"requires": {
"@types/color-name": "^1.1.1",
"color-convert": "^2.0.1"
}
},
"chalk": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz",
"integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==",
"dev": true,
"requires": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
}
},
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"requires": {
"color-name": "~1.1.4"
}
},
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
}
}
},
"jest-get-type": {
"version": "25.2.6",
"resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-25.2.6.tgz",
"integrity": "sha512-DxjtyzOHjObRM+sM1knti6or+eOgcGU4xVSb2HNP1TqO4ahsT+rqZg+nyqHWJSvWgKC5cG3QjGFBqxLghiF/Ig==",
"dev": true
},
"jest-util": {
"version": "26.3.0",
"resolved": "https://registry.npmjs.org/jest-util/-/jest-util-26.3.0.tgz",
"integrity": "sha512-4zpn6bwV0+AMFN0IYhH/wnzIQzRaYVrz1A8sYnRnj4UXDXbOVtWmlaZkO9mipFqZ13okIfN87aDoJWB7VH6hcw==",
"dev": true,
"requires": {
"@jest/types": "^26.3.0",
"@types/node": "*",
"chalk": "^4.0.0",
"graceful-fs": "^4.2.4",
"is-ci": "^2.0.0",
"micromatch": "^4.0.2"
},
"dependencies": {
"@jest/types": {
"version": "26.3.0",
"resolved": "https://registry.npmjs.org/@jest/types/-/types-26.3.0.tgz",
"integrity": "sha512-BDPG23U0qDeAvU4f99haztXwdAg3hz4El95LkAM+tHAqqhiVzRpEGHHU8EDxT/AnxOrA65YjLBwDahdJ9pTLJQ==",
"dev": true,
"requires": {
"@types/istanbul-lib-coverage": "^2.0.0",
"@types/istanbul-reports": "^3.0.0",
"@types/node": "*",
"@types/yargs": "^15.0.0",
"chalk": "^4.0.0"
}
},
"@types/istanbul-reports": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz",
"integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==",
"dev": true,
"requires": {
"@types/istanbul-lib-report": "*"
}
}
}
},
"js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@ -1620,6 +1982,15 @@
"integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=",
"dev": true
},
"json5": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz",
"integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==",
"dev": true,
"requires": {
"minimist": "^1.2.5"
}
},
"jsprim": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz",
@ -1658,9 +2029,15 @@
}
},
"lodash": {
"version": "4.17.19",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz",
"integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==",
"version": "4.17.15",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==",
"dev": true
},
"lodash.memoize": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
"integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=",
"dev": true
},
"log-driver": {
@ -1678,6 +2055,12 @@
"chalk": "^4.0.0"
}
},
"make-error": {
"version": "1.3.6",
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
"integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
"dev": true
},
"merge2": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
@ -1986,6 +2369,45 @@
"integrity": "sha512-16c7K+x4qVlJg9rEbXl7HEGmQyZlG4R9AgP+oHKRMsMsuk8s+ATStlf1NpDqyBI1HpVyfjLOeMhH2LvuNvV5Vg==",
"dev": true
},
"pretty-format": {
"version": "25.5.0",
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-25.5.0.tgz",
"integrity": "sha512-kbo/kq2LQ/A/is0PQwsEHM7Ca6//bGPPvU6UnsdDRSKTWxT/ru/xb88v4BJf6a69H+uTytOEsTusT9ksd/1iWQ==",
"dev": true,
"requires": {
"@jest/types": "^25.5.0",
"ansi-regex": "^5.0.0",
"ansi-styles": "^4.0.0",
"react-is": "^16.12.0"
},
"dependencies": {
"ansi-styles": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz",
"integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==",
"dev": true,
"requires": {
"@types/color-name": "^1.1.1",
"color-convert": "^2.0.1"
}
},
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"requires": {
"color-name": "~1.1.4"
}
},
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
}
}
},
"progress": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
@ -2032,6 +2454,12 @@
"safe-buffer": "^5.1.0"
}
},
"react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
"dev": true
},
"readdirp": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.4.0.tgz",
@ -2195,6 +2623,24 @@
"amdefine": ">=0.0.4"
}
},
"source-map-support": {
"version": "0.5.19",
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz",
"integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==",
"dev": true,
"requires": {
"buffer-from": "^1.0.0",
"source-map": "^0.6.0"
},
"dependencies": {
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true
}
}
},
"sprintf-js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
@ -2282,12 +2728,12 @@
"dev": true
},
"supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dev": true,
"requires": {
"has-flag": "^3.0.0"
"has-flag": "^4.0.0"
}
},
"table": {
@ -2327,10 +2773,60 @@
"punycode": "^2.1.1"
}
},
"ts-jest": {
"version": "26.3.0",
"resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-26.3.0.tgz",
"integrity": "sha512-Jq2uKfx6bPd9+JDpZNMBJMdMQUC3sJ08acISj8NXlVgR2d5OqslEHOR2KHMgwymu8h50+lKIm0m0xj/ioYdW2Q==",
"dev": true,
"requires": {
"@types/jest": "26.x",
"bs-logger": "0.x",
"buffer-from": "1.x",
"fast-json-stable-stringify": "2.x",
"jest-util": "26.x",
"json5": "2.x",
"lodash.memoize": "4.x",
"make-error": "1.x",
"mkdirp": "1.x",
"semver": "7.x",
"yargs-parser": "18.x"
},
"dependencies": {
"mkdirp": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
"dev": true
},
"yargs-parser": {
"version": "18.1.3",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz",
"integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==",
"dev": true,
"requires": {
"camelcase": "^5.0.0",
"decamelize": "^1.2.0"
}
}
}
},
"ts-node": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.0.0.tgz",
"integrity": "sha512-/TqB4SnererCDR/vb4S/QvSZvzQMJN8daAslg7MeaiHvD8rDZsSfXmNeNumyZZzMned72Xoq/isQljYSt8Ynfg==",
"dev": true,
"requires": {
"arg": "^4.1.0",
"diff": "^4.0.1",
"make-error": "^1.1.1",
"source-map-support": "^0.5.17",
"yn": "3.1.1"
}
},
"tslib": {
"version": "1.13.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz",
"integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==",
"version": "1.11.2",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.11.2.tgz",
"integrity": "sha512-tTSkux6IGPnUGUd1XAZHcpu85MOkIl5zX49pO+jfsie3eP0B6pyhOlLXm3cAC6T7s+euSDDUUV+Acop5WmtkVg==",
"dev": true
},
"tsutils": {
@ -2372,6 +2868,12 @@
"integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==",
"dev": true
},
"typescript": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.0.2.tgz",
"integrity": "sha512-e4ERvRV2wb+rRZ/IQeb3jm2VxBsirQLpQhdxplZ2MEzGvDkkMmPglecnNDfSUBivMjP93vRbngYYDQqQ/78bcQ==",
"dev": true
},
"uglify-js": {
"version": "3.9.2",
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.9.2.tgz",
@ -2444,6 +2946,12 @@
"integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
"dev": true
},
"is-fullwidth-code-point": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
"integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
"dev": true
},
"string-width": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
@ -2500,6 +3008,29 @@
"integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
"dev": true
},
"emoji-regex": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
"integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==",
"dev": true
},
"is-fullwidth-code-point": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
"integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
"dev": true
},
"string-width": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
"integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
"dev": true,
"requires": {
"emoji-regex": "^7.0.1",
"is-fullwidth-code-point": "^2.0.0",
"strip-ansi": "^5.1.0"
}
},
"strip-ansi": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
@ -2550,6 +3081,18 @@
"yargs-parser": "^13.1.2"
},
"dependencies": {
"ansi-regex": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
"integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
"dev": true
},
"emoji-regex": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
"integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==",
"dev": true
},
"find-up": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
@ -2559,6 +3102,12 @@
"locate-path": "^3.0.0"
}
},
"is-fullwidth-code-point": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
"integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
"dev": true
},
"locate-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
@ -2592,6 +3141,26 @@
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
"integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=",
"dev": true
},
"string-width": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
"integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
"dev": true,
"requires": {
"emoji-regex": "^7.0.1",
"is-fullwidth-code-point": "^2.0.0",
"strip-ansi": "^5.1.0"
}
},
"strip-ansi": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
"integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
"dev": true,
"requires": {
"ansi-regex": "^4.1.0"
}
}
}
},
@ -2618,6 +3187,18 @@
"yargs": "^14.2.3"
},
"dependencies": {
"ansi-regex": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
"integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
"dev": true
},
"emoji-regex": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
"integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==",
"dev": true
},
"find-up": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
@ -2627,6 +3208,12 @@
"locate-path": "^3.0.0"
}
},
"is-fullwidth-code-point": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
"integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
"dev": true
},
"locate-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
@ -2661,6 +3248,26 @@
"integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=",
"dev": true
},
"string-width": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
"integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
"dev": true,
"requires": {
"emoji-regex": "^7.0.1",
"is-fullwidth-code-point": "^2.0.0",
"strip-ansi": "^5.1.0"
}
},
"strip-ansi": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
"integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
"dev": true,
"requires": {
"ansi-regex": "^4.1.0"
}
},
"yargs": {
"version": "14.2.3",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-14.2.3.tgz",
@ -2691,6 +3298,12 @@
}
}
}
},
"yn": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
"integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
"dev": true
}
}
}

@ -1,6 +1,6 @@
{
"name": "css-select",
"version": "2.1.0",
"version": "2.0.2",
"description": "a CSS selector compiler/engine",
"author": "Felix Boehm <me@feedic.com>",
"keywords": [
@ -12,35 +12,41 @@
"type": "git",
"url": "git://github.com/fb55/css-select.git"
},
"main": "lib/index.js",
"files": [
"index.js",
"index.d.ts",
"lib"
],
"dependencies": {
"boolbase": "^1.0.0",
"css-what": "^3.2.1",
"css-what": "^3.3.0",
"domutils": "^2.1.0",
"nth-check": "^1.0.2"
},
"devDependencies": {
"@types/jest": "^26.0.14",
"@types/node": "^14.0.5",
"@typescript-eslint/eslint-plugin": "^4.1.0",
"@typescript-eslint/parser": "^4.1.0",
"cheerio-soupselect": "^0.1.1",
"coveralls": "^3.0.2",
"eslint": "^7.0.0",
"eslint-config-prettier": "^6.11.0",
"eslint": "^7.9.0",
"eslint-config-prettier": "^6.0.0",
"expect.js": "^0.3.1",
"htmlparser2": "^4.0.0",
"istanbul": "^0.4.5",
"mocha": "^8.0.1",
"mocha-lcov-reporter": "^1.3.0",
"prettier": "^2.0.5"
"prettier": "^2.1.2",
"ts-jest": "^26.0.0",
"ts-node": "^9.0.0",
"typescript": "^4.0.2"
},
"scripts": {
"test": "mocha --parallel && npm run lint",
"lint": "eslint .",
"lcov": "istanbul cover _mocha --report lcovonly -- -R spec",
"coveralls": "npm run lint && npm run lcov && (cat coverage/lcov.info | coveralls || exit 0)"
"coveralls": "npm run lint && npm run lcov && (cat coverage/lcov.info | coveralls || exit 0)",
"build": "tsc"
},
"license": "BSD-2-Clause",
"types": "index.d.ts",

@ -1,13 +1,29 @@
const { falseFunc } = require("boolbase");
//https://github.com/slevithan/XRegExp/blob/master/src/xregexp.js#L469
import { falseFunc } from "boolbase";
import { CompiledQuery, InternalOptions } from "./types";
import type { AttributeSelector, AttributeAction } from "css-what";
/**
* All reserved characters in a regex, used for escaping.
*
* Taken from XRegExp, (c) 2007-2020 Steven Levithan under the MIT license
* https://github.com/slevithan/xregexp/blob/95eeebeb8fac8754d54eafe2b4743661ac1cf028/src/xregexp.js#L794
*/
const reChars = /[-[\]{}()*+?.,\\^$|#\s]/g;
/*
attribute selectors
*/
const attributeRules = {
__proto__: null,
function escapeRegex(value: string): string {
return value.replace(reChars, "\\$&");
}
/**
* Attribute selectors
*/
export const attributeRules: Record<
AttributeAction,
<Node, ElementNode extends Node>(
next: CompiledQuery<ElementNode>,
data: AttributeSelector,
options: InternalOptions<Node, ElementNode>
) => CompiledQuery<ElementNode>
> = {
equals(next, data, { adapter }) {
const { name } = data;
let { value } = data;
@ -15,12 +31,9 @@ const attributeRules = {
if (data.ignoreCase) {
value = value.toLowerCase();
return function equalsIC(elem) {
const attr = adapter.getAttributeValue(elem, name);
return (
attr != null && attr.toLowerCase() === value && next(elem)
);
};
return (elem) =>
adapter.getAttributeValue(elem, name)?.toLowerCase() ===
value && next(elem);
}
return (elem) =>
@ -55,19 +68,15 @@ const attributeRules = {
);
};
},
element(next, data, { adapter }) {
const { name } = data;
let { value } = data;
element(next, { name, value, ignoreCase }, { adapter }) {
if (/\s/.test(value)) {
return falseFunc;
}
value = value.replace(reChars, "\\$&");
const pattern = `(?:^|\\s)${value}(?:$|\\s)`;
const flags = data.ignoreCase ? "i" : "";
const regex = new RegExp(pattern, flags);
const regex = new RegExp(
`(?:^|\\s)${escapeRegex(value)}(?:$|\\s)`,
ignoreCase ? "i" : ""
);
return function element(elem) {
const attr = adapter.getAttributeValue(elem, name);
@ -89,20 +98,16 @@ const attributeRules = {
if (data.ignoreCase) {
value = value.toLowerCase();
return function startIC(elem) {
const attr = adapter.getAttributeValue(elem, name);
return (
attr != null &&
attr.substr(0, len).toLowerCase() === value &&
next(elem)
);
};
return (elem) =>
adapter
.getAttributeValue(elem, name)
?.substr(0, len)
.toLowerCase() === value && next(elem);
}
return function start(elem) {
const attr = adapter.getAttributeValue(elem, name);
return attr != null && attr.startsWith(value) && next(elem);
};
return (elem) =>
!!adapter.getAttributeValue(elem, name)?.startsWith(value) &&
next(elem);
},
end(next, data, { adapter }) {
const { name } = data;
@ -116,20 +121,16 @@ const attributeRules = {
if (data.ignoreCase) {
value = value.toLowerCase();
return function endIC(elem) {
const attr = adapter.getAttributeValue(elem, name);
return (
attr != null &&
attr.substr(len).toLowerCase() === value &&
next(elem)
);
};
return (elem) =>
adapter
.getAttributeValue(elem, name)
?.substr(len)
.toLowerCase() === value && next(elem);
}
return function end(elem) {
const attr = adapter.getAttributeValue(elem, name);
return attr != null && attr.endsWith(value) && next(elem);
};
return (elem) =>
!!adapter.getAttributeValue(elem, name)?.endsWith(value) &&
next(elem);
},
any(next, data, { adapter }) {
const { name, value } = data;
@ -139,7 +140,7 @@ const attributeRules = {
}
if (data.ignoreCase) {
const regex = new RegExp(value.replace(reChars, "\\$&"), "i");
const regex = new RegExp(escapeRegex(value), "i");
return function anyIC(elem) {
const attr = adapter.getAttributeValue(elem, name);
@ -147,10 +148,9 @@ const attributeRules = {
};
}
return function any(elem) {
const attr = adapter.getAttributeValue(elem, name);
return attr != null && attr.includes(value) && next(elem);
};
return (elem) =>
!!adapter.getAttributeValue(elem, name)?.includes(value) &&
next(elem);
},
not(next, data, { adapter }) {
const { name } = data;
@ -162,10 +162,13 @@ const attributeRules = {
} else if (data.ignoreCase) {
value = value.toLowerCase();
return function notIC(elem) {
return (elem) => {
const attr = adapter.getAttributeValue(elem, name);
return (
attr != null && attr.toLowerCase() !== value && next(elem)
attr != null &&
attr.toLocaleLowerCase() !== value &&
next(elem)
);
};
}
@ -174,13 +177,3 @@ const attributeRules = {
adapter.getAttributeValue(elem, name) !== value && next(elem);
},
};
module.exports = {
compile(next, data, options) {
if (options.strict && (data.ignoreCase || data.action === "not")) {
throw new Error("Unsupported attribute selector");
}
return attributeRules[data.action](next, data, options);
},
rules: attributeRules,
};

@ -0,0 +1,163 @@
import { InternalSelector } from "./types";
import { parse, Selector } from "css-what";
import { trueFunc, falseFunc } from "boolbase";
import sortRules from "./sort";
import { isTraversal } from "./procedure";
import { compileGeneralSelector } from "./general";
import {
ensureIsTag,
PLACEHOLDER_ELEMENT,
} from "./pseudo-selectors/subselects";
import type { CompiledQuery, InternalOptions } from "./types";
/**
* Compiles a selector to an executable function.
*
* @param selector Selector to compile.
* @param options Compilation options.
* @param context Optional context for the selector.
*/
export function compile<Node, ElementNode extends Node>(
selector: string,
options: InternalOptions<Node, ElementNode>,
context?: ElementNode[]
): CompiledQuery<ElementNode> {
const next = compileUnsafe(selector, options, context);
return ensureIsTag(next, options.adapter);
}
export function compileUnsafe<Node, ElementNode extends Node>(
selector: string,
options: InternalOptions<Node, ElementNode>,
context?: ElementNode[] | ElementNode
): CompiledQuery<ElementNode> {
const token = parse(selector, options);
return compileToken<Node, ElementNode>(token, options, context);
}
function includesScopePseudo(t: InternalSelector): boolean {
return (
t.type === "pseudo" &&
(t.name === "scope" ||
(Array.isArray(t.data) &&
t.data.some((data) => data.some(includesScopePseudo))))
);
}
const DESCENDANT_TOKEN: Selector = { type: "descendant" };
const FLEXIBLE_DESCENDANT_TOKEN: InternalSelector = {
type: "_flexibleDescendant",
};
const SCOPE_TOKEN: Selector = { type: "pseudo", name: "scope", data: null };
/*
* CSS 4 Spec (Draft): 3.3.1. Absolutizing a Scope-relative Selector
* http://www.w3.org/TR/selectors4/#absolutizing
*/
function absolutize<Node, ElementNode extends Node>(
token: InternalSelector[][],
{ adapter }: InternalOptions<Node, ElementNode>,
context?: ElementNode[]
) {
// TODO Use better check if the context is a document
const hasContext = !!context?.every(
(e) => e === PLACEHOLDER_ELEMENT || !!adapter.getParent(e)
);
for (const t of token) {
if (t.length > 0 && isTraversal(t[0]) && t[0].type !== "descendant") {
// Don't continue in else branch
} else if (hasContext && !t.some(includesScopePseudo)) {
t.unshift(DESCENDANT_TOKEN);
} else {
continue;
}
t.unshift(SCOPE_TOKEN);
}
}
export function compileToken<Node, ElementNode extends Node>(
token: InternalSelector[][],
options: InternalOptions<Node, ElementNode>,
context?: ElementNode[] | ElementNode
): CompiledQuery<ElementNode> {
token = token.filter((t) => t.length > 0);
token.forEach(sortRules);
context = options.context ?? context;
const isArrayContext = Array.isArray(context);
const finalContext =
context && (Array.isArray(context) ? context : [context]);
absolutize(token, options, finalContext);
let shouldTestNextSiblings = false;
const query = token
.map((rules) => {
if (rules.length >= 2) {
const [first, second] = rules;
if (first.type !== "pseudo" || first.name !== "scope") {
// Ignore
} else if (isArrayContext && second.type === "descendant") {
rules[1] = FLEXIBLE_DESCENDANT_TOKEN;
} else if (
second.type === "adjacent" ||
second.type === "sibling"
) {
shouldTestNextSiblings = true;
}
}
return compileRules<Node, ElementNode>(
rules,
options,
finalContext
);
})
.reduce(reduceRules, falseFunc);
query.shouldTestNextSiblings = shouldTestNextSiblings;
return query;
}
function compileRules<Node, ElementNode extends Node>(
rules: InternalSelector[],
options: InternalOptions<Node, ElementNode>,
context?: ElementNode[]
): CompiledQuery<ElementNode> {
return rules.reduce<CompiledQuery<ElementNode>>(
(previous, rule) =>
previous === falseFunc
? falseFunc
: compileGeneralSelector(
previous,
rule,
options,
context,
compileToken
),
options.rootFunc ?? trueFunc
);
}
function reduceRules<Node, ElementNode extends Node>(
a: CompiledQuery<ElementNode>,
b: CompiledQuery<ElementNode>
): CompiledQuery<ElementNode> {
if (b === falseFunc || a === trueFunc) {
return a;
}
if (a === falseFunc || b === trueFunc) {
return b;
}
return function combine(elem) {
return a(elem) || b(elem);
};
}

@ -0,0 +1,4 @@
declare module "boolbase" {
export function trueFunc(...args: unknown[]): true;
export function falseFunc(...args: unknown[]): false;
}

@ -0,0 +1,9 @@
declare module "nth-check" {
export default function nthCheck(
formula: string
): (position: number) => boolean;
export function parse(formula: string): [number, number];
export function compile(
parsed: [number, number]
): (position: number) => boolean;
}

@ -0,0 +1,144 @@
import { attributeRules } from "./attributes";
import { compilePseudoSelector } from "./pseudo-selectors";
import type {
CompiledQuery,
InternalOptions,
InternalSelector,
CompileToken,
} from "./types";
/*
* All available rules
*/
export function compileGeneralSelector<Node, ElementNode extends Node>(
next: CompiledQuery<ElementNode>,
selector: InternalSelector,
options: InternalOptions<Node, ElementNode>,
context: ElementNode[] | undefined,
compileToken: CompileToken<Node, ElementNode>
): CompiledQuery<ElementNode> {
const { adapter } = options;
switch (selector.type) {
case "pseudo-element":
throw new Error("Pseudo-elements are not supported by css-select");
case "attribute":
if (
options.strict &&
(selector.ignoreCase || selector.action === "not")
) {
throw new Error("Unsupported attribute selector");
}
return attributeRules[selector.action](next, selector, options);
case "pseudo":
return compilePseudoSelector(
next,
selector,
options,
context,
compileToken
);
// Tags
case "tag":
return function tag(elem: ElementNode): boolean {
return adapter.getName(elem) === selector.name && next(elem);
};
// Traversal
case "descendant":
if (typeof WeakSet === "undefined") {
return function descendant(elem: ElementNode): boolean {
let current: ElementNode | null = elem;
while ((current = adapter.getParent(current))) {
if (next(current)) return true;
}
return false;
};
}
// @ts-expect-error `ElementNode` is not extending object
// eslint-disable-next-line no-case-declarations
const isFalseCache = new WeakSet<ElementNode>();
return function cachedDescendant(elem: ElementNode): boolean {
let current: ElementNode | null = elem;
while ((current = adapter.getParent(current))) {
if (!isFalseCache.has(elem)) {
if (next(current)) return true;
isFalseCache.add(current);
}
}
return false;
};
case "_flexibleDescendant":
// Include element itself, only used while querying an array
return function flexibleDescendant(elem: ElementNode): boolean {
let current: ElementNode | null = elem;
do {
if (next(current)) return true;
} while ((current = adapter.getParent(current)));
return false;
};
case "parent":
if (options.strict) {
throw new Error("Parent selector isn't part of CSS3");
}
return function parent(elem: ElementNode): boolean {
return adapter
.getChildren(elem)
.some((elem) => adapter.isTag(elem) && next(elem));
};
case "child":
return function child(elem: ElementNode): boolean {
const parent = adapter.getParent(elem);
return !!parent && next(parent);
};
case "sibling":
return function sibling(elem: ElementNode): boolean {
const siblings = adapter.getSiblings(elem);
for (let i = 0; i < siblings.length; i++) {
const currentSibling = siblings[i];
if (adapter.isTag(currentSibling)) {
if (currentSibling === elem) break;
if (next(currentSibling)) return true;
}
}
return false;
};
case "adjacent":
return function adjacent(elem: ElementNode): boolean {
const siblings = adapter.getSiblings(elem);
let lastElement;
for (let i = 0; i < siblings.length; i++) {
const currentSibling = siblings[i];
if (adapter.isTag(currentSibling)) {
if (currentSibling === elem) break;
lastElement = currentSibling;
}
}
return !!lastElement && next(lastElement);
};
case "universal":
return next;
}
}

@ -0,0 +1,189 @@
import * as DomUtils from "domutils";
import { falseFunc } from "boolbase";
import { compile as compileRaw, compileUnsafe, compileToken } from "./compile";
import {
CompiledQuery,
Options,
InternalOptions,
Query,
Adapter,
Predicate,
} from "./types";
export type { Options };
const defaultOptions = { adapter: DomUtils };
function convertOptionFormats<Node, ElementNode extends Node>(
options?: Options<Node, ElementNode>
): InternalOptions<Node, ElementNode> {
/*
* We force one format of options to the other one.
*/
// @ts-expect-error Default options may have incompatible `Node` / `ElementNode`.
const opts: Options<Node, ElementNode> = options ?? defaultOptions;
// @ts-expect-error Same as above.
opts.adapter = opts.adapter ?? DomUtils;
return opts as InternalOptions<Node, ElementNode>;
}
function wrapCompile<Selector, Node, ElementNode extends Node>(
func: (
selector: Selector,
options: InternalOptions<Node, ElementNode>,
context?: Node[]
) => CompiledQuery<ElementNode>
) {
return function addAdapter(
selector: Selector,
options?: Options<Node, ElementNode>,
context?: Node[]
) {
const opts = convertOptionFormats(options);
return func(selector, opts, context);
};
}
/**
* Compiles the query, returns a function.
*/
export const compile = wrapCompile(compileRaw);
export const _compileUnsafe = wrapCompile(compileUnsafe);
export const _compileToken = wrapCompile(compileToken);
function getSelectorFunc<Node, ElementNode extends Node, T>(
searchFunc: (
query: Predicate<ElementNode>,
elems: Array<Node>,
options: InternalOptions<Node, ElementNode>
) => T
) {
return function select(
query: Query<ElementNode>,
elements: ElementNode[] | ElementNode,
options?: Options<Node, ElementNode>
): T {
const opts = convertOptionFormats(options);
let elems: ElementNode[] | ElementNode = elements;
if (typeof query !== "function") {
query = compileUnsafe<Node, ElementNode>(query, opts, elems);
}
/*
* Add siblings if the query requires them.
* See https://github.com/fb55/css-select/pull/43#issuecomment-225414692
*/
if (query.shouldTestNextSiblings) {
elems = appendNextSiblings(opts.context ?? elems, opts.adapter);
}
const filteredElements = Array.isArray(elems)
? opts.adapter.removeSubsets(elems)
: opts.adapter.getChildren(elems);
return searchFunc(query, filteredElements, opts);
};
}
function getNextSiblings<Node, ElementNode extends Node>(
elem: Node,
adapter: Adapter<Node, ElementNode>
): Node[] {
const siblings = adapter.getSiblings(elem);
if (siblings.length <= 1) return [];
const elemIndex = siblings.indexOf(elem);
if (elemIndex < 0 || elemIndex === siblings.length - 1) return [];
return siblings.slice(elemIndex + 1);
}
function appendNextSiblings<Node, ElementNode extends Node>(
elem: ElementNode | ElementNode[],
adapter: Adapter<Node, ElementNode>
): ElementNode[] {
// Order matters because jQuery seems to check the children before the siblings
const elems = Array.isArray(elem) ? elem.slice(0) : [elem];
for (let i = 0; i < elems.length; i++) {
const nextSiblings = getNextSiblings(elems[i], adapter);
elems.push(
...nextSiblings.filter((sibling): sibling is ElementNode =>
adapter.isTag(sibling)
)
);
}
return elems;
}
/**
* @template Node The generic Node type for the DOM adapter being used.
* @template ElementNode The Node type for elements for the DOM adapter being used.
* @param elems Elements to query. If it is an element, its children will be queried..
* @param query can be either a CSS selector string or a compiled query function.
* @param [options] options for querying the document.
* @see compile for supported selector queries.
* @returns All matching elements.
*
*/
export const selectAll = getSelectorFunc(
<Node, ElementNode extends Node>(
query: Predicate<ElementNode>,
elems: Node[] | null,
options: InternalOptions<Node, ElementNode>
) =>
query === falseFunc || !elems || elems.length === 0
? []
: options.adapter.findAll(query, elems)
);
/**
* @template Node The generic Node type for the DOM adapter being used.
* @template ElementNode The Node type for elements for the DOM adapter being used.
* @param elems Elements to query. If it is an element, its children will be queried..
* @param query can be either a CSS selector string or a compiled query function.
* @param [options] options for querying the document.
* @see compile for supported selector queries.
* @returns the first match, or null if there was no match.
*/
export const selectOne = getSelectorFunc(
<Node, ElementNode extends Node>(
query: Predicate<ElementNode>,
elems: Node[] | null,
options: InternalOptions<Node, ElementNode>
) =>
query === falseFunc || !elems || elems.length === 0
? null
: options.adapter.findOne(query, elems)
);
/**
* Tests whether or not an element is matched by query.
*
* @template Node The generic Node type for the DOM adapter being used.
* @template ElementNode The Node type for elements for the DOM adapter being used.
* @param elem The element to test if it matches the query.
* @param query can be either a CSS selector string or a compiled query function.
* @param [options] options for querying the document.
* @see compile for supported selector queries.
* @returns
*/
export function is<Node, ElementNode extends Node>(
elem: ElementNode,
query: Query<ElementNode>,
options?: Options<Node, ElementNode>
): boolean {
const opts = convertOptionFormats(options);
return (typeof query === "function" ? query : compileRaw(query, opts))(
elem
);
}
/**
* Alias for selectAll(query, elems, options).
* @see [compile] for supported selector queries.
*/
export default selectAll;
// Export filters and pseudos to allow users to supply their own.
export { filters, pseudos } from "./pseudo-selectors";

@ -0,0 +1,20 @@
import type { Traversal } from "css-what";
import type { InternalSelector } from "./types";
export const procedure: Record<InternalSelector["type"], number> = {
universal: 50,
tag: 30,
attribute: 1,
pseudo: 0,
"pseudo-element": 0, // Here to make TS happy, we don't support this.
descendant: -1,
child: -1,
parent: -1,
sibling: -1,
adjacent: -1,
_flexibleDescendant: -1,
};
export function isTraversal(t: InternalSelector): t is Traversal {
return procedure[t.type] < 0;
}

@ -0,0 +1,219 @@
import getNCheck from "nth-check";
import { trueFunc, falseFunc } from "boolbase";
import { attributeRules } from "../attributes";
import type { CompiledQuery, InternalOptions, Adapter } from "../types";
import type { AttributeSelector } from "css-what";
export type Filter = <Node, ElementNode extends Node>(
next: CompiledQuery<ElementNode>,
text: string,
options: InternalOptions<Node, ElementNode>,
context?: ElementNode[]
) => CompiledQuery<ElementNode>;
const checkAttrib = attributeRules.equals;
function getAttribFunc(name: string, value: string): Filter {
const data: AttributeSelector = {
type: "attribute",
action: "equals",
ignoreCase: false,
name,
value,
};
return function attribFunc(next, _rule, options) {
return checkAttrib(next, data, options);
};
}
function getChildFunc<Node, ElementNode extends Node>(
next: CompiledQuery<ElementNode>,
adapter: Adapter<Node, ElementNode>
): CompiledQuery<ElementNode> {
return (elem) => !!adapter.getParent(elem) && next(elem);
}
export const filters: Record<string, Filter> = {
contains(next, text, { adapter }) {
return function contains(elem) {
return next(elem) && adapter.getText(elem).includes(text);
};
},
icontains(next, text, { adapter }) {
const itext = text.toLowerCase();
return function icontains(elem) {
return (
next(elem) &&
adapter.getText(elem).toLowerCase().includes(itext)
);
};
},
// Location specific methods
"nth-child"(next, rule, { adapter }) {
const func = getNCheck(rule);
if (func === falseFunc) return falseFunc;
if (func === trueFunc) return getChildFunc(next, adapter);
return function nthChild(elem) {
const siblings = adapter.getSiblings(elem);
let pos = 0;
for (let i = 0; i < siblings.length; i++) {
if (adapter.isTag(siblings[i])) {
if (siblings[i] === elem) break;
else pos++;
}
}
return func(pos) && next(elem);
};
},
"nth-last-child"(next, rule, { adapter }) {
const func = getNCheck(rule);
if (func === falseFunc) return falseFunc;
if (func === trueFunc) return getChildFunc(next, adapter);
return function nthLastChild(elem) {
const siblings = adapter.getSiblings(elem);
let pos = 0;
for (let i = siblings.length - 1; i >= 0; i--) {
if (adapter.isTag(siblings[i])) {
if (siblings[i] === elem) break;
else pos++;
}
}
return func(pos) && next(elem);
};
},
"nth-of-type"(next, rule, { adapter }) {
const func = getNCheck(rule);
if (func === falseFunc) return falseFunc;
if (func === trueFunc) return getChildFunc(next, adapter);
return function nthOfType(elem) {
const siblings = adapter.getSiblings(elem);
let pos = 0;
for (let i = 0; i < siblings.length; i++) {
const currentSibling = siblings[i];
if (adapter.isTag(currentSibling)) {
if (currentSibling === elem) break;
if (
adapter.getName(currentSibling) ===
adapter.getName(elem)
) {
pos++;
}
}
}
return func(pos) && next(elem);
};
},
"nth-last-of-type"(next, rule, { adapter }) {
const func = getNCheck(rule);
if (func === falseFunc) return falseFunc;
if (func === trueFunc) return getChildFunc(next, adapter);
return function nthLastOfType(elem) {
const siblings = adapter.getSiblings(elem);
let pos = 0;
for (let i = siblings.length - 1; i >= 0; i--) {
const currentSibling = siblings[i];
if (adapter.isTag(currentSibling)) {
if (currentSibling === elem) break;
if (
adapter.getName(currentSibling) ===
adapter.getName(elem)
) {
pos++;
}
}
}
return func(pos) && next(elem);
};
},
// TODO determine the actual root element
root(next, _rule, { adapter }) {
return (elem) => !adapter.getParent(elem) && next(elem);
},
scope<Node, ElementNode extends Node>(
next: CompiledQuery<ElementNode>,
rule: string,
options: InternalOptions<Node, ElementNode>,
context?: ElementNode[]
): CompiledQuery<ElementNode> {
const { adapter } = options;
if (!context || context.length === 0) {
// Equivalent to :root
return filters.root(next, rule, options);
}
const equals: (a: ElementNode, b: ElementNode) => boolean =
typeof adapter.equals === "function"
? adapter.equals
: (a, b) => a === b;
if (context.length === 1) {
// NOTE: can't be unpacked, as :has uses this for side-effects
return (elem) => equals(context[0], elem) && next(elem);
}
return (elem) => context.includes(elem) && next(elem);
},
// JQuery extensions (others follow as pseudos)
checkbox: getAttribFunc("type", "checkbox"),
file: getAttribFunc("type", "file"),
password: getAttribFunc("type", "password"),
radio: getAttribFunc("type", "radio"),
reset: getAttribFunc("type", "reset"),
image: getAttribFunc("type", "image"),
submit: getAttribFunc("type", "submit"),
// Dynamic state pseudos. These depend on optional Adapter methods.
hover(next, _rule, { adapter }) {
const { isHovered } = adapter;
if (typeof isHovered !== "function") {
return falseFunc;
}
return function hover(elem) {
return isHovered(elem) && next(elem);
};
},
visited(next, _rule, { adapter }) {
const { isVisited } = adapter;
if (typeof isVisited !== "function") {
return falseFunc;
}
return function visited(elem) {
return isVisited(elem) && next(elem);
};
},
active(next, _rule, { adapter }) {
const { isActive } = adapter;
if (typeof isActive !== "function") {
return falseFunc;
}
return function active(elem) {
return isActive(elem) && next(elem);
};
},
};

@ -0,0 +1,58 @@
/*
* Pseudo selectors
*
* Pseudo selectors are available in three forms:
*
* 1. Filters are called when the selector is compiled and return a function
* that has to return either false, or the results of `next()`.
* 2. Pseudos are called on execution. They have to return a boolean.
* 3. Subselects work like filters, but have an embedded selector that will be run separately.
*
* Filters are great if you want to do some pre-processing, or change the call order
* of `next()` and your code.
* Pseudos should be used to implement simple checks.
*/
import { trueFunc, falseFunc } from "boolbase";
import type { CompiledQuery, InternalOptions, CompileToken } from "../types";
import type { PseudoSelector } from "css-what";
import { filters } from "./filters";
import { pseudos, verifyPseudoArgs } from "./pseudos";
import { subselects } from "./subselects";
export { filters, pseudos };
// FIXME This is pretty hacky
const reCSS3 = /^(?:(?:nth|last|first|only)-(?:child|of-type)|root|empty|(?:en|dis)abled|checked|not)$/;
export function compilePseudoSelector<Node, ElementNode extends Node>(
next: CompiledQuery<ElementNode>,
selector: PseudoSelector,
options: InternalOptions<Node, ElementNode>,
context: ElementNode[] | undefined,
compileToken: CompileToken<Node, ElementNode>
): CompiledQuery<ElementNode> {
const { name, data } = selector;
const { adapter } = options;
if (options.strict && !reCSS3.test(name)) {
throw new Error(`:${name} isn't part of CSS3`);
}
if (Array.isArray(data)) {
return subselects[name](next, data, options, context, compileToken);
}
if (name in filters) {
return filters[name](next, data as string, options, context);
}
if (name in pseudos) {
const pseudo = pseudos[name];
verifyPseudoArgs(pseudo, name, data);
return pseudo === falseFunc
? falseFunc
: next === trueFunc
? (elem) => pseudo(elem, adapter, data)
: (elem) => pseudo(elem, adapter, data) && next(elem);
}
throw new Error(`unmatched pseudo-class :${name}`);
}

@ -0,0 +1,226 @@
import { PseudoSelector } from "css-what";
import type { Adapter } from "../types";
export type Pseudo = <Node, ElementNode extends Node>(
elem: ElementNode,
adapter: Adapter<Node, ElementNode>,
subselect?: ElementNode | string | null
) => boolean;
// While filters are precompiled, pseudos get called when they are needed
export const pseudos: Record<string, Pseudo> = {
empty(elem, adapter) {
return !adapter.getChildren(elem).some(
(elem) =>
// FIXME: `getText` call is potentially expensive.
adapter.isTag(elem) || adapter.getText(elem) !== ""
);
},
"first-child"(elem, adapter) {
return (
adapter.getSiblings(elem).find((elem) => adapter.isTag(elem)) ===
elem
);
},
"last-child"(elem, adapter) {
const siblings = adapter.getSiblings(elem);
for (let i = siblings.length - 1; i >= 0; i--) {
if (siblings[i] === elem) return true;
if (adapter.isTag(siblings[i])) break;
}
return false;
},
"first-of-type"(elem, adapter) {
const siblings = adapter.getSiblings(elem);
for (let i = 0; i < siblings.length; i++) {
const currentSibling = siblings[i];
if (adapter.isTag(currentSibling)) {
if (siblings[i] === elem) return true;
if (adapter.getName(currentSibling) === adapter.getName(elem)) {
break;
}
}
}
return false;
},
"last-of-type"(elem, adapter) {
const siblings = adapter.getSiblings(elem);
for (let i = siblings.length - 1; i >= 0; i--) {
const currentSibling = siblings[i];
if (adapter.isTag(currentSibling)) {
if (siblings[i] === elem) return true;
if (adapter.getName(currentSibling) === adapter.getName(elem)) {
break;
}
}
}
return false;
},
"only-of-type"(elem, adapter) {
const siblings = adapter.getSiblings(elem);
for (let i = 0, j = siblings.length; i < j; i++) {
const currentSibling = siblings[i];
if (adapter.isTag(currentSibling)) {
if (currentSibling === elem) continue;
if (adapter.getName(currentSibling) === adapter.getName(elem)) {
return false;
}
}
}
return true;
},
"only-child"(elem, adapter) {
const siblings = adapter.getSiblings(elem);
for (let i = 0; i < siblings.length; i++) {
if (adapter.isTag(siblings[i]) && siblings[i] !== elem) {
return false;
}
}
return true;
},
// :matches(a, area, link)[href]
link(elem, adapter) {
return adapter.hasAttrib(elem, "href");
},
// TODO: :any-link once the name is finalized (as an alias of :link)
/*
* Forms
* to consider: :target
*/
// :matches([selected], select:not([multiple]):not(> option[selected]) > option:first-of-type)
selected(elem, adapter) {
if (adapter.hasAttrib(elem, "selected")) return true;
else if (adapter.getName(elem) !== "option") return false;
// The first <option> in a <select> is also selected
const parent = adapter.getParent(elem);
if (
!parent ||
adapter.getName(parent) !== "select" ||
adapter.hasAttrib(parent, "multiple")
) {
return false;
}
const siblings = adapter.getChildren(parent);
let sawElem = false;
for (let i = 0; i < siblings.length; i++) {
const currentSibling = siblings[i];
if (adapter.isTag(currentSibling)) {
if (currentSibling === elem) {
sawElem = true;
} else if (!sawElem) {
return false;
} else if (adapter.hasAttrib(currentSibling, "selected")) {
return false;
}
}
}
return sawElem;
},
/*
* https://html.spec.whatwg.org/multipage/scripting.html#disabled-elements
* :matches(
* :matches(button, input, select, textarea, menuitem, optgroup, option)[disabled],
* optgroup[disabled] > option),
* fieldset[disabled] * //TODO not child of first <legend>
* )
*/
disabled(elem, adapter) {
return adapter.hasAttrib(elem, "disabled");
},
enabled(elem, adapter) {
return !adapter.hasAttrib(elem, "disabled");
},
// :matches(:matches(:radio, :checkbox)[checked], :selected) (TODO menuitem)
checked(elem, adapter) {
return (
adapter.hasAttrib(elem, "checked") ||
pseudos.selected(elem, adapter)
);
},
// :matches(input, select, textarea)[required]
required(elem, adapter) {
return adapter.hasAttrib(elem, "required");
},
// :matches(input, select, textarea):not([required])
optional(elem, adapter) {
return !adapter.hasAttrib(elem, "required");
},
// JQuery extensions
// :not(:empty)
parent(elem, adapter) {
return !pseudos.empty(elem, adapter);
},
// :matches(h1, h2, h3, h4, h5, h6)
header: namePseudo(["h1", "h2", "h3", "h4", "h5", "h6"]),
// :matches(button, input[type=button])
button(elem, adapter) {
const name = adapter.getName(elem);
return (
name === "button" ||
(name === "input" &&
adapter.getAttributeValue(elem, "type") === "button")
);
},
// :matches(input, textarea, select, button)
input: namePseudo(["input", "textarea", "select", "button"]),
// `input:matches(:not([type!='']), [type='text' i])`
text(elem, adapter) {
const type = adapter.getAttributeValue(elem, "type");
return (
adapter.getName(elem) === "input" &&
(!type || type.toLowerCase() === "text")
);
},
};
function namePseudo<Node, ElementNode extends Node>(names: string[]) {
if (typeof Set !== "undefined") {
const nameSet = new Set(names);
return (elem: ElementNode, adapter: Adapter<Node, ElementNode>) =>
nameSet.has(adapter.getName(elem));
}
return (elem: ElementNode, adapter: Adapter<Node, ElementNode>) =>
names.includes(adapter.getName(elem));
}
export function verifyPseudoArgs(
func: Pseudo,
name: string,
subselect: PseudoSelector["data"]
): void {
if (subselect === null) {
if (func.length > 2 && name !== "scope") {
throw new Error(`pseudo-selector :${name} requires an argument`);
}
} else {
if (func.length === 2) {
throw new Error(
`pseudo-selector :${name} doesn't have any arguments`
);
}
}
}

@ -0,0 +1,118 @@
import { CompileToken } from "./../types";
import type { Selector } from "css-what";
import { trueFunc, falseFunc } from "boolbase";
import type { CompiledQuery, InternalOptions, Adapter } from "../types";
import { isTraversal } from "../procedure";
/** Used as a placeholder for :has. Will be replaced with the actual element. */
export const PLACEHOLDER_ELEMENT = {};
function containsTraversal(t: Selector[]): boolean {
return t.some(isTraversal);
}
export function ensureIsTag<Node, ElementNode extends Node>(
next: CompiledQuery<ElementNode>,
adapter: Adapter<Node, ElementNode>
): CompiledQuery<ElementNode> {
if (next === falseFunc) return next;
return (elem: Node) => adapter.isTag(elem) && next(elem);
}
export type Subselect = <Node, ElementNode extends Node>(
next: CompiledQuery<ElementNode>,
subselect: Selector[][],
options: InternalOptions<Node, ElementNode>,
context: ElementNode[] | undefined,
compileToken: CompileToken<Node, ElementNode>
) => CompiledQuery<ElementNode>;
/*
* :not, :has and :matches have to compile selectors
* doing this in src/pseudos.ts would lead to circular dependencies,
* so we add them here
*/
export const subselects: Record<string, Subselect> = {
/**
* `:is` is an alias for `:matches`.
*/
is(next, token, options, context, compileToken) {
return subselects.matches(next, token, options, context, compileToken);
},
matches(next, token, options, context, compileToken) {
const opts = {
xmlMode: !!options.xmlMode,
strict: !!options.strict,
adapter: options.adapter,
rootFunc: next,
};
return compileToken(token, opts, context);
},
not(next, token, options, context, compileToken) {
const opts = {
xmlMode: !!options.xmlMode,
strict: !!options.strict,
adapter: options.adapter,
};
if (opts.strict) {
if (token.length > 1 || token.some(containsTraversal)) {
throw new Error(
"complex selectors in :not aren't allowed in strict mode"
);
}
}
const func = compileToken(token, opts, context);
if (func === falseFunc) return next;
if (func === trueFunc) return falseFunc;
return function not(elem) {
return !func(elem) && next(elem);
};
},
has<Node, ElementNode extends Node>(
next: CompiledQuery<ElementNode>,
subselect: Selector[][],
options: InternalOptions<Node, ElementNode>,
_context: ElementNode[] | undefined,
compileToken: CompileToken<Node, ElementNode>
): CompiledQuery<ElementNode> {
const { adapter } = options;
const opts = {
xmlMode: options.xmlMode,
strict: options.strict,
adapter,
};
// @ts-expect-error Uses an array as a pointer to the current element (side effects)
const context: ElementNode[] | undefined = subselect.some(
containsTraversal
)
? [PLACEHOLDER_ELEMENT]
: undefined;
const compiled = compileToken(subselect, opts, context);
if (compiled === falseFunc) return falseFunc;
if (compiled === trueFunc) {
return (elem) =>
adapter.getChildren(elem).some(adapter.isTag) && next(elem);
}
const hasElement = ensureIsTag(compiled, adapter);
if (context) {
return (elem) =>
next(elem) &&
((context[0] = elem),
adapter.existsOne(hasElement, adapter.getChildren(elem)));
}
return (elem) =>
next(elem) &&
adapter.existsOne(hasElement, adapter.getChildren(elem));
},
};

@ -1,15 +1,8 @@
module.exports = sortByProcedure;
import type { InternalSelector } from "./types";
import type { AttributeAction } from "css-what";
import { procedure } from "./procedure";
/*
sort the parts of the passed selector,
as there is potential for optimization
(some types of selectors are faster than others)
*/
const procedure = require("./procedure.json");
const attributes = {
__proto__: null,
const attributes: Record<AttributeAction, number> = {
exists: 10,
equals: 8,
not: 7,
@ -20,7 +13,14 @@ const attributes = {
element: 4,
};
function sortByProcedure(arr) {
/**
* Sort the parts of the passed selector,
* as there is potential for optimization
* (some types of selectors are faster than others)
*
* @param arr Selector to sort
*/
export default function sortByProcedure(arr: InternalSelector[]): void {
const procs = arr.map(getProcedure);
for (let i = 1; i < arr.length; i++) {
const procNew = procs[i];
@ -37,34 +37,38 @@ function sortByProcedure(arr) {
}
}
function getProcedure(token) {
function getProcedure(token: InternalSelector): number {
let proc = procedure[token.type];
if (proc === procedure.attribute) {
if (token.type === "attribute") {
proc = attributes[token.action];
if (proc === attributes.equals && token.name === "id") {
//prefer ID selectors (eg. #ID)
// Prefer ID selectors (eg. #ID)
proc = 9;
}
if (token.ignoreCase) {
//ignoreCase adds some overhead, prefer "normal" token
//this is a binary operation, to ensure it's still an int
/*
* IgnoreCase adds some overhead, prefer "normal" token
* this is a binary operation, to ensure it's still an int
*/
proc >>= 1;
}
} else if (proc === procedure.pseudo) {
} else if (token.type === "pseudo") {
if (!token.data) {
proc = 3;
} else if (token.name === "has" || token.name === "contains") {
proc = 0; //expensive in any case
} else if (token.name === "matches" || token.name === "not") {
proc = 0; // Expensive in any case
} else if (Array.isArray(token.data)) {
// "matches" and "not"
proc = 0;
for (let i = 0; i < token.data.length; i++) {
//TODO better handling of complex selectors
// TODO better handling of complex selectors
if (token.data[i].length !== 1) continue;
const cur = getProcedure(token.data[i][0]);
//avoid executing :has or :contains
// Avoid executing :has or :contains
if (cur === 0) {
proc = 0;
break;

@ -0,0 +1,145 @@
import type { Selector } from "css-what";
export type InternalSelector = Selector | { type: "_flexibleDescendant" };
export type Predicate<Value> = (v: Value) => boolean;
export interface Adapter<Node, ElementNode extends Node> {
/**
* Is the node a tag?
*/
isTag: (node: Node) => node is ElementNode;
/**
* Does at least one of passed element nodes pass the test predicate?
*/
existsOne: (test: Predicate<ElementNode>, elems: Node[]) => boolean;
/**
* Get the attribute value.
*/
getAttributeValue: (elem: ElementNode, name: string) => string | undefined;
/**
* Get the node's children
*/
getChildren: (node: Node) => Node[];
/**
* Get the name of the tag
*/
getName: (elem: ElementNode) => string;
/**
* Get the parent of the node
*/
getParent: (node: ElementNode) => ElementNode | null;
/*
*Get the siblings of the node. Note that unlike jQuery's `siblings` method,
*this is expected to include the current node as well
*/
getSiblings: (node: Node) => Node[];
/*
* Get the text content of the node, and its children if it has any.
*/
getText: (node: Node) => string;
/**
* Does the element have the named attribute?
*/
hasAttrib: (elem: ElementNode, name: string) => boolean;
/**
* Takes an array of nodes, and removes any duplicates, as well as any
* nodes whose ancestors are also in the array.
*/
removeSubsets: (nodes: Node[]) => Node[];
/**
* Finds all of the element nodes in the array that match the test predicate,
* as well as any of their children that match it.
*/
findAll: (test: Predicate<ElementNode>, nodes: Node[]) => ElementNode[];
/**
* Finds the first node in the array that matches the test predicate, or one
* of its children.
*/
findOne: (
test: Predicate<ElementNode>,
elems: Node[]
) => ElementNode | null;
/**
* The adapter can also optionally include an equals method, if your DOM
* structure needs a custom equality test to compare two objects which refer
* to the same underlying node. If not provided, `css-select` will fall back to
* `a === b`.
*/
equals?: (a: Node, b: Node) => boolean;
/**
* Is the element in hovered state?
*/
isHovered?: (elem: ElementNode) => boolean;
/**
* Is the element in visited state?
*/
isVisited?: (elem: ElementNode) => boolean;
/**
* Is the element in active state?
*/
isActive?: (elem: ElementNode) => boolean;
}
import * as DomUtils from "domutils";
import { Node, Element } from "domhandler";
// Ensure that DomUtils (the default adapter) matches the given specification
DomUtils as Adapter<Node, Element>;
// TODO default types to the domutil/htmlparser2 types
export interface Options<Node, ElementNode extends Node> {
/**
* When enabled, tag names will be case-sensitive. Default: false.
*/
xmlMode?: boolean;
/**
* Limits the module to only use CSS3 selectors. Default: false.
*/
strict?: boolean;
/**
* The last function in the stack, will be called with the last element
* that's looked at.
*/
rootFunc?: (element: ElementNode) => boolean;
/**
* The adapter to use when interacting with the backing DOM structure. By
* default it uses domutils.
*/
adapter?: Adapter<Node, ElementNode>;
/**
* The context of the current query. Used to
*/
context?: ElementNode[];
}
// Internally, we want to ensure that no propterties are accessed on the passed objects
export interface InternalOptions<Node, ElementNode extends Node>
extends Options<Node, ElementNode> {
adapter: Adapter<Node, ElementNode>;
}
export interface CompiledQuery<ElementNode> {
(node: ElementNode): boolean;
shouldTestNextSiblings?: boolean;
}
export type Query<ElementNode> = string | CompiledQuery<ElementNode>;
export type CompileToken<Node, ElementNode extends Node> = (
token: InternalSelector[][],
options: InternalOptions<Node, ElementNode>,
context?: ElementNode[]
) => CompiledQuery<ElementNode>;

@ -1,5 +1,9 @@
{
"env": {
"mocha": true
},
"rules": {
"capitalized-comments": 0,
"multiline-comment-style": 0
}
}

@ -1,4 +1,4 @@
const CSSselect = require("..");
const CSSselect = require("../src");
const makeDom = require("htmlparser2").parseDOM;
const bools = require("boolbase");
const assert = require("assert");
@ -10,15 +10,15 @@ describe("API", () => {
describe("removes duplicates", () => {
it("between identical trees", () => {
const matches = CSSselect.selectAll("div", [dom, dom]);
assert.equal(matches.length, 1, "Removes duplicate matches");
assert.strictEqual(matches.length, 1, "Removes duplicate matches");
});
it("between a superset and subset", () => {
const matches = CSSselect.selectAll("p", [dom, dom.children[0]]);
assert.equal(matches.length, 1, "Removes duplicate matches");
assert.strictEqual(matches.length, 1, "Removes duplicate matches");
});
it("betweeen a subset and superset", () => {
const matches = CSSselect.selectAll("p", [dom.children[0], dom]);
assert.equal(matches.length, 1, "Removes duplicate matches");
assert.strictEqual(matches.length, 1, "Removes duplicate matches");
});
});
@ -26,7 +26,7 @@ describe("API", () => {
it("in `is`", () => {
assert(CSSselect.is(dom, (elem) => elem.attribs.id === "foo"));
});
//probably more cases should be added here
// Probably more cases should be added here
});
describe("selectAll", () => {
@ -43,79 +43,82 @@ describe("API", () => {
describe("unsatisfiable and universally valid selectors", () => {
it("in :not", () => {
let func = CSSselect._compileUnsafe(":not(*)");
assert.equal(func, bools.falseFunc);
assert.strictEqual(func, bools.falseFunc);
func = CSSselect._compileUnsafe(":not(:nth-child(-1n-1))");
assert.equal(func, bools.trueFunc);
assert.strictEqual(func, bools.trueFunc);
func = CSSselect._compileUnsafe(":not(:not(:not(*)))");
assert.equal(func, bools.falseFunc);
assert.strictEqual(func, bools.falseFunc);
});
it("in :has", () => {
const matches = CSSselect.selectAll(":has(*)", [dom]);
assert.equal(matches.length, 1);
assert.equal(matches[0], dom);
assert.strictEqual(matches.length, 1);
assert.strictEqual(matches[0], dom);
const func = CSSselect._compileUnsafe(":has(:nth-child(-1n-1))");
assert.equal(func, bools.falseFunc);
assert.strictEqual(func, bools.falseFunc);
});
it("should skip unsatisfiable", () => {
const func = CSSselect._compileUnsafe("* :not(*) foo");
assert.equal(func, bools.falseFunc);
assert.strictEqual(func, bools.falseFunc);
});
it("should promote universally valid", () => {
const func = CSSselect._compileUnsafe("*, foo");
assert.equal(func, bools.trueFunc);
assert.strictEqual(func, bools.trueFunc);
});
});
describe(":matches", () => {
it("should select multiple elements", () => {
let matches = CSSselect.selectAll(":matches(p, div)", [dom]);
assert.equal(matches.length, 2);
assert.strictEqual(matches.length, 2);
matches = CSSselect.selectAll(":matches(div, :not(div))", [dom]);
assert.equal(matches.length, 2);
assert.strictEqual(matches.length, 2);
matches = CSSselect.selectAll(
":matches(boo, baa, tag, div, foo, bar, baz)",
[dom]
);
assert.equal(matches.length, 1);
assert.equal(matches[0], dom);
assert.strictEqual(matches.length, 1);
assert.strictEqual(matches[0], dom);
});
it("should strip quotes", () => {
let matches = CSSselect.selectAll(":matches('p, div')", [dom]);
assert.equal(matches.length, 2);
assert.strictEqual(matches.length, 2);
matches = CSSselect.selectAll(':matches("p, div")', [dom]);
assert.equal(matches.length, 2);
assert.strictEqual(matches.length, 2);
});
});
describe("parent selector (<)", () => {
it("should select the right element", () => {
const matches = CSSselect.selectAll("p < div", [dom]);
assert.equal(matches.length, 1);
assert.equal(matches[0], dom);
assert.strictEqual(matches.length, 1);
assert.strictEqual(matches[0], dom);
});
it("should not select nodes without children", () => {
const matches = CSSselect.selectAll("p < div", [dom]);
assert.deepEqual(matches, CSSselect.selectAll("* < *", [dom]));
assert.deepStrictEqual(
matches,
CSSselect.selectAll("* < *", [dom])
);
});
});
describe("selectOne", () => {
it("should select elements in traversal order", () => {
let match = CSSselect.selectOne("p", [dom]);
assert.equal(match, dom.children[0]);
assert.strictEqual(match, dom.children[0]);
match = CSSselect.selectOne(":contains(foo)", [dom]);
assert.equal(match, dom);
assert.strictEqual(match, dom);
});
it("should take shortcuts when applicable", () => {
//TODO this is currently only visible in coverage reports
// TODO this is currently only visible in coverage reports
let match = CSSselect.selectOne(bools.falseFunc, [dom]);
assert.equal(match, null);
assert.strictEqual(match, null);
match = CSSselect.selectOne("*", []);
assert.equal(match, null);
assert.strictEqual(match, null);
});
});
@ -131,50 +134,35 @@ describe("API", () => {
it("should be strict", () => {
const opts = { strict: true };
assert.throws(() => CSSselect.compile(":checkbox", opts), Error);
assert.throws(() => CSSselect.compile("[attr=val i]", opts), Error);
assert.throws(() => CSSselect.compile("[attr!=val]", opts), Error);
assert.throws(
CSSselect.compile.bind(null, ":checkbox", opts),
Error
);
assert.throws(
CSSselect.compile.bind(null, "[attr=val i]", opts),
Error
);
assert.throws(
CSSselect.compile.bind(null, "[attr!=val]", opts),
Error
);
assert.throws(
CSSselect.compile.bind(null, "[attr!=val i]", opts),
() => CSSselect.compile("[attr!=val i]", opts),
Error
);
assert.throws(() => CSSselect.compile("foo < bar", opts), Error);
assert.throws(
CSSselect.compile.bind(null, "foo < bar", opts),
Error
);
assert.throws(
CSSselect.compile.bind(null, ":not(:parent)", opts),
Error
);
assert.throws(
CSSselect.compile.bind(null, ":not(a > b)", opts),
Error
);
assert.throws(
CSSselect.compile.bind(null, ":not(a, b)", opts),
() => CSSselect.compile(":not(:parent)", opts),
Error
);
assert.throws(() => CSSselect.compile(":not(a > b)", opts), Error);
assert.throws(() => CSSselect.compile(":not(a, b)", opts), Error);
});
it("should recognize contexts", () => {
const div = CSSselect.selectAll("div", [dom]);
const p = CSSselect.selectAll("p", [dom]);
assert.equal(
assert.strictEqual(
CSSselect.selectOne("div", div, { context: div }),
div[0]
);
assert.equal(CSSselect.selectOne("div", div, { context: p }), null);
assert.deepEqual(
assert.strictEqual(
CSSselect.selectOne("div", div, { context: p }),
null
);
assert.deepStrictEqual(
CSSselect.selectAll("p", div, { context: div }),
p
);

@ -1,4 +1,4 @@
const CSSselect = require("../");
const CSSselect = require("../src");
const makeDom = require("htmlparser2").parseDOM;
const { falseFunc } = require("boolbase");
const assert = require("assert");

@ -23,9 +23,8 @@ function removeCircularRefs(value) {
return value.map(NodeWithoutCircularReferences);
} else if (isNode(value)) {
return NodeWithoutCircularReferences(value);
} else {
return value;
}
return value;
}
module.exports = removeCircularRefs;

@ -1,4 +1,4 @@
const CSSselect = require("../");
const CSSselect = require("../src");
const makeDom = require("htmlparser2").parseDOM;
const assert = require("assert");

@ -1,2 +0,0 @@
--check-leaks
--reporter spec

@ -8,35 +8,34 @@ const assert = require("assert");
const path = require("path");
const decircularize = require("../decircularize");
const document = helper.getDocument(path.join(__dirname, "test.html"));
const { CSSselect } = helper;
const CSSselect = require("../../src");
//Prototype's `$` function
function getById(element) {
if (arguments.length === 1) {
if (typeof element === "string") {
return DomUtils.getElementById(element, document);
}
return element;
} else {
return Array.prototype.map.call(arguments, (elem) => getById(elem));
}
// Prototype's `$` function
function getById(...args) {
if (args.some(arg => typeof arg !== 'string')) throw new Error()
const elements = args.map(id => DomUtils.getElementById(id, document))
return elements.length === 1 ? elements[0] : elements
}
//NWMatcher methods
// NWMatcher methods
const select = (query, doc = document) =>
CSSselect(query, typeof doc === "string" ? select(doc) : doc);
CSSselect.selectAll(query, typeof doc === "string" ? select(doc) : doc);
const match = CSSselect.is;
const validators = {
assert,
assertEqual: assert.equal,
assertEqual: assert.strictEqual,
assertEquivalent(v1, v2, message) {
return assert.deepEqual(decircularize(v1), decircularize(v2), message);
return assert.deepStrictEqual(
decircularize(v1),
decircularize(v2),
message
);
},
refute: function refute(a, msg) {
assert(!a, msg);
},
assertThrowsException() {}, //not implemented
assertThrowsException() {}, // not implemented
};
const runner = {
@ -52,27 +51,27 @@ const runner = {
} else run();
function run() {
Object.keys(tests).forEach((name) => {
for (const name of Object.keys(tests)) {
it(name, () => {
tests[name].call(validators);
});
});
}
}
},
};
const RUN_BENCHMARKS = false;
//The tests...
// The tests...
(function (runner) {
runner.addGroup("Basic Selectors").addTests(null, {
"*"() {
//Universal selector
// Universal selector
const results = [];
const nodes = document.getElementsByTagName("*");
let index = 0;
const { length } = nodes;
let node;
//Collect all element nodes, excluding comments (IE)
// Collect all element nodes, excluding comments (IE)
for (; index < length; index++) {
if ((node = nodes[index]).tagName !== "!") {
results[results.length] = node;
@ -85,13 +84,9 @@ const RUN_BENCHMARKS = false;
);
},
E() {
//Type selector
const results = [];
let index = 0;
// Type selector
const nodes = document.getElementsByTagName("li");
while ((results[index] = nodes[index++]));
results.length--;
// this.assertEquivalent(select("li"), results); //TODO
this.assertEquivalent(select("li"), nodes);
this.assertEqual(
select("strong", getById("fixtures"))[0],
getById("strong")
@ -99,13 +94,13 @@ const RUN_BENCHMARKS = false;
this.assertEquivalent(select("nonexistent"), []);
},
"#id"() {
//ID selector
// ID selector
this.assertEqual(select("#fixtures")[0], getById("fixtures"));
this.assertEquivalent(select("nonexistent"), []);
this.assertEqual(select("#troubleForm")[0], getById("troubleForm"));
},
".class"() {
//Class selector
// Class selector
this.assertEquivalent(
select(".first"),
getById("p", "link_1", "item_1")
@ -201,17 +196,17 @@ const RUN_BENCHMARKS = false;
select('#troubleForm2 input[name="brackets[5][]"]'),
getById("chk_1", "chk_2")
);
//Brackets in attribute value
// Brackets in attribute value
this.assertEqual(
select('#troubleForm2 input[name="brackets[5][]"]:checked')[0],
getById("chk_1")
);
//Space in attribute value
// Space in attribute value
this.assertEqual(
select('cite[title="hello world!"]')[0],
getById("with_title")
);
//Namespaced attributes
// Namespaced attributes
// this.assertEquivalent(select('[xml:lang]'), [document.documentElement, getById("item_3")]);
// this.assertEquivalent(select('*[xml:lang]'), [document.documentElement, getById("item_3")]);
},
@ -460,7 +455,7 @@ const RUN_BENCHMARKS = false;
select("#level1 *:only-child")[0],
getById("level_only_child")
);
//Shouldn't return anything
// Shouldn't return anything
this.assertEquivalent(select("#level1>*:only-child"), []);
this.assertEquivalent(select("#level1:only-child"), []);
this.assertEquivalent(
@ -498,16 +493,16 @@ const RUN_BENCHMARKS = false;
getById("level3_1"),
"IE forced empty content!"
);
//this.skip("IE forced empty content!");
// this.skip("IE forced empty content!");
}
//Shouldn't return anything
// Shouldn't return anything
this.assertEquivalent(select("span:empty > *"), []);
},
});
runner.addTests(null, {
"E:not(s)"() {
//Negation pseudo-class
// Negation pseudo-class
this.assertEquivalent(select('a:not([href="#"])'), []);
this.assertEquivalent(select("div.brothers:not(.brothers)"), []);
this.assertEquivalent(
@ -594,7 +589,7 @@ const RUN_BENCHMARKS = false;
runner.addGroup("Combinators").addTests(null, {
"E F"() {
//Descendant
// Descendant
this.assertEquivalent(
select("#fixtures a *"),
getById("em2", "em", "span")
@ -602,7 +597,7 @@ const RUN_BENCHMARKS = false;
this.assertEqual(select("div#fixtures p")[0], getById("p"));
},
"E + F"() {
//Adjacent sibling
// Adjacent sibling
this.assertEqual(
select("div.brothers + div.brothers")[0],
getById("uncle")
@ -664,7 +659,7 @@ const RUN_BENCHMARKS = false;
}
},
"E > F"() {
//Child
// Child
this.assertEquivalent(
select("p.first > a"),
getById("link_1", "link_2")
@ -718,7 +713,7 @@ const RUN_BENCHMARKS = false;
}
},
"E ~ F"() {
//General sibling
// General sibling
this.assertEqual(select("h1 ~ ul")[0], getById("list"));
this.assertEquivalent(select("#level2_2 ~ span"), []);
this.assertEquivalent(select("#level3_2 ~ *"), []);
@ -778,7 +773,7 @@ const RUN_BENCHMARKS = false;
runner.addTests(null, {
"NW.Dom.match"() {
const element = getById("dupL1");
//Assertions
// Assertions
this.assert(match(element, "span"));
this.assert(match(element, "span#dupL1"));
this.assert(match(element, "div > span"), "child combinator");
@ -793,7 +788,7 @@ const RUN_BENCHMARKS = false;
match(element, "span:first-child"),
"first-child pseudoclass"
);
//Refutations
// Refutations
this.refute(match(element, "span.span_wtf"), "bogus class name");
this.refute(match(element, "#dupL2"), "different ID");
this.refute(match(element, "div"), "different tag name");
@ -803,7 +798,7 @@ const RUN_BENCHMARKS = false;
match(element, "span:nth-child(5)"),
"different pseudoclass"
);
//Misc.
// Misc.
this.refute(match(getById("link_2"), "a[rel^=external]"));
this.assert(match(getById("link_1"), "a[rel^=external]"));
this.assert(match(getById("link_1"), 'a[rel^="external"]'));
@ -859,7 +854,7 @@ const RUN_BENCHMARKS = false;
);
},
"Multiple Selectors"() {
//The next two assertions should return document-ordered lists of matching elements --Diego Perini
// The next two assertions should return document-ordered lists of matching elements --Diego Perini
// this.assertEquivalent(select('#list, .first,*[xml:lang="es-us"] , #troubleForm'), getById('p', 'link_1', 'list', 'item_1', 'item_3', 'troubleForm'));
// this.assertEquivalent(select('#list, .first, *[xml:lang="es-us"], #troubleForm'), getById('p', 'link_1', 'list', 'item_1', 'item_3', 'troubleForm'));
this.assertEquivalent(

File diff suppressed because it is too large Load Diff

@ -1,25 +1,10 @@
const assert = require("assert");
const decircularize = require("../../decircularize");
const helper = require("../../tools/helper.js");
const { CSSselect } = helper;
const helper = require("../../tools/helper");
const CSSselect = require("../../../src").default;
const path = require("path");
const docPath = path.join(__dirname, "index.html");
let document = null;
//in this module, the only use-case is to compare arrays of
function deepEqual(a, b, msg) {
try {
assert.deepEqual(decircularize(a), decircularize(b), msg);
} catch (e) {
e.actual = JSON.stringify(a.map(getId), 0, 2);
e.expected = JSON.stringify(b.map(getId), 0, 2);
throw e;
}
function getId(n) {
return n && n.attribs.id;
}
}
function loadDoc() {
return (document = helper.getDocument(docPath));
@ -37,32 +22,23 @@ module.exports = {
* @example q("main", "foo", "bar")
* @result [<div id="main">, <span id="foo">, <input id="bar">]
*/
function q(...args) {
const r = [];
for (let i = 0; i < args.length; i++) {
r.push(document.getElementById(args[i]));
}
return r;
function q(...ids) {
return ids.map(id => document.getElementById(id))
}
/**
* Asserts that a select matches the given IDs
* @param {String} a - Assertion name
* @param {String} b - Sizzle selector
* @param {String} c - Array of ids to construct what is expected
* @param {String} assertionName - Assertion name
* @param {String} sizzleSelector - Sizzle selector
* @param {String} expectedIds - Array of ids to construct what is expected
* @example t("Check for something", "//[a]", ["foo", "baar"]);
* @result returns true if "//[a]" return two elements with the IDs 'foo' and 'baar'
* @returns `true` iff the selector produces the expected elements.
*/
function t(a, b, c) {
const f = CSSselect(b, document);
let s = "";
for (let i = 0; i < f.length; i++) {
s += `${s && ","}"${f[i].id}"`;
}
function t(assertionName, sizzleSelector, expectedIds) {
const actual = CSSselect(sizzleSelector, document);
const actualIds = actual.map(e => e.attribs.id);
deepEqual(f, q.apply(q, c), `${a} (${b})`);
assert.deepStrictEqual(actualIds, expectedIds, `${assertionName} (${sizzleSelector})`);
}
const xmlDoc = helper.getDOMFromPath(path.join(__dirname, "fries.xml"), {

@ -1,6 +1,6 @@
const DomUtils = require("domutils");
const helper = require("../tools/helper.js");
const { CSSselect } = helper;
const helper = require("../tools/helper");
const CSSselect = require("../../src");
const assert = require("assert");
const { throws: raises, equal, ok } = assert;
const testInit = require("./data/testinit.js");
@ -11,11 +11,11 @@ const test = it;
const decircularize = require("../decircularize");
function deepEqual(e, a, m) {
return assert.deepEqual(decircularize(e), decircularize(a), m);
return assert.deepStrictEqual(decircularize(e), decircularize(a), m);
}
function Sizzle(str, doc = document) {
return CSSselect(str, doc);
return CSSselect.selectAll(str, doc);
}
Sizzle.matches = (selector, elements) =>
@ -287,7 +287,7 @@ test("element", () => {
"Finding elements with id of ID."
);
const siblingTest = document.getElementById("siblingTest"); // TODO
const siblingTest = document.getElementById("siblingTest");
deepEqual(
Sizzle("div em", siblingTest),
[],
@ -305,12 +305,12 @@ test("element", () => {
);
const iframe = document.getElementById("iframe");
//iframeDoc.open();
// iframeDoc.open();
iframe.children = helper.getDOM("<body><p id='foo'>bar</p></body>");
iframe.children.forEach((e) => {
e.parent = iframe;
});
//iframeDoc.close();
// iframeDoc.close();
deepEqual(
Sizzle("p:contains(bar)", iframe),
[DomUtils.getElementById("foo", iframe.children)],
@ -616,7 +616,7 @@ test("class", () => {
!Sizzle.matchesSelector(document, ".foo"),
"testing class on document doesn't error"
);
//ok( !Sizzle.matchesSelector( window, ".foo" ), "testing class on window doesn't error" );
// ok( !Sizzle.matchesSelector( window, ".foo" ), "testing class on window doesn't error" );
lastChild.attribs.class += " hasOwnProperty toString";
deepEqual(
@ -827,7 +827,7 @@ test("child and adjacent", () => {
q("siblingnext"),
"Element Directly Preceded By with a context."
);
//deepEqual( Sizzle("~ em:first", siblingFirst), q("siblingnext"), "Element Preceded By positional with a context." );
// deepEqual( Sizzle("~ em:first", siblingFirst), q("siblingnext"), "Element Preceded By positional with a context." );
const en = document.getElementById("en");
deepEqual(
@ -867,7 +867,7 @@ test("child and adjacent", () => {
1,
"Parent div for next test is found via ID (#8310)"
);
//equal( Sizzle("#listWithTabIndex li:eq(2) ~ li").length, 1, "Find by general sibling combinator (#8310)" );
// equal( Sizzle("#listWithTabIndex li:eq(2) ~ li").length, 1, "Find by general sibling combinator (#8310)" );
equal(
Sizzle("#__sizzle__").length,
0,
@ -883,9 +883,9 @@ test("child and adjacent", () => {
t("No element deep selector", "div.foo > span > a", []);
//deepEqual( Sizzle("> :first", nothiddendiv), q("nothiddendivchild"), "Verify child context positional selector" );
//deepEqual( Sizzle("> :eq(0)", nothiddendiv), q("nothiddendivchild"), "Verify child context positional selector" );
//deepEqual( Sizzle("> *:first", nothiddendiv), q("nothiddendivchild"), "Verify child context positional selector" );
// deepEqual( Sizzle("> :first", nothiddendiv), q("nothiddendivchild"), "Verify child context positional selector" );
// deepEqual( Sizzle("> :eq(0)", nothiddendiv), q("nothiddendivchild"), "Verify child context positional selector" );
// deepEqual( Sizzle("> *:first", nothiddendiv), q("nothiddendivchild"), "Verify child context positional selector" );
t("Non-existant ancestors", ".fototab > .thumbnails > a", []);
deepEqual(
@ -1622,7 +1622,7 @@ test("pseudo - misc", () => {
// http://dev.w3.org/html5/spec/single-page.html#focus-management
document.body.children.push(tmp);
tmp.tabIndex = 0;
//tmp.focus();
// tmp.focus();
if (
document.activeElement !== tmp ||
(document.hasFocus && !document.hasFocus()) ||
@ -1640,9 +1640,9 @@ test("pseudo - misc", () => {
}
// Blur tmp
//tmp.blur();
//document.body.focus();
//ok( !Sizzle.matchesSelector( tmp, ":focus" ), ":focus doesn't match tabIndex div" );
// tmp.blur();
// document.body.focus();
// ok( !Sizzle.matchesSelector( tmp, ":focus" ), ":focus doesn't match tabIndex div" );
document.body.children.pop();
// Input focus/active
@ -1651,7 +1651,7 @@ test("pseudo - misc", () => {
input.attribs.id = "focus-input";
document.body.children.push(input);
//input.focus();
// input.focus();
// Inputs can't be focused unless the document has focus
if (
@ -1667,14 +1667,14 @@ test("pseudo - misc", () => {
ok(Sizzle.matchesSelector(input, ":focus"), ":focus matches");
}
//input.blur();
// input.blur();
// When IE is out of focus, blur does not work. Force it here.
if (document.activeElement === input) {
document.body.focus();
}
//ok( !Sizzle.matchesSelector( input, ":focus" ), ":focus doesn't match" );
// ok( !Sizzle.matchesSelector( input, ":focus" ), ":focus doesn't match" );
document.body.children.pop();
deepEqual(
@ -1717,13 +1717,13 @@ test("pseudo - misc", () => {
);
t("Multi-pseudo", "#ap:has(*), #ap:has(*)", ["ap"]);
//t( "Multi-positional", "#ap:gt(0), #ap:lt(1)", ["ap"] );
// t( "Multi-positional", "#ap:gt(0), #ap:lt(1)", ["ap"] );
t(
"Multi-pseudo with leading nonexistent id",
"#nonexistent:has(*), #ap:has(*)",
["ap"]
);
//t( "Multi-positional with leading nonexistent id", "#nonexistent:gt(0), #ap:lt(1)", ["ap"] );
// t( "Multi-positional with leading nonexistent id", "#nonexistent:gt(0), #ap:lt(1)", ["ap"] );
t(
"Tokenization stressor",
@ -1736,7 +1736,7 @@ test("pseudo - :not", () => {
expect(43);
t("Not", "a.blog:not(.link)", ["mark"]);
//t( ":not() with :first", "#foo p:not(:first) .link", ["simon"] );
// t( ":not() with :first", "#foo p:not(:first) .link", ["simon"] );
t(
"Not - multiple",

@ -1,6 +1,4 @@
describe("nwmatcher", () => {
require("./nwmatcher/");
});
describe("nwmatcher", () => require("./nwmatcher/"));
describe("sizzle", () => {
describe("selector", () => {
@ -8,16 +6,14 @@ describe("sizzle", () => {
});
});
describe("qwery", () => {
exportsRun(require("./qwery/"));
});
describe("qwery", () => exportsRun(require("./qwery/")));
function exportsRun(mod) {
Object.keys(mod).forEach((name) => {
if (typeof mod[name] === "object") {
describe(name, () => {
exportsRun(mod[name]);
});
} else it(name, mod[name]);
});
for (const [name, suite] of Object.entries(mod)) {
if (typeof suite === "object") {
describe(name, () => exportsRun(suite));
} else {
it(name, suite);
}
}
}

@ -1,16 +1,13 @@
const ben = require("ben");
const testString =
'doo, *#foo > elem.bar[class$=bAz i]:not([ id *= "2" ]):nth-child(2n)';
const helper = require("./helper.js");
const { CSSselect } = helper;
const { compile } = CSSselect;
const helper = require("./helper");
const CSSselect = require("../../src");
const dom = helper.getDefaultDom();
//console.log("Parsing took:", ben(1e5, function(){compile(testString);}));
const compiled = compile(testString);
// console.log("Parsing took:", ben(1e5, function(){compile(testString);}));
const compiled = CSSselect.compile(testString);
console.log(
"Executing took:",
ben(1e6, () => {
CSSselect(compiled, dom);
}) * 1e3
ben(1e6, () => CSSselect.selectAll(compiled, dom)) * 1e3
);

@ -1,15 +1,13 @@
const fs = require("fs");
const path = require("path");
const htmlparser2 = require("htmlparser2");
const { DomUtils } = htmlparser2;
const CSSselect = require("../../");
const DomUtils = require("domutils");
function getDOMFromPath(path, options) {
return htmlparser2.parseDOM(fs.readFileSync(path).toString(), options);
}
module.exports = {
CSSselect,
getFile(name, options) {
return getDOMFromPath(path.join(__dirname, "docs", name), options);
},

@ -1,6 +1,6 @@
const helper = require("./helper.js");
const helper = require("./helper");
const doc = helper.getFile("W3C_Selectors.html");
const { CSSselect } = helper;
const CSSselect = require("../../src");
const soupselect = require("cheerio-soupselect");
const selectors = [
"body",
@ -32,30 +32,30 @@ const selectors = [
"div[class*=e]",
"div[class|=dialog]",
"div[class!=made_up]",
"div[class~=example]" /*, "div:not(.example)", "p:contains(selectors)", "p:nth-child(even)", "p:nth-child(2n)", "p:nth-child(odd)", "p:nth-child(2n+1)", "p:nth-child(n)", "p:only-child", "p:last-child", "p:first-child"*/,
"div[class~=example]" /* , "div:not(.example)", "p:contains(selectors)", "p:nth-child(even)", "p:nth-child(2n)", "p:nth-child(odd)", "p:nth-child(2n+1)", "p:nth-child(n)", "p:only-child", "p:last-child", "p:first-child"*/,
];
const engines = [(a, b) => CSSselect(b, a), soupselect.select];
//returns true when an error occurs
// returns true when an error occurs
function testResult(rule) {
const results = engines.map((func) => func(doc, rule));
//check if both had the same result
// check if both had the same result
for (let i = 1; i < results.length; i++) {
//TODO: might be hard to debug with more engines
// TODO: might be hard to debug with more engines
if (results[i - 1].length !== results[i].length) {
//console.log(rule, results[i-1].length, results[i].length);
// console.log(rule, results[i-1].length, results[i].length);
return true;
}
for (let j = 0; j < results[i].length; j++) {
if (results[i - 1][j] !== results[i][j]) {
if (results[i - 1].indexOf(results[i][j]) === -1) {
if (!results[i - 1].includes(results[i][j])) {
return true;
}
}
}
//require("assert").deepEqual(results[i-1], results[i], rule + ": not the same elements");
// require("assert").deepEqual(results[i-1], results[i], rule + ": not the same elements");
}
return false;
@ -65,20 +65,20 @@ selectors.filter(testResult).forEach((rule) => {
print(rule, "failed!\n");
});
process.exit(0); //don't run speed tests
process.exit(0); // don't run speed tests
print("-----\n\nChecking performance\n\n");
//test the speed
// test the speed
const ben = require("ben");
function testSpeed(rule) {
print(rule, Array(28 - rule.length).join(" "));
print(rule.padEnd(28));
let results = engines.map((func) => () => func(doc, rule));
//also add a precompiled CSSselect test
const compiled = CSSselect(rule);
// also add a precompiled CSSselect test
const compiled = CSSselect.selectAll(rule);
results.unshift(() => CSSselect.iterate(compiled, doc));
results = results.map(ben);

@ -0,0 +1,5 @@
{
"extends": "./tsconfig.json",
"include": ["src"],
"exclude": []
}

@ -0,0 +1,38 @@
{
"compilerOptions": {
/* Basic Options */
"target": "es5" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */,
"module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */,
// "lib": [], /* Specify library files to be included in the compilation. */
"declaration": true /* Generates corresponding '.d.ts' file. */,
"declarationMap": true /* Generates a sourcemap for each corresponding '.d.ts' file. */,
// "sourceMap": true, /* Generates corresponding '.map' file. */
"outDir": "lib" /* Redirect output structure to the directory. */,
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
/* Strict Type-Checking Options */
"strict": true /* Enable all strict type-checking options. */,
/* Additional Checks */
"noUnusedLocals": true /* Report errors on unused locals. */,
"noUnusedParameters": true /* Report errors on unused parameters. */,
"noImplicitReturns": true /* Report error when not all code paths in function return a value. */,
"noFallthroughCasesInSwitch": true /* Report errors for fallthrough cases in switch statement. */,
/* Module Resolution Options */
"baseUrl": "./" /* Base directory to resolve non-absolute module names. */,
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */,
"resolveJsonModule": true,
"paths": {
"*": ["src/declarations/*", "*"]
}
},
"include": ["src"],
"exclude": [
"**/*.spec.ts",
"**/__fixtures__/*",
"**/__tests__/*",
"**/__snapshots__/*"
]
}
Loading…
Cancel
Save