Skip to content

Latest commit

 

History

History
613 lines (401 loc) · 21.4 KB

CHANGELOG.md

File metadata and controls

613 lines (401 loc) · 21.4 KB

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

  1. 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'}]}
  2. 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'}]}
  3. 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'}]}
  4. 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'}]}
  5. 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'}}]}
  6. 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: {
            // In 1.x any pseudo-classes were accepted.
            // in 2.x parser only accepts known psuedo-classes unless `unknown: accept` was specified. 
            unknown: 'accept',
            definitions: {
                // This is a replacement for registerSelectorPseudos().
                Selector: ['has'],
                // This is a replacement for registerNumericPseudos().
                Formula: ['nth-child']
            }
        },
        // This is a replacement for registerNestingOperators().
        combinators: ['>', '+', '~'],
        attributes: {
            // This a replacement for registerAttrEqualityMods().
            // Note that equals sign ("=") is included into the operator definitions.
            operators: ['^=', '$=', '*=', '~=']
        }
    },
    // This is a replacement for enableSubstitutes()
    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'); // doesn't crash

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)); // div#user-123.user:lang(en)::before

AST changes

AST had a lot of changes.

Selector

New type info.

  1. Type changed: selector -> Selector.
  2. 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.

  1. Type changed: rule -> Rule.
  2. 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.
  3. Prop renamed: nestingOperator -> combinator. A proper name according to CSS spec was chosen.
  4. Prop renamed: rule -> nestedRule. A proper name to indicate nesting was chosen.
  5. 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 (\*).
  6. Prop changed: attrs -> items: [<ATTRIBUTE>, ...]. Attribute type was changed, see below.
  7. 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.

  1. Type introduced: Attribute.
  2. 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.

  1. Type introduced: PseudoClass.
  2. 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.

  1. 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

  • Update published docs.

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: {
            // In 1.x any pseudo-classes were accepted.
            // in 2.x parser only accepts known psuedo-classes unless `unknown: accept` was specified. 
            unknown: 'accept',
            definitions: {
                // This is a replacement for registerSelectorPseudos().
                Selector: ['has'],
                // This is a replacement for registerNumericPseudos().
                Formula: ['nth-child']
            }
        },
        // This is a replacement for registerNestingOperators().
        combinators: ['>', '+', '~'],
        attributes: {
            // This a replacement for registerAttrEqualityMods().
            // Note that equals sign ("=") is included into the operator definitions.
            operators: ['^=', '$=', '*=', '~=']
        }
    },
    // This is a replacement for enableSubstitutes()
    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'); // doesn't crash

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)); // div#user-123.user:lang(en)

AST changes

AST had a lot of changes.

Selector

New type info.

  1. Type changed: selector -> Selector.
  2. 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.

  1. Type changed: rule -> Rule.
  2. 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.
  3. Prop renamed: nestingOperator -> combinator. A proper name according to CSS spec was chosen.
  4. Prop renamed: rule -> nestedRule. A proper name to indicate nesting was chosen.
  5. 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 (\*).
  6. Prop changed: attrs -> attributes. Attribute type was changed, see below.
  7. 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.

  1. Type introduced: Attribute.
  2. 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.

  1. Type introduced: PseudoClass.
  2. 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'}}]}}.