@hypergood/css-core 0.0.1 → 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 +153 -24
- package/dist/plugin.cjs.map +1 -1
- package/dist/plugin.js +153 -24
- package/dist/plugin.js.map +1 -1
- package/package.json +2 -1
- package/src/evaluate.ts +158 -19
- 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,23 +19,61 @@ 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);
|
|
40
|
+
case "TemplateLiteral":
|
|
41
|
+
return evaluateTemplateLiteral(node, knownConstants);
|
|
42
|
+
case "UnaryExpression":
|
|
43
|
+
return evaluateUnaryExpression(node, knownConstants);
|
|
33
44
|
default:
|
|
34
|
-
return UNKNOWN;
|
|
45
|
+
return UNKNOWN(node);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
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
|
+
}
|
|
35
74
|
}
|
|
75
|
+
|
|
76
|
+
return known(elements);
|
|
36
77
|
}
|
|
37
78
|
|
|
38
79
|
function evaluateBinaryExpression(
|
|
@@ -42,15 +83,56 @@ function evaluateBinaryExpression(
|
|
|
42
83
|
const left = evaluateBabelExpression(node.left, knownConstants);
|
|
43
84
|
const right = evaluateBabelExpression(node.right, knownConstants);
|
|
44
85
|
|
|
45
|
-
if (!left.confident
|
|
86
|
+
if (!left.confident) return UNKNOWN(left.badNode);
|
|
87
|
+
if (!right.confident) return UNKNOWN(right.badNode);
|
|
46
88
|
|
|
47
89
|
switch (node.operator) {
|
|
48
|
-
case "*":
|
|
49
|
-
return known(left.value * right.value);
|
|
50
90
|
case "+":
|
|
51
91
|
return known(left.value + right.value);
|
|
92
|
+
case "-":
|
|
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);
|
|
52
134
|
default:
|
|
53
|
-
return UNKNOWN;
|
|
135
|
+
return UNKNOWN(node);
|
|
54
136
|
}
|
|
55
137
|
}
|
|
56
138
|
|
|
@@ -61,7 +143,10 @@ function evaluateIdentifier(
|
|
|
61
143
|
if (node.name in knownConstants) {
|
|
62
144
|
return known(knownConstants[node.name]);
|
|
63
145
|
}
|
|
64
|
-
|
|
146
|
+
if (node.name === "undefined") {
|
|
147
|
+
return known(undefined);
|
|
148
|
+
}
|
|
149
|
+
return UNKNOWN(node);
|
|
65
150
|
}
|
|
66
151
|
|
|
67
152
|
function evaluateMemberExpression(
|
|
@@ -78,15 +163,19 @@ function evaluateMemberExpression(
|
|
|
78
163
|
if (propertyNode.type === "Identifier") {
|
|
79
164
|
property = known(propertyNode.name);
|
|
80
165
|
} else {
|
|
81
|
-
return UNKNOWN;
|
|
166
|
+
return UNKNOWN(node);
|
|
82
167
|
}
|
|
83
168
|
}
|
|
84
169
|
|
|
85
|
-
if (!obj.confident || !property.confident) return UNKNOWN;
|
|
170
|
+
if (!obj.confident || !property.confident) return UNKNOWN(node);
|
|
86
171
|
|
|
87
172
|
return known(obj.value[property.value]);
|
|
88
173
|
}
|
|
89
174
|
|
|
175
|
+
function evaluateNullLiteral(node: babel.types.NullLiteral): EvaluationResult {
|
|
176
|
+
return known(null);
|
|
177
|
+
}
|
|
178
|
+
|
|
90
179
|
function evaluateNumericLiteral(
|
|
91
180
|
node: babel.types.NumericLiteral,
|
|
92
181
|
): EvaluationResult {
|
|
@@ -102,9 +191,9 @@ function evaluateObjectExpression(
|
|
|
102
191
|
for (const property of node.properties) {
|
|
103
192
|
switch (property.type) {
|
|
104
193
|
case "ObjectMethod":
|
|
105
|
-
return UNKNOWN;
|
|
194
|
+
return UNKNOWN(property);
|
|
106
195
|
case "SpreadElement":
|
|
107
|
-
return UNKNOWN;
|
|
196
|
+
return UNKNOWN(property);
|
|
108
197
|
case "ObjectProperty": {
|
|
109
198
|
const keyNode = property.key;
|
|
110
199
|
let key: EvaluationResult;
|
|
@@ -116,11 +205,12 @@ function evaluateObjectExpression(
|
|
|
116
205
|
} else if (keyNode.type === "Identifier") {
|
|
117
206
|
key = known(keyNode.name);
|
|
118
207
|
} else {
|
|
119
|
-
return UNKNOWN;
|
|
208
|
+
return UNKNOWN(property);
|
|
120
209
|
}
|
|
121
210
|
}
|
|
122
211
|
const value = evaluateBabelExpression(property.value, knownConstants);
|
|
123
|
-
if (!key || !key.confident || !value.confident)
|
|
212
|
+
if (!key || !key.confident || !value.confident)
|
|
213
|
+
return UNKNOWN(property);
|
|
124
214
|
result[key.value] = value.value;
|
|
125
215
|
}
|
|
126
216
|
}
|
|
@@ -134,3 +224,52 @@ function evaluateStringLiteral(
|
|
|
134
224
|
): EvaluationResult {
|
|
135
225
|
return known(node.value);
|
|
136
226
|
}
|
|
227
|
+
|
|
228
|
+
function evaluateTemplateLiteral(
|
|
229
|
+
node: babel.types.TemplateLiteral,
|
|
230
|
+
knownConstants: Record<string, any>,
|
|
231
|
+
): EvaluationResult {
|
|
232
|
+
const quasisAsStrings = node.quasis.map((q) => q.value.cooked ?? q.value.raw);
|
|
233
|
+
const expressions = node.expressions.map((e) =>
|
|
234
|
+
evaluateBabelExpression(e, knownConstants),
|
|
235
|
+
);
|
|
236
|
+
if (expressions.some((e) => !e.confident)) {
|
|
237
|
+
return UNKNOWN(node);
|
|
238
|
+
}
|
|
239
|
+
const expressionValues = expressions.map((e) => e.value);
|
|
240
|
+
let result = "";
|
|
241
|
+
for (let i = 0; i < quasisAsStrings.length; i++) {
|
|
242
|
+
result += quasisAsStrings[i];
|
|
243
|
+
if (i < expressionValues.length) {
|
|
244
|
+
let value = expressionValues[i];
|
|
245
|
+
result += value;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return known(result);
|
|
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
|
+
});
|