From 570ba03b60add6aec2d59a1b52bb750323f26b66 Mon Sep 17 00:00:00 2001 From: Bart Veneman Date: Sun, 25 May 2025 21:34:17 +0200 Subject: [PATCH 1/4] =?UTF-8?q?=F0=9F=92=A5=20Do=20not=20crash=20on=20empt?= =?UTF-8?q?y=20`::view-transition-old()`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/core/calculate.js | 2 +- test/index.js | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/core/calculate.js b/src/core/calculate.js index a9c3f18..52235e6 100644 --- a/src/core/calculate.js +++ b/src/core/calculate.js @@ -153,7 +153,7 @@ const calculateForAST = (selectorAST) => { case 'view-transition-old': case 'view-transition-new': // The specificity of a view-transition selector with a * argument is zero. - if (child.children && child.children.first.value === '*') { + if (child.children && child.children.first && child.children.first.value === '*') { break; } // The specificity of a view-transition selector with an argument is the same diff --git a/test/index.js b/test/index.js index 9b071b2..b6b28d3 100644 --- a/test/index.js +++ b/test/index.js @@ -105,6 +105,9 @@ describe('CALCULATE', () => { it('::view-transition', () => { deepEqual(Specificity.calculate('::view-transition')[0].toObject(), { a: 0, b: 0, c: 1 }); }); + it('::view-transition-old() & do not crash', () => { + deepEqual(Specificity.calculate('::view-transition-old()')[0].toObject(), { a: 0, b: 0, c: 1 }); + }); it('::view-transition-group(test)', () => { deepEqual(Specificity.calculate('::view-transition-group(test)')[0].toObject(), { a: 0, b: 0, c: 1 }); }); From 081d0064ed11df44ca8abbf61086d7b9362fdc48 Mon Sep 17 00:00:00 2001 From: Bart Veneman Date: Sun, 25 May 2025 21:44:42 +0200 Subject: [PATCH 2/4] prevent more incorrect selectors crashing calculation --- src/core/calculate.js | 8 ++++---- test/index.js | 22 +++++++++++++++++++--- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/src/core/calculate.js b/src/core/calculate.js index 52235e6..0cc58ec 100644 --- a/src/core/calculate.js +++ b/src/core/calculate.js @@ -34,7 +34,7 @@ const calculateForAST = (selectorAST) => { case '-webkit-any': case 'any': - if (child.children) { + if (child.children && child.children.first) { b += 1; } break; @@ -45,7 +45,7 @@ const calculateForAST = (selectorAST) => { case 'matches': case 'not': case 'has': - if (child.children) { + if (child.children && child.children.first) { // Calculate Specificity from nested SelectorList const max1 = max(...calculate(child.children.first)); @@ -79,7 +79,7 @@ const calculateForAST = (selectorAST) => { case 'host': b += 1; - if (child.children) { + if (child.children && child.children.first) { // Workaround to a css-tree bug in which it allows complex selectors instead of only compound selectors // We work around it by filtering out any Combinator and successive Selectors const childAST = { type: 'Selector', children: [] }; @@ -124,7 +124,7 @@ const calculateForAST = (selectorAST) => { case 'slotted': c += 1; - if (child.children) { + if (child.children && child.children.first) { // Workaround to a css-tree bug in which it allows complex selectors instead of only compound selectors // We work around it by filtering out any Combinator and successive Selectors const childAST = { type: 'Selector', children: [] }; diff --git a/test/index.js b/test/index.js index b6b28d3..08f0888 100644 --- a/test/index.js +++ b/test/index.js @@ -83,7 +83,7 @@ describe('CALCULATE', () => { it('::slotted', () => { deepEqual(Specificity.calculate('::slotted')[0].toObject(), { a: 0, b: 0, c: 1 }); }); - it.skip('::slotted()', () => { + it('::slotted() & do not crash', () => { deepEqual(Specificity.calculate('::slotted()')[0].toObject(), { a: 0, b: 0, c: 1 }); }); it('::slotted(div#foo)', () => { @@ -186,6 +186,9 @@ describe('CALCULATE', () => { it(':-moz-any(#foo, .bar, baz) = (1,0,0)', () => { deepEqual(Specificity.calculate(':-moz-any(#foo, .bar, baz)')[0].toObject(), { a: 1, b: 0, c: 0 }); }); + it(':has() & do not crash', () => { + deepEqual(Specificity.calculate(':has()')[0].toObject(), { a: 0, b: 0, c: 0 }); + }); }); describe('CSS :any() = (0,1,0)', () => { @@ -210,6 +213,19 @@ describe('CALCULATE', () => { it(':where = (0,0,0)', () => { deepEqual(Specificity.calculate(':where')[0].toObject(), { a: 0, b: 0, c: 0 }); }); + + it(':is() = (0,0,0)', () => { + deepEqual(Specificity.calculate(':is()')[0].toObject(), { a: 0, b: 0, c: 0 }); + }); + it(':matches() = (0,0,0)', () => { + deepEqual(Specificity.calculate(':matches()')[0].toObject(), { a: 0, b: 0, c: 0 }); + }); + it(':any() = (0,0,0)', () => { + deepEqual(Specificity.calculate(':any()')[0].toObject(), { a: 0, b: 0, c: 0 }); + }); + it(':where() = (0,0,0)', () => { + deepEqual(Specificity.calculate(':where()')[0].toObject(), { a: 0, b: 0, c: 0 }); + }); }); describe('CSS :has() = Specificity of the most specific complex selector in its selector list argument', () => { @@ -234,7 +250,7 @@ describe('CALCULATE', () => { it(':host = (0,1,0)', () => { deepEqual(Specificity.calculate(':host')[0].toObject(), { a: 0, b: 1, c: 0 }); }); - it.skip(':host() = (0,1,0)', () => { + it(':host() = (0,1,0) & do not crash', () => { deepEqual(Specificity.calculate(':host()')[0].toObject(), { a: 0, b: 1, c: 0 }); }); it(':host(#foo.bar) = (1,2,0)', () => { @@ -243,7 +259,7 @@ describe('CALCULATE', () => { it(':host(#foo.bar invalid) = (1,2,0)', () => { deepEqual(Specificity.calculate(':host(#foo.bar invalid)')[0].toObject(), { a: 1, b: 2, c: 0 }); }); - it.skip(':host-context() = (0,1,0)', () => { + it(':host-context() = (0,1,0) & do not crash', () => { deepEqual(Specificity.calculate(':host-context()')[0].toObject(), { a: 0, b: 1, c: 0 }); }); it(':host-context(#foo.bar) = (1,2,0)', () => { From 1a23e737429875138ce4b94a50c063aefd91c176 Mon Sep 17 00:00:00 2001 From: Bart Veneman Date: Sun, 25 May 2025 21:52:19 +0200 Subject: [PATCH 3/4] even more safe guards --- src/core/calculate.js | 6 +++--- test/index.js | 3 +++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/core/calculate.js b/src/core/calculate.js index 0cc58ec..0c723e7 100644 --- a/src/core/calculate.js +++ b/src/core/calculate.js @@ -62,7 +62,7 @@ const calculateForAST = (selectorAST) => { case 'nth-last-child': b += 1; - if (child.children && child.children.first.selector) { + if (child.children && child.children.first && child.children.first.selector) { // Calculate Specificity from SelectorList const max2 = max(...calculate(child.children.first.selector)); @@ -79,7 +79,7 @@ const calculateForAST = (selectorAST) => { case 'host': b += 1; - if (child.children && child.children.first) { + if (child.children && child.children.first && child.children.first.children) { // Workaround to a css-tree bug in which it allows complex selectors instead of only compound selectors // We work around it by filtering out any Combinator and successive Selectors const childAST = { type: 'Selector', children: [] }; @@ -124,7 +124,7 @@ const calculateForAST = (selectorAST) => { case 'slotted': c += 1; - if (child.children && child.children.first) { + if (child.children && child.children.first && child.children.first.children) { // Workaround to a css-tree bug in which it allows complex selectors instead of only compound selectors // We work around it by filtering out any Combinator and successive Selectors const childAST = { type: 'Selector', children: [] }; diff --git a/test/index.js b/test/index.js index 08f0888..6d32d1f 100644 --- a/test/index.js +++ b/test/index.js @@ -174,6 +174,9 @@ describe('CALCULATE', () => { it('p:nth-child = (0,1,1) & do not crash', () => { deepEqual(Specificity.calculate('p:nth-child')[0].toObject(), { a: 0, b: 1, c: 1 }); }); + it('p:nth-child() = (0,1,1) & do not crash', () => { + deepEqual(Specificity.calculate('p:nth-child()')[0].toObject(), { a: 0, b: 1, c: 1 }); + }); }); describe('CSS :is(), :matches(), :-moz-any = Specificity of the most specific complex selector in its selector list argument', () => { From ed4570d19b5d1c273a6d78e2bb9e5b3138d2f962 Mon Sep 17 00:00:00 2001 From: Bart Veneman Date: Mon, 26 May 2025 09:44:11 +0200 Subject: [PATCH 4/4] apply optional chaining instead of verbose checks --- src/core/calculate.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/core/calculate.js b/src/core/calculate.js index 0c723e7..f05b1ae 100644 --- a/src/core/calculate.js +++ b/src/core/calculate.js @@ -34,7 +34,7 @@ const calculateForAST = (selectorAST) => { case '-webkit-any': case 'any': - if (child.children && child.children.first) { + if (child.children?.first) { b += 1; } break; @@ -45,7 +45,7 @@ const calculateForAST = (selectorAST) => { case 'matches': case 'not': case 'has': - if (child.children && child.children.first) { + if (child.children?.first) { // Calculate Specificity from nested SelectorList const max1 = max(...calculate(child.children.first)); @@ -62,7 +62,7 @@ const calculateForAST = (selectorAST) => { case 'nth-last-child': b += 1; - if (child.children && child.children.first && child.children.first.selector) { + if (child.children?.first?.selector) { // Calculate Specificity from SelectorList const max2 = max(...calculate(child.children.first.selector)); @@ -79,7 +79,7 @@ const calculateForAST = (selectorAST) => { case 'host': b += 1; - if (child.children && child.children.first && child.children.first.children) { + if (child.children?.first?.children) { // Workaround to a css-tree bug in which it allows complex selectors instead of only compound selectors // We work around it by filtering out any Combinator and successive Selectors const childAST = { type: 'Selector', children: [] }; @@ -124,7 +124,7 @@ const calculateForAST = (selectorAST) => { case 'slotted': c += 1; - if (child.children && child.children.first && child.children.first.children) { + if (child.children?.first?.children) { // Workaround to a css-tree bug in which it allows complex selectors instead of only compound selectors // We work around it by filtering out any Combinator and successive Selectors const childAST = { type: 'Selector', children: [] }; @@ -153,7 +153,7 @@ const calculateForAST = (selectorAST) => { case 'view-transition-old': case 'view-transition-new': // The specificity of a view-transition selector with a * argument is zero. - if (child.children && child.children.first && child.children.first.value === '*') { + if (child.children?.first?.value === '*') { break; } // The specificity of a view-transition selector with an argument is the same