Skip to content

Commit

Permalink
#74068 nth child specificity (#369)
Browse files Browse the repository at this point in the history
* fix(): nth-child specificity

* fix(selectorPrinting.test): added more tests

* fix(selectorPrinting): complete

* fix(): comment formatting

* fix(selectorPrinting): different logical approach
fix(cssParser): parseSelector; optional boolean
test(): additional test for complex-selector list syntax

* chore(): tidying up

* fix(selectorPrinting.ts): formatting
  • Loading branch information
SStranks authored Nov 7, 2023
1 parent 0443e38 commit f55163f
Show file tree
Hide file tree
Showing 3 changed files with 89 additions and 3 deletions.
2 changes: 1 addition & 1 deletion src/parser/cssParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -452,7 +452,7 @@ export class Parser {
return this.finish(node);
}

public _parseSelector(isNested: boolean): nodes.Selector | null {
public _parseSelector(isNested?: boolean): nodes.Selector | null {
const node = this.create(nodes.Selector);

let hasContent = false;
Expand Down
50 changes: 48 additions & 2 deletions src/services/selectorPrinting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { MarkedString } from '../cssLanguageTypes';
import { Scanner } from '../parser/cssScanner';
import * as l10n from '@vscode/l10n';
import { CSSDataManager } from '../languageFacts/dataManager';
import { Parser } from '../parser/cssParser';

export class Element {

Expand Down Expand Up @@ -456,10 +457,9 @@ export class SelectorPrinting {
continue elementLoop;
}

if (text.match(/^:(?:nth-child|nth-last-child|host|host-context)/i) && childElements.length > 0) {
if (text.match(/^:(?:host|host-context)/i) && childElements.length > 0) {
// The specificity of :host() is that of a pseudo-class, plus the specificity of its argument.
// The specificity of :host-context() is that of a pseudo-class, plus the specificity of its argument.
// The specificity of an :nth-child() or :nth-last-child() selector is the specificity of the pseudo class itself (counting as one pseudo-class selector) plus the specificity of the most specific complex selector in its selector list argument.
specificity.attr++;

let mostSpecificListItem = calculateMostSpecificListItem(childElements);
Expand All @@ -470,6 +470,52 @@ export class SelectorPrinting {
continue elementLoop;
}

if (text.match(/^:(?:nth-child|nth-last-child)/i) && childElements.length > 0) {
/* The specificity of the :nth-child(An+B [of S]?) pseudo-class is the specificity of a single pseudo-class plus, if S is specified, the specificity of the most specific complex selector in S */
// https://www.w3.org/TR/selectors-4/#the-nth-child-pseudo
specificity.attr++;

// 23 = Binary Expression.
if (childElements.length === 3 && childElements[1].type === 23) {
let mostSpecificListItem = calculateMostSpecificListItem(childElements[2].getChildren());

specificity.id += mostSpecificListItem.id;
specificity.attr += mostSpecificListItem.attr;
specificity.tag += mostSpecificListItem.tag;

continue elementLoop;
}

// Edge case: 'n' without integer prefix A, with B integer non-existent, is not regarded as a binary expression token.
const parser = new Parser();
const pseudoSelectorText = childElements[1].getText();
parser.scanner.setSource(pseudoSelectorText);
const firstToken = parser.scanner.scan();
const secondToken = parser.scanner.scan();

if (firstToken.text === 'n' || firstToken.text === '-n' && secondToken.text === 'of') {
const complexSelectorListNodes: nodes.Node[] = [];
const complexSelectorText = pseudoSelectorText.slice(secondToken.offset + 2);
const complexSelectorArray = complexSelectorText.split(',');

for (const selector of complexSelectorArray) {
const node = parser.internalParse(selector, parser._parseSelector);
if (node) {
complexSelectorListNodes.push(node);
}
}

let mostSpecificListItem = calculateMostSpecificListItem(complexSelectorListNodes);

specificity.id += mostSpecificListItem.id;
specificity.attr += mostSpecificListItem.attr;
specificity.tag += mostSpecificListItem.tag;
continue elementLoop;
}

continue elementLoop;
}

specificity.attr++; //pseudo class
continue elementLoop;
}
Expand Down
40 changes: 40 additions & 0 deletions src/test/css/selectorPrinting.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -309,11 +309,51 @@ suite('CSS - MarkedStringPrinter selectors specificities', () => {
});

test('nth-child, nth-last-child specificity', function () {
assertSelectorMarkdown(p, '#foo:nth-child(2)', '#foo', [
{ language: 'html', value: '<element id="foo" :nth-child>' },
'[Selector Specificity](https://developer.mozilla.org/docs/Web/CSS/Specificity): (1, 1, 0)'
]);

assertSelectorMarkdown(p, '#foo:nth-child(even)', '#foo', [
{ language: 'html', value: '<element id="foo" :nth-child>' },
'[Selector Specificity](https://developer.mozilla.org/docs/Web/CSS/Specificity): (1, 1, 0)'
]);

assertSelectorMarkdown(p, '#foo:nth-child(-n + 2)', '#foo', [
{ language: 'html', value: '<element id="foo" :nth-child>' },
'[Selector Specificity](https://developer.mozilla.org/docs/Web/CSS/Specificity): (1, 1, 0)'
]);

assertSelectorMarkdown(p, '#foo:nth-child(n of.li)', '#foo', [
{ language: 'html', value: '<element id="foo" :nth-child>' },
'[Selector Specificity](https://developer.mozilla.org/docs/Web/CSS/Specificity): (1, 2, 0)'
]);

assertSelectorMarkdown(p, '#foo:nth-child(n of.li,.li.li)', '#foo', [
{ language: 'html', value: '<element id="foo" :nth-child>' },
'[Selector Specificity](https://developer.mozilla.org/docs/Web/CSS/Specificity): (1, 3, 0)'
]);

assertSelectorMarkdown(p, '#foo:nth-child(n of.li, .li.li)', '#foo', [
{ language: 'html', value: '<element id="foo" :nth-child>' },
'[Selector Specificity](https://developer.mozilla.org/docs/Web/CSS/Specificity): (1, 3, 0)'
]);

assertSelectorMarkdown(p, '#foo:nth-child(n of li)', '#foo', [
{ language: 'html', value: '<element id="foo" :nth-child>' },
'[Selector Specificity](https://developer.mozilla.org/docs/Web/CSS/Specificity): (1, 1, 1)'
]);

assertSelectorMarkdown(p, '#foo:nth-child(-n+3 of li.important)', '#foo', [
{ language: 'html', value: '<element id="foo" :nth-child>' },
'[Selector Specificity](https://developer.mozilla.org/docs/Web/CSS/Specificity): (1, 2, 1)'
]);

assertSelectorMarkdown(p, '#foo:nth-child(-n+3 of li.important, .class1.class2.class3)', '#foo', [
{ language: 'html', value: '<element id="foo" :nth-child>' },
'[Selector Specificity](https://developer.mozilla.org/docs/Web/CSS/Specificity): (1, 4, 0)'
]);

assertSelectorMarkdown(p, '#foo:nth-last-child(-n+3 of li, .important)', '#foo', [
{ language: 'html', value: '<element id="foo" :nth-last-child>' },
'[Selector Specificity](https://developer.mozilla.org/docs/Web/CSS/Specificity): (1, 2, 0)'
Expand Down

0 comments on commit f55163f

Please sign in to comment.