Skip to content

Commit 577eec0

Browse files
committed
feat(selector-parsing): added helper function to parse style selectors
1 parent 2fef280 commit 577eec0

File tree

2 files changed

+65
-2
lines changed

2 files changed

+65
-2
lines changed

src/parser/index.ts

+14
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import type {
1010
import type { Program } from "estree";
1111
import type { ScopeManager } from "eslint-scope";
1212
import { Variable } from "eslint-scope";
13+
import type { Rule } from "postcss";
14+
import type { Root as SelectorRoot } from "postcss-selector-parser";
1315
import { parseScript, parseScriptInSvelte } from "./script.js";
1416
import type * as SvAST from "./svelte-ast-types.js";
1517
import type * as Compiler from "./svelte-ast-types-for-v5.js";
@@ -29,6 +31,7 @@ import {
2931
import { addReference } from "../scope/index.js";
3032
import {
3133
parseStyleContext,
34+
parseSelector,
3235
type StyleContext,
3336
type StyleContextNoStyleElement,
3437
type StyleContextParseError,
@@ -84,6 +87,7 @@ type ParseResult = {
8487
isSvelteScript: false;
8588
getSvelteHtmlAst: () => SvAST.Fragment | Compiler.Fragment;
8689
getStyleContext: () => StyleContext;
90+
getStyleSelectorAST: (rule: Rule) => SelectorRoot;
8791
svelteParseContext: SvelteParseContext;
8892
}
8993
| {
@@ -221,6 +225,7 @@ function parseAsSvelte(
221225
(b): b is SvelteStyleElement => b.type === "SvelteStyleElement",
222226
);
223227
let styleContext: StyleContext | null = null;
228+
const selectorASTs: Map<Rule, SelectorRoot> = new Map();
224229

225230
resultScript.ast = ast as any;
226231
resultScript.services = Object.assign(resultScript.services || {}, {
@@ -235,6 +240,15 @@ function parseAsSvelte(
235240
}
236241
return styleContext;
237242
},
243+
getStyleSelectorAST(rule: Rule) {
244+
const cached = selectorASTs.get(rule);
245+
if (cached !== undefined) {
246+
return cached;
247+
}
248+
const ast = parseSelector(rule);
249+
selectorASTs.set(rule, ast);
250+
return ast;
251+
},
238252
styleNodeLoc,
239253
styleNodeRange,
240254
svelteParseContext,

src/parser/style-context.ts

+51-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
1-
import type { Node, Parser, Root } from "postcss";
1+
import type { Node, Parser, Root, Rule } from "postcss";
22
import postcss from "postcss";
33
import { parse as SCSSparse } from "postcss-scss";
4+
import {
5+
default as selectorParser,
6+
type Node as SelectorNode,
7+
type Root as SelectorRoot,
8+
} from "postcss-selector-parser";
49

510
import type { Context } from "../context/index.js";
611
import type { SourceLocation, SvelteStyleElement } from "../ast/index.js";
@@ -77,10 +82,25 @@ export function parseStyleContext(
7782
return { status: "parse-error", sourceLang, error };
7883
}
7984
fixPostCSSNodeLocation(sourceAst, styleElement);
80-
sourceAst.walk((node) => fixPostCSSNodeLocation(node, styleElement));
85+
sourceAst.walk((node) => {
86+
fixPostCSSNodeLocation(node, styleElement);
87+
});
8188
return { status: "success", sourceLang, sourceAst };
8289
}
8390

91+
/**
92+
* Parses a PostCSS Rule node's selector and returns its AST.
93+
*/
94+
export function parseSelector(rule: Rule): SelectorRoot {
95+
const processor = selectorParser();
96+
const root = processor.astSync(rule.selector);
97+
fixSelectorNodeLocation(root, rule);
98+
root.walk((node) => {
99+
fixSelectorNodeLocation(node, rule);
100+
});
101+
return root;
102+
}
103+
84104
/**
85105
* Extracts a node location (like that of any ESLint node) from a parsed svelte style node.
86106
*/
@@ -144,3 +164,32 @@ function fixPostCSSNodeLocation(node: Node, styleElement: SvelteStyleElement) {
144164
node.source.end.column += styleElement.startTag.loc.end.column;
145165
}
146166
}
167+
168+
/**
169+
* Fixes selector AST locations to be relative to the whole file instead of relative to their parent rule.
170+
*/
171+
function fixSelectorNodeLocation(node: SelectorNode, rule: Rule) {
172+
if (node.source === undefined) {
173+
return;
174+
}
175+
const ruleLoc = styleNodeLoc(rule);
176+
177+
if (node.source.start !== undefined && ruleLoc.start !== undefined) {
178+
if (node.source.start.line === 1) {
179+
node.source.start.column += ruleLoc.start.column;
180+
}
181+
node.source.start.line += ruleLoc.start.line - 1;
182+
} else {
183+
node.source.start = undefined;
184+
}
185+
186+
if (node.source.end !== undefined && ruleLoc.start !== undefined) {
187+
if (node.source.end.line === 1) {
188+
node.source.end.column += ruleLoc.start.column;
189+
}
190+
node.source.end.column += 1;
191+
node.source.end.line += ruleLoc.start.line - 1;
192+
} else {
193+
node.source.end = undefined;
194+
}
195+
}

0 commit comments

Comments
 (0)