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/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/src/parser/index.ts b/src/parser/index.ts index f5722ae9..f7ad5aa8 100644 --- a/src/parser/index.ts +++ b/src/parser/index.ts @@ -2,6 +2,7 @@ import { KEYS } from "../visitor-keys.js"; import { Context } from "../context/index.js"; import type { Comment, + SourceLocation, SvelteProgram, SvelteScriptElement, SvelteStyleElement, @@ -10,6 +11,11 @@ import type { import type { Program } from "estree"; import type { ScopeManager } from "eslint-scope"; import { Variable } from "eslint-scope"; +import type { Rule, Node } from "postcss"; +import type { + Node as SelectorNode, + 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 +35,7 @@ import { import { addReference } from "../scope/index.js"; import { parseStyleContext, + parseSelector, type StyleContext, type StyleContextNoStyleElement, type StyleContextParseError, @@ -36,6 +43,7 @@ import { type StyleContextUnknownLang, styleNodeLoc, styleNodeRange, + styleSelectorNodeLoc, } from "./style-context.js"; import { getGlobalsForSvelte, getGlobalsForSvelteScript } from "./globals.js"; import type { NormalizedParserOptions } from "./parser-options.js"; @@ -84,6 +92,12 @@ type ParseResult = { isSvelteScript: false; getSvelteHtmlAst: () => SvAST.Fragment | Compiler.Fragment; getStyleContext: () => StyleContext; + getStyleSelectorAST: (rule: Rule) => SelectorRoot; + styleNodeLoc: (node: Node) => Partial; + styleNodeRange: ( + node: Node, + ) => [number | undefined, number | undefined]; + styleSelectorNodeLoc: (node: SelectorNode) => Partial; svelteParseContext: SvelteParseContext; } | { @@ -221,6 +235,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,8 +250,18 @@ 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, + styleSelectorNodeLoc, svelteParseContext, }); resultScript.visitorKeys = Object.assign({}, KEYS, resultScript.visitorKeys); diff --git a/src/parser/style-context.ts b/src/parser/style-context.ts index 249b9353..fa161c03 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. */ @@ -121,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 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/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/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/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/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/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/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 07532fff..64a13fb3 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,8 +12,10 @@ import { astToJson, normalizeError, scopeToJSON, + 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"; @@ -27,10 +31,18 @@ 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", ); +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", @@ -165,7 +177,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 +188,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), ]); @@ -197,6 +212,54 @@ 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", + ); +} + +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: {