Visitar URL original
feat(css): Added optional css-tree parser (#8076) · rynop/NativeScript@edb7fed · GitHub
Skip to content

Commit edb7fed

Browse files
darindrynop
authored andcommitted
feat(css): Added optional css-tree parser (NativeScript#8076)
* feat(css): Added optional css-tree parser * test: css-tree parser compat tests * test: more css-tree compat tests
1 parent bcf0def commit edb7fed

6 files changed

Lines changed: 242 additions & 5 deletions

File tree

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
import {
2+
parse
3+
} from "css-tree";
4+
5+
function mapSelectors(selector: string): string[] {
6+
if (!selector) {
7+
return [];
8+
}
9+
10+
return selector.split(/\s*(?![^(]*\)),\s*/).map(s => s.replace(/\u200C/g, ","));
11+
}
12+
13+
function mapPosition(node, css) {
14+
let res: any = {
15+
start: {
16+
line: node.loc.start.line,
17+
column: node.loc.start.column
18+
},
19+
end: {
20+
line: node.loc.end.line,
21+
column: node.loc.end.column
22+
},
23+
content: css
24+
};
25+
26+
if (node.loc.source && node.loc.source !== "<unknown>") {
27+
res.source = node.loc.source;
28+
}
29+
30+
return res;
31+
}
32+
33+
function transformAst(node, css, type = null) {
34+
if (!node) {
35+
return;
36+
}
37+
38+
if (node.type === "StyleSheet") {
39+
return {
40+
type: "stylesheet",
41+
stylesheet: {
42+
rules: node.children.map(child => transformAst(child, css)).toArray(),
43+
parsingErrors: []
44+
}
45+
};
46+
}
47+
48+
if (node.type === "Atrule") {
49+
let atrule: any = {
50+
type: node.name,
51+
};
52+
53+
if (node.name === "supports" || node.name === "media") {
54+
atrule[node.name] = node.prelude.value;
55+
atrule.rules = transformAst(node.block, css);
56+
} else if (node.name === "page") {
57+
atrule.selectors = node.prelude ? mapSelectors(node.prelude.value) : [];
58+
atrule.declarations = transformAst(node.block, css);
59+
} else if (node.name === "document") {
60+
atrule.document = node.prelude ? node.prelude.value : "";
61+
atrule.vendor = "";
62+
atrule.rules = transformAst(node.block, css);
63+
} else if (node.name === "font-face") {
64+
atrule.declarations = transformAst(node.block, css);
65+
} else if (node.name === "import" || node.name === "charset" || node.name === "namespace") {
66+
atrule[node.name] = node.prelude ? node.prelude.value : "";
67+
} else if (node.name === "keyframes") {
68+
atrule.name = node.prelude ? node.prelude.value : "";
69+
atrule.keyframes = transformAst(node.block, css, "keyframe");
70+
atrule.vendor = undefined;
71+
} else {
72+
atrule.rules = transformAst(node.block, css);
73+
}
74+
75+
atrule.position = mapPosition(node, css);
76+
77+
return atrule;
78+
}
79+
80+
if (node.type === "Block") {
81+
return node.children.map(child => transformAst(child, css, type)).toArray();
82+
}
83+
84+
if (node.type === "Rule") {
85+
let value = node.prelude.value;
86+
87+
let res: any = {
88+
type: type != null ? type : "rule",
89+
declarations: transformAst(node.block, css),
90+
position: mapPosition(node, css)
91+
};
92+
93+
if (type === "keyframe") {
94+
res.values = mapSelectors(value);
95+
} else {
96+
res.selectors = mapSelectors(value);
97+
}
98+
99+
return res;
100+
}
101+
102+
if (node.type === "Comment") {
103+
return {
104+
type: "comment",
105+
comment: node.value,
106+
position: mapPosition(node, css)
107+
};
108+
}
109+
110+
if (node.type === "Declaration") {
111+
return {
112+
type: "declaration",
113+
property: node.property,
114+
value: node.value.value ? node.value.value.trim() : "",
115+
position: mapPosition(node, css)
116+
};
117+
}
118+
119+
throw Error(`Unknown node type ${node.type}`);
120+
}
121+
122+
export function cssTreeParse(css, source): any {
123+
let errors = [];
124+
let ast = parse(css, {
125+
parseValue: false,
126+
parseAtrulePrelude: false,
127+
parseRulePrelude: false,
128+
positions: true,
129+
filename: source,
130+
onParseError: error => {
131+
errors.push(`${source}:${error.line}:${error.column}: ${error.formattedMessage}`);
132+
}
133+
});
134+
135+
if (errors.length > 0) {
136+
throw new Error(errors[0]);
137+
}
138+
139+
return transformAst(ast, css);
140+
}

‎nativescript-core/package.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
"dependencies": {
2121
"nativescript-hook": "0.2.5",
2222
"reduce-css-calc": "^2.1.6",
23+
"css-tree": "^1.0.0-alpha.37",
2324
"semver": "6.3.0",
2425
"tns-core-modules-widgets": "next",
2526
"tslib": "1.10.0"

‎nativescript-core/ui/styling/style-scope.ts‎

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ import {
1212
CSS3Parser,
1313
CSSNativeScript
1414
} from "../../css/parser";
15+
import {
16+
cssTreeParse
17+
} from "../../css/css-tree-parser";
1518

1619
import {
1720
RuleSet,
@@ -50,11 +53,15 @@ function ensureCssAnimationParserModule() {
5053
}
5154
}
5255

53-
let parser: "rework" | "nativescript" = "rework";
56+
let parser: "rework" | "nativescript" | "css-tree" = "rework";
5457
try {
5558
const appConfig = require("~/package.json");
56-
if (appConfig && appConfig.cssParser === "nativescript") {
57-
parser = "nativescript";
59+
if (appConfig) {
60+
if (appConfig.cssParser === "css-tree") {
61+
parser = "css-tree";
62+
} else if (appConfig.cssParser === "nativescript") {
63+
parser = "nativescript";
64+
}
5865
}
5966
} catch (e) {
6067
//
@@ -220,6 +227,10 @@ class CSSSource {
220227
private parseCSSAst() {
221228
if (this._source) {
222229
switch (parser) {
230+
case "css-tree":
231+
this._ast = cssTreeParse(this._source, this._file);
232+
233+
return;
223234
case "nativescript":
224235
const cssparser = new CSS3Parser(this._source);
225236
const stylesheet = cssparser.parseAStylesheet();

‎package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
"@types/node": "~10.12.18",
1616
"chai": "^4.1.2",
1717
"css": "^2.2.1",
18-
"css-tree": "^1.0.0-alpha24",
18+
"css-tree": "^1.0.0-alpha.37",
1919
"gonzales": "^1.0.7",
2020
"madge": "^2.2.0",
2121
"markdown-snippet-injector": "0.2.2",

‎tests/app/ui/styling/style-tests.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1584,7 +1584,7 @@ export function test_nested_css_calc() {
15841584
stack.className = "wide";
15851585
TKUnit.assertEqual(stack.width as any, 125, "Stack - width === 125");
15861586

1587-
(stack as any).style = `width: calc(100% * calc(1 / 2)`;
1587+
(stack as any).style = `width: calc(100% * calc(1 / 2))`;
15881588

15891589
TKUnit.assertDeepEqual(stack.width, { unit: "%", value: 0.5 }, "Stack - width === 50%");
15901590
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import { cssTreeParse } from "@nativescript/core/css/css-tree-parser";
2+
import { parse as reworkCssParse } from "@nativescript/core/css";
3+
import { assert } from "chai";
4+
5+
describe("css-tree parser compatible with rework ", () => {
6+
it("basic selector", () => {
7+
const testCase = ".test { color: red; }";
8+
const reworkAST = reworkCssParse(testCase, { source: "file.css" });
9+
const cssTreeAST = cssTreeParse(testCase, "file.css");
10+
11+
assert.deepEqual(cssTreeAST, reworkAST);
12+
});
13+
14+
it("@keyframes", () => {
15+
const testCase = ".test { animation-name: test; } @keyframes test { from { background-color: red; } to { background-color: blue; } } .test { color: red; }";
16+
const reworkAST = reworkCssParse(testCase, { source: "file.css" });
17+
const cssTreeAST = cssTreeParse(testCase, "file.css");
18+
19+
assert.deepEqual(cssTreeAST, reworkAST);
20+
});
21+
22+
it("@media", () => {
23+
const testCase = "@media screen and (max-width: 600px) { body { background-color: olive; } } .test { color: red; }";
24+
const reworkAST = reworkCssParse(testCase, { source: "file.css" });
25+
const cssTreeAST = cssTreeParse(testCase, "file.css");
26+
27+
assert.deepEqual(cssTreeAST, reworkAST);
28+
});
29+
30+
it("@supports", () => {
31+
const testCase = "@supports not (display: grid) { div { float: right; } } .test { color: red; }";
32+
const reworkAST = reworkCssParse(testCase, { source: "file.css" });
33+
const cssTreeAST = cssTreeParse(testCase, "file.css");
34+
35+
assert.deepEqual(cssTreeAST, reworkAST);
36+
});
37+
38+
it("@page", () => {
39+
const testCase = "@page :first { margin: 2cm; } .test { color: red; }";
40+
const reworkAST = reworkCssParse(testCase, { source: "file.css" });
41+
const cssTreeAST = cssTreeParse(testCase, "file.css");
42+
43+
assert.deepEqual(cssTreeAST, reworkAST);
44+
});
45+
46+
it("@document", () => {
47+
const testCase = "@document url(\"https://www.example.com/\") { h1 { color: green; } } .test { color: red; }";
48+
const reworkAST = reworkCssParse(testCase, { source: "file.css" });
49+
const cssTreeAST = cssTreeParse(testCase, "file.css");
50+
51+
assert.deepEqual(cssTreeAST, reworkAST);
52+
});
53+
54+
it("@font-face", () => {
55+
const testCase = "@font-face { font-family: \"Open Sans\"; src: url(\"/fonts/OpenSans-Regular-webfont.woff2\") format(\"woff2\"), url(\"/fonts/OpenSans-Regular-webfont.woff\") format(\"woff\"); } .test { color: red; }";
56+
const reworkAST = reworkCssParse(testCase, { source: "file.css" });
57+
const cssTreeAST = cssTreeParse(testCase, "file.css");
58+
59+
assert.deepEqual(cssTreeAST, reworkAST);
60+
});
61+
62+
it("@import", () => {
63+
const testCase = "@import url('landscape.css') screen and (orientation:landscape); @import url(\"fineprint.css\") print; .test { color: red; }";
64+
const reworkAST = reworkCssParse(testCase, { source: "file.css" });
65+
const cssTreeAST = cssTreeParse(testCase, "file.css");
66+
67+
assert.deepEqual(cssTreeAST, reworkAST);
68+
});
69+
70+
it("@charset", () => {
71+
const testCase = "@charset \"utf-8\"; .test { color: red; }";
72+
const reworkAST = reworkCssParse(testCase, { source: "file.css" });
73+
const cssTreeAST = cssTreeParse(testCase, "file.css");
74+
75+
assert.deepEqual(cssTreeAST, reworkAST);
76+
});
77+
78+
it("@namespace", () => {
79+
const testCase = "@namespace svg url(http://www.w3.org/2000/svg); .test { color: red; }";
80+
const reworkAST = reworkCssParse(testCase, { source: "file.css" });
81+
const cssTreeAST = cssTreeParse(testCase, "file.css");
82+
83+
assert.deepEqual(cssTreeAST, reworkAST);
84+
});
85+
});

0 commit comments

Comments
 (0)