Skip to content

feat: upgrade API in order to reflect upcoming complexity in CSS selectors #30

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Oct 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
304 changes: 304 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,310 @@

All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.

## [3.0.0](https://github.com/mdevils/css-selector-parser/compare/v2.3.2...v3.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:

```javascript
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:

```javascript
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)');
```

* [All syntax definition options.](docs/interfaces/SyntaxDefinition.md)
* [All the psudo-class definition options.](docs/interfaces/SyntaxDefinition.md#pseudoclasses)
* [All the attribute definition options.](docs/interfaces/SyntaxDefinition.md#attributes)

#### 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:

```javascript
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:

* `css1`: https://www.w3.org/TR/CSS1/
* `css2`: https://www.w3.org/TR/CSS2/
* `css3`/`selectors-3`: https://www.w3.org/TR/selectors-3/
* `selectors-4`: https://www.w3.org/TR/selectors-4/
* `latest`: refers to `selectors-4`
* `progressive`: `latest` + accepts unknown psudo-classes, psudo-elements and attribute case sensitivity modifiers

#### 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.:

```javascript
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:

```javascript
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.](docs/interfaces/AstSelector.md)

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.](docs/interfaces/AstRule.md)

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` -> `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` type). Pseudo class type was changed, see below.

Before:

```javascript
({
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:

```javascript
({
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.](docs/interfaces/AstAttribute.md)

1. Type introduced: `Attribute`.
2. Prop `value` and `valueType` were combined to a single prop `value` with a field `type`.

[All possible value types.](docs/interfaces/AstAttribute.md#value)


##### 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.](docs/interfaces/AstPseudoClass.md)

1. Type introduced: `PseudoClass`.
2. Prop `value` and `valueType` were combined to a single prop `argument` with a field `type`.

[All possible argument types.](docs/interfaces/AstPseudoClass.md#argument)

##### 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.](docs/interfaces/AstPseudoElement.md)

1. Type introduced: `PseudoElement`.

[All possible argument types.](docs/interfaces/AstPseudoElement.md#argument)

### Features

* upgrade API in order to reflect upcoming complexity in CSS selectors ([cece4df](https://github.com/mdevils/css-selector-parser/commit/cece4dff647b19c6211dd6c9defbd7887eca62b5))

### [2.3.2](https://github.com/mdevils/css-selector-parser/compare/v2.3.1...v2.3.2) (2023-06-25)


Expand Down
Loading