diff --git a/src/parser/cssParser.ts b/src/parser/cssParser.ts index 074ccf1b..a42b1274 100644 --- a/src/parser/cssParser.ts +++ b/src/parser/cssParser.ts @@ -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; diff --git a/src/services/selectorPrinting.ts b/src/services/selectorPrinting.ts index 6e49f66e..23750200 100644 --- a/src/services/selectorPrinting.ts +++ b/src/services/selectorPrinting.ts @@ -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 { @@ -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); @@ -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; } diff --git a/src/test/css/selectorPrinting.test.ts b/src/test/css/selectorPrinting.test.ts index 8b1cc56b..e9b7c6c3 100644 --- a/src/test/css/selectorPrinting.test.ts +++ b/src/test/css/selectorPrinting.test.ts @@ -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: '' }, + '[Selector Specificity](https://developer.mozilla.org/docs/Web/CSS/Specificity): (1, 1, 0)' + ]); + + assertSelectorMarkdown(p, '#foo:nth-child(even)', '#foo', [ + { language: 'html', value: '' }, + '[Selector Specificity](https://developer.mozilla.org/docs/Web/CSS/Specificity): (1, 1, 0)' + ]); + + assertSelectorMarkdown(p, '#foo:nth-child(-n + 2)', '#foo', [ + { language: 'html', value: '' }, + '[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: '' }, + '[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: '' }, + '[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: '' }, + '[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: '' }, + '[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: '' }, '[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: '' }, + '[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: '' }, '[Selector Specificity](https://developer.mozilla.org/docs/Web/CSS/Specificity): (1, 2, 0)'