@hypergood/css-core 0.0.2 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/plugin.cjs +131 -24
- package/dist/plugin.cjs.map +1 -1
- package/dist/plugin.js +131 -24
- package/dist/plugin.js.map +1 -1
- package/package.json +2 -1
- package/src/evaluate.ts +132 -20
- package/src/plugin.ts +13 -4
- package/src/print.ts +1 -9
- package/tests/evaluate.test.ts +73 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hypergood/css-core",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
"devDependencies": {
|
|
29
29
|
"@babel/core": "^7.28.5",
|
|
30
30
|
"@babel/helper-module-imports": "^7.27.1",
|
|
31
|
+
"@babel/parser": "^7.29.3",
|
|
31
32
|
"@babel/preset-typescript": "^7.28.5",
|
|
32
33
|
"@types/babel__core": "^7.20.5",
|
|
33
34
|
"@types/babel__helper-module-imports": "^7.18.3",
|
package/src/evaluate.ts
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
export type EvaluationResult = {
|
|
2
2
|
confident: boolean;
|
|
3
3
|
value: any;
|
|
4
|
+
badNode?: babel.types.Node | null | undefined;
|
|
4
5
|
};
|
|
5
6
|
|
|
6
|
-
const UNKNOWN
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
const UNKNOWN = (badNode?: babel.types.Node | null | undefined) =>
|
|
8
|
+
({
|
|
9
|
+
confident: false,
|
|
10
|
+
value: undefined,
|
|
11
|
+
badNode,
|
|
12
|
+
}) as EvaluationResult;
|
|
10
13
|
const known = (value: any) => ({
|
|
11
14
|
confident: true,
|
|
12
15
|
value,
|
|
@@ -16,27 +19,63 @@ export function evaluateBabelExpression(
|
|
|
16
19
|
node: babel.types.Node | null | undefined,
|
|
17
20
|
knownConstants: Record<string, any>,
|
|
18
21
|
): EvaluationResult {
|
|
19
|
-
if (!node) return UNKNOWN;
|
|
22
|
+
if (!node) return UNKNOWN(node);
|
|
20
23
|
switch (node.type) {
|
|
24
|
+
case "ArrayExpression":
|
|
25
|
+
return evaluateArrayExpression(node, knownConstants);
|
|
21
26
|
case "BinaryExpression":
|
|
22
27
|
return evaluateBinaryExpression(node, knownConstants);
|
|
23
28
|
case "Identifier":
|
|
24
29
|
return evaluateIdentifier(node, knownConstants);
|
|
25
|
-
case "NumericLiteral":
|
|
26
|
-
return evaluateNumericLiteral(node);
|
|
27
30
|
case "MemberExpression":
|
|
28
31
|
return evaluateMemberExpression(node, knownConstants);
|
|
32
|
+
case "NullLiteral":
|
|
33
|
+
return evaluateNullLiteral(node);
|
|
34
|
+
case "NumericLiteral":
|
|
35
|
+
return evaluateNumericLiteral(node);
|
|
29
36
|
case "ObjectExpression":
|
|
30
37
|
return evaluateObjectExpression(node, knownConstants);
|
|
31
38
|
case "StringLiteral":
|
|
32
39
|
return evaluateStringLiteral(node);
|
|
33
40
|
case "TemplateLiteral":
|
|
34
41
|
return evaluateTemplateLiteral(node, knownConstants);
|
|
42
|
+
case "UnaryExpression":
|
|
43
|
+
return evaluateUnaryExpression(node, knownConstants);
|
|
35
44
|
default:
|
|
36
|
-
return UNKNOWN;
|
|
45
|
+
return UNKNOWN(node);
|
|
37
46
|
}
|
|
38
47
|
}
|
|
39
48
|
|
|
49
|
+
function evaluateArrayExpression(
|
|
50
|
+
node: babel.types.ArrayExpression,
|
|
51
|
+
knownConstants: Record<string, any>,
|
|
52
|
+
): EvaluationResult {
|
|
53
|
+
const elements: any[] = [];
|
|
54
|
+
for (const item of node.elements) {
|
|
55
|
+
if (!item) return UNKNOWN(node);
|
|
56
|
+
if (item.type === "SpreadElement") {
|
|
57
|
+
const spreadElements = evaluateBabelExpression(
|
|
58
|
+
item.argument,
|
|
59
|
+
knownConstants,
|
|
60
|
+
);
|
|
61
|
+
if (spreadElements.confident) {
|
|
62
|
+
elements.push(...spreadElements.value);
|
|
63
|
+
continue;
|
|
64
|
+
} else {
|
|
65
|
+
return UNKNOWN(spreadElements.badNode);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
const result = evaluateBabelExpression(item, knownConstants);
|
|
69
|
+
if (result.confident) {
|
|
70
|
+
elements.push(result.value);
|
|
71
|
+
} else {
|
|
72
|
+
return UNKNOWN(result.badNode);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return known(elements);
|
|
77
|
+
}
|
|
78
|
+
|
|
40
79
|
function evaluateBinaryExpression(
|
|
41
80
|
node: babel.types.BinaryExpression,
|
|
42
81
|
knownConstants: Record<string, any>,
|
|
@@ -44,17 +83,56 @@ function evaluateBinaryExpression(
|
|
|
44
83
|
const left = evaluateBabelExpression(node.left, knownConstants);
|
|
45
84
|
const right = evaluateBabelExpression(node.right, knownConstants);
|
|
46
85
|
|
|
47
|
-
if (!left.confident
|
|
86
|
+
if (!left.confident) return UNKNOWN(left.badNode);
|
|
87
|
+
if (!right.confident) return UNKNOWN(right.badNode);
|
|
48
88
|
|
|
49
89
|
switch (node.operator) {
|
|
50
|
-
case "*":
|
|
51
|
-
return known(left.value * right.value);
|
|
52
90
|
case "+":
|
|
53
91
|
return known(left.value + right.value);
|
|
54
92
|
case "-":
|
|
55
93
|
return known(left.value - right.value);
|
|
94
|
+
case "/":
|
|
95
|
+
return known(left.value / right.value);
|
|
96
|
+
case "%":
|
|
97
|
+
return known(left.value % right.value);
|
|
98
|
+
case "*":
|
|
99
|
+
return known(left.value * right.value);
|
|
100
|
+
case "**":
|
|
101
|
+
return known(left.value ** right.value);
|
|
102
|
+
case "&":
|
|
103
|
+
return known(left.value & right.value);
|
|
104
|
+
case "|":
|
|
105
|
+
return known(left.value | right.value);
|
|
106
|
+
case ">>":
|
|
107
|
+
return known(left.value >> right.value);
|
|
108
|
+
case ">>>":
|
|
109
|
+
return known(left.value >>> right.value);
|
|
110
|
+
case "<<":
|
|
111
|
+
return known(left.value << right.value);
|
|
112
|
+
case "^":
|
|
113
|
+
return known(left.value ^ right.value);
|
|
114
|
+
case "==":
|
|
115
|
+
return known(left.value == right.value);
|
|
116
|
+
case "===":
|
|
117
|
+
return known(left.value === right.value);
|
|
118
|
+
case "!=":
|
|
119
|
+
return known(left.value != right.value);
|
|
120
|
+
case "!==":
|
|
121
|
+
return known(left.value !== right.value);
|
|
122
|
+
case "in":
|
|
123
|
+
return known(left.value in right.value);
|
|
124
|
+
case "instanceof":
|
|
125
|
+
return known(left.value instanceof right.value);
|
|
126
|
+
case ">":
|
|
127
|
+
return known(left.value > right.value);
|
|
128
|
+
case "<":
|
|
129
|
+
return known(left.value < right.value);
|
|
130
|
+
case ">=":
|
|
131
|
+
return known(left.value >= right.value);
|
|
132
|
+
case "<=":
|
|
133
|
+
return known(left.value <= right.value);
|
|
56
134
|
default:
|
|
57
|
-
return UNKNOWN;
|
|
135
|
+
return UNKNOWN(node);
|
|
58
136
|
}
|
|
59
137
|
}
|
|
60
138
|
|
|
@@ -65,7 +143,10 @@ function evaluateIdentifier(
|
|
|
65
143
|
if (node.name in knownConstants) {
|
|
66
144
|
return known(knownConstants[node.name]);
|
|
67
145
|
}
|
|
68
|
-
|
|
146
|
+
if (node.name === "undefined") {
|
|
147
|
+
return known(undefined);
|
|
148
|
+
}
|
|
149
|
+
return UNKNOWN(node);
|
|
69
150
|
}
|
|
70
151
|
|
|
71
152
|
function evaluateMemberExpression(
|
|
@@ -82,15 +163,19 @@ function evaluateMemberExpression(
|
|
|
82
163
|
if (propertyNode.type === "Identifier") {
|
|
83
164
|
property = known(propertyNode.name);
|
|
84
165
|
} else {
|
|
85
|
-
return UNKNOWN;
|
|
166
|
+
return UNKNOWN(node);
|
|
86
167
|
}
|
|
87
168
|
}
|
|
88
169
|
|
|
89
|
-
if (!obj.confident || !property.confident) return UNKNOWN;
|
|
170
|
+
if (!obj.confident || !property.confident) return UNKNOWN(node);
|
|
90
171
|
|
|
91
172
|
return known(obj.value[property.value]);
|
|
92
173
|
}
|
|
93
174
|
|
|
175
|
+
function evaluateNullLiteral(node: babel.types.NullLiteral): EvaluationResult {
|
|
176
|
+
return known(null);
|
|
177
|
+
}
|
|
178
|
+
|
|
94
179
|
function evaluateNumericLiteral(
|
|
95
180
|
node: babel.types.NumericLiteral,
|
|
96
181
|
): EvaluationResult {
|
|
@@ -106,9 +191,9 @@ function evaluateObjectExpression(
|
|
|
106
191
|
for (const property of node.properties) {
|
|
107
192
|
switch (property.type) {
|
|
108
193
|
case "ObjectMethod":
|
|
109
|
-
return UNKNOWN;
|
|
194
|
+
return UNKNOWN(property);
|
|
110
195
|
case "SpreadElement":
|
|
111
|
-
return UNKNOWN;
|
|
196
|
+
return UNKNOWN(property);
|
|
112
197
|
case "ObjectProperty": {
|
|
113
198
|
const keyNode = property.key;
|
|
114
199
|
let key: EvaluationResult;
|
|
@@ -120,11 +205,12 @@ function evaluateObjectExpression(
|
|
|
120
205
|
} else if (keyNode.type === "Identifier") {
|
|
121
206
|
key = known(keyNode.name);
|
|
122
207
|
} else {
|
|
123
|
-
return UNKNOWN;
|
|
208
|
+
return UNKNOWN(property);
|
|
124
209
|
}
|
|
125
210
|
}
|
|
126
211
|
const value = evaluateBabelExpression(property.value, knownConstants);
|
|
127
|
-
if (!key || !key.confident || !value.confident)
|
|
212
|
+
if (!key || !key.confident || !value.confident)
|
|
213
|
+
return UNKNOWN(property);
|
|
128
214
|
result[key.value] = value.value;
|
|
129
215
|
}
|
|
130
216
|
}
|
|
@@ -148,7 +234,7 @@ function evaluateTemplateLiteral(
|
|
|
148
234
|
evaluateBabelExpression(e, knownConstants),
|
|
149
235
|
);
|
|
150
236
|
if (expressions.some((e) => !e.confident)) {
|
|
151
|
-
return UNKNOWN;
|
|
237
|
+
return UNKNOWN(node);
|
|
152
238
|
}
|
|
153
239
|
const expressionValues = expressions.map((e) => e.value);
|
|
154
240
|
let result = "";
|
|
@@ -161,3 +247,29 @@ function evaluateTemplateLiteral(
|
|
|
161
247
|
}
|
|
162
248
|
return known(result);
|
|
163
249
|
}
|
|
250
|
+
|
|
251
|
+
function evaluateUnaryExpression(
|
|
252
|
+
node: babel.types.UnaryExpression,
|
|
253
|
+
knownConstants: Record<string, any>,
|
|
254
|
+
): EvaluationResult {
|
|
255
|
+
const argument = evaluateBabelExpression(node.argument, knownConstants);
|
|
256
|
+
|
|
257
|
+
if (!argument.confident) return UNKNOWN(argument.badNode);
|
|
258
|
+
|
|
259
|
+
switch (node.operator) {
|
|
260
|
+
case "+":
|
|
261
|
+
return known(+argument.value);
|
|
262
|
+
case "-":
|
|
263
|
+
return known(-argument.value);
|
|
264
|
+
case "void":
|
|
265
|
+
return known(void argument.value);
|
|
266
|
+
case "!":
|
|
267
|
+
return known(!argument.value);
|
|
268
|
+
case "~":
|
|
269
|
+
return known(~argument.value);
|
|
270
|
+
case "typeof":
|
|
271
|
+
return known(typeof argument.value);
|
|
272
|
+
default:
|
|
273
|
+
return UNKNOWN(node);
|
|
274
|
+
}
|
|
275
|
+
}
|
package/src/plugin.ts
CHANGED
|
@@ -180,7 +180,7 @@ function babelPluginReplaceCssProp({
|
|
|
180
180
|
}
|
|
181
181
|
},
|
|
182
182
|
},
|
|
183
|
-
JSXAttribute(path) {
|
|
183
|
+
JSXAttribute(path, state) {
|
|
184
184
|
if (
|
|
185
185
|
path.node.name &&
|
|
186
186
|
path.node.name.type === "JSXIdentifier" &&
|
|
@@ -199,9 +199,18 @@ function babelPluginReplaceCssProp({
|
|
|
199
199
|
);
|
|
200
200
|
let styleObject = styleObjectResult.value;
|
|
201
201
|
if (!styleObjectResult.confident) {
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
202
|
+
const badNode = styleObjectResult.badNode;
|
|
203
|
+
if (badNode) {
|
|
204
|
+
const { start, end } = badNode;
|
|
205
|
+
const rawCode = state.file.code.slice(start, end);
|
|
206
|
+
console.error(`COULD NOT EVALUATE CSS: \`${rawCode}\``);
|
|
207
|
+
return;
|
|
208
|
+
} else {
|
|
209
|
+
console.error(
|
|
210
|
+
"COULD NOT EVALUATE CSS: L" + expression.loc?.start.line,
|
|
211
|
+
);
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
205
214
|
}
|
|
206
215
|
const classNames = styleObjectToClassNames(styleObject);
|
|
207
216
|
|
package/src/print.ts
CHANGED
|
@@ -25,15 +25,7 @@ ${styleRules.map((rule) => ` ${rule.property}: ${rule.value};`).join("\n")}
|
|
|
25
25
|
|
|
26
26
|
const tree = groupStyleRules(styleRules, []);
|
|
27
27
|
const rulesSection = renderStyleRuleNode(tree, styleRuleToClassName);
|
|
28
|
-
return
|
|
29
|
-
keyframesSection +
|
|
30
|
-
"\n" +
|
|
31
|
-
rulesSection +
|
|
32
|
-
"\n" +
|
|
33
|
-
"/* " +
|
|
34
|
-
JSON.stringify(styleRules, null, 2) +
|
|
35
|
-
" */"
|
|
36
|
-
);
|
|
28
|
+
return keyframesSection + "\n" + rulesSection;
|
|
37
29
|
}
|
|
38
30
|
|
|
39
31
|
function renderStyleRuleNode(
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import parser from "@babel/parser";
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
import { evaluateBabelExpression } from "../src/evaluate.js";
|
|
4
|
+
|
|
5
|
+
const KNOWN = (value: any) => ({ confident: true, value });
|
|
6
|
+
|
|
7
|
+
const evaluateString = (str: string) =>
|
|
8
|
+
evaluateBabelExpression(parser.parseExpression(str), {});
|
|
9
|
+
|
|
10
|
+
describe("evaluateBabelExpression", () => {
|
|
11
|
+
it("handles primitive literals", () => {
|
|
12
|
+
expect(evaluateString("1")).toStrictEqual(KNOWN(1));
|
|
13
|
+
expect(evaluateString("1.1")).toStrictEqual(KNOWN(1.1));
|
|
14
|
+
expect(evaluateString("1e2")).toStrictEqual(KNOWN(100));
|
|
15
|
+
expect(evaluateString('"1"')).toStrictEqual(KNOWN("1"));
|
|
16
|
+
expect(evaluateString("null")).toStrictEqual(KNOWN(null));
|
|
17
|
+
expect(evaluateString("undefined")).toStrictEqual(KNOWN(undefined));
|
|
18
|
+
});
|
|
19
|
+
it("handles ArrayExpression", () => {
|
|
20
|
+
expect(evaluateString('[1,"2",3]')).toStrictEqual(KNOWN([1, "2", 3]));
|
|
21
|
+
expect(evaluateString("[1,...([2,3])]")).toStrictEqual(KNOWN([1, 2, 3]));
|
|
22
|
+
});
|
|
23
|
+
it("handles BinaryExpression", () => {
|
|
24
|
+
expect(evaluateString("1+1")).toStrictEqual(KNOWN(2));
|
|
25
|
+
expect(evaluateString("1-1")).toStrictEqual(KNOWN(0));
|
|
26
|
+
expect(evaluateString("4/2")).toStrictEqual(KNOWN(2));
|
|
27
|
+
expect(evaluateString("4%3")).toStrictEqual(KNOWN(1));
|
|
28
|
+
expect(evaluateString("2*3")).toStrictEqual(KNOWN(6));
|
|
29
|
+
expect(evaluateString("2**3")).toStrictEqual(KNOWN(8));
|
|
30
|
+
expect(evaluateString("2 & 3")).toStrictEqual(KNOWN(2));
|
|
31
|
+
expect(evaluateString("2 | 3")).toStrictEqual(KNOWN(3));
|
|
32
|
+
expect(evaluateString("8 >> 2")).toStrictEqual(KNOWN(2));
|
|
33
|
+
expect(evaluateString("8 >>> 2")).toStrictEqual(KNOWN(2));
|
|
34
|
+
expect(evaluateString("8 << 2")).toStrictEqual(KNOWN(32));
|
|
35
|
+
expect(evaluateString("8 ^ 2")).toStrictEqual(KNOWN(10));
|
|
36
|
+
expect(evaluateString("1 == 1")).toStrictEqual(KNOWN(true));
|
|
37
|
+
expect(evaluateString("1 === 1")).toStrictEqual(KNOWN(true));
|
|
38
|
+
expect(evaluateString("1 != 1")).toStrictEqual(KNOWN(false));
|
|
39
|
+
expect(evaluateString("1 !== 1")).toStrictEqual(KNOWN(false));
|
|
40
|
+
expect(evaluateString("1 > 0")).toStrictEqual(KNOWN(true));
|
|
41
|
+
expect(evaluateString("1 < 0")).toStrictEqual(KNOWN(false));
|
|
42
|
+
expect(evaluateString("1 >= 0")).toStrictEqual(KNOWN(true));
|
|
43
|
+
expect(evaluateString("1 <= 0")).toStrictEqual(KNOWN(false));
|
|
44
|
+
});
|
|
45
|
+
it("handles MemberExpression", () => {
|
|
46
|
+
expect(evaluateString("({a:1}).a")).toStrictEqual(KNOWN(1));
|
|
47
|
+
expect(evaluateString('({a:1})["a"]')).toStrictEqual(KNOWN(1));
|
|
48
|
+
});
|
|
49
|
+
it("handles ObjectExpression", () => {
|
|
50
|
+
expect(evaluateString("{a:1}")).toStrictEqual(KNOWN({ a: 1 }));
|
|
51
|
+
expect(evaluateString('{["a"]:1}')).toStrictEqual(KNOWN({ a: 1 }));
|
|
52
|
+
});
|
|
53
|
+
it("handles TemplateLiteral", () => {
|
|
54
|
+
expect(evaluateString("`Something`")).toStrictEqual(KNOWN("Something"));
|
|
55
|
+
expect(evaluateString('`Something ${"cool"}`')).toStrictEqual(
|
|
56
|
+
KNOWN("Something cool"),
|
|
57
|
+
);
|
|
58
|
+
expect(evaluateString("`Something ${1}`")).toStrictEqual(
|
|
59
|
+
KNOWN("Something 1"),
|
|
60
|
+
);
|
|
61
|
+
expect(evaluateString("`Something ${1} ${2} ${3}`")).toStrictEqual(
|
|
62
|
+
KNOWN("Something 1 2 3"),
|
|
63
|
+
);
|
|
64
|
+
});
|
|
65
|
+
it("handles UnaryExpression", () => {
|
|
66
|
+
expect(evaluateString("+1")).toStrictEqual(KNOWN(1));
|
|
67
|
+
expect(evaluateString("-1")).toStrictEqual(KNOWN(-1));
|
|
68
|
+
expect(evaluateString("void 0")).toStrictEqual(KNOWN(undefined));
|
|
69
|
+
expect(evaluateString("!1")).toStrictEqual(KNOWN(false));
|
|
70
|
+
expect(evaluateString("~1")).toStrictEqual(KNOWN(-2));
|
|
71
|
+
expect(evaluateString("typeof 1")).toStrictEqual(KNOWN("number"));
|
|
72
|
+
});
|
|
73
|
+
});
|