Skip to content

Added style AST to parser services #340

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 33 commits into from
Jun 10, 2023
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
b84790c
Added postcss and postcss-scss as dependencies
marekdedic May 22, 2023
cef3dd9
Parsing style AST as part of parser services
marekdedic May 22, 2023
763c22c
Added a JSDoc for parseStyleAst, removed commented out code
marekdedic May 22, 2023
9dc1043
Added a changeset for PostCSS AST with parser services
marekdedic May 22, 2023
74ced11
Wrapped style AST in StyleContext
marekdedic May 25, 2023
f625abd
Logging postcss parse error to StyleContext
marekdedic May 26, 2023
37e1c2d
Moved style constext parsing to own file
marekdedic May 26, 2023
9ccf103
Fixed PostCSS node locations
marekdedic May 26, 2023
624beb9
Not logging PostCSS errors for now
marekdedic May 26, 2023
229a729
Using a discriminated union for StyleContext
marekdedic May 29, 2023
1a4b9bd
Including parsing errors in StyleContext
marekdedic May 29, 2023
7734a26
Added infrastructure for style-context tests
marekdedic May 29, 2023
778770c
Added a test with self-closing style element
marekdedic May 29, 2023
9cb0fd0
Not printing file name in style-context test fixtures
marekdedic May 29, 2023
c07e674
Passing style element to fixPostCSSNodeLocation
marekdedic May 30, 2023
013d80c
Fixed column offset on style root node
marekdedic May 30, 2023
5e4f2f6
Added a PostCSS AST test with simple CSS
marekdedic May 30, 2023
202760e
Added a PostCSS AST test with empty <style> element
marekdedic May 30, 2023
82425f7
Fixed column offset in one-line styles
marekdedic May 30, 2023
9700b22
Added a test for one-line style
marekdedic May 30, 2023
2f20d8c
Ignoring another possible filename location in style-context tests
marekdedic May 30, 2023
43f0697
Added tests with invalid style
marekdedic May 30, 2023
da929b1
Added tests with unknown style lang
marekdedic May 30, 2023
222caed
Added a test for parsing SCSS
marekdedic May 30, 2023
330792d
Added a test with unrelated style attribute
marekdedic May 30, 2023
bc91e40
ESLint fix
marekdedic May 30, 2023
ac6a03c
Merge branch 'main' into postcss-style-parsing-parser-services
marekdedic Jun 1, 2023
af4b391
Added a converter functions for ESTree-style range and location for P…
marekdedic Jun 1, 2023
3397a63
Fixed incorrect Range conversion
marekdedic Jun 1, 2023
cdeed7c
Added tests for converter functions for ESTree-style range and locati…
marekdedic Jun 1, 2023
3b7a64c
Exposing the StyleContext interface
marekdedic Jun 8, 2023
9d280d2
Exposing styleNodeLoc and styleNodeRange functions as part of the par…
marekdedic Jun 8, 2023
fc476cf
Merge branch 'main' into postcss-style-parsing-parser-services
ota-meshi Jun 10, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/short-ducks-attend.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"svelte-eslint-parser": minor
---

added PostCSS AST of styles to parser services
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,9 @@
"dependencies": {
"eslint-scope": "^7.0.0",
"eslint-visitor-keys": "^3.0.0",
"espree": "^9.0.0"
"espree": "^9.0.0",
"postcss": "^8.4.23",
"postcss-scss": "^4.0.6"
},
"devDependencies": {
"@changesets/changelog-github": "^0.4.6",
Expand Down
11 changes: 11 additions & 0 deletions src/parser/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type {
Comment,
SvelteProgram,
SvelteScriptElement,
SvelteStyleElement,
Token,
} from "../ast";
import type { Program } from "estree";
Expand All @@ -21,6 +22,7 @@ import {
import { ParseError } from "../errors";
import { parseTypeScript } from "./typescript";
import { addReference } from "../scope";
import { parseStyleContext, type StyleContext } from "./style-context";

export interface ESLintProgram extends Program {
comments: Comment[];
Expand Down Expand Up @@ -50,6 +52,7 @@ export function parseForESLint(
services: Record<string, any> & {
isSvelte: true;
getSvelteHtmlAst: () => SvAST.Fragment;
getStyleContext: () => StyleContext;
};
visitorKeys: { [type: string]: string[] };
scopeManager: ScopeManager;
Expand Down Expand Up @@ -166,12 +169,20 @@ export function parseForESLint(
);
}

const styleElement = ast.body.find(
(b): b is SvelteStyleElement => b.type === "SvelteStyleElement"
);
const styleContext = parseStyleContext(styleElement, ctx);

resultScript.ast = ast as any;
resultScript.services = Object.assign(resultScript.services || {}, {
isSvelte: true,
getSvelteHtmlAst() {
return resultTemplate.svelteAst.html;
},
getStyleContext() {
return styleContext;
},
});
resultScript.visitorKeys = Object.assign({}, KEYS, resultScript.visitorKeys);

Expand Down
94 changes: 94 additions & 0 deletions src/parser/style-context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import type { Node, Parser, Root } from "postcss";
import postcss from "postcss";
import { parse as SCSSparse } from "postcss-scss";

import type { Context } from "../context";
import type { SourceLocation, SvelteStyleElement } from "../ast";

export interface StyleContext {
sourceLang: string | null;
sourceAst: Root | null;
}

/**
* Extracts style source from a SvelteStyleElement and parses it into a PostCSS AST.
*/
export function parseStyleContext(
styleElement: SvelteStyleElement | undefined,
ctx: Context
): StyleContext {
const styleContext: StyleContext = {
sourceLang: null,
sourceAst: null,
};
if (!styleElement || !styleElement.endTag) {
return styleContext;
}
styleContext.sourceLang = "css";
for (const attribute of styleElement.startTag.attributes) {
if (
attribute.type === "SvelteAttribute" &&
attribute.key.name === "lang" &&
attribute.value.length > 0 &&
attribute.value[0].type === "SvelteLiteral"
) {
styleContext.sourceLang = attribute.value[0].value;
}
}
let parseFn: Parser<Root>;
switch (styleContext.sourceLang) {
case "css":
parseFn = postcss.parse;
break;
case "scss":
parseFn = SCSSparse;
break;
default:
return styleContext;
}
const styleCode = ctx.code.slice(
styleElement.startTag.range[1],
styleElement.endTag.range[0]
);
try {
styleContext.sourceAst = parseFn(styleCode, {
from: ctx.parserOptions.filePath,
});
// eslint-disable-next-line no-empty -- Catching errors is not a good way to go around this, a safe parser should be used instead.
} catch {}
fixPostCSSNodeLocation(
styleContext.sourceAst,
styleElement.loc,
styleElement.startTag.range[1]
);
styleContext.sourceAst.walk((node) =>
fixPostCSSNodeLocation(
node,
styleElement.loc,
styleElement.startTag.range[1]
)
);
return styleContext;
}

/**
* Fixes PostCSS AST locations to be relative to the whole file instead of relative to the <style> element.
*/
function fixPostCSSNodeLocation(
node: Node,
styleElementLoc: SourceLocation,
styleElementOffset: number
) {
if (node.source?.start?.offset !== undefined) {
node.source.start.offset += styleElementOffset;
}
if (node.source?.start?.line !== undefined) {
node.source.start.line += styleElementLoc.start.line - 1;
}
if (node.source?.end?.offset !== undefined) {
node.source.end.offset += styleElementOffset;
}
if (node.source?.end?.line !== undefined) {
node.source.end.line += styleElementLoc.start.line - 1;
}
}