css-selector-parser
Version:
Powerful and compliant CSS selector parser.
297 lines (296 loc) • 11.8 kB
TypeScript
/**
* CSS Selector AST root.
* Contains list of CSS rules (separated by a comma in the input CSS selector string).
* Generated by {@link AstFactory.selector ast.selector}.
*/
export interface AstSelector {
type: 'Selector';
/**
* List of CSS rules. Every rule contains conditions. Selector is considered matched once at least one rule matches.
*/
rules: AstRule[];
}
/**
* A single CSS rule that contains match conditions.
* Can nest another rule with or without a combinator (i.e. `"div > span"`).
* Generated by {@link AstFactory.rule ast.rule}.
*/
export interface AstRule {
type: 'Rule';
/** Items of a CSS rule. Can be tag, ids, class names, pseudo-classes and pseudo-elements. */
items: (AstTagName | AstWildcardTag | AstId | AstClassName | AstAttribute | AstPseudoClass | AstPseudoElement | AstNestingSelector)[];
/** Rule combinator which was used to nest this rule (i.e. `">"` in case of `"div > span"` if the current rule is `"span"`). */
combinator?: string;
/** Nested rule if specified (i.e. `"div > span"`). */
nestedRule?: AstRule;
}
/**
* Named tag, i.e. `"div"`. Part of CSS Qualified Names.
* Generated by {@link AstFactory.tagName ast.tagName}.
* @see https://developer.mozilla.org/en-US/docs/Web/CSS/Type_selectors
* @see https://drafts.csswg.org/css-namespaces-3/#css-qnames
*/
export interface AstTagName {
type: 'TagName';
/** Tag name, i.e. `"div"`. */
name: string;
/** Namespace according to https://www.w3.org/TR/css3-namespace/. */
namespace?: AstNamespaceName | AstWildcardNamespace | AstNoNamespace;
}
/**
* ID condition. Matches by id attribute value.
* Generated by {@link AstFactory.id ast.id}.
* https://developer.mozilla.org/en-US/docs/Web/CSS/ID_selectors
* @example "#root"
*/
export interface AstId {
type: 'Id';
/** ID name. I.e. `#root` -> `"root"`. */
name: string;
}
/**
* Class name condition. Matches by class attribute value.
* Generated by {@link AstFactory.className ast.className}.
* https://developer.mozilla.org/en-US/docs/Web/CSS/ID_selectors
* @example ".user"
*/
export interface AstClassName {
type: 'ClassName';
/** ID name. I.e. `.user` -> `"user"`. */
name: string;
}
/**
* Wildcard tag (universal selector): `*`.
* Generated by {@link AstFactory.wildcardTag ast.wildcardTag}.
* @see https://developer.mozilla.org/en-US/docs/Web/CSS/Universal_selectors
* @see https://drafts.csswg.org/css-namespaces-3/#css-qnames
*/
export interface AstWildcardTag {
type: 'WildcardTag';
/** Namespace according to https://www.w3.org/TR/css3-namespace/. */
namespace?: AstNamespaceName | AstWildcardNamespace | AstNoNamespace;
}
/**
* Named namespace declaration (i.e. `ns|div`).
* Generated by {@link AstFactory.namespaceName ast.namespaceName}.
* @see https://drafts.csswg.org/css-namespaces-3/#css-qnames
*/
export interface AstNamespaceName {
type: 'NamespaceName';
/** Namespace name (i.e. `"ns"` in case of `"ns|div"`). "*/
name: string;
}
/**
* Wildcard namespace (universal selector): `*`.
* Generated by {@link AstFactory.wildcardNamespace ast.wildcardNamespace}.
* @see https://developer.mozilla.org/en-US/docs/Web/CSS/Universal_selectors
* @see https://drafts.csswg.org/css-namespaces-3/#css-qnames
*/
export interface AstWildcardNamespace {
type: 'WildcardNamespace';
}
/**
* Explicit no-namespace declaration (i.e. `|div`).
* Generated by {@link AstFactory.noNamespace ast.noNamespace}.
* @see https://drafts.csswg.org/css-namespaces-3/#css-qnames
*/
export interface AstNoNamespace {
type: 'NoNamespace';
}
/**
* Attribute selector.
* Generated by {@link AstFactory.attribute ast.attribute}.
* @example "[role='button' i]"
*/
export interface AstAttribute {
type: 'Attribute';
/** Attribute name (i.e. `"href"` in case if `"[href]"`). */
name: string;
/** Namespace according to https://drafts.csswg.org/selectors/#attrnmsp. */
namespace?: AstNamespaceName | AstWildcardNamespace | AstNoNamespace;
/** Comparison operator (i.e. `"|="` in case if `"[role|=button]"`). */
operator?: string;
/** Comparison value (i.e. `"button"` in case if `"[role=button]"`). */
value?: AstString | AstSubstitution;
/** Comparison case sensitivity modifier (i.e. `"i"` in case if `"[role='button' i]"`). */
caseSensitivityModifier?: string;
}
/**
* Pseudo-class selector.
* Generated by {@link AstFactory.pseudoClass ast.pseudoClass}.
* @see https://developer.mozilla.org/en-US/docs/Learn/CSS/Building_blocks/Selectors/Pseudo-classes_and_pseudo-elements
* @example ":lang(en)"
*/
export interface AstPseudoClass {
type: 'PseudoClass';
/** Pseudo-class name (i.e. `"hover"` in case of `":hover"`). */
name: string;
/** Pseudo-class value (i.e. `"en"` in case of `":lang(en)"`). */
argument?: AstSubstitution | AstSelector | AstString | AstFormula | AstFormulaOfSelector;
}
/**
* Pseudo-class selector.
* Generated by {@link AstFactory.pseudoElement ast.pseudoElement}.
* @see https://developer.mozilla.org/en-US/docs/Learn/CSS/Building_blocks/Selectors/Pseudo-classes_and_pseudo-elements
* @example "::before"
*/
export interface AstPseudoElement {
type: 'PseudoElement';
/** Pseudo-element name (i.e. `"before"` in case of `"::before"`). */
name: string;
/** Pseudo-element value (i.e. `"foo"` in case of `"::part(foo)"`). */
argument?: AstSubstitution | AstString | AstSelector;
}
/**
* String value. Can be used as attribute value of pseudo-class string value.
* For instance `:lang(en)` -> `{type: 'AstPseudoClass'..., argument: {type: 'String', value: 'en'}}`.
* Generated by {@link AstFactory.string ast.string}.
*/
export interface AstString {
type: 'String';
/** The actual string value. */
value: string;
}
/**
* Pseudo-class formula value. `a` is multiplier of `n` and `b` us added on top. Formula: `an + b`.
* For instance `:nth-child(2n + 1)` -> `{type: 'AstPseudoClass'..., argument: {type: 'Formula', a: 2, b: 1}}`.
* Generated by {@link AstFactory.formula ast.formula}.
* @see https://developer.mozilla.org/en-US/docs/Web/CSS/:nth-child#functional_notation
*/
export interface AstFormula {
type: 'Formula';
/** Multiplier of `n`. */
a: number;
/** Constant added to `a*n`. */
b: number;
}
/**
* Pseudo-class formula of selector value. `a` is multiplier of `n` and `b` us added on top. Formula: `an + b`.
* Formula is followed by `of` keyword and then goes a CSS selector.
* For instance `:nth-child(2n + 1 of div)` ->
* `{type: 'AstPseudoClass'..., argument: {type: 'FormulaOfSelector', a: 2, b: 1, selector: {type: 'Selector', rules: [{type: 'Rule', items: [{type: 'TagName', name: 'div'}]}]}}}`.
* Generated by {@link AstFactory.formulaOfSelector ast.formulaOfSelector}.
* @see https://developer.mozilla.org/en-US/docs/Web/CSS/:nth-child#functional_notation
*/
export interface AstFormulaOfSelector {
type: 'FormulaOfSelector';
/** Multiplier of `n`. */
a: number;
/** Constant added to `a*n`. */
b: number;
/** Selector that goes after formula (i.e. `"div -> span"` in case of `":nth-child(2n + 1 of div > span)"` */
selector: AstRule;
}
/**
* CSS Nesting Selector: `&`.
* Represents the parent selector in nested CSS.
* Generated by {@link AstFactory.nestingSelector ast.nestingSelector}.
* @see https://www.w3.org/TR/css-nesting-1/#nest-selector
* @example "&:hover"
*/
export interface AstNestingSelector {
type: 'NestingSelector';
}
/**
* Substitution is not part of CSS spec, but rather a useful extension on top of CSS if you need to pass variables.
* Generated by {@link AstFactory.substitution ast.substitution}.
*/
export interface AstSubstitution {
type: 'Substitution';
/** Substitution name (i.e. "var" in case of `"[role=$var]"` or `":lang($var)"`). */
name: string;
}
/** One of pseudo-class argument types. */
export type AstPseudoClassArgument = AstSubstitution | AstSelector | AstString | AstFormula | AstFormulaOfSelector;
/** One of pseudo-element argument types. */
export type AstPseudoElementArgument = AstSubstitution | AstString | AstSelector;
/** One of CSS AST entity types. */
export type AstEntity = AstSelector | AstRule | AstTagName | AstWildcardTag | AstId | AstClassName | AstNamespaceName | AstWildcardNamespace | AstNoNamespace | AstNestingSelector | AstSubstitution | AstString | AstFormula | AstFormulaOfSelector | AstPseudoClass | AstAttribute | AstPseudoElement;
type UnionToIntersection<T> = (T extends any ? (x: T) => any : never) extends (x: infer R) => any ? R : never;
type ToAstFactory<T> = UnionToIntersection<T extends {
type: infer Type;
} ? Type extends string ? {
[K in Uncapitalize<Type>]: {} extends Omit<T, 'type'> ? (props?: {
[PK in keyof Omit<T, 'type'>]: Omit<T, 'type'>[PK];
}) => T : (props: {
[PK in keyof Omit<T, 'type'>]: Omit<T, 'type'>[PK];
}) => T;
} & {
[K in `is${Type}`]: (entity: unknown) => entity is T;
} : never : never>;
/** @internal */
type AstFactoryBase = {
[K in keyof ToAstFactory<AstEntity>]: ToAstFactory<AstEntity>[K];
};
/**
* AST structure generators and matchers.
* For instance, `ast.selector({rules: [...]})` creates AstSelector and `ast.isSelector(...)` checks if
* AstSelector was specified.
*
* @example
*
* // Represents CSS selector: ns|div#user-34.user.user-active[role="button"]:lang(en)::before > *
* const selector = ast.selector({
* rules: [
* ast.rule({
* items: [
* ast.tagName({name: 'div', namespace: ast.namespaceName({name: 'ns'})}),
* ast.id({name: 'user-34'}),
* ast.className({name: 'user'}),
* ast.className({name: 'user-active'}),
* ast.attribute({
* name: 'role',
* operator: '=',
* value: ast.string({value: 'button'})
* }),
* ast.pseudoClass({
* name: 'lang',
* argument: ast.string({value: 'en'})
* }),
* ast.pseudoElement({name: 'before'})
* ],
* nestedRule: ast.rule({combinator: '>', items: [ast.wildcardTag()]})
* })
* ]
* });
* console.log(ast.isSelector(selector)); // prints true
* console.log(ast.isRule(selector)); // prints false
*/
export interface AstFactory extends AstFactoryBase {
}
/**
* AST structure generators and matchers.
* For instance, `ast.selector({rules: [...]})` creates AstSelector and `ast.isSelector(...)` checks if
* AstSelector was specified.
*
* @example
*
* // Represents CSS selector: ns|div#user-34.user.user-active[role="button"]:lang(en)::before > *
* const selector = ast.selector({
* rules: [
* ast.rule({
* items: [
* ast.tagName({name: 'div', namespace: ast.namespaceName({name: 'ns'})}),
* ast.id({name: 'user-34'}),
* ast.className({name: 'user'}),
* ast.className({name: 'user-active'}),
* ast.attribute({
* name: 'role',
* operator: '=',
* value: ast.string({value: 'button'})
* }),
* ast.pseudoClass({
* name: 'lang',
* argument: ast.string({value: 'en'})
* }),
* ast.pseudoElement({name: 'before'})
* ],
* nestedRule: ast.rule({combinator: '>', items: [ast.wildcardTag()]})
* })
* ]
* });
* console.log(ast.isSelector(selector)); // prints true
* console.log(ast.isRule(selector)); // prints false
*/
export declare const ast: AstFactory;
export {};