From 33d298bcf9bae95ea64c35cb7f49a66d7f113d8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20D=C4=9Bdi=C4=8D?= Date: Thu, 12 Dec 2024 15:40:46 +0100 Subject: [PATCH 1/6] test(style-node-loc): added node types to tests --- .../parser/style-location-converter/simple-css-output.json | 5 +++++ .../style-location-converter/simple-postcss-output.json | 4 ++++ .../style-location-converter/simple-scss-output.json | 7 +++++++ ...le-location-coverter.ts => style-location-converter.ts} | 7 +++++-- tools/update-fixtures.ts | 7 +++++-- 5 files changed, 26 insertions(+), 4 deletions(-) rename tests/src/parser/{style-location-coverter.ts => style-location-converter.ts} (90%) diff --git a/tests/fixtures/parser/style-location-converter/simple-css-output.json b/tests/fixtures/parser/style-location-converter/simple-css-output.json index abbfd386..910598f6 100644 --- a/tests/fixtures/parser/style-location-converter/simple-css-output.json +++ b/tests/fixtures/parser/style-location-converter/simple-css-output.json @@ -1,5 +1,6 @@ [ [ + "root", { "start": { "line": 9, @@ -16,6 +17,7 @@ ] ], [ + "rule", { "start": { "line": 10, @@ -32,6 +34,7 @@ ] ], [ + "decl", { "start": { "line": 11, @@ -48,6 +51,7 @@ ] ], [ + "rule", { "start": { "line": 14, @@ -64,6 +68,7 @@ ] ], [ + "decl", { "start": { "line": 15, diff --git a/tests/fixtures/parser/style-location-converter/simple-postcss-output.json b/tests/fixtures/parser/style-location-converter/simple-postcss-output.json index 3b37803a..1f5c377b 100644 --- a/tests/fixtures/parser/style-location-converter/simple-postcss-output.json +++ b/tests/fixtures/parser/style-location-converter/simple-postcss-output.json @@ -1,5 +1,6 @@ [ [ + "root", { "start": { "line": 7, @@ -16,6 +17,7 @@ ] ], [ + "rule", { "start": { "line": 8, @@ -32,6 +34,7 @@ ] ], [ + "decl", { "start": { "line": 9, @@ -48,6 +51,7 @@ ] ], [ + "decl", { "start": { "line": 10, diff --git a/tests/fixtures/parser/style-location-converter/simple-scss-output.json b/tests/fixtures/parser/style-location-converter/simple-scss-output.json index fa998415..95bdb91e 100644 --- a/tests/fixtures/parser/style-location-converter/simple-scss-output.json +++ b/tests/fixtures/parser/style-location-converter/simple-scss-output.json @@ -1,5 +1,6 @@ [ [ + "root", { "start": { "line": 7, @@ -16,6 +17,7 @@ ] ], [ + "rule", { "start": { "line": 8, @@ -32,6 +34,7 @@ ] ], [ + "rule", { "start": { "line": 9, @@ -48,6 +51,7 @@ ] ], [ + "comment", { "start": { "line": 10, @@ -64,6 +68,7 @@ ] ], [ + "decl", { "start": { "line": 11, @@ -80,6 +85,7 @@ ] ], [ + "rule", { "start": { "line": 14, @@ -96,6 +102,7 @@ ] ], [ + "decl", { "start": { "line": 15, diff --git a/tests/src/parser/style-location-coverter.ts b/tests/src/parser/style-location-converter.ts similarity index 90% rename from tests/src/parser/style-location-coverter.ts rename to tests/src/parser/style-location-converter.ts index cbe96d2a..b7666177 100644 --- a/tests/src/parser/style-location-coverter.ts +++ b/tests/src/parser/style-location-converter.ts @@ -8,7 +8,7 @@ import { generateParserOptions, listupFixtures } from "./test-utils.js"; import type { SourceLocation } from "../../../src/ast/common.js"; const dirname = path.dirname(new URL(import.meta.url).pathname); -const STYLE_CONTEXT_FIXTURE_ROOT = path.resolve( +const STYLE_LOCATION_CONVERTER_FIXTURE_ROOT = path.resolve( dirname, "../../fixtures/parser/style-location-converter", ); @@ -24,7 +24,7 @@ describe("Check for AST.", () => { outputFileName, config, meetRequirements, - } of listupFixtures(STYLE_CONTEXT_FIXTURE_ROOT)) { + } of listupFixtures(STYLE_LOCATION_CONVERTER_FIXTURE_ROOT)) { describe(inputFileName, () => { let services: any; @@ -36,16 +36,19 @@ describe("Check for AST.", () => { const styleContext = services.getStyleContext(); assert.strictEqual(styleContext.status, "success"); const locations: [ + string, Partial, [number | undefined, number | undefined], ][] = [ [ + "root", services.styleNodeLoc(styleContext.sourceAst), services.styleNodeRange(styleContext.sourceAst), ], ]; styleContext.sourceAst.walk((node: Node) => { locations.push([ + node.type, services.styleNodeLoc(node), services.styleNodeRange(node), ]); diff --git a/tools/update-fixtures.ts b/tools/update-fixtures.ts index 07532fff..7dfedb29 100644 --- a/tools/update-fixtures.ts +++ b/tools/update-fixtures.ts @@ -27,7 +27,7 @@ const STYLE_CONTEXT_FIXTURE_ROOT = path.resolve( dirname, "../tests/fixtures/parser/style-context", ); -const STYLE_LOCATION_FIXTURE_ROOT = path.resolve( +const STYLE_LOCATION_CONVERTER_FIXTURE_ROOT = path.resolve( dirname, "../tests/fixtures/parser/style-location-converter", ); @@ -165,7 +165,7 @@ for (const { outputFileName, config, meetRequirements, -} of listupFixtures(STYLE_LOCATION_FIXTURE_ROOT)) { +} of listupFixtures(STYLE_LOCATION_CONVERTER_FIXTURE_ROOT)) { if (!meetRequirements("parse")) { continue; } @@ -176,16 +176,19 @@ for (const { continue; } const locations: [ + string, Partial, [number | undefined, number | undefined], ][] = [ [ + "root", services.styleNodeLoc(styleContext.sourceAst), services.styleNodeRange(styleContext.sourceAst), ], ]; styleContext.sourceAst.walk((node) => { locations.push([ + node.type, services.styleNodeLoc(node), services.styleNodeRange(node), ]); From 5953dba47a630341f1b511e04d65e0eaf32cb858 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20D=C4=9Bdi=C4=8D?= Date: Thu, 12 Dec 2024 21:17:54 +0100 Subject: [PATCH 2/6] test(selector-parsing): added tests --- package.json | 3 +- .../selector-parsing/simple-css-input.svelte | 25 + .../selector-parsing/simple-css-output.json | 494 ++++++++++++++++ .../simple-postcss-input.svelte | 20 + .../simple-postcss-output.json | 433 ++++++++++++++ .../selector-parsing/simple-scss-input.svelte | 26 + .../selector-parsing/simple-scss-output.json | 554 ++++++++++++++++++ tests/src/parser/selector-parsing.ts | 56 ++ tests/src/parser/test-utils.ts | 11 + tools/update-fixtures.ts | 32 + 10 files changed, 1653 insertions(+), 1 deletion(-) create mode 100644 tests/fixtures/parser/selector-parsing/simple-css-input.svelte create mode 100644 tests/fixtures/parser/selector-parsing/simple-css-output.json create mode 100644 tests/fixtures/parser/selector-parsing/simple-postcss-input.svelte create mode 100644 tests/fixtures/parser/selector-parsing/simple-postcss-output.json create mode 100644 tests/fixtures/parser/selector-parsing/simple-scss-input.svelte create mode 100644 tests/fixtures/parser/selector-parsing/simple-scss-output.json create mode 100644 tests/src/parser/selector-parsing.ts diff --git a/package.json b/package.json index 7184ee88..3b08f94b 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,8 @@ "eslint-visitor-keys": "^4.0.0", "espree": "^10.0.0", "postcss": "^8.4.49", - "postcss-scss": "^4.0.9" + "postcss-scss": "^4.0.9", + "postcss-selector-parser": "^7.0.0" }, "devDependencies": { "@changesets/changelog-github": "^0.5.0", diff --git a/tests/fixtures/parser/selector-parsing/simple-css-input.svelte b/tests/fixtures/parser/selector-parsing/simple-css-input.svelte new file mode 100644 index 00000000..15be4986 --- /dev/null +++ b/tests/fixtures/parser/selector-parsing/simple-css-input.svelte @@ -0,0 +1,25 @@ + + +Hello! + +{a} + + diff --git a/tests/fixtures/parser/selector-parsing/simple-css-output.json b/tests/fixtures/parser/selector-parsing/simple-css-output.json new file mode 100644 index 00000000..1c1e4c5c --- /dev/null +++ b/tests/fixtures/parser/selector-parsing/simple-css-output.json @@ -0,0 +1,494 @@ +[ + { + "source": { + "start": { + "line": 10, + "column": 3 + }, + "end": { + "line": 10, + "column": 10 + } + }, + "spaces": { + "before": "", + "after": "" + }, + "nodes": [ + { + "source": { + "start": { + "line": 10, + "column": 3 + }, + "end": { + "line": 10, + "column": 10 + } + }, + "sourceIndex": 0, + "spaces": { + "before": "", + "after": "" + }, + "nodes": [ + { + "source": { + "start": { + "line": 10, + "column": 3 + }, + "end": { + "line": 10, + "column": 10 + } + }, + "sourceIndex": 0, + "spaces": { + "before": "", + "after": "" + }, + "type": "class" + } + ], + "type": "selector", + "lastEach": 1, + "indexes": {} + } + ], + "type": "root", + "lastEach": 1, + "indexes": {} + }, + { + "source": { + "start": { + "line": 14, + "column": 3 + }, + "end": { + "line": 14, + "column": 3 + } + }, + "spaces": { + "before": "", + "after": "" + }, + "nodes": [ + { + "source": { + "start": { + "line": 14, + "column": 3 + }, + "end": { + "line": 14, + "column": 3 + } + }, + "sourceIndex": 0, + "spaces": { + "before": "", + "after": "" + }, + "nodes": [ + { + "value": "b", + "source": { + "start": { + "line": 14, + "column": 3 + }, + "end": { + "line": 14, + "column": 3 + } + }, + "sourceIndex": 0, + "spaces": { + "before": "", + "after": "" + }, + "type": "tag" + } + ], + "type": "selector", + "lastEach": 1, + "indexes": {} + } + ], + "type": "root", + "lastEach": 1, + "indexes": {} + }, + { + "source": { + "start": { + "line": 18, + "column": 3 + }, + "end": { + "line": 22, + "column": 21 + } + }, + "spaces": { + "before": "", + "after": "" + }, + "nodes": [ + { + "source": { + "start": { + "line": 18, + "column": 3 + }, + "end": { + "line": 18, + "column": 10 + } + }, + "sourceIndex": 0, + "spaces": { + "before": "", + "after": "" + }, + "nodes": [ + { + "value": "a", + "source": { + "start": { + "line": 18, + "column": 3 + }, + "end": { + "line": 18, + "column": 3 + } + }, + "sourceIndex": 0, + "spaces": { + "before": "", + "after": "" + }, + "type": "tag" + }, + { + "value": ":active", + "source": { + "start": { + "line": 18, + "column": 4 + }, + "end": { + "line": 18, + "column": 10 + } + }, + "sourceIndex": 1, + "spaces": { + "before": "", + "after": "" + }, + "nodes": [], + "type": "pseudo" + } + ], + "type": "selector", + "lastEach": 1, + "indexes": {} + }, + { + "source": { + "start": { + "line": 19, + "column": 0 + }, + "end": { + "line": 19, + "column": 11 + } + }, + "sourceIndex": 9, + "spaces": { + "before": "", + "after": "" + }, + "nodes": [ + { + "value": "a", + "source": { + "start": { + "line": 19, + "column": 3 + }, + "end": { + "line": 19, + "column": 3 + } + }, + "sourceIndex": 12, + "spaces": { + "before": "\n ", + "after": "" + }, + "type": "tag" + }, + { + "value": "::before", + "source": { + "start": { + "line": 19, + "column": 4 + }, + "end": { + "line": 19, + "column": 11 + } + }, + "sourceIndex": 13, + "spaces": { + "before": "", + "after": "" + }, + "nodes": [], + "type": "pseudo" + } + ], + "type": "selector", + "lastEach": 1, + "indexes": {} + }, + { + "source": { + "start": { + "line": 20, + "column": 0 + }, + "end": { + "line": 20, + "column": 7 + } + }, + "sourceIndex": 22, + "spaces": { + "before": "", + "after": "" + }, + "nodes": [ + { + "value": "b", + "source": { + "start": { + "line": 20, + "column": 3 + }, + "end": { + "line": 20, + "column": 3 + } + }, + "sourceIndex": 25, + "spaces": { + "before": "\n ", + "after": "" + }, + "type": "tag" + }, + { + "value": "+", + "source": { + "start": { + "line": 20, + "column": 5 + }, + "end": { + "line": 20, + "column": 5 + } + }, + "sourceIndex": 27, + "spaces": { + "before": " ", + "after": " " + }, + "type": "combinator", + "raws": { + "spaces": {} + } + }, + { + "value": "a", + "source": { + "start": { + "line": 20, + "column": 7 + }, + "end": { + "line": 20, + "column": 7 + } + }, + "sourceIndex": 29, + "spaces": { + "before": "", + "after": "" + }, + "type": "tag" + } + ], + "type": "selector", + "lastEach": 1, + "indexes": {} + }, + { + "source": { + "start": { + "line": 21, + "column": 0 + }, + "end": { + "line": 21, + "column": 14 + } + }, + "sourceIndex": 31, + "spaces": { + "before": "", + "after": "" + }, + "nodes": [ + { + "value": "b", + "source": { + "start": { + "line": 21, + "column": 3 + }, + "end": { + "line": 21, + "column": 3 + } + }, + "sourceIndex": 34, + "spaces": { + "before": "\n ", + "after": "" + }, + "type": "tag" + }, + { + "value": "+", + "source": { + "start": { + "line": 21, + "column": 5 + }, + "end": { + "line": 21, + "column": 5 + } + }, + "sourceIndex": 36, + "spaces": { + "before": " ", + "after": " " + }, + "type": "combinator", + "raws": { + "spaces": {} + } + }, + { + "source": { + "start": { + "line": 21, + "column": 7 + }, + "end": { + "line": 21, + "column": 14 + } + }, + "sourceIndex": 38, + "spaces": { + "before": "", + "after": "" + }, + "type": "class" + } + ], + "type": "selector", + "lastEach": 1, + "indexes": {} + }, + { + "source": { + "start": { + "line": 22, + "column": 0 + }, + "end": { + "line": 22, + "column": 21 + } + }, + "sourceIndex": 47, + "spaces": { + "before": "", + "after": "" + }, + "nodes": [ + { + "value": "a", + "source": { + "start": { + "line": 22, + "column": 3 + }, + "end": { + "line": 22, + "column": 3 + } + }, + "sourceIndex": 50, + "spaces": { + "before": "\n ", + "after": "" + }, + "type": "tag" + }, + { + "source": { + "start": { + "line": 22, + "column": 4 + }, + "end": { + "line": 22, + "column": 21 + } + }, + "sourceIndex": 51, + "operator": "=", + "raws": { + "value": "\"value\"" + }, + "spaces": { + "before": "", + "after": "" + }, + "type": "attribute" + } + ], + "type": "selector", + "lastEach": 1, + "indexes": {} + } + ], + "type": "root", + "lastEach": 1, + "indexes": {} + } +] diff --git a/tests/fixtures/parser/selector-parsing/simple-postcss-input.svelte b/tests/fixtures/parser/selector-parsing/simple-postcss-input.svelte new file mode 100644 index 00000000..68cfd1a3 --- /dev/null +++ b/tests/fixtures/parser/selector-parsing/simple-postcss-input.svelte @@ -0,0 +1,20 @@ +
+
Hello
+ + World! +
+ + diff --git a/tests/fixtures/parser/selector-parsing/simple-postcss-output.json b/tests/fixtures/parser/selector-parsing/simple-postcss-output.json new file mode 100644 index 00000000..03598a7e --- /dev/null +++ b/tests/fixtures/parser/selector-parsing/simple-postcss-output.json @@ -0,0 +1,433 @@ +[ + { + "source": { + "start": { + "line": 8, + "column": 3 + }, + "end": { + "line": 8, + "column": 6 + } + }, + "spaces": { + "before": "", + "after": "" + }, + "nodes": [ + { + "source": { + "start": { + "line": 8, + "column": 3 + }, + "end": { + "line": 8, + "column": 6 + } + }, + "sourceIndex": 0, + "spaces": { + "before": "", + "after": "" + }, + "nodes": [ + { + "value": "body", + "source": { + "start": { + "line": 8, + "column": 3 + }, + "end": { + "line": 8, + "column": 6 + } + }, + "sourceIndex": 0, + "spaces": { + "before": "", + "after": "" + }, + "type": "tag" + } + ], + "type": "selector", + "lastEach": 1, + "indexes": {} + } + ], + "type": "root", + "lastEach": 1, + "indexes": {} + }, + { + "source": { + "start": { + "line": 13, + "column": 3 + }, + "end": { + "line": 17, + "column": 21 + } + }, + "spaces": { + "before": "", + "after": "" + }, + "nodes": [ + { + "source": { + "start": { + "line": 13, + "column": 3 + }, + "end": { + "line": 13, + "column": 10 + } + }, + "sourceIndex": 0, + "spaces": { + "before": "", + "after": "" + }, + "nodes": [ + { + "value": "a", + "source": { + "start": { + "line": 13, + "column": 3 + }, + "end": { + "line": 13, + "column": 3 + } + }, + "sourceIndex": 0, + "spaces": { + "before": "", + "after": "" + }, + "type": "tag" + }, + { + "value": ":active", + "source": { + "start": { + "line": 13, + "column": 4 + }, + "end": { + "line": 13, + "column": 10 + } + }, + "sourceIndex": 1, + "spaces": { + "before": "", + "after": "" + }, + "nodes": [], + "type": "pseudo" + } + ], + "type": "selector", + "lastEach": 1, + "indexes": {} + }, + { + "source": { + "start": { + "line": 14, + "column": 0 + }, + "end": { + "line": 14, + "column": 11 + } + }, + "sourceIndex": 9, + "spaces": { + "before": "", + "after": "" + }, + "nodes": [ + { + "value": "a", + "source": { + "start": { + "line": 14, + "column": 3 + }, + "end": { + "line": 14, + "column": 3 + } + }, + "sourceIndex": 12, + "spaces": { + "before": "\n ", + "after": "" + }, + "type": "tag" + }, + { + "value": "::before", + "source": { + "start": { + "line": 14, + "column": 4 + }, + "end": { + "line": 14, + "column": 11 + } + }, + "sourceIndex": 13, + "spaces": { + "before": "", + "after": "" + }, + "nodes": [], + "type": "pseudo" + } + ], + "type": "selector", + "lastEach": 1, + "indexes": {} + }, + { + "source": { + "start": { + "line": 15, + "column": 0 + }, + "end": { + "line": 15, + "column": 7 + } + }, + "sourceIndex": 22, + "spaces": { + "before": "", + "after": "" + }, + "nodes": [ + { + "value": "b", + "source": { + "start": { + "line": 15, + "column": 3 + }, + "end": { + "line": 15, + "column": 3 + } + }, + "sourceIndex": 25, + "spaces": { + "before": "\n ", + "after": "" + }, + "type": "tag" + }, + { + "value": "+", + "source": { + "start": { + "line": 15, + "column": 5 + }, + "end": { + "line": 15, + "column": 5 + } + }, + "sourceIndex": 27, + "spaces": { + "before": " ", + "after": " " + }, + "type": "combinator", + "raws": { + "spaces": {} + } + }, + { + "value": "a", + "source": { + "start": { + "line": 15, + "column": 7 + }, + "end": { + "line": 15, + "column": 7 + } + }, + "sourceIndex": 29, + "spaces": { + "before": "", + "after": "" + }, + "type": "tag" + } + ], + "type": "selector", + "lastEach": 1, + "indexes": {} + }, + { + "source": { + "start": { + "line": 16, + "column": 0 + }, + "end": { + "line": 16, + "column": 14 + } + }, + "sourceIndex": 31, + "spaces": { + "before": "", + "after": "" + }, + "nodes": [ + { + "value": "b", + "source": { + "start": { + "line": 16, + "column": 3 + }, + "end": { + "line": 16, + "column": 3 + } + }, + "sourceIndex": 34, + "spaces": { + "before": "\n ", + "after": "" + }, + "type": "tag" + }, + { + "value": "+", + "source": { + "start": { + "line": 16, + "column": 5 + }, + "end": { + "line": 16, + "column": 5 + } + }, + "sourceIndex": 36, + "spaces": { + "before": " ", + "after": " " + }, + "type": "combinator", + "raws": { + "spaces": {} + } + }, + { + "source": { + "start": { + "line": 16, + "column": 7 + }, + "end": { + "line": 16, + "column": 14 + } + }, + "sourceIndex": 38, + "spaces": { + "before": "", + "after": "" + }, + "type": "class" + } + ], + "type": "selector", + "lastEach": 1, + "indexes": {} + }, + { + "source": { + "start": { + "line": 17, + "column": 0 + }, + "end": { + "line": 17, + "column": 21 + } + }, + "sourceIndex": 47, + "spaces": { + "before": "", + "after": "" + }, + "nodes": [ + { + "value": "a", + "source": { + "start": { + "line": 17, + "column": 3 + }, + "end": { + "line": 17, + "column": 3 + } + }, + "sourceIndex": 50, + "spaces": { + "before": "\n ", + "after": "" + }, + "type": "tag" + }, + { + "source": { + "start": { + "line": 17, + "column": 4 + }, + "end": { + "line": 17, + "column": 21 + } + }, + "sourceIndex": 51, + "operator": "=", + "raws": { + "value": "\"value\"" + }, + "spaces": { + "before": "", + "after": "" + }, + "type": "attribute" + } + ], + "type": "selector", + "lastEach": 1, + "indexes": {} + } + ], + "type": "root", + "lastEach": 1, + "indexes": {} + } +] diff --git a/tests/fixtures/parser/selector-parsing/simple-scss-input.svelte b/tests/fixtures/parser/selector-parsing/simple-scss-input.svelte new file mode 100644 index 00000000..4f5df63b --- /dev/null +++ b/tests/fixtures/parser/selector-parsing/simple-scss-input.svelte @@ -0,0 +1,26 @@ +
+
Hello
+ + World! +
+ + diff --git a/tests/fixtures/parser/selector-parsing/simple-scss-output.json b/tests/fixtures/parser/selector-parsing/simple-scss-output.json new file mode 100644 index 00000000..47b74f7c --- /dev/null +++ b/tests/fixtures/parser/selector-parsing/simple-scss-output.json @@ -0,0 +1,554 @@ +[ + { + "source": { + "start": { + "line": 8, + "column": 3 + }, + "end": { + "line": 8, + "column": 12 + } + }, + "spaces": { + "before": "", + "after": "" + }, + "nodes": [ + { + "source": { + "start": { + "line": 8, + "column": 3 + }, + "end": { + "line": 8, + "column": 12 + } + }, + "sourceIndex": 0, + "spaces": { + "before": "", + "after": "" + }, + "nodes": [ + { + "source": { + "start": { + "line": 8, + "column": 3 + }, + "end": { + "line": 8, + "column": 12 + } + }, + "sourceIndex": 0, + "spaces": { + "before": "", + "after": "" + }, + "type": "class" + } + ], + "type": "selector", + "lastEach": 1, + "indexes": {} + } + ], + "type": "root", + "lastEach": 1, + "indexes": {} + }, + { + "source": { + "start": { + "line": 9, + "column": 5 + }, + "end": { + "line": 9, + "column": 14 + } + }, + "spaces": { + "before": "", + "after": "" + }, + "nodes": [ + { + "source": { + "start": { + "line": 9, + "column": 5 + }, + "end": { + "line": 9, + "column": 14 + } + }, + "sourceIndex": 0, + "spaces": { + "before": "", + "after": "" + }, + "nodes": [ + { + "source": { + "start": { + "line": 9, + "column": 5 + }, + "end": { + "line": 9, + "column": 14 + } + }, + "sourceIndex": 0, + "spaces": { + "before": "", + "after": "" + }, + "type": "class" + } + ], + "type": "selector", + "lastEach": 1, + "indexes": {} + } + ], + "type": "root", + "lastEach": 1, + "indexes": {} + }, + { + "source": { + "start": { + "line": 14, + "column": 5 + }, + "end": { + "line": 14, + "column": 15 + } + }, + "spaces": { + "before": "", + "after": "" + }, + "nodes": [ + { + "source": { + "start": { + "line": 14, + "column": 5 + }, + "end": { + "line": 14, + "column": 15 + } + }, + "sourceIndex": 0, + "spaces": { + "before": "", + "after": "" + }, + "nodes": [ + { + "source": { + "start": { + "line": 14, + "column": 5 + }, + "end": { + "line": 14, + "column": 15 + } + }, + "sourceIndex": 0, + "spaces": { + "before": "", + "after": "" + }, + "type": "class" + } + ], + "type": "selector", + "lastEach": 1, + "indexes": {} + } + ], + "type": "root", + "lastEach": 1, + "indexes": {} + }, + { + "source": { + "start": { + "line": 18, + "column": 5 + }, + "end": { + "line": 22, + "column": 23 + } + }, + "spaces": { + "before": "", + "after": "" + }, + "nodes": [ + { + "source": { + "start": { + "line": 18, + "column": 5 + }, + "end": { + "line": 18, + "column": 12 + } + }, + "sourceIndex": 0, + "spaces": { + "before": "", + "after": "" + }, + "nodes": [ + { + "value": "a", + "source": { + "start": { + "line": 18, + "column": 5 + }, + "end": { + "line": 18, + "column": 5 + } + }, + "sourceIndex": 0, + "spaces": { + "before": "", + "after": "" + }, + "type": "tag" + }, + { + "value": ":active", + "source": { + "start": { + "line": 18, + "column": 6 + }, + "end": { + "line": 18, + "column": 12 + } + }, + "sourceIndex": 1, + "spaces": { + "before": "", + "after": "" + }, + "nodes": [], + "type": "pseudo" + } + ], + "type": "selector", + "lastEach": 1, + "indexes": {} + }, + { + "source": { + "start": { + "line": 19, + "column": 0 + }, + "end": { + "line": 19, + "column": 13 + } + }, + "sourceIndex": 9, + "spaces": { + "before": "", + "after": "" + }, + "nodes": [ + { + "value": "a", + "source": { + "start": { + "line": 19, + "column": 5 + }, + "end": { + "line": 19, + "column": 5 + } + }, + "sourceIndex": 14, + "spaces": { + "before": "\n ", + "after": "" + }, + "type": "tag" + }, + { + "value": "::before", + "source": { + "start": { + "line": 19, + "column": 6 + }, + "end": { + "line": 19, + "column": 13 + } + }, + "sourceIndex": 15, + "spaces": { + "before": "", + "after": "" + }, + "nodes": [], + "type": "pseudo" + } + ], + "type": "selector", + "lastEach": 1, + "indexes": {} + }, + { + "source": { + "start": { + "line": 20, + "column": 0 + }, + "end": { + "line": 20, + "column": 9 + } + }, + "sourceIndex": 24, + "spaces": { + "before": "", + "after": "" + }, + "nodes": [ + { + "value": "b", + "source": { + "start": { + "line": 20, + "column": 5 + }, + "end": { + "line": 20, + "column": 5 + } + }, + "sourceIndex": 29, + "spaces": { + "before": "\n ", + "after": "" + }, + "type": "tag" + }, + { + "value": "+", + "source": { + "start": { + "line": 20, + "column": 7 + }, + "end": { + "line": 20, + "column": 7 + } + }, + "sourceIndex": 31, + "spaces": { + "before": " ", + "after": " " + }, + "type": "combinator", + "raws": { + "spaces": {} + } + }, + { + "value": "a", + "source": { + "start": { + "line": 20, + "column": 9 + }, + "end": { + "line": 20, + "column": 9 + } + }, + "sourceIndex": 33, + "spaces": { + "before": "", + "after": "" + }, + "type": "tag" + } + ], + "type": "selector", + "lastEach": 1, + "indexes": {} + }, + { + "source": { + "start": { + "line": 21, + "column": 0 + }, + "end": { + "line": 21, + "column": 16 + } + }, + "sourceIndex": 35, + "spaces": { + "before": "", + "after": "" + }, + "nodes": [ + { + "value": "b", + "source": { + "start": { + "line": 21, + "column": 5 + }, + "end": { + "line": 21, + "column": 5 + } + }, + "sourceIndex": 40, + "spaces": { + "before": "\n ", + "after": "" + }, + "type": "tag" + }, + { + "value": "+", + "source": { + "start": { + "line": 21, + "column": 7 + }, + "end": { + "line": 21, + "column": 7 + } + }, + "sourceIndex": 42, + "spaces": { + "before": " ", + "after": " " + }, + "type": "combinator", + "raws": { + "spaces": {} + } + }, + { + "source": { + "start": { + "line": 21, + "column": 9 + }, + "end": { + "line": 21, + "column": 16 + } + }, + "sourceIndex": 44, + "spaces": { + "before": "", + "after": "" + }, + "type": "class" + } + ], + "type": "selector", + "lastEach": 1, + "indexes": {} + }, + { + "source": { + "start": { + "line": 22, + "column": 0 + }, + "end": { + "line": 22, + "column": 23 + } + }, + "sourceIndex": 53, + "spaces": { + "before": "", + "after": "" + }, + "nodes": [ + { + "value": "a", + "source": { + "start": { + "line": 22, + "column": 5 + }, + "end": { + "line": 22, + "column": 5 + } + }, + "sourceIndex": 58, + "spaces": { + "before": "\n ", + "after": "" + }, + "type": "tag" + }, + { + "source": { + "start": { + "line": 22, + "column": 6 + }, + "end": { + "line": 22, + "column": 23 + } + }, + "sourceIndex": 59, + "operator": "=", + "raws": { + "value": "\"value\"" + }, + "spaces": { + "before": "", + "after": "" + }, + "type": "attribute" + } + ], + "type": "selector", + "lastEach": 1, + "indexes": {} + } + ], + "type": "root", + "lastEach": 1, + "indexes": {} + } +] diff --git a/tests/src/parser/selector-parsing.ts b/tests/src/parser/selector-parsing.ts new file mode 100644 index 00000000..407de46b --- /dev/null +++ b/tests/src/parser/selector-parsing.ts @@ -0,0 +1,56 @@ +import assert from "assert"; +import fs from "fs"; +import path from "path"; +import type { Node } from "postcss"; +import type { Root as SelectorRoot } from "postcss-selector-parser"; + +import { parseForESLint } from "../../../src/index.js"; +import { + generateParserOptions, + listupFixtures, + selectorAstToJson, +} from "./test-utils.js"; + +const dirname = path.dirname(new URL(import.meta.url).pathname); +const SELECTOR_PARSING_FIXTURE_ROOT = path.resolve( + dirname, + "../../fixtures/parser/selector-parsing", +); + +function parse(code: string, filePath: string, config: any) { + return parseForESLint(code, generateParserOptions({ filePath }, config)); +} + +describe("Check for AST.", () => { + for (const { + input, + inputFileName, + outputFileName, + config, + meetRequirements, + } of listupFixtures(SELECTOR_PARSING_FIXTURE_ROOT)) { + if (!meetRequirements("parse")) { + continue; + } + describe(inputFileName, () => { + let services: any; + + it("most to generate the expected selector AST.", () => { + services = parse(input, inputFileName, config).services; + if (!meetRequirements("test")) { + return; + } + const styleContext = services.getStyleContext(); + assert.strictEqual(styleContext.status, "success"); + const selectorASTs: SelectorRoot[] = []; + styleContext.sourceAst.walk((node: Node) => { + if (node.type === "rule") { + selectorASTs.push(services.getStyleSelectorAST(node)); + } + }); + const output = fs.readFileSync(outputFileName, "utf8"); + assert.strictEqual(`${selectorAstToJson(selectorASTs)}\n`, output); + }); + }); + } +}); diff --git a/tests/src/parser/test-utils.ts b/tests/src/parser/test-utils.ts index b269816f..bec79c79 100644 --- a/tests/src/parser/test-utils.ts +++ b/tests/src/parser/test-utils.ts @@ -480,6 +480,17 @@ export function styleContextToJson(styleContext: StyleContext): string { } } +export function selectorAstToJson(ast: any): string { + return JSON.stringify(ast, nodeReplacer, 2); + + function nodeReplacer(key: string, value: any): any { + if (key === "parent" || key.startsWith("_")) { + return undefined; + } + return value; + } +} + function normalizeScope(scope: Scope | TSESScopes.Scope): any { let variables = scope.variables as TSESScopes.Variable[]; if (scope.type === "global") { diff --git a/tools/update-fixtures.ts b/tools/update-fixtures.ts index 7dfedb29..41b30bfa 100644 --- a/tools/update-fixtures.ts +++ b/tools/update-fixtures.ts @@ -1,6 +1,8 @@ import fs from "fs"; import path from "path"; import { Linter } from "eslint"; +import type { Node } from "postcss"; +import type { Root as SelectorRoot } from "postcss-selector-parser"; import * as parser from "../src/index.js"; import { parseForESLint } from "../src/parser/index.js"; import { @@ -10,6 +12,7 @@ import { astToJson, normalizeError, scopeToJSON, + selectorAstToJson, styleContextToJson, } from "../tests/src/parser/test-utils.js"; import type ts from "typescript"; @@ -31,6 +34,10 @@ const STYLE_LOCATION_CONVERTER_FIXTURE_ROOT = path.resolve( dirname, "../tests/fixtures/parser/style-location-converter", ); +const SELECTOR_PARSING_FIXTURE_ROOT = path.resolve( + dirname, + "../tests/fixtures/parser/selector-parsing", +); const RULES = [ "no-unused-labels", @@ -200,6 +207,31 @@ for (const { ); } +for (const { + input, + inputFileName, + outputFileName, + config, + meetRequirements, +} of listupFixtures(SELECTOR_PARSING_FIXTURE_ROOT)) { + if (!meetRequirements("parse")) { + continue; + } + const services = parse(input, inputFileName, config).services; + const styleContext = services.getStyleContext(); + const selectorASTs: SelectorRoot[] = []; + styleContext.sourceAst.walk((node: Node) => { + if (node.type === "rule") { + selectorASTs.push(services.getStyleSelectorAST(node)); + } + }); + fs.writeFileSync( + outputFileName, + `${selectorAstToJson(selectorASTs)}\n`, + "utf8", + ); +} + function buildTypes( input: string, result: { From ba985771084d2fae2ed71cac6c72db4d9cec0247 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20D=C4=9Bdi=C4=8D?= Date: Fri, 13 Dec 2024 15:53:31 +0100 Subject: [PATCH 3/6] feat(selector-parsing): added helper function to parse style selectors --- .changeset/tricky-melons-complain.md | 5 +++ src/parser/index.ts | 14 ++++++++ src/parser/style-context.ts | 52 ++++++++++++++++++++++++++-- 3 files changed, 69 insertions(+), 2 deletions(-) create mode 100644 .changeset/tricky-melons-complain.md diff --git a/.changeset/tricky-melons-complain.md b/.changeset/tricky-melons-complain.md new file mode 100644 index 00000000..56fe7974 --- /dev/null +++ b/.changeset/tricky-melons-complain.md @@ -0,0 +1,5 @@ +--- +"svelte-eslint-parser": minor +--- + +feat: added support for style selector parsing diff --git a/src/parser/index.ts b/src/parser/index.ts index f5722ae9..01f33526 100644 --- a/src/parser/index.ts +++ b/src/parser/index.ts @@ -10,6 +10,8 @@ import type { import type { Program } from "estree"; import type { ScopeManager } from "eslint-scope"; import { Variable } from "eslint-scope"; +import type { Rule } from "postcss"; +import type { Root as SelectorRoot } from "postcss-selector-parser"; import { parseScript, parseScriptInSvelte } from "./script.js"; import type * as SvAST from "./svelte-ast-types.js"; import type * as Compiler from "./svelte-ast-types-for-v5.js"; @@ -29,6 +31,7 @@ import { import { addReference } from "../scope/index.js"; import { parseStyleContext, + parseSelector, type StyleContext, type StyleContextNoStyleElement, type StyleContextParseError, @@ -84,6 +87,7 @@ type ParseResult = { isSvelteScript: false; getSvelteHtmlAst: () => SvAST.Fragment | Compiler.Fragment; getStyleContext: () => StyleContext; + getStyleSelectorAST: (rule: Rule) => SelectorRoot; svelteParseContext: SvelteParseContext; } | { @@ -221,6 +225,7 @@ function parseAsSvelte( (b): b is SvelteStyleElement => b.type === "SvelteStyleElement", ); let styleContext: StyleContext | null = null; + const selectorASTs: Map = new Map(); resultScript.ast = ast as any; resultScript.services = Object.assign(resultScript.services || {}, { @@ -235,6 +240,15 @@ function parseAsSvelte( } return styleContext; }, + getStyleSelectorAST(rule: Rule) { + const cached = selectorASTs.get(rule); + if (cached !== undefined) { + return cached; + } + const ast = parseSelector(rule); + selectorASTs.set(rule, ast); + return ast; + }, styleNodeLoc, styleNodeRange, svelteParseContext, diff --git a/src/parser/style-context.ts b/src/parser/style-context.ts index 249b9353..a4f0ca90 100644 --- a/src/parser/style-context.ts +++ b/src/parser/style-context.ts @@ -1,6 +1,11 @@ -import type { Node, Parser, Root } from "postcss"; +import type { Node, Parser, Root, Rule } from "postcss"; import postcss from "postcss"; import { parse as SCSSparse } from "postcss-scss"; +import { + default as selectorParser, + type Node as SelectorNode, + type Root as SelectorRoot, +} from "postcss-selector-parser"; import type { Context } from "../context/index.js"; import type { SourceLocation, SvelteStyleElement } from "../ast/index.js"; @@ -77,10 +82,25 @@ export function parseStyleContext( return { status: "parse-error", sourceLang, error }; } fixPostCSSNodeLocation(sourceAst, styleElement); - sourceAst.walk((node) => fixPostCSSNodeLocation(node, styleElement)); + sourceAst.walk((node) => { + fixPostCSSNodeLocation(node, styleElement); + }); return { status: "success", sourceLang, sourceAst }; } +/** + * Parses a PostCSS Rule node's selector and returns its AST. + */ +export function parseSelector(rule: Rule): SelectorRoot { + const processor = selectorParser(); + const root = processor.astSync(rule.selector); + fixSelectorNodeLocation(root, rule); + root.walk((node) => { + fixSelectorNodeLocation(node, rule); + }); + return root; +} + /** * Extracts a node location (like that of any ESLint node) from a parsed svelte style node. */ @@ -144,3 +164,31 @@ function fixPostCSSNodeLocation(node: Node, styleElement: SvelteStyleElement) { node.source.end.column += styleElement.startTag.loc.end.column; } } + +/** + * Fixes selector AST locations to be relative to the whole file instead of relative to their parent rule. + */ +function fixSelectorNodeLocation(node: SelectorNode, rule: Rule) { + if (node.source === undefined) { + return; + } + const ruleLoc = styleNodeLoc(rule); + + if (node.source.start !== undefined && ruleLoc.start !== undefined) { + if (node.source.start.line === 1) { + node.source.start.column += ruleLoc.start.column; + } + node.source.start.line += ruleLoc.start.line - 1; + } else { + node.source.start = undefined; + } + + if (node.source.end !== undefined && ruleLoc.start !== undefined) { + if (node.source.end.line === 1) { + node.source.end.column += ruleLoc.start.column; + } + node.source.end.line += ruleLoc.start.line - 1; + } else { + node.source.end = undefined; + } +} From 652e0024c9101e81fef79c4264128c427540bc33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20D=C4=9Bdi=C4=8D?= Date: Wed, 11 Dec 2024 21:45:02 +0100 Subject: [PATCH 4/6] test(selector-node-loc-fixing): added tests --- .../simple-css-input.svelte | 25 ++ .../simple-css-output.json | 281 ++++++++++++++++ .../simple-postcss-input.svelte | 20 ++ .../simple-postcss-output.json | 253 ++++++++++++++ .../simple-scss-input.svelte | 26 ++ .../simple-scss-output.json | 309 ++++++++++++++++++ ...style-selector-location-converter-utils.ts | 25 ++ .../style-selector-location-converter.ts | 49 +++ tools/update-fixtures.ts | 28 ++ 9 files changed, 1016 insertions(+) create mode 100644 tests/fixtures/parser/style-selector-location-converter/simple-css-input.svelte create mode 100644 tests/fixtures/parser/style-selector-location-converter/simple-css-output.json create mode 100644 tests/fixtures/parser/style-selector-location-converter/simple-postcss-input.svelte create mode 100644 tests/fixtures/parser/style-selector-location-converter/simple-postcss-output.json create mode 100644 tests/fixtures/parser/style-selector-location-converter/simple-scss-input.svelte create mode 100644 tests/fixtures/parser/style-selector-location-converter/simple-scss-output.json create mode 100644 tests/src/parser/style-selector-location-converter-utils.ts create mode 100644 tests/src/parser/style-selector-location-converter.ts diff --git a/tests/fixtures/parser/style-selector-location-converter/simple-css-input.svelte b/tests/fixtures/parser/style-selector-location-converter/simple-css-input.svelte new file mode 100644 index 00000000..15be4986 --- /dev/null +++ b/tests/fixtures/parser/style-selector-location-converter/simple-css-input.svelte @@ -0,0 +1,25 @@ + + +Hello! + +{a} + + diff --git a/tests/fixtures/parser/style-selector-location-converter/simple-css-output.json b/tests/fixtures/parser/style-selector-location-converter/simple-css-output.json new file mode 100644 index 00000000..e64b6c58 --- /dev/null +++ b/tests/fixtures/parser/style-selector-location-converter/simple-css-output.json @@ -0,0 +1,281 @@ +[ + [ + [ + "selector", + { + "start": { + "line": 10, + "column": 2 + }, + "end": { + "line": 10, + "column": 10 + } + } + ], + [ + "class", + { + "start": { + "line": 10, + "column": 2 + }, + "end": { + "line": 10, + "column": 10 + } + } + ] + ], + [ + [ + "selector", + { + "start": { + "line": 14, + "column": 2 + }, + "end": { + "line": 14, + "column": 3 + } + } + ], + [ + "tag", + { + "start": { + "line": 14, + "column": 2 + }, + "end": { + "line": 14, + "column": 3 + } + } + ] + ], + [ + [ + "selector", + { + "start": { + "line": 18, + "column": 2 + }, + "end": { + "line": 18, + "column": 10 + } + } + ], + [ + "tag", + { + "start": { + "line": 18, + "column": 2 + }, + "end": { + "line": 18, + "column": 3 + } + } + ], + [ + "pseudo", + { + "start": { + "line": 18, + "column": 3 + }, + "end": { + "line": 18, + "column": 10 + } + } + ], + [ + "selector", + { + "start": { + "line": 19, + "column": -1 + }, + "end": { + "line": 19, + "column": 11 + } + } + ], + [ + "tag", + { + "start": { + "line": 19, + "column": 2 + }, + "end": { + "line": 19, + "column": 3 + } + } + ], + [ + "pseudo", + { + "start": { + "line": 19, + "column": 3 + }, + "end": { + "line": 19, + "column": 11 + } + } + ], + [ + "selector", + { + "start": { + "line": 20, + "column": -1 + }, + "end": { + "line": 20, + "column": 7 + } + } + ], + [ + "tag", + { + "start": { + "line": 20, + "column": 2 + }, + "end": { + "line": 20, + "column": 3 + } + } + ], + [ + "combinator", + { + "start": { + "line": 20, + "column": 4 + }, + "end": { + "line": 20, + "column": 5 + } + } + ], + [ + "tag", + { + "start": { + "line": 20, + "column": 6 + }, + "end": { + "line": 20, + "column": 7 + } + } + ], + [ + "selector", + { + "start": { + "line": 21, + "column": -1 + }, + "end": { + "line": 21, + "column": 14 + } + } + ], + [ + "tag", + { + "start": { + "line": 21, + "column": 2 + }, + "end": { + "line": 21, + "column": 3 + } + } + ], + [ + "combinator", + { + "start": { + "line": 21, + "column": 4 + }, + "end": { + "line": 21, + "column": 5 + } + } + ], + [ + "class", + { + "start": { + "line": 21, + "column": 6 + }, + "end": { + "line": 21, + "column": 14 + } + } + ], + [ + "selector", + { + "start": { + "line": 22, + "column": -1 + }, + "end": { + "line": 22, + "column": 21 + } + } + ], + [ + "tag", + { + "start": { + "line": 22, + "column": 2 + }, + "end": { + "line": 22, + "column": 3 + } + } + ], + [ + "attribute", + { + "start": { + "line": 22, + "column": 3 + }, + "end": { + "line": 22, + "column": 21 + } + } + ] + ] +] diff --git a/tests/fixtures/parser/style-selector-location-converter/simple-postcss-input.svelte b/tests/fixtures/parser/style-selector-location-converter/simple-postcss-input.svelte new file mode 100644 index 00000000..68cfd1a3 --- /dev/null +++ b/tests/fixtures/parser/style-selector-location-converter/simple-postcss-input.svelte @@ -0,0 +1,20 @@ +
+
Hello
+ + World! +
+ + diff --git a/tests/fixtures/parser/style-selector-location-converter/simple-postcss-output.json b/tests/fixtures/parser/style-selector-location-converter/simple-postcss-output.json new file mode 100644 index 00000000..ce795299 --- /dev/null +++ b/tests/fixtures/parser/style-selector-location-converter/simple-postcss-output.json @@ -0,0 +1,253 @@ +[ + [ + [ + "selector", + { + "start": { + "line": 8, + "column": 2 + }, + "end": { + "line": 8, + "column": 6 + } + } + ], + [ + "tag", + { + "start": { + "line": 8, + "column": 2 + }, + "end": { + "line": 8, + "column": 6 + } + } + ] + ], + [ + [ + "selector", + { + "start": { + "line": 13, + "column": 2 + }, + "end": { + "line": 13, + "column": 10 + } + } + ], + [ + "tag", + { + "start": { + "line": 13, + "column": 2 + }, + "end": { + "line": 13, + "column": 3 + } + } + ], + [ + "pseudo", + { + "start": { + "line": 13, + "column": 3 + }, + "end": { + "line": 13, + "column": 10 + } + } + ], + [ + "selector", + { + "start": { + "line": 14, + "column": -1 + }, + "end": { + "line": 14, + "column": 11 + } + } + ], + [ + "tag", + { + "start": { + "line": 14, + "column": 2 + }, + "end": { + "line": 14, + "column": 3 + } + } + ], + [ + "pseudo", + { + "start": { + "line": 14, + "column": 3 + }, + "end": { + "line": 14, + "column": 11 + } + } + ], + [ + "selector", + { + "start": { + "line": 15, + "column": -1 + }, + "end": { + "line": 15, + "column": 7 + } + } + ], + [ + "tag", + { + "start": { + "line": 15, + "column": 2 + }, + "end": { + "line": 15, + "column": 3 + } + } + ], + [ + "combinator", + { + "start": { + "line": 15, + "column": 4 + }, + "end": { + "line": 15, + "column": 5 + } + } + ], + [ + "tag", + { + "start": { + "line": 15, + "column": 6 + }, + "end": { + "line": 15, + "column": 7 + } + } + ], + [ + "selector", + { + "start": { + "line": 16, + "column": -1 + }, + "end": { + "line": 16, + "column": 14 + } + } + ], + [ + "tag", + { + "start": { + "line": 16, + "column": 2 + }, + "end": { + "line": 16, + "column": 3 + } + } + ], + [ + "combinator", + { + "start": { + "line": 16, + "column": 4 + }, + "end": { + "line": 16, + "column": 5 + } + } + ], + [ + "class", + { + "start": { + "line": 16, + "column": 6 + }, + "end": { + "line": 16, + "column": 14 + } + } + ], + [ + "selector", + { + "start": { + "line": 17, + "column": -1 + }, + "end": { + "line": 17, + "column": 21 + } + } + ], + [ + "tag", + { + "start": { + "line": 17, + "column": 2 + }, + "end": { + "line": 17, + "column": 3 + } + } + ], + [ + "attribute", + { + "start": { + "line": 17, + "column": 3 + }, + "end": { + "line": 17, + "column": 21 + } + } + ] + ] +] diff --git a/tests/fixtures/parser/style-selector-location-converter/simple-scss-input.svelte b/tests/fixtures/parser/style-selector-location-converter/simple-scss-input.svelte new file mode 100644 index 00000000..4f5df63b --- /dev/null +++ b/tests/fixtures/parser/style-selector-location-converter/simple-scss-input.svelte @@ -0,0 +1,26 @@ +
+
Hello
+ + World! +
+ + diff --git a/tests/fixtures/parser/style-selector-location-converter/simple-scss-output.json b/tests/fixtures/parser/style-selector-location-converter/simple-scss-output.json new file mode 100644 index 00000000..edc20de4 --- /dev/null +++ b/tests/fixtures/parser/style-selector-location-converter/simple-scss-output.json @@ -0,0 +1,309 @@ +[ + [ + [ + "selector", + { + "start": { + "line": 8, + "column": 2 + }, + "end": { + "line": 8, + "column": 12 + } + } + ], + [ + "class", + { + "start": { + "line": 8, + "column": 2 + }, + "end": { + "line": 8, + "column": 12 + } + } + ] + ], + [ + [ + "selector", + { + "start": { + "line": 9, + "column": 4 + }, + "end": { + "line": 9, + "column": 14 + } + } + ], + [ + "class", + { + "start": { + "line": 9, + "column": 4 + }, + "end": { + "line": 9, + "column": 14 + } + } + ] + ], + [ + [ + "selector", + { + "start": { + "line": 14, + "column": 4 + }, + "end": { + "line": 14, + "column": 15 + } + } + ], + [ + "class", + { + "start": { + "line": 14, + "column": 4 + }, + "end": { + "line": 14, + "column": 15 + } + } + ] + ], + [ + [ + "selector", + { + "start": { + "line": 18, + "column": 4 + }, + "end": { + "line": 18, + "column": 12 + } + } + ], + [ + "tag", + { + "start": { + "line": 18, + "column": 4 + }, + "end": { + "line": 18, + "column": 5 + } + } + ], + [ + "pseudo", + { + "start": { + "line": 18, + "column": 5 + }, + "end": { + "line": 18, + "column": 12 + } + } + ], + [ + "selector", + { + "start": { + "line": 19, + "column": -1 + }, + "end": { + "line": 19, + "column": 13 + } + } + ], + [ + "tag", + { + "start": { + "line": 19, + "column": 4 + }, + "end": { + "line": 19, + "column": 5 + } + } + ], + [ + "pseudo", + { + "start": { + "line": 19, + "column": 5 + }, + "end": { + "line": 19, + "column": 13 + } + } + ], + [ + "selector", + { + "start": { + "line": 20, + "column": -1 + }, + "end": { + "line": 20, + "column": 9 + } + } + ], + [ + "tag", + { + "start": { + "line": 20, + "column": 4 + }, + "end": { + "line": 20, + "column": 5 + } + } + ], + [ + "combinator", + { + "start": { + "line": 20, + "column": 6 + }, + "end": { + "line": 20, + "column": 7 + } + } + ], + [ + "tag", + { + "start": { + "line": 20, + "column": 8 + }, + "end": { + "line": 20, + "column": 9 + } + } + ], + [ + "selector", + { + "start": { + "line": 21, + "column": -1 + }, + "end": { + "line": 21, + "column": 16 + } + } + ], + [ + "tag", + { + "start": { + "line": 21, + "column": 4 + }, + "end": { + "line": 21, + "column": 5 + } + } + ], + [ + "combinator", + { + "start": { + "line": 21, + "column": 6 + }, + "end": { + "line": 21, + "column": 7 + } + } + ], + [ + "class", + { + "start": { + "line": 21, + "column": 8 + }, + "end": { + "line": 21, + "column": 16 + } + } + ], + [ + "selector", + { + "start": { + "line": 22, + "column": -1 + }, + "end": { + "line": 22, + "column": 23 + } + } + ], + [ + "tag", + { + "start": { + "line": 22, + "column": 4 + }, + "end": { + "line": 22, + "column": 5 + } + } + ], + [ + "attribute", + { + "start": { + "line": 22, + "column": 5 + }, + "end": { + "line": 22, + "column": 23 + } + } + ] + ] +] diff --git a/tests/src/parser/style-selector-location-converter-utils.ts b/tests/src/parser/style-selector-location-converter-utils.ts new file mode 100644 index 00000000..4c49aec6 --- /dev/null +++ b/tests/src/parser/style-selector-location-converter-utils.ts @@ -0,0 +1,25 @@ +import type { AnyNode, Root } from "postcss"; +import type { Node as SelectorNode } from "postcss-selector-parser"; + +import type { SourceLocation } from "../../../src/ast/common.js"; + +export function extractSelectorLocations( + services: Record, + styleAST: Root, +): [string, Partial][][] { + const locations: [string, Partial][][] = []; + styleAST.walk((node: AnyNode) => { + if (node.type === "rule") { + const selectorAst = services.getStyleSelectorAST(node); + const selectorLocations: [string, Partial][] = []; + selectorAst.walk((selectorNode: SelectorNode) => { + selectorLocations.push([ + selectorNode.type, + services.styleSelectorNodeLoc(selectorNode, node), + ]); + }); + locations.push(selectorLocations); + } + }); + return locations; +} diff --git a/tests/src/parser/style-selector-location-converter.ts b/tests/src/parser/style-selector-location-converter.ts new file mode 100644 index 00000000..69946a39 --- /dev/null +++ b/tests/src/parser/style-selector-location-converter.ts @@ -0,0 +1,49 @@ +import assert from "assert"; +import fs from "fs"; +import path from "path"; + +import { parseForESLint } from "../../../src/index.js"; +import { extractSelectorLocations } from "./style-selector-location-converter-utils.js"; +import { generateParserOptions, listupFixtures } from "./test-utils.js"; + +const dirname = path.dirname(new URL(import.meta.url).pathname); +const SELECTOR_CONVERTER_FIXTURE_ROOT = path.resolve( + dirname, + "../../fixtures/parser/style-selector-location-converter", +); + +function parse(code: string, filePath: string, config: any) { + return parseForESLint(code, generateParserOptions({ filePath }, config)); +} + +describe("Check for AST.", () => { + for (const { + input, + inputFileName, + outputFileName, + config, + meetRequirements, + } of listupFixtures(SELECTOR_CONVERTER_FIXTURE_ROOT)) { + describe(inputFileName, () => { + let services: any; + + it("most to generate the expected style context.", () => { + services = parse(input, inputFileName, config).services; + if (!meetRequirements("test")) { + return; + } + const styleContext = services.getStyleContext(); + assert.strictEqual(styleContext.status, "success"); + const locations = extractSelectorLocations( + services, + styleContext.sourceAst, + ); + const output = fs.readFileSync(outputFileName, "utf8"); + assert.strictEqual( + `${JSON.stringify(locations, undefined, 2)}\n`, + output, + ); + }); + }); + } +}); diff --git a/tools/update-fixtures.ts b/tools/update-fixtures.ts index 41b30bfa..64a13fb3 100644 --- a/tools/update-fixtures.ts +++ b/tools/update-fixtures.ts @@ -15,6 +15,7 @@ import { selectorAstToJson, styleContextToJson, } from "../tests/src/parser/test-utils.js"; +import { extractSelectorLocations } from "../tests/src/parser/style-selector-location-converter-utils.js"; import type ts from "typescript"; import type ESTree from "estree"; import globals from "globals"; @@ -38,6 +39,10 @@ const SELECTOR_PARSING_FIXTURE_ROOT = path.resolve( dirname, "../tests/fixtures/parser/selector-parsing", ); +const SELECTOR_CONVERTER_FIXTURE_ROOT = path.resolve( + dirname, + "../tests/fixtures/parser/style-selector-location-converter", +); const RULES = [ "no-unused-labels", @@ -232,6 +237,29 @@ for (const { ); } +for (const { + input, + inputFileName, + outputFileName, + config, + meetRequirements, +} of listupFixtures(SELECTOR_CONVERTER_FIXTURE_ROOT)) { + if (!meetRequirements("parse")) { + continue; + } + const services = parse(input, inputFileName, config).services; + const styleContext = services.getStyleContext(); + if (styleContext.status !== "success") { + continue; + } + const locations = extractSelectorLocations(services, styleContext.sourceAst); + fs.writeFileSync( + outputFileName, + `${JSON.stringify(locations, undefined, 2)}\n`, + "utf8", + ); +} + function buildTypes( input: string, result: { From 0c66ec66ed716642013c6a394741f6c3aa865875 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20D=C4=9Bdi=C4=8D?= Date: Fri, 13 Dec 2024 18:07:24 +0100 Subject: [PATCH 5/6] feat(selector-parsing): added helper function to convert style selector locations to eslint locations --- src/parser/index.ts | 2 ++ src/parser/style-context.ts | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/src/parser/index.ts b/src/parser/index.ts index 01f33526..084b9002 100644 --- a/src/parser/index.ts +++ b/src/parser/index.ts @@ -39,6 +39,7 @@ import { type StyleContextUnknownLang, styleNodeLoc, styleNodeRange, + styleSelectorNodeLoc, } from "./style-context.js"; import { getGlobalsForSvelte, getGlobalsForSvelteScript } from "./globals.js"; import type { NormalizedParserOptions } from "./parser-options.js"; @@ -251,6 +252,7 @@ function parseAsSvelte( }, styleNodeLoc, styleNodeRange, + styleSelectorNodeLoc, svelteParseContext, }); resultScript.visitorKeys = Object.assign({}, KEYS, resultScript.visitorKeys); diff --git a/src/parser/style-context.ts b/src/parser/style-context.ts index a4f0ca90..fa161c03 100644 --- a/src/parser/style-context.ts +++ b/src/parser/style-context.ts @@ -141,6 +141,24 @@ export function styleNodeRange( ]; } +/** + * Extracts a node location (like that of any ESLint node) from a parsed svelte selector node. + */ +export function styleSelectorNodeLoc( + node: SelectorNode, +): Partial { + return { + start: + node.source?.start !== undefined + ? { + line: node.source.start.line, + column: node.source.start.column - 1, + } + : undefined, + end: node.source?.end, + }; +} + /** * Fixes PostCSS AST locations to be relative to the whole file instead of relative to the