Changelog
All notable changes to this project will be documented in this file. See standard-version for commit guidelines.
3.0.5 (2024-03-02)
Bug Fixes
- single hyphen is not a valid identifier, throwing an exception, closes #38 (2520a49)
3.0.4 (2023-12-15)
Bug Fixes
- align identifier and string parsing and rendering with CSS standards, closes #36, closes #37 (ac0dbc0)
3.0.3 (2023-12-08)
Bug Fixes
- align identifier parsing with CSS standards and browser behaviour (6087705)
3.0.2 (2023-11-21)
Bug Fixes
- identifier parsing for ids, classes, pseudo-classes and pseudo-elements (d222dfd)
3.0.1 (2023-11-20)
Bug Fixes
3.0.0 (2023-09-26)
⚠ BREAKING CHANGES
- API is backwards incompatible.
Migrating from 2.x to 3.x
Rule.tag
was moved to Rule.items
.
Example selector: div
.
- Before:
{type: 'Rule', tagName: {type: 'TagName', name: 'div'}}
- After:
{type: 'Rule', items: [{type: 'TagName', name: 'div'}]}
Rule.classNames
was converted to an AST entity and moved to Rule.items
.
Example selector: .user.hidden
- Before:
{type: 'Rule', classNames: ['user', 'hidden']}
- After:
{type: 'Rule', items: [{type: 'ClassName', name: 'user'}, {type: 'ClassName', name: 'hidden'}]}
Rule.ids
was converted to an AST entity and moved to Rule.items
.
Example selector: #root#user-1
- Before:
{type: 'Rule', ids: ['root', 'user-1']}
- After:
{type: 'Rule', items: [{type: 'Id', name: 'root'}, {type: 'Id', name: 'user-1'}]}
Rule.attributes
was moved to Rule.items
.
Example selector: [href^=/][role]
- Before:
{type: 'Rule', attributes: [{type: 'Attribute', name: 'href', operator: '^=', value: {type: 'String', value: '/'}}, {type: 'Attribute', name: 'role'}]}
- After:
{type: 'Rule', items: [{type: 'Attribute', name: 'href', operator: '^=', value: {type: 'String', value: '/'}}, {type: 'Attribute', name: 'role'}]}
Rule.pseudoClasses
was moved to Rule.items
.
Example selector: :hover:lang(en)
- Before:
{type: 'Rule', pseudoClasses: [{type: 'PseudoClass', name: 'hover'}, {type: 'PseudoClass', name: 'lang', value: {type: 'String', value: 'en'}}]}
- After:
{type: 'Rule', items: [{type: 'PseudoClass', name: 'hover'}, {type: 'PseudoClass', name: 'lang', value: {type: 'String', value: 'en'}}]}
Rule.pseudoElement
was converted to an AST entity and moved to Rule.items
.
Example selector: ::before
- Before:
{type: 'Rule', pseudoElement: 'before'}
- After:
{type: 'Rule', items: [{type: 'PseudoElement', name: 'before'}]}
New AST methods
ast.id
and ast.isId
to create and test ast nodes with type Id
.
ast.className
and ast.isClassName
to create and test ast nodes with type ClassName
.
ast.pseudoElement
and ast.isPseudoElement
to create and test ast nodes with type PseudoElement
.
New Syntax Definition configuration
pseudoElements.definitions
was updated to accept signatures in otder to support specifying pseudo-elements with
an argument.
Example: createParser({syntax: {pseudoElements: {definitions: {NoArgument: ['before'], String: ['highlight'], Selector: ['slotted']}}}})
.
Migrating from 1.x to 3.x
CssSelectorParser
-> createParser
In 1.x versions there was CssSelectorParser
class which had to be contructed and then configured.
In 3.x versions there is createParser()
function which returns a parse()
function. All the configutation is passed
to createParser()
params.
Before:
var CssSelectorParser = require('css-selector-parser').CssSelectorParser,
parser = new CssSelectorParser();
parser.registerSelectorPseudos('has');
parser.registerNumericPseudos('nth-child');
parser.registerNestingOperators('>', '+', '~');
parser.registerAttrEqualityMods('^', '$', '*', '~');
const selector = parser.parse('a[href^=/], .container:has(nav) > a[href]:lt($var):nth-child(5)');
After:
import {createParser} from 'css-selector-parser';
const parse = createParser({
syntax: {
pseudoClasses: {
unknown: 'accept',
definitions: {
Selector: ['has'],
Formula: ['nth-child']
}
},
combinators: ['>', '+', '~'],
attributes: {
operators: ['^=', '$=', '*=', '~=']
}
},
substitutes: true
});
const selector = parse('a[href^=/], .container:has(nav) > a[href]:lt($var):nth-child(5)');
Predefined CSS syntax definitions
You no longer need to make an extensive configuration of css-selector-parser
in order to make it understand
the necessary CSS standards. You can now just define CSS/CSS selectors version directly:
import {createParser} from 'css-selector-parser';
const parse = createParser({syntax: 'css3'});
const selector = parse('a[href^=/], .container:has(nav) > a[href]:nth-child(2n + 1)::before');
Here are the pre-defined CSS standards for your disposal:
Make sure you use proper strict
value
CSS selector parser in modern browsers is very forgiving. For instance, it works fine with unclosed attribute
selectors: "[attr=value"
. If you would like to mimic this behavior from browsers, set strict
to false
, i.e.:
import {createParser} from 'css-selector-parser';
const parse = createParser({syntax: 'css3', strict: false});
const selector = parse(':lang(en');
Render is now a separate export
render()
method used to be a method of CssSelectorParser
class. Now it can be imported directly and used.
Example:
import {createParser, render} from 'css-selector-parser';
const parse = createParser({syntax: 'progressive'});
const selector = parse('div#user-123.user:lang(en)::before');
console.log(render(selector));
AST changes
AST had a lot of changes.
Selector
New type info.
- Type changed:
selector
-> Selector
.
- Prop changed:
selectors
-> rules
, also selectors
contained ruleSet[]
, which in turn has rule
field,
and new rules
contains Rule[]
directly.
Before: {type: 'selector', selectors: [ {type: 'ruleSet', rule: {<RULE 1 DATA>}}, {type: 'ruleSet', rule: {<RULE 2 DATA>}} ]}
.
After: {type: 'Selector', rules: [ {<RULE 1 DATA>}, {<RULE 2 DATA>} ]}
.
Rule
New type info.
- Type changed:
rule
-> Rule
.
- Prop changed:
id: string
-> items: [{type: 'Id', name: '<ID>'}, ...]
. According to the CSS spec one rule may have
more than 1 id
, so #root#root
is a valid selector.
- Prop renamed:
nestingOperator
-> combinator
. A proper name according to CSS spec was chosen.
- Prop renamed:
rule
-> nestedRule
. A proper name to indicate nesting was chosen.
- Prop changed:
tagName: string
-> items: [TagName | WildcardTag, ...]
. Using explicit distinction between
TagName (i.e. div
) and WildcardTag (*
), because tag name can also be *
if escaped properly (\*
).
- Prop changed:
attrs
-> items: [<ATTRIBUTE>, ...]
. Attribute type was changed, see below.
- Prop changed:
pseudos
-> items: [<PSEUDO CLASS>, ...]
. There are pseudo-elements and pseudo-classes, now they are
separated properly (there is a separate pseudoElement
type). Pseudo class type was changed, see below.
Before:
({
type: 'rule',
tagName: 'div',
id: 'user-123',
classNames: ['user'],
attrs: [
{name: 'role', operator: '$=', valueType: 'string', value: 'button'}
],
pseudos: [
{name: 'lang', valueType: 'string', value: 'en'}
],
nestingOperator: '>'
})
After:
({
type: 'Rule',
items: [
{type: 'TagName', name: 'div'},
{type: 'Id', name: 'user-123'},
{type: 'ClassName', name: 'user'},
{type: 'Attribute', name: 'role', operator: '$=', value: {type: 'String', value: 'button'}},
{type: 'PseudoClass', name: 'lang', value: {type: 'String', value: 'en'}}
],
combinator: '>'
})
Attribute
New type info.
- Type introduced:
Attribute
.
- Prop
value
and valueType
were combined to a single prop value
with a field type
.
All possible value types.
Example 1
Before: {name: 'role'}
.
After: {type: 'Attribute', name: 'role'}
.
Example 2
Before: {name: 'role', operator: '$=', valueType: 'string', value: 'button'}
.
After: {type: 'Attribute', name: 'role', operator: '$=', value: {type: 'String', value: 'button'}}
.
Example 3
Before: {name: 'role', operator: '=', valueType: 'substitute', value: 'var'}
.
After: {type: 'Attribute', name: 'role', operator: '=', value: {type: 'Substitute', name: 'var'}}
.
Pseudo Classes
New type info.
- Type introduced:
PseudoClass
.
- Prop
value
and valueType
were combined to a single prop argument
with a field type
.
All possible argument types.
Example 1
Before: {name: 'visited'}
.
After: {type: 'PseudoClass', name: 'visited'}
.
Example 2
Before: {name: 'lang', valueType: 'string', value: 'en'}
.
After: {type: 'PseudoClass', name: 'lang', argument: {type: 'String', value: 'en'}}
.
Example 3
Before: {name: 'lang', valueType: 'substitute', value: 'var'}
.
After: {type: 'PseudoClass', name: 'lang', argument: {type: 'Substitute', name: 'var'}}
.
Example 4
Before: {name: 'has', valueType: 'selector', value: {type: 'selector', selectors: [{type: 'ruleSet', rule: {type: 'rule', tagName: 'div'}}]}}
.
After: {type: 'PseudoClass', name: 'has', argument: {type: 'Selector', rules: [{type: 'Rule', tag: {type: 'TagName', name: 'div'}}]}}
.
Pseudo Elements
New type info.
- Type introduced:
PseudoElement
.
All possible argument types.
Features
- upgrade API in order to reflect upcoming complexity in CSS selectors (cece4df)
2.3.2 (2023-06-25)
Bug Fixes
- fix foruma parsing with negative A, closes #28 (824312f)
- include js file extension into the mjs build, closes #22 (f50b350)
- rendering nested selectors with combinators, closes #27 (40fb434)
2.3.1 (2023-06-24)
Bug Fixes
2.3.0 (2023-06-24)
Features
- publish hybrid package: CommonJS and ESM modules (16fd8a1)
2.2.1-2.2.3
2.2.0
- Full refactoring.
- Switch to typescript.
- Pre-defined CSS syntaxes were included.
- The whole AST was documented.
Migrating from 1.x
CssSelectorParser
-> createParser
In 1.x versions there was CssSelectorParser
class which had to be contructed and then configured.
In 2.x versions there is createParser()
function which returns a parse()
function. All the configutation is passed
to createParser()
params.
Before:
var CssSelectorParser = require('css-selector-parser').CssSelectorParser,
parser = new CssSelectorParser();
parser.registerSelectorPseudos('has');
parser.registerNumericPseudos('nth-child');
parser.registerNestingOperators('>', '+', '~');
parser.registerAttrEqualityMods('^', '$', '*', '~');
const selector = parser.parse('a[href^=/], .container:has(nav) > a[href]:lt($var):nth-child(5)');
After:
import {createParser} from 'css-selector-parser';
const parse = createParser({
syntax: {
pseudoClasses: {
unknown: 'accept',
definitions: {
Selector: ['has'],
Formula: ['nth-child']
}
},
combinators: ['>', '+', '~'],
attributes: {
operators: ['^=', '$=', '*=', '~=']
}
},
substitutes: true
});
const selector = parse('a[href^=/], .container:has(nav) > a[href]:lt($var):nth-child(5)');
Predefined CSS syntax definitions
You no longer need to make an extensive configuration of css-selector-parser
in order to make it understand
the necessary CSS standards. You can now just define CSS/CSS selectors version directly:
import {createParser} from 'css-selector-parser';
const parse = createParser({syntax: 'css3'});
const selector = parse('a[href^=/], .container:has(nav) > a[href]:nth-child(2n + 1)::before');
Here are the pre-defined CSS standards for your disposal:
Make sure you use proper strict
value
CSS selector parser in modern browsers is very forgiving. For instance, it works fine with unclosed attribute
selectors: "[attr=value"
. If you would like to mimic this behavior from browsers, set strict
to false
, i.e.:
import {createParser} from 'css-selector-parser';
const parse = createParser({syntax: 'css3', strict: false});
const selector = parse(':lang(en');
Render is now a separate export
render()
method used to be a method of CssSelectorParser
class. Now it can be imported directly and used.
Example:
import {createParser, render} from 'css-selector-parser';
const parse = createParser({syntax: 'progressive'});
const selector = parse('div#user-123.user:lang(en)');
console.log(render(selector));
AST changes
AST had a lot of changes.
Selector
New type info.
- Type changed:
selector
-> Selector
.
- Prop changed:
selectors
-> rules
, also selectors
contained ruleSet[]
, which in turn has rule
field,
and new rules
contains Rule[]
directly.
Before: {type: 'selector', selectors: [ {type: 'ruleSet', rule: {<RULE 1 DATA>}}, {type: 'ruleSet', rule: {<RULE 2 DATA>}} ]}
.
After: {type: 'Selector', rules: [ {<RULE 1 DATA>}, {<RULE 2 DATA>} ]}
.
Rule
New type info.
- Type changed:
rule
-> Rule
.
- Prop changed:
id: string
-> ids: string[]
. According to the CSS spec one rule may have more than 1 id
,
so #root#root
is a valid selector.
- Prop renamed:
nestingOperator
-> combinator
. A proper name according to CSS spec was chosen.
- Prop renamed:
rule
-> nestedRule
. A proper name to indicate nesting was chosen.
- Prop changed:
tagName: string
-> tag: TagName | WildcardTag
. Using explicit distinction between
TagName (i.e. div
) and WildcardTag (*
), because tag name can also be *
if escaped properly (\*
).
- Prop changed:
attrs
-> attributes
. Attribute type was changed, see below.
- Prop changed:
pseudos
-> pseudoClasses
. There are pseudo-elements and pseudo-classes, now they are separated
properly (there is a separate pseudoElement
property). Pseudo class type was changed, see below.
Before:
({
type: 'rule',
tagName: 'div',
id: 'user-123',
classNames: ['user'],
attrs: [
{name: 'role', operator: '$=', valueType: 'string', value: 'button'}
],
pseudos: [
{name: 'lang', valueType: 'string', value: 'en'}
],
nestingOperator: '>'
})
After:
({
type: 'Rule',
tag: {type: 'TagName', name: 'div'},
ids: ['user-123'],
classNames: ['user'],
attributes: [
{type: 'Attribute', name: 'role', operator: '$=', value: {type: 'String', value: 'button'}}
],
pseudoClasses: [
{type: 'PseudoClass', name: 'lang', value: {type: 'String', value: 'en'}}
],
combinator: '>'
})
Attribute
New type info.
- Type introduced:
Attribute
.
- Prop
value
and valueType
were combined to a single prop value
with a field type
.
All possible value types.
Example 1
Before: {name: 'role'}
.
After: {type: 'Attribute', name: 'role'}
.
Example 2
Before: {name: 'role', operator: '$=', valueType: 'string', value: 'button'}
.
After: {type: 'Attribute', name: 'role', operator: '$=', value: {type: 'String', value: 'button'}}
.
Example 3
Before: {name: 'role', operator: '=', valueType: 'substitute', value: 'var'}
.
After: {type: 'Attribute', name: 'role', operator: '=', value: {type: 'Substitute', name: 'var'}}
.
Pseudo Classes
New type info.
- Type introduced:
PseudoClass
.
- Prop
value
and valueType
were combined to a single prop argument
with a field type
.
All possible argument types.
Example 1
Before: {name: 'visited'}
.
After: {type: 'PseudoClass', name: 'visited'}
.
Example 2
Before: {name: 'lang', valueType: 'string', value: 'en'}
.
After: {type: 'PseudoClass', name: 'lang', argument: {type: 'String', value: 'en'}}
.
Example 3
Before: {name: 'lang', valueType: 'substitute', value: 'var'}
.
After: {type: 'PseudoClass', name: 'lang', argument: {type: 'Substitute', name: 'var'}}
.
Example 4
Before: {name: 'has', valueType: 'selector', value: {type: 'selector', selectors: [{type: 'ruleSet', rule: {type: 'rule', tagName: 'div'}}]}}
.
After: {type: 'PseudoClass', name: 'has', argument: {type: 'Selector', rules: [{type: 'Rule', tag: {type: 'TagName', name: 'div'}}]}}
.