selderee
Version:
Selectors decision tree - choose matching selectors, fast
290 lines (279 loc) • 8.78 kB
text/typescript
/**
* Decision tree node definitions.
*
* @packageDocumentation
*/
/**
* Specificity as defined by Selectors spec.
*
* {@link https://www.w3.org/TR/selectors/#specificity}
*
* Three levels: for id, class, tag (type).
*
* Extra level(s) used in HTML styling don't fit the scope of this package
* and no space reserved for them.
*/
type Specificity = [number, number, number];
/**
* Container for the associated value,
* selector specificity and position in the selectors collection.
*
* @typeParam V - the type of the associated value.
*/
type ValueContainer<V> = {
index: number;
specificity: Specificity;
value: V;
};
/**
* When reached a terminal node, decision tree adds
* the value container to the list of successful matches.
*/
type TerminalNode<V> = {
type: 'terminal';
valueContainer: ValueContainer<V>;
};
/**
* Tag name has to be checked.
* Underlying variants can be assembled
* into a dictionary key check.
*/
type TagNameNode<V> = {
type: 'tagName';
variants: VariantNode<V>[];
};
/**
* String value variant.
*/
type VariantNode<V> = {
type: 'variant';
value: string;
cont: DecisionTreeNode<V>[];
};
/**
* Have to check the presence of an element attribute
* with the given name.
*/
type AttrPresenceNode<V> = {
type: 'attrPresence';
name: string;
cont: DecisionTreeNode<V>[];
};
/**
* Have to check the value of an element attribute
* with the given name.
* It usually requires to run all underlying matchers
* one after another.
*/
type AttrValueNode<V> = {
type: 'attrValue';
name: string;
matchers: MatcherNode<V>[];
};
/**
* String value matcher.
* Contains the predicate so no need to reimplement it
* from descriptive parameters.
*/
type MatcherNode<V> = {
type: 'matcher';
matcher: '=' | '~=' | '|=' | '^=' | '$=' | '*=';
modifier: 'i' | 's' | null;
value: string;
predicate: (prop: string) => boolean;
cont: DecisionTreeNode<V>[];
};
/**
* Simple pseudo-class condition has to be checked.
*/
type PseudoClassNode<V> = {
type: 'pseudoClass';
name: string;
cont: DecisionTreeNode<V>[];
};
/**
* Push next element on the stack, defined by the combinator.
* Only `>` and `+` are expected to be supported.
*
* All checks are performed on the element on top of the stack.
*/
type PushElementNode<V> = {
type: 'pushElement';
combinator: '>' | '+';
cont: DecisionTreeNode<V>[];
};
/**
* Remove the top element from the stack -
* following checks are performed on the previous element.
*/
type PopElementNode<V> = {
type: 'popElement';
cont: DecisionTreeNode<V>[];
};
type DecisionTreeNode<V> = TerminalNode<V> | TagNameNode<V> | AttrPresenceNode<V> | AttrValueNode<V> | PseudoClassNode<V> | PushElementNode<V> | PopElementNode<V>;
type Ast_AttrPresenceNode<V> = AttrPresenceNode<V>;
type Ast_AttrValueNode<V> = AttrValueNode<V>;
type Ast_DecisionTreeNode<V> = DecisionTreeNode<V>;
type Ast_MatcherNode<V> = MatcherNode<V>;
type Ast_PopElementNode<V> = PopElementNode<V>;
type Ast_PseudoClassNode<V> = PseudoClassNode<V>;
type Ast_PushElementNode<V> = PushElementNode<V>;
type Ast_Specificity = Specificity;
type Ast_TagNameNode<V> = TagNameNode<V>;
type Ast_TerminalNode<V> = TerminalNode<V>;
type Ast_ValueContainer<V> = ValueContainer<V>;
type Ast_VariantNode<V> = VariantNode<V>;
declare namespace Ast {
export type { Ast_AttrPresenceNode as AttrPresenceNode, Ast_AttrValueNode as AttrValueNode, Ast_DecisionTreeNode as DecisionTreeNode, Ast_MatcherNode as MatcherNode, Ast_PopElementNode as PopElementNode, Ast_PseudoClassNode as PseudoClassNode, Ast_PushElementNode as PushElementNode, Ast_Specificity as Specificity, Ast_TagNameNode as TagNameNode, Ast_TerminalNode as TerminalNode, Ast_ValueContainer as ValueContainer, Ast_VariantNode as VariantNode };
}
/**
* Function types for builders and matchers.
*
* @packageDocumentation
*/
/**
* A function that turn a decision tree into a callable form.
*
* To be implemented by builder plugins.
*
* @typeParam V - the type of associated value.
*
* @typeParam R - return type for this builder
* (Consider using {@link Picker}.)
*/
type BuilderFunction<V, R> = (nodes: DecisionTreeNode<V>[]) => R;
/**
* Recommended matcher function shape to implement in builders.
*
* The elements stack is represented as the arguments array.
*
* @typeParam L - the type of elements in a particular DOM AST.
* @typeParam V - the type of associated value.
*/
type MatcherFunction<L, V> = (el: L, ...tail: L[]) => ValueContainer<V>[];
type Types_BuilderFunction<V, R> = BuilderFunction<V, R>;
type Types_MatcherFunction<L, V> = MatcherFunction<L, V>;
declare namespace Types {
export type { Types_BuilderFunction as BuilderFunction, Types_MatcherFunction as MatcherFunction };
}
/**
* Basic {@link BuilderFunction} implementation
* for decision tree visualization.
*
* @packageDocumentation
*/
/**
* A {@link BuilderFunction} implementation.
*
* Produces a string representation of the tree
* for testing and debug purposes.
*
* Only accepts `string` as the associated value type.
* Map your input collection before creating a {@link DecisionTree}
* if you want to use it with a different type -
* the decision on how to stringify the value is up to you.
*
* @param nodes - nodes from the root level of the decision tree.
* @returns the string representation of the tree.
*/
declare function treeify(nodes: DecisionTreeNode<string>[]): string;
declare const TreeifyBuilder_treeify: typeof treeify;
declare namespace TreeifyBuilder {
export {
TreeifyBuilder_treeify as treeify,
};
}
/**
* Options for DecisionTree construction.
*/
type DecisionTreeOptions = {
/**
* Attribute names whose values should be case-insensitive by default.
*
* Only affects attribute value selectors (`[name="value"]`) with no explicit case-sensitivity modifier (`i` or `s`).
*/
attributesWithNormalizedValues?: string[];
};
/**
* CSS selectors decision tree.
* Data structure that weaves similar selectors together
* in order to minimize the number of checks required
* to find the ones matching a given HTML element.
*
* Converted into a functioning implementation via plugins
* tailored for specific DOM ASTs.
*
* @typeParam V - the type of values associated with selectors.
*/
declare class DecisionTree<V> {
private readonly branches;
/**
* Create new DecisionTree object.
*
* @param input - an array containing all selectors
* paired with associated values.
* @param options - optional configuration for the decision tree.
*
* @typeParam V - the type of values associated with selectors.
*/
constructor(input: [string, V][], options?: DecisionTreeOptions);
/**
* Turn this decision tree into a callable form.
*
* @typeParam R - return type defined by the builder function.
*
* @param builder - the builder function.
*
* @returns the decision tree in a form ready for use.
*/
build<R>(builder: BuilderFunction<V, R>): R;
}
/**
* Simple wrapper around the matcher function.
* Recommended return type for builder plugins.
*
* @typeParam L - the type of HTML Element in the targeted DOM AST.
* @typeParam V - the type of associated values.
*/
declare class Picker<L, V> {
private f;
/**
* Create new Picker object.
*
* @typeParam L - the type of HTML Element in the targeted DOM AST.
* @typeParam V - the type of associated values.
*
* @param f - the function that matches an element
* and returns all associated values.
*/
constructor(f: MatcherFunction<L, V>);
/**
* Run the selectors decision tree against one HTML Element
* and return all matched associated values
* along with selector specificities.
*
* Client code then decides how to further process them
* (sort, filter, etc).
*
* @param el - an HTML Element.
*
* @returns all associated values along with
* selector specificities for all matched selectors.
*/
pickAll(el: L): ValueContainer<V>[];
/**
* Run the selectors decision tree against one HTML Element
* and choose the value from the most specific matched selector.
*
* @param el - an HTML Element.
*
* @param preferFirst - option to define which value to choose
* when there are multiple matches with equal specificity.
*
* @returns the value from the most specific matched selector
* or `null` if nothing matched.
*/
pick1(el: L, preferFirst?: boolean): V | null;
}
export { Ast, DecisionTree, Picker, TreeifyBuilder as Treeify, Types };
export type { DecisionTreeOptions };