From 0443e38f54e2bedb26d837716a8c147340a52b55 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Tue, 7 Nov 2023 16:47:20 +0100 Subject: [PATCH 1/4] [scss] support interpolation in layer name. For #180631 (#370) --- src/parser/cssParser.ts | 5 ++ src/parser/lessParser.ts | 2 +- src/parser/scssParser.ts | 6 +- src/test/less/parser.test.ts | 99 ++++++++++++----------- src/test/scss/parser.test.ts | 149 ++++++++++++++++++----------------- 5 files changed, 134 insertions(+), 127 deletions(-) diff --git a/src/parser/cssParser.ts b/src/parser/cssParser.ts index 074ccf1b..0c453275 100644 --- a/src/parser/cssParser.ts +++ b/src/parser/cssParser.ts @@ -704,6 +704,11 @@ export class Parser { return this.finish(node, ParseError.URIOrStringExpected); } + return this._completeParseImport(node); + } + + + public _completeParseImport(node: nodes.Import): nodes.Node | null { if (this.acceptIdent('layer')) { if (this.accept(TokenType.ParenthesisL)) { if (!node.addChild(this._parseLayerName())) { diff --git a/src/parser/lessParser.ts b/src/parser/lessParser.ts index e0b9fd11..8d34c4c2 100644 --- a/src/parser/lessParser.ts +++ b/src/parser/lessParser.ts @@ -65,7 +65,7 @@ export class LESSParser extends cssParser.Parser { node.setMedialist(this._parseMediaQueryList()); } - return this.finish(node); + return this._completeParseImport(node); } public _parsePlugin(): nodes.Node | null { diff --git a/src/parser/scssParser.ts b/src/parser/scssParser.ts index 787cc7eb..7ec1d008 100644 --- a/src/parser/scssParser.ts +++ b/src/parser/scssParser.ts @@ -55,11 +55,7 @@ export class SCSSParser extends cssParser.Parser { } } - if (!this.peek(TokenType.SemiColon) && !this.peek(TokenType.EOF)) { - node.setMedialist(this._parseMediaQueryList()); - } - - return this.finish(node); + return this._completeParseImport(node); } // scss variables: $font-size: 12px; diff --git a/src/test/less/parser.test.ts b/src/test/less/parser.test.ts index ceb862da..dd8f1ba5 100644 --- a/src/test/less/parser.test.ts +++ b/src/test/less/parser.test.ts @@ -12,7 +12,7 @@ import { assertNode, assertNoNode, assertError } from '../css/parser.test'; suite('LESS - Parser', () => { test('Variable', function () { - let parser = new LESSParser(); + const parser = new LESSParser(); assertNode('@color', parser, parser._parseVariable.bind(parser)); assertNode('$color', parser, parser._parseVariable.bind(parser)); assertNode('$$color', parser, parser._parseVariable.bind(parser)); @@ -36,7 +36,7 @@ suite('LESS - Parser', () => { }); test('Media', function () { - let parser = new LESSParser(); + const parser = new LESSParser(); assertNode('@media @phone {}', parser, parser._parseMedia.bind(parser)); assertNode('@media(max-width: 767px) { .mixinRef() }', parser, parser._parseMedia.bind(parser)); assertNode('@media(max-width: 767px) { .mixinDec() {} }', parser, parser._parseMedia.bind(parser)); @@ -48,7 +48,7 @@ suite('LESS - Parser', () => { }); test('VariableDeclaration', function () { - let parser = new LESSParser(); + const parser = new LESSParser(); assertNode('@color: #F5F5F5', parser, parser._parseVariableDeclaration.bind(parser)); assertNode('@color: 0', parser, parser._parseVariableDeclaration.bind(parser)); assertNode('@color: 255', parser, parser._parseVariableDeclaration.bind(parser)); @@ -67,7 +67,7 @@ suite('LESS - Parser', () => { }); test('MixinDeclaration', function () { - let parser = new LESSParser(); + const parser = new LESSParser(); assertNode('.color (@color: 25.5px) { }', parser, parser._tryParseMixinDeclaration.bind(parser)); assertNode('.color(@color: 25.5px) { }', parser, parser._tryParseMixinDeclaration.bind(parser)); assertNode('.color(@color) { }', parser, parser._tryParseMixinDeclaration.bind(parser)); @@ -90,7 +90,7 @@ suite('LESS - Parser', () => { }); test('MixinReference', function () { - let parser = new LESSParser(); + const parser = new LESSParser(); assertNode('.box-shadow(0 0 5px, 30%)', parser, parser._tryParseMixinReference.bind(parser)); assertNode('.box-shadow', parser, parser._tryParseMixinReference.bind(parser)); assertNode('.mixin(10) !important', parser, parser._tryParseMixinReference.bind(parser)); @@ -105,7 +105,7 @@ suite('LESS - Parser', () => { }); test('DetachedRuleSet', function () { - let parser = new LESSParser(); + const parser = new LESSParser(); assertNode('.foo { @greeting(); }', parser, parser._parseStylesheet.bind(parser)); assertNode('.media-switch(@styles) { @media(orientation:landscape){ @styles(); @foo: 9; } }', parser, parser._parseStylesheet.bind(parser)); assertNode('.media-switch({ flex-direction: row; });', parser, parser._parseStylesheet.bind(parser)); @@ -120,10 +120,10 @@ suite('LESS - Parser', () => { }); test('MixinParameter', function () { - let parser = new LESSParser(); + const parser = new LESSParser(); assertNode('@_', parser, parser._parseMixinParameter.bind(parser)); - assertNode('@let: value', parser, parser._parseMixinParameter.bind(parser)); - assertNode('@let', parser, parser._parseMixinParameter.bind(parser)); + assertNode('@const: value', parser, parser._parseMixinParameter.bind(parser)); + assertNode('@const', parser, parser._parseMixinParameter.bind(parser)); assertNode('@rest...', parser, parser._parseMixinParameter.bind(parser)); assertNode('...', parser, parser._parseMixinParameter.bind(parser)); assertNode('value', parser, parser._parseMixinParameter.bind(parser)); @@ -132,7 +132,7 @@ suite('LESS - Parser', () => { }); test('Function', function () { - let parser = new LESSParser(); + const parser = new LESSParser(); assertNode('%()', parser, parser._parseFunction.bind(parser)); assertNoNode('% ()', parser, parser._parseFunction.bind(parser)); @@ -145,25 +145,25 @@ suite('LESS - Parser', () => { }); test('Expr', function () { - let parser = new LESSParser(); - assertNode('(@let + 20)', parser, parser._parseExpr.bind(parser)); - assertNode('(@let - 20)', parser, parser._parseExpr.bind(parser)); - assertNode('(@let * 20)', parser, parser._parseExpr.bind(parser)); - assertNode('(@let / 20)', parser, parser._parseExpr.bind(parser)); - assertNode('(20 + @let)', parser, parser._parseExpr.bind(parser)); - assertNode('(20 - @let)', parser, parser._parseExpr.bind(parser)); - assertNode('(20 * @let)', parser, parser._parseExpr.bind(parser)); - assertNode('(20 / @let)', parser, parser._parseExpr.bind(parser)); - assertNode('(20 / 20 + @let)', parser, parser._parseExpr.bind(parser)); - assertNode('(20 + 20 + @let)', parser, parser._parseExpr.bind(parser)); - assertNode('(20 + 20 + 20 + @let)', parser, parser._parseExpr.bind(parser)); - assertNode('(20 + 20 + 20 + 20 + @let)', parser, parser._parseExpr.bind(parser)); - assertNode('(20 + 20 + @let + 20 + 20 + @let)', parser, parser._parseExpr.bind(parser)); + const parser = new LESSParser(); + assertNode('(@const + 20)', parser, parser._parseExpr.bind(parser)); + assertNode('(@const - 20)', parser, parser._parseExpr.bind(parser)); + assertNode('(@const * 20)', parser, parser._parseExpr.bind(parser)); + assertNode('(@const / 20)', parser, parser._parseExpr.bind(parser)); + assertNode('(20 + @const)', parser, parser._parseExpr.bind(parser)); + assertNode('(20 - @const)', parser, parser._parseExpr.bind(parser)); + assertNode('(20 * @const)', parser, parser._parseExpr.bind(parser)); + assertNode('(20 / @const)', parser, parser._parseExpr.bind(parser)); + assertNode('(20 / 20 + @const)', parser, parser._parseExpr.bind(parser)); + assertNode('(20 + 20 + @const)', parser, parser._parseExpr.bind(parser)); + assertNode('(20 + 20 + 20 + @const)', parser, parser._parseExpr.bind(parser)); + assertNode('(20 + 20 + 20 + 20 + @const)', parser, parser._parseExpr.bind(parser)); + assertNode('(20 + 20 + @const + 20 + 20 + @const)', parser, parser._parseExpr.bind(parser)); assertNode('(20 + 20)', parser, parser._parseExpr.bind(parser)); assertNode('(@var1 + @var2)', parser, parser._parseExpr.bind(parser)); - assertNode('((@let + 5) * 2)', parser, parser._parseExpr.bind(parser)); - assertNode('((@let + (5 + 2)) * 2)', parser, parser._parseExpr.bind(parser)); - assertNode('(@let + ((5 + 2) * 2))', parser, parser._parseExpr.bind(parser)); + assertNode('((@const + 5) * 2)', parser, parser._parseExpr.bind(parser)); + assertNode('((@const + (5 + 2)) * 2)', parser, parser._parseExpr.bind(parser)); + assertNode('(@const + ((5 + 2) * 2))', parser, parser._parseExpr.bind(parser)); assertNode('@color', parser, parser._parseExpr.bind(parser)); assertNode('@color, @color', parser, parser._parseExpr.bind(parser)); assertNode('@color, 42%', parser, parser._parseExpr.bind(parser)); @@ -175,7 +175,7 @@ suite('LESS - Parser', () => { }); test('LessOperator', function () { - let parser = new LESSParser(); + const parser = new LESSParser(); assertNode('>=', parser, parser._parseOperator.bind(parser)); assertNode('>', parser, parser._parseOperator.bind(parser)); assertNode('<', parser, parser._parseOperator.bind(parser)); @@ -183,7 +183,7 @@ suite('LESS - Parser', () => { }); test('Extend', function () { - let parser = new LESSParser(); + const parser = new LESSParser(); assertNode('nav { &:extend(.inline); }', parser, parser._parseRuleset.bind(parser)); assertNode('nav { &:extend(.test all); }', parser, parser._parseRuleset.bind(parser)); assertNode('.big-bucket:extend(.bucket all) { }', parser, parser._parseRuleset.bind(parser)); @@ -193,12 +193,12 @@ suite('LESS - Parser', () => { }); test('Declaration', function () { - let parser = new LESSParser(); + const parser = new LESSParser(); assertNode('border: thin solid 1px', parser, parser._parseDeclaration.bind(parser)); assertNode('dummy: @color', parser, parser._parseDeclaration.bind(parser)); assertNode('dummy: blue', parser, parser._parseDeclaration.bind(parser)); - assertNode('dummy: (20 / @let)', parser, parser._parseDeclaration.bind(parser)); - assertNode('dummy: (20 / 20 + @let)', parser, parser._parseDeclaration.bind(parser)); + assertNode('dummy: (20 / @const)', parser, parser._parseDeclaration.bind(parser)); + assertNode('dummy: (20 / 20 + @const)', parser, parser._parseDeclaration.bind(parser)); assertNode('dummy: func(@red)', parser, parser._parseDeclaration.bind(parser)); assertNode('dummy: desaturate(@red, 10%)', parser, parser._parseDeclaration.bind(parser)); assertNode('dummy: desaturate(16, 10%)', parser, parser._parseDeclaration.bind(parser)); @@ -218,7 +218,7 @@ suite('LESS - Parser', () => { }); test('Stylesheet', function () { - let parser = new LESSParser(); + const parser = new LESSParser(); assertNode('.color (@radius: 5px){ -border-radius: #F5F5F5 }', parser, parser._parseStylesheet.bind(parser)); assertNode('.color (@radius: 5px){ -border-radius: @radius }', parser, parser._parseStylesheet.bind(parser)); assertNode('.color (@radius: 5px){ -border-radius: #F5F5F5 } .color (@radius: 5px) { -border-radius: #F5F5F5 }', parser, parser._parseStylesheet.bind(parser)); @@ -236,6 +236,11 @@ suite('LESS - Parser', () => { assertNode('@color: #F5F5F5; @color: #F5F5F5;', parser, parser._parseStylesheet.bind(parser)); assertNode('@color: #F5F5F5; @color: #F5F5F5; @color: #F5F5F5;', parser, parser._parseStylesheet.bind(parser)); assertNode('@color: #F5F5F5; .color (@radius: 5px) { -border-radius: #F5F5F5 } @color: #F5F5F5;', parser, parser._parseStylesheet.bind(parser)); + }); + + test('@iport', function () { + const parser = new LESSParser(); + assertNode('@import url("override.css") layer;', parser, parser._parseStylesheet.bind(parser)); assertNode('@import-once "lib";', parser, parser._parseStylesheet.bind(parser)); assertNode('@import-once (css) "hello";', parser, parser._parseStylesheet.bind(parser)); assertError('@import-once () "hello";', parser, parser._parseStylesheet.bind(parser), ParseError.IdentifierExpected); @@ -247,8 +252,8 @@ suite('LESS - Parser', () => { }); test('Ruleset', function () { - let parser = new LESSParser(); - assertNode('.selector { prop: erty @let 1px; }', parser, parser._parseRuleset.bind(parser)); + const parser = new LESSParser(); + assertNode('.selector { prop: erty @const 1px; }', parser, parser._parseRuleset.bind(parser)); assertNode('selector { .mixin; }', parser, parser._parseRuleset.bind(parser)); assertNode('selector { .mixin(1px); .mixin(blue, 1px, \'farboo\') }', parser, parser._parseRuleset.bind(parser)); assertNode('selector { .mixin(blue; 1px;\'farboo\') }', parser, parser._parseRuleset.bind(parser)); @@ -264,16 +269,16 @@ suite('LESS - Parser', () => { }); test('term', function () { - let parser = new LESSParser(); + const parser = new LESSParser(); assertNode('%(\'repetitions: %S file: %S\', 1 + 2, "directory/file.less")', parser, parser._parseTerm.bind(parser)); assertNode('~"ms:alwaysHasItsOwnSyntax.For.Stuff()"', parser, parser._parseTerm.bind(parser)); // less syntax - assertNode('~`colorPalette("@{blue}", 1)`', parser, parser._parseTerm.bind(parser)); // less syntax + assertNode('~`colorPaconstte("@{blue}", 1)`', parser, parser._parseTerm.bind(parser)); // less syntax }); test('Nested Ruleset', function () { - let parser = new LESSParser(); - assertNode('.class1 { @let: 1; .class { @let: 2; three: @let; let: 3; } one: @let; }', parser, parser._parseRuleset.bind(parser)); - assertNode('.class1 { @let: 1; > .class2 { display: none; } }', parser, parser._parseRuleset.bind(parser)); + const parser = new LESSParser(); + assertNode('.class1 { @const: 1; .class { @const: 2; three: @const; const: 3; } one: @const; }', parser, parser._parseRuleset.bind(parser)); + assertNode('.class1 { @const: 1; > .class2 { display: none; } }', parser, parser._parseRuleset.bind(parser)); assertNode('.foo { @supports(display: grid) { .bar { display: none; }}}', parser, parser._parseRuleset.bind(parser)); assertNode('.foo { @supports(display: grid) { display: none; }}', parser, parser._parseRuleset.bind(parser)); assertNode('.parent { color:green; @document url-prefix() { .child { color:red; }}}', parser, parser._parseStylesheet.bind(parser)); @@ -281,7 +286,7 @@ suite('LESS - Parser', () => { }); test('Interpolation', function () { - let parser = new LESSParser(); + const parser = new LESSParser(); assertNode('.@{name} { }', parser, parser._parseRuleset.bind(parser)); assertNode('.${name} { }', parser, parser._parseRuleset.bind(parser)); assertNode('.my-element:not(.prefix-@{sub-element}) { }', parser, parser._parseStylesheet.bind(parser)); @@ -294,7 +299,7 @@ suite('LESS - Parser', () => { }); test('Selector Combinator', function () { - let parser = new LESSParser(); + const parser = new LESSParser(); assertNode('&:hover', parser, parser._parseSimpleSelector.bind(parser)); assertNode('&.float', parser, parser._parseSimpleSelector.bind(parser)); assertNode('&-foo', parser, parser._parseSimpleSelector.bind(parser)); @@ -307,20 +312,20 @@ suite('LESS - Parser', () => { }); test('CSS Guards', function () { - let parser = new LESSParser(); + const parser = new LESSParser(); assertNode('button when (@my-option = true) { color: white; }', parser, parser._parseStylesheet.bind(parser)); assertNode('.something .other when (@my-option = true) { color: white; }', parser, parser._parseStylesheet.bind(parser)); assertNode('& when (@my-option = true) { button { color: white; } }', parser, parser._parseStylesheet.bind(parser)); }); test('Merge', function () { - let parser = new LESSParser(); + const parser = new LESSParser(); assertNode('.mixin() { transform+_: scale(2); }', parser, parser._parseStylesheet.bind(parser)); assertNode('.myclass { box-shadow+: inset 0 0 10px #555; }', parser, parser._parseStylesheet.bind(parser)); }); test('url', function () { - let parser = new LESSParser(); + const parser = new LESSParser(); assertNode('url(//yourdomain/yourpath.png)', parser, parser._parseURILiteral.bind(parser)); assertNode('url(\'http://msft.com\')', parser, parser._parseURILiteral.bind(parser)); assertNode('url("http://msft.com")', parser, parser._parseURILiteral.bind(parser)); @@ -339,7 +344,7 @@ suite('LESS - Parser', () => { }); test('@plugin', function () { - let parser = new LESSParser(); + const parser = new LESSParser(); assertNode('@plugin "my-plugin";', parser, parser._parseStylesheet.bind(parser)); }); -}); \ No newline at end of file +}); diff --git a/src/test/scss/parser.test.ts b/src/test/scss/parser.test.ts index 1de09e02..a7b4161e 100644 --- a/src/test/scss/parser.test.ts +++ b/src/test/scss/parser.test.ts @@ -14,21 +14,21 @@ import { assertNode, assertError } from '../css/parser.test'; suite('SCSS - Parser', () => { test('Comments', function () { - let parser = new SCSSParser(); + const parser = new SCSSParser(); assertNode(' a { b: /* comment */ c }', parser, parser._parseStylesheet.bind(parser)); assertNode(' a { b: /* comment \n * is several\n * lines long\n */ c }', parser, parser._parseStylesheet.bind(parser)); assertNode(' a { b: // single line comment\n c }', parser, parser._parseStylesheet.bind(parser)); }); test('Variable', function () { - let parser = new SCSSParser(); + const parser = new SCSSParser(); assertNode('$color', parser, parser._parseVariable.bind(parser)); assertNode('$co42lor', parser, parser._parseVariable.bind(parser)); assertNode('$-co42lor', parser, parser._parseVariable.bind(parser)); }); test('Module variable', function () { - let parser = new SCSSParser(); + const parser = new SCSSParser(); assertNode('module.$color', parser, parser._parseModuleMember.bind(parser)); assertNode('module.$co42lor', parser, parser._parseModuleMember.bind(parser)); assertNode('module.$-co42lor', parser, parser._parseModuleMember.bind(parser)); @@ -38,7 +38,7 @@ suite('SCSS - Parser', () => { }); test('VariableDeclaration', function () { - let parser = new SCSSParser(); + const parser = new SCSSParser(); assertNode('$color: #F5F5F5', parser, parser._parseVariableDeclaration.bind(parser)); assertNode('$color: 0', parser, parser._parseVariableDeclaration.bind(parser)); assertNode('$color: 255', parser, parser._parseVariableDeclaration.bind(parser)); @@ -57,25 +57,25 @@ suite('SCSS - Parser', () => { }); test('Expr', function () { - let parser = new SCSSParser(); - assertNode('($let + 20)', parser, parser._parseExpr.bind(parser)); - assertNode('($let - 20)', parser, parser._parseExpr.bind(parser)); - assertNode('($let * 20)', parser, parser._parseExpr.bind(parser)); - assertNode('($let / 20)', parser, parser._parseExpr.bind(parser)); - assertNode('(20 + $let)', parser, parser._parseExpr.bind(parser)); - assertNode('(20 - $let)', parser, parser._parseExpr.bind(parser)); - assertNode('(20 * $let)', parser, parser._parseExpr.bind(parser)); - assertNode('(20 / $let)', parser, parser._parseExpr.bind(parser)); - assertNode('(20 / 20 + $let)', parser, parser._parseExpr.bind(parser)); - assertNode('(20 + 20 + $let)', parser, parser._parseExpr.bind(parser)); - assertNode('(20 + 20 + 20 + $let)', parser, parser._parseExpr.bind(parser)); - assertNode('(20 + 20 + 20 + 20 + $let)', parser, parser._parseExpr.bind(parser)); - assertNode('(20 + 20 + $let + 20 + 20 + $let)', parser, parser._parseExpr.bind(parser)); + const parser = new SCSSParser(); + assertNode('($const + 20)', parser, parser._parseExpr.bind(parser)); + assertNode('($const - 20)', parser, parser._parseExpr.bind(parser)); + assertNode('($const * 20)', parser, parser._parseExpr.bind(parser)); + assertNode('($const / 20)', parser, parser._parseExpr.bind(parser)); + assertNode('(20 + $const)', parser, parser._parseExpr.bind(parser)); + assertNode('(20 - $const)', parser, parser._parseExpr.bind(parser)); + assertNode('(20 * $const)', parser, parser._parseExpr.bind(parser)); + assertNode('(20 / $const)', parser, parser._parseExpr.bind(parser)); + assertNode('(20 / 20 + $const)', parser, parser._parseExpr.bind(parser)); + assertNode('(20 + 20 + $const)', parser, parser._parseExpr.bind(parser)); + assertNode('(20 + 20 + 20 + $const)', parser, parser._parseExpr.bind(parser)); + assertNode('(20 + 20 + 20 + 20 + $const)', parser, parser._parseExpr.bind(parser)); + assertNode('(20 + 20 + $const + 20 + 20 + $const)', parser, parser._parseExpr.bind(parser)); assertNode('(20 + 20)', parser, parser._parseExpr.bind(parser)); assertNode('($var1 + $var2)', parser, parser._parseExpr.bind(parser)); - assertNode('(($let + 5) * 2)', parser, parser._parseExpr.bind(parser)); - assertNode('(($let + (5 + 2)) * 2)', parser, parser._parseExpr.bind(parser)); - assertNode('($let + ((5 + 2) * 2))', parser, parser._parseExpr.bind(parser)); + assertNode('(($const + 5) * 2)', parser, parser._parseExpr.bind(parser)); + assertNode('(($const + (5 + 2)) * 2)', parser, parser._parseExpr.bind(parser)); + assertNode('($const + ((5 + 2) * 2))', parser, parser._parseExpr.bind(parser)); assertNode('$color', parser, parser._parseExpr.bind(parser)); assertNode('$color, $color', parser, parser._parseExpr.bind(parser)); assertNode('$color, 42%', parser, parser._parseExpr.bind(parser)); @@ -86,24 +86,24 @@ suite('SCSS - Parser', () => { assertNode('100% / 2 + $filler', parser, parser._parseExpr.bind(parser)); assertNode('not ($v and $b) or $c', parser, parser._parseExpr.bind(parser)); - assertNode('(module.$let + 20)', parser, parser._parseExpr.bind(parser)); - assertNode('(module.$let - 20)', parser, parser._parseExpr.bind(parser)); - assertNode('(module.$let * 20)', parser, parser._parseExpr.bind(parser)); - assertNode('(module.$let / 20)', parser, parser._parseExpr.bind(parser)); - assertNode('(20 + module.$let)', parser, parser._parseExpr.bind(parser)); - assertNode('(20 - module.$let)', parser, parser._parseExpr.bind(parser)); - assertNode('(20 * module.$let)', parser, parser._parseExpr.bind(parser)); - assertNode('(20 / module.$let)', parser, parser._parseExpr.bind(parser)); - assertNode('(20 + 20 + module.$let)', parser, parser._parseExpr.bind(parser)); - assertNode('(20 + 20 + 20 + module.$let)', parser, parser._parseExpr.bind(parser)); - assertNode('(20 + 20 + 20 + 20 + module.$let)', parser, parser._parseExpr.bind(parser)); - assertNode('(20 + 20 + module.$let + 20 + 20 + module.$let)', parser, parser._parseExpr.bind(parser)); + assertNode('(module.$const + 20)', parser, parser._parseExpr.bind(parser)); + assertNode('(module.$const - 20)', parser, parser._parseExpr.bind(parser)); + assertNode('(module.$const * 20)', parser, parser._parseExpr.bind(parser)); + assertNode('(module.$const / 20)', parser, parser._parseExpr.bind(parser)); + assertNode('(20 + module.$const)', parser, parser._parseExpr.bind(parser)); + assertNode('(20 - module.$const)', parser, parser._parseExpr.bind(parser)); + assertNode('(20 * module.$const)', parser, parser._parseExpr.bind(parser)); + assertNode('(20 / module.$const)', parser, parser._parseExpr.bind(parser)); + assertNode('(20 + 20 + module.$const)', parser, parser._parseExpr.bind(parser)); + assertNode('(20 + 20 + 20 + module.$const)', parser, parser._parseExpr.bind(parser)); + assertNode('(20 + 20 + 20 + 20 + module.$const)', parser, parser._parseExpr.bind(parser)); + assertNode('(20 + 20 + module.$const + 20 + 20 + module.$const)', parser, parser._parseExpr.bind(parser)); assertNode('($var1 + module.$var2)', parser, parser._parseExpr.bind(parser)); assertNode('(module.$var1 + $var2)', parser, parser._parseExpr.bind(parser)); assertNode('(module.$var1 + module.$var2)', parser, parser._parseExpr.bind(parser)); - assertNode('((module.$let + 5) * 2)', parser, parser._parseExpr.bind(parser)); - assertNode('((module.$let + (5 + 2)) * 2)', parser, parser._parseExpr.bind(parser)); - assertNode('(module.$let + ((5 + 2) * 2))', parser, parser._parseExpr.bind(parser)); + assertNode('((module.$const + 5) * 2)', parser, parser._parseExpr.bind(parser)); + assertNode('((module.$const + (5 + 2)) * 2)', parser, parser._parseExpr.bind(parser)); + assertNode('(module.$const + ((5 + 2) * 2))', parser, parser._parseExpr.bind(parser)); assertNode('module.$color', parser, parser._parseExpr.bind(parser)); assertNode('module.$color, $color', parser, parser._parseExpr.bind(parser)); assertNode('$color, module.$color', parser, parser._parseExpr.bind(parser)); @@ -133,7 +133,7 @@ suite('SCSS - Parser', () => { }); test('SCSSOperator', function () { - let parser = new SCSSParser(); + const parser = new SCSSParser(); assertNode('>=', parser, parser._parseOperator.bind(parser)); assertNode('>', parser, parser._parseOperator.bind(parser)); assertNode('<', parser, parser._parseOperator.bind(parser)); @@ -150,7 +150,7 @@ suite('SCSS - Parser', () => { }); test('Interpolation', function () { - let parser = new SCSSParser(); + const parser = new SCSSParser(); // assertNode('#{red}', parser, parser._parseIdent.bind(parser)); // assertNode('#{$color}', parser, parser._parseIdent.bind(parser)); // assertNode('#{3 + 4}', parser, parser._parseIdent.bind(parser)); @@ -187,12 +187,12 @@ suite('SCSS - Parser', () => { }); test('Declaration', function () { - let parser = new SCSSParser(); + const parser = new SCSSParser(); assertNode('border: thin solid 1px', parser, parser._parseDeclaration.bind(parser)); assertNode('dummy: $color', parser, parser._parseDeclaration.bind(parser)); assertNode('dummy: blue', parser, parser._parseDeclaration.bind(parser)); - assertNode('dummy: (20 / $let)', parser, parser._parseDeclaration.bind(parser)); - assertNode('dummy: (20 / 20 + $let)', parser, parser._parseDeclaration.bind(parser)); + assertNode('dummy: (20 / $const)', parser, parser._parseDeclaration.bind(parser)); + assertNode('dummy: (20 / 20 + $const)', parser, parser._parseDeclaration.bind(parser)); assertNode('dummy: func($red)', parser, parser._parseDeclaration.bind(parser)); assertNode('dummy: func($red) !important', parser, parser._parseDeclaration.bind(parser)); assertNode('dummy: desaturate($red, 10%)', parser, parser._parseDeclaration.bind(parser)); @@ -213,8 +213,8 @@ suite('SCSS - Parser', () => { assertNode('color: selector-replace(&, 1)', parser, parser._parseDeclaration.bind(parser)); assertNode('dummy: module.$color', parser, parser._parseDeclaration.bind(parser)); - assertNode('dummy: (20 / module.$let)', parser, parser._parseDeclaration.bind(parser)); - assertNode('dummy: (20 / 20 + module.$let)', parser, parser._parseDeclaration.bind(parser)); + assertNode('dummy: (20 / module.$const)', parser, parser._parseDeclaration.bind(parser)); + assertNode('dummy: (20 / 20 + module.$const)', parser, parser._parseDeclaration.bind(parser)); assertNode('dummy: module.func($red)', parser, parser._parseDeclaration.bind(parser)); assertNode('dummy: module.func($red) !important', parser, parser._parseDeclaration.bind(parser)); assertNode('dummy: module.desaturate($red, 10%)', parser, parser._parseDeclaration.bind(parser)); @@ -249,7 +249,7 @@ suite('SCSS - Parser', () => { }); test('Stylesheet', function () { - let parser = new SCSSParser(); + const parser = new SCSSParser(); assertNode('$color: #F5F5F5;', parser, parser._parseStylesheet.bind(parser)); assertNode('$color: #F5F5F5; $color: #F5F5F5;', parser, parser._parseStylesheet.bind(parser)); assertNode('$color: #F5F5F5; $color: #F5F5F5; $color: #F5F5F5;', parser, parser._parseStylesheet.bind(parser)); @@ -270,7 +270,7 @@ suite('SCSS - Parser', () => { }); test('@import', function () { - let parser = new SCSSParser(); + const parser = new SCSSParser(); assertNode('@import "test.css"', parser, parser._parseImport.bind(parser)); assertNode('@import url("test.css")', parser, parser._parseImport.bind(parser)); assertNode('@import "test.css", "bar.css"', parser, parser._parseImport.bind(parser)); @@ -280,10 +280,11 @@ suite('SCSS - Parser', () => { assertError('@import "test.css" "bar.css"', parser, parser._parseStylesheet.bind(parser), ParseError.MediaQueryExpected); assertError('@import "test.css", screen', parser, parser._parseImport.bind(parser), ParseError.URIOrStringExpected); assertError('@import', parser, parser._parseImport.bind(parser), ParseError.URIOrStringExpected); + assertNode('@import url("override.css") layer;', parser, parser._parseStylesheet.bind(parser)); }); test('@use', function () { - let parser = new SCSSParser(); + const parser = new SCSSParser(); assertNode('@use "test"', parser, parser._parseUse.bind(parser)); assertNode('@use "test" as foo', parser, parser._parseUse.bind(parser)); assertNode('@use "test" as *', parser, parser._parseUse.bind(parser)); @@ -305,7 +306,7 @@ suite('SCSS - Parser', () => { }); test('@forward', function () { - let parser = new SCSSParser(); + const parser = new SCSSParser(); assertNode('@forward "test"', parser, parser._parseForward.bind(parser)); assertNode('@forward "test" as foo-*', parser, parser._parseForward.bind(parser)); assertNode('@forward "test" hide this', parser, parser._parseForward.bind(parser)); @@ -335,7 +336,7 @@ suite('SCSS - Parser', () => { }); test('@media', function () { - let parser = new SCSSParser(); + const parser = new SCSSParser(); assertNode('@media screen { .sidebar { @media (orientation: landscape) { width: 500px; } } }', parser, parser._parseStylesheet.bind(parser)); assertNode('@media #{$media} and ($feature: $value) {}', parser, parser._parseStylesheet.bind(parser)); assertNode('@media only screen and #{$query} {}', parser, parser._parseStylesheet.bind(parser)); @@ -370,7 +371,7 @@ suite('SCSS - Parser', () => { }); test('@keyframe', function () { - let parser = new SCSSParser(); + const parser = new SCSSParser(); assertNode('@keyframes name { @content; }', parser, parser._parseKeyframe.bind(parser)); assertNode('@keyframes name { @for $i from 0 through $steps { #{$i * (100%/$steps)} { transform: $rotate $translate; } } }', parser, parser._parseKeyframe.bind(parser)); // issue 42086 assertNode('@keyframes test-keyframe { @for $i from 1 through 60 { $s: ($i * 100) / 60 + "%"; } }', parser, parser._parseKeyframe.bind(parser)); @@ -379,7 +380,7 @@ suite('SCSS - Parser', () => { }); test('@extend', function () { - let parser = new SCSSParser(); + const parser = new SCSSParser(); assertNode('foo { @extend .error; border-width: 3px; }', parser, parser._parseStylesheet.bind(parser)); assertNode('a.important { @extend .notice !optional; }', parser, parser._parseStylesheet.bind(parser)); assertNode('.hoverlink { @extend a:hover; }', parser, parser._parseStylesheet.bind(parser)); @@ -395,7 +396,7 @@ suite('SCSS - Parser', () => { }); test('@debug', function () { - let parser = new SCSSParser(); + const parser = new SCSSParser(); assertNode('@debug test;', parser, parser._parseStylesheet.bind(parser)); assertNode('foo { @debug 1 + 4; nested { @warn 1 4; } }', parser, parser._parseStylesheet.bind(parser)); assertNode('@if $foo == 1 { @debug 1 + 4 }', parser, parser._parseStylesheet.bind(parser)); @@ -403,11 +404,11 @@ suite('SCSS - Parser', () => { }); test('@if', function () { - let parser = new SCSSParser(); + const parser = new SCSSParser(); assertNode('@if 1 + 1 == 2 { border: 1px solid; }', parser, parser._parseRuleSetDeclaration.bind(parser)); assertNode('@if 5 < 3 { border: 2px dotted; }', parser, parser._parseRuleSetDeclaration.bind(parser)); assertNode('@if null { border: 3px double; }', parser, parser._parseRuleSetDeclaration.bind(parser)); - assertNode('@if 1 <= $let { border: 3px; } @else { border: 4px; }', parser, parser._parseRuleSetDeclaration.bind(parser)); + assertNode('@if 1 <= $const { border: 3px; } @else { border: 4px; }', parser, parser._parseRuleSetDeclaration.bind(parser)); assertNode('@if 1 >= (1 + $foo) { border: 3px; } @else if 1 + 1 == 2 { border: 4px; }', parser, parser._parseRuleSetDeclaration.bind(parser)); assertNode('p { @if $i == 1 { x: 3px; } @else if $i == 1 { x: 4px; } @else { x: 4px; } }', parser, parser._parseStylesheet.bind(parser)); assertNode('@if (index($_RESOURCES, "clean") != null) { @error "sdssd"; }', parser, parser._parseStylesheet.bind(parser)); @@ -415,7 +416,7 @@ suite('SCSS - Parser', () => { assertError('@if { border: 1px solid; }', parser, parser._parseRuleSetDeclaration.bind(parser), ParseError.ExpressionExpected); assertError('@if 1 }', parser, parser._parseRuleSetDeclaration.bind(parser), ParseError.LeftCurlyExpected); - assertNode('@if 1 <= m.$let { border: 3px; } @else { border: 4px; }', parser, parser._parseRuleSetDeclaration.bind(parser)); + assertNode('@if 1 <= m.$const { border: 3px; } @else { border: 4px; }', parser, parser._parseRuleSetDeclaration.bind(parser)); assertNode('@if 1 >= (1 + m.$foo) { border: 3px; } @else if 1 + 1 == 2 { border: 4px; }', parser, parser._parseRuleSetDeclaration.bind(parser)); assertNode('p { @if m.$i == 1 { x: 3px; } @else if $i == 1 { x: 4px; } @else { x: 4px; } }', parser, parser._parseStylesheet.bind(parser)); assertNode('p { @if $i == 1 { x: 3px; } @else if m.$i == 1 { x: 4px; } @else { x: 4px; } }', parser, parser._parseStylesheet.bind(parser)); @@ -426,7 +427,7 @@ suite('SCSS - Parser', () => { }); test('@for', function () { - let parser = new SCSSParser(); + const parser = new SCSSParser(); assertNode('@for $i from 1 to 5 { .item-#{$i} { width: 2em * $i; } }', parser, parser._parseRuleSetDeclaration.bind(parser)); assertNode('@for $k from 1 + $x through 5 + $x { }', parser, parser._parseRuleSetDeclaration.bind(parser)); assertError('@for i from 0 to 4 {}', parser, parser._parseRuleSetDeclaration.bind(parser), ParseError.VariableNameExpected); @@ -442,7 +443,7 @@ suite('SCSS - Parser', () => { }); test('@each', function () { - let parser = new SCSSParser(); + const parser = new SCSSParser(); assertNode('@each $i in 1, 2, 3 { }', parser, parser._parseRuleSetDeclaration.bind(parser)); assertNode('@each $i in 1 2 3 { }', parser, parser._parseRuleSetDeclaration.bind(parser)); assertNode('@each $animal, $color, $cursor in (puma, black, default), (egret, white, move) {}', parser, parser._parseRuleSetDeclaration.bind(parser)); @@ -453,7 +454,7 @@ suite('SCSS - Parser', () => { }); test('@while', function () { - let parser = new SCSSParser(); + const parser = new SCSSParser(); assertNode('@while $i < 0 { .item-#{$i} { width: 2em * $i; } $i: $i - 2; }', parser, parser._parseRuleSetDeclaration.bind(parser)); assertError('@while {}', parser, parser._parseRuleSetDeclaration.bind(parser), ParseError.ExpressionExpected); assertError('@while $i != 4', parser, parser._parseRuleSetDeclaration.bind(parser), ParseError.LeftCurlyExpected); @@ -461,7 +462,7 @@ suite('SCSS - Parser', () => { }); test('@mixin', function () { - let parser = new SCSSParser(); + const parser = new SCSSParser(); assertNode('@mixin large-text { font: { family: Arial; size: 20px; } color: #ff0000; }', parser, parser._parseStylesheet.bind(parser)); assertNode('@mixin sexy-border($color, $width: 1in) { color: black; }', parser, parser._parseStylesheet.bind(parser)); assertNode('@mixin box-shadow($shadows...) { -moz-box-shadow: $shadows; }', parser, parser._parseStylesheet.bind(parser)); @@ -480,13 +481,13 @@ suite('SCSS - Parser', () => { }); test('@content', function () { - let parser = new SCSSParser(); + const parser = new SCSSParser(); assertNode('@content', parser, parser._parseMixinContent.bind(parser)); assertNode('@content($type)', parser, parser._parseMixinContent.bind(parser)); }); test('@include', function () { - let parser = new SCSSParser(); + const parser = new SCSSParser(); assertNode('p { @include sexy-border(blue); }', parser, parser._parseStylesheet.bind(parser)); assertNode('.shadows { @include box-shadow(0px 4px 5px #666, 2px 6px 10px #999); }', parser, parser._parseStylesheet.bind(parser)); assertNode('$values: #ff0000, #00ff00, #0000ff; .primary { @include colors($values...); }', parser, parser._parseStylesheet.bind(parser)); @@ -522,7 +523,7 @@ suite('SCSS - Parser', () => { }); test('@function', function () { - let parser = new SCSSParser(); + const parser = new SCSSParser(); assertNode('@function grid-width($n) { @return $n * $grid-width + ($n - 1) * $gutter-width; }', parser, parser._parseStylesheet.bind(parser)); assertNode('@function grid-width($n: 1, $e) { @return 0; }', parser, parser._parseStylesheet.bind(parser)); assertNode('@function foo($total, $a) { @for $i from 0 to $total { } @return $grid; }', parser, parser._parseStylesheet.bind(parser)); @@ -541,7 +542,7 @@ suite('SCSS - Parser', () => { }); test('@at-root', function () { - let parser = new SCSSParser(); + const parser = new SCSSParser(); assertNode('@mixin unify-parent($child) { @at-root #{selector.unify(&, $child)} { }}', parser, parser._parseStylesheet.bind(parser)); assertNode('@at-root #main2 .some-class { padding-left: calc( #{$a-variable} + 8px ); }', parser, parser._parseStylesheet.bind(parser)); assertNode('@media print { .page { @at-root (without: media) { } } }', parser, parser._parseStylesheet.bind(parser)); @@ -549,9 +550,9 @@ suite('SCSS - Parser', () => { }); test('Ruleset', function () { - let parser = new SCSSParser(); - assertNode('.selector { prop: erty $let 1px; }', parser, parser._parseRuleset.bind(parser)); - assertNode('.selector { prop: erty $let 1px m.$foo; }', parser, parser._parseRuleset.bind(parser)); + const parser = new SCSSParser(); + assertNode('.selector { prop: erty $const 1px; }', parser, parser._parseRuleset.bind(parser)); + assertNode('.selector { prop: erty $const 1px m.$foo; }', parser, parser._parseRuleset.bind(parser)); assertNode('selector:active { property:value; nested:hover {}}', parser, parser._parseRuleset.bind(parser)); assertNode('selector {}', parser, parser._parseRuleset.bind(parser)); assertNode('selector { property: declaration }', parser, parser._parseRuleset.bind(parser)); @@ -564,9 +565,9 @@ suite('SCSS - Parser', () => { }); test('Nested Ruleset', function () { - let parser = new SCSSParser(); - assertNode('.class1 { $let: 1; .class { $let: 2; three: $let; let: 3; } one: $let; }', parser, parser._parseRuleset.bind(parser)); - assertNode('.class1 { $let: 1; .class { $let: m.$foo; } one: $let; }', parser, parser._parseRuleset.bind(parser)); + const parser = new SCSSParser(); + assertNode('.class1 { $const: 1; .class { $const: 2; three: $const; const: 3; } one: $const; }', parser, parser._parseRuleset.bind(parser)); + assertNode('.class1 { $const: 1; .class { $const: m.$foo; } one: $const; }', parser, parser._parseRuleset.bind(parser)); assertNode('.class1 { > .class2 { & > .class4 { rule1: v1; } } }', parser, parser._parseRuleset.bind(parser)); assertNode('foo { @at-root { display: none; } }', parser, parser._parseRuleset.bind(parser)); assertNode('th, tr { @at-root #{selector-replace(&, "tr")} { border-bottom: 0; } }', parser, parser._parseRuleset.bind(parser)); @@ -576,7 +577,7 @@ suite('SCSS - Parser', () => { }); test('Selector Interpolation', function () { - let parser = new SCSSParser(); + const parser = new SCSSParser(); assertNode('.#{$name} { }', parser, parser._parseRuleset.bind(parser)); assertNode('.#{$name}-foo { }', parser, parser._parseRuleset.bind(parser)); assertNode('.#{$name}-foo-3 { }', parser, parser._parseRuleset.bind(parser)); @@ -602,7 +603,7 @@ suite('SCSS - Parser', () => { }); test('Parent Selector', function () { - let parser = new SCSSParser(); + const parser = new SCSSParser(); assertNode('&:hover', parser, parser._parseSimpleSelector.bind(parser)); assertNode('&.float', parser, parser._parseSimpleSelector.bind(parser)); assertNode('&-bar', parser, parser._parseSimpleSelector.bind(parser)); @@ -614,19 +615,19 @@ suite('SCSS - Parser', () => { }); test('Selector Placeholder', function () { - let parser = new SCSSParser(); + const parser = new SCSSParser(); assertNode('%hover', parser, parser._parseSimpleSelector.bind(parser)); assertNode('a%float', parser, parser._parseSimpleSelector.bind(parser)); }); test('Map', function () { - let parser = new SCSSParser(); + const parser = new SCSSParser(); assertNode('(key1: 1px, key2: solid + px, key3: (2+3))', parser, parser._parseExpr.bind(parser)); assertNode('($key1 + 3: 1px)', parser, parser._parseExpr.bind(parser)); }); test('Url', function () { - let parser = new SCSSParser(); + const parser = new SCSSParser(); assertNode('url(foo())', parser, parser._parseURILiteral.bind(parser)); assertNode('url(\'data:image/svg+xml;utf8,%3Csvg%20fill%3D%22%23\' + $color + \'foo\')', parser, parser._parseURILiteral.bind(parser)); assertNode('url(//yourdomain/yourpath.png)', parser, parser._parseURILiteral.bind(parser)); @@ -647,7 +648,7 @@ suite('SCSS - Parser', () => { }); test('@font-face', function () { - let parser = new SCSSParser(); + const parser = new SCSSParser(); assertNode('@font-face {}', parser, parser._parseFontFace.bind(parser)); assertNode('@font-face { src: url(http://test) }', parser, parser._parseFontFace.bind(parser)); assertNode('@font-face { font-style: normal; font-stretch: normal; }', parser, parser._parseFontFace.bind(parser)); From f55163f1c30a48f0254b8d40b5b9ac1f56253c53 Mon Sep 17 00:00:00 2001 From: Simon Stranks Date: Tue, 7 Nov 2023 16:42:27 +0000 Subject: [PATCH 2/4] #74068 nth child specificity (#369) * 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 --- src/parser/cssParser.ts | 2 +- src/services/selectorPrinting.ts | 50 +++++++++++++++++++++++++-- src/test/css/selectorPrinting.test.ts | 40 +++++++++++++++++++++ 3 files changed, 89 insertions(+), 3 deletions(-) diff --git a/src/parser/cssParser.ts b/src/parser/cssParser.ts index 0c453275..4653f70c 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)' From 5d302380d46eb3cdc3319186fa6fc31f371650b1 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Mon, 13 Nov 2023 15:32:45 +0100 Subject: [PATCH 3/4] fix layer and container in scss (#371) --- src/parser/cssParser.ts | 5 ++--- src/parser/cssScanner.ts | 4 ++-- src/test/scss/parser.test.ts | 10 ++++++++++ 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/parser/cssParser.ts b/src/parser/cssParser.ts index 4653f70c..5811965f 100644 --- a/src/parser/cssParser.ts +++ b/src/parser/cssParser.ts @@ -950,11 +950,10 @@ export class Parser { public _parseLayerName(): nodes.Node | null { // = [ '.' ]* - if (!this.peek(TokenType.Ident)) { + const node = this.createNode(nodes.NodeType.LayerName); + if (!node.addChild(this._parseIdent()) ) { return null; } - const node = this.createNode(nodes.NodeType.LayerName); - node.addChild(this._parseIdent()); while (!this.hasWhitespace() && this.acceptDelim('.')) { if (this.hasWhitespace() || !node.addChild(this._parseIdent())) { return this.finish(node, ParseError.IdentifierExpected); diff --git a/src/parser/cssScanner.ts b/src/parser/cssScanner.ts index e8058e2d..049c18ed 100644 --- a/src/parser/cssScanner.ts +++ b/src/parser/cssScanner.ts @@ -47,8 +47,8 @@ export enum TokenType { Comment, SingleLineComment, EOF, - CustomToken, - ContainerQueryLength + ContainerQueryLength, + CustomToken // must be last token type } export interface IToken { diff --git a/src/test/scss/parser.test.ts b/src/test/scss/parser.test.ts index a7b4161e..f17874ad 100644 --- a/src/test/scss/parser.test.ts +++ b/src/test/scss/parser.test.ts @@ -283,6 +283,16 @@ suite('SCSS - Parser', () => { assertNode('@import url("override.css") layer;', parser, parser._parseStylesheet.bind(parser)); }); + test('@layer', function () { + const parser = new SCSSParser(); + assertNode('@layer #{$layer} { }', parser, parser._parseLayer.bind(parser)); + }); + + test('@container', function () { + const parser = new SCSSParser(); + assertNode(`@container (min-width: #{$minWidth}) { .scss-interpolation { line-height: 10cqh; } }`, parser, parser._parseStylesheet.bind(parser)); + }); + test('@use', function () { const parser = new SCSSParser(); assertNode('@use "test"', parser, parser._parseUse.bind(parser)); From 9734506d8704582a62c6a57b1aedbce84ba86074 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Tue, 14 Nov 2023 09:48:52 +0100 Subject: [PATCH 4/4] [scss] error when function/include inside @keyframes (#372) * [scss] error when @function inside @keyframes * support include in keyframes --- src/parser/scssParser.ts | 3 +++ src/test/scss/parser.test.ts | 2 ++ 2 files changed, 5 insertions(+) diff --git a/src/parser/scssParser.ts b/src/parser/scssParser.ts index 7ec1d008..ab5cfa3f 100644 --- a/src/parser/scssParser.ts +++ b/src/parser/scssParser.ts @@ -118,6 +118,9 @@ export class SCSSParser extends cssParser.Parser { public _parseKeyframeSelector(): nodes.Node | null { return this._tryParseKeyframeSelector() || this._parseControlStatement(this._parseKeyframeSelector.bind(this)) + || this._parseWarnAndDebug() // @warn, @debug and @error statements + || this._parseMixinReference() // @include + || this._parseFunctionDeclaration() // @function || this._parseVariableDeclaration() || this._parseMixinContent(); } diff --git a/src/test/scss/parser.test.ts b/src/test/scss/parser.test.ts index f17874ad..c9efe74f 100644 --- a/src/test/scss/parser.test.ts +++ b/src/test/scss/parser.test.ts @@ -387,6 +387,8 @@ suite('SCSS - Parser', () => { assertNode('@keyframes test-keyframe { @for $i from 1 through 60 { $s: ($i * 100) / 60 + "%"; } }', parser, parser._parseKeyframe.bind(parser)); assertNode('@keyframes name { @for $i from 0 through m.$steps { #{$i * (100%/$steps)} { transform: $rotate $translate; } } }', parser, parser._parseKeyframe.bind(parser)); + assertNode('@keyframes name { @function bar() { } }', parser, parser._parseKeyframe.bind(parser)); // #197742 + assertNode('@keyframes name { @include keyframe-mixin(); }', parser, parser._parseKeyframe.bind(parser)); // #197742 }); test('@extend', function () {