sculpted 0.0.0 → 0.1.0
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/LICENSE +105 -0
- package/README.md +233 -0
- package/dist/index.d.mts +129 -0
- package/dist/index.mjs +3 -0
- package/dist/patcher-BAw2kF1Q.mjs +2594 -0
- package/dist/protocol-BJm-xGHP.mjs +54 -0
- package/dist/runtime-DwE3PVhB.d.mts +64 -0
- package/dist/runtime.d.mts +2 -0
- package/dist/runtime.mjs +613 -0
- package/dist/sourceSyntax-DanNzS7Y.d.mts +103 -0
- package/dist/types-CdByW0ji.d.mts +381 -0
- package/dist/ui.d.mts +54 -0
- package/dist/ui.mjs +11125 -0
- package/dist/vite.d.mts +85 -0
- package/dist/vite.mjs +2325 -0
- package/examples/manual-vite-preact-pandacss/README.md +75 -0
- package/examples/manual-vite-preact-pandacss/index.html +12 -0
- package/examples/manual-vite-preact-pandacss/package.json +23 -0
- package/examples/manual-vite-preact-pandacss/panda.config.ts +43 -0
- package/examples/manual-vite-preact-pandacss/pnpm-lock.yaml +3359 -0
- package/examples/manual-vite-preact-pandacss/pnpm-workspace.yaml +2 -0
- package/examples/manual-vite-preact-pandacss/postcss.config.cjs +5 -0
- package/examples/manual-vite-preact-pandacss/src/TsrxManualPanel.tsrx +47 -0
- package/examples/manual-vite-preact-pandacss/src/index.css +8 -0
- package/examples/manual-vite-preact-pandacss/src/main.style.ts +33 -0
- package/examples/manual-vite-preact-pandacss/src/main.tsx +484 -0
- package/examples/manual-vite-preact-pandacss/src/tsrx.d.ts +5 -0
- package/examples/manual-vite-preact-pandacss/tsconfig.json +21 -0
- package/examples/manual-vite-preact-pandacss/vite.config.ts +20 -0
- package/package.json +66 -8
- package/readme.md +0 -1
|
@@ -0,0 +1,2594 @@
|
|
|
1
|
+
import "./protocol-BJm-xGHP.mjs";
|
|
2
|
+
import { parseModule } from "@tsrx/core";
|
|
3
|
+
import ts from "typescript";
|
|
4
|
+
import { resolve } from "node:path";
|
|
5
|
+
//#region src/sourceSyntax.ts
|
|
6
|
+
function parseWithSourceParserAdapter(adapter, options) {
|
|
7
|
+
return resolveSourceParserAdapter(options.filePath, adapter).parse(options);
|
|
8
|
+
}
|
|
9
|
+
function parsePandaSource(adapter, options) {
|
|
10
|
+
const parsed = parseWithSourceParserAdapter(adapter, options);
|
|
11
|
+
if (parsed.kind === "typescript") return {
|
|
12
|
+
astKind: "typescript",
|
|
13
|
+
languageId: parsed.languageId,
|
|
14
|
+
filePath: options.filePath,
|
|
15
|
+
sourceText: options.sourceText,
|
|
16
|
+
root: parsed.sourceFile,
|
|
17
|
+
ops: typescriptPandaAstAdapter
|
|
18
|
+
};
|
|
19
|
+
const ops = createEstreePandaAstAdapter(parsed.program);
|
|
20
|
+
return {
|
|
21
|
+
astKind: "estree",
|
|
22
|
+
languageId: parsed.languageId,
|
|
23
|
+
filePath: options.filePath,
|
|
24
|
+
sourceText: options.sourceText,
|
|
25
|
+
root: parsed.program,
|
|
26
|
+
ops
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function resolveSourceParserAdapter(filePath, adapter) {
|
|
30
|
+
return [tsxSourceParserAdapter, ...Array.isArray(adapter) ? adapter : adapter ? [adapter] : []].find((item) => item.isSupportedFile(filePath)) ?? tsxSourceParserAdapter;
|
|
31
|
+
}
|
|
32
|
+
const tsxSourceParserAdapter = {
|
|
33
|
+
languageId: "tsx",
|
|
34
|
+
kind: "typescript",
|
|
35
|
+
isSupportedFile(filePath) {
|
|
36
|
+
return /\.(?:[cm]?tsx?|[cm]?jsx)$/.test(filePath);
|
|
37
|
+
},
|
|
38
|
+
parse(options) {
|
|
39
|
+
return {
|
|
40
|
+
kind: "typescript",
|
|
41
|
+
languageId: "tsx",
|
|
42
|
+
sourceFile: ts.createSourceFile(options.filePath, options.sourceText, ts.ScriptTarget.Latest, true, scriptKindForFile(options.filePath))
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
const tsxSourceSyntaxAdapter = tsxSourceParserAdapter;
|
|
47
|
+
const tsrxSourceParserAdapter = {
|
|
48
|
+
languageId: "tsrx",
|
|
49
|
+
kind: "estree",
|
|
50
|
+
isSupportedFile(filePath) {
|
|
51
|
+
return filePath.endsWith(".tsrx");
|
|
52
|
+
},
|
|
53
|
+
parse(options) {
|
|
54
|
+
const comments = [];
|
|
55
|
+
return {
|
|
56
|
+
kind: "estree",
|
|
57
|
+
languageId: "tsrx",
|
|
58
|
+
program: parseModule(options.sourceText, options.filePath, {
|
|
59
|
+
collect: true,
|
|
60
|
+
comments
|
|
61
|
+
}),
|
|
62
|
+
comments
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
function scriptKindForFile(filePath) {
|
|
67
|
+
if (/\.[cm]?tsx$/.test(filePath)) return ts.ScriptKind.TSX;
|
|
68
|
+
if (/\.[cm]?jsx$/.test(filePath)) return ts.ScriptKind.JSX;
|
|
69
|
+
return ts.ScriptKind.TS;
|
|
70
|
+
}
|
|
71
|
+
const typescriptPandaAstAdapter = {
|
|
72
|
+
astKind: "typescript",
|
|
73
|
+
walk(root, visit) {
|
|
74
|
+
const walkNode = (node, parent) => {
|
|
75
|
+
visit(node, parent);
|
|
76
|
+
ts.forEachChild(node, (child) => walkNode(child, node));
|
|
77
|
+
};
|
|
78
|
+
walkNode(root, void 0);
|
|
79
|
+
},
|
|
80
|
+
topLevelStatements(root) {
|
|
81
|
+
return [...root.statements];
|
|
82
|
+
},
|
|
83
|
+
isImportDeclaration(node) {
|
|
84
|
+
return ts.isImportDeclaration(node);
|
|
85
|
+
},
|
|
86
|
+
getImportSource(node) {
|
|
87
|
+
return ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier) ? node.moduleSpecifier.text : void 0;
|
|
88
|
+
},
|
|
89
|
+
getNamedImportBindings(node) {
|
|
90
|
+
if (!ts.isImportDeclaration(node)) return [];
|
|
91
|
+
const namedBindings = node.importClause?.namedBindings;
|
|
92
|
+
if (!namedBindings || !ts.isNamedImports(namedBindings)) return [];
|
|
93
|
+
return namedBindings.elements.map((element) => ({
|
|
94
|
+
imported: element.propertyName?.text ?? element.name.text,
|
|
95
|
+
local: element.name.text
|
|
96
|
+
}));
|
|
97
|
+
},
|
|
98
|
+
isCallExpression(node) {
|
|
99
|
+
return ts.isCallExpression(node);
|
|
100
|
+
},
|
|
101
|
+
getCallCalleeIdentifier(node) {
|
|
102
|
+
return ts.isCallExpression(node) && ts.isIdentifier(node.expression) ? node.expression.text : void 0;
|
|
103
|
+
},
|
|
104
|
+
getCallArguments(node) {
|
|
105
|
+
return ts.isCallExpression(node) ? [...node.arguments] : [];
|
|
106
|
+
},
|
|
107
|
+
isObjectExpression(node) {
|
|
108
|
+
return ts.isObjectLiteralExpression(node);
|
|
109
|
+
},
|
|
110
|
+
getObjectProperties(node) {
|
|
111
|
+
return ts.isObjectLiteralExpression(node) ? [...node.properties] : [];
|
|
112
|
+
},
|
|
113
|
+
isPlainProperty(node) {
|
|
114
|
+
return ts.isPropertyAssignment(node);
|
|
115
|
+
},
|
|
116
|
+
isSpreadProperty(node) {
|
|
117
|
+
return ts.isSpreadAssignment(node);
|
|
118
|
+
},
|
|
119
|
+
getPropertyName(node) {
|
|
120
|
+
if (!ts.isPropertyAssignment(node)) return void 0;
|
|
121
|
+
const name = node.name;
|
|
122
|
+
if (ts.isIdentifier(name) || ts.isStringLiteral(name) || ts.isNumericLiteral(name)) return name.text;
|
|
123
|
+
},
|
|
124
|
+
getPropertyValue(node) {
|
|
125
|
+
return ts.isPropertyAssignment(node) ? node.initializer : void 0;
|
|
126
|
+
},
|
|
127
|
+
getLiteralValue(node) {
|
|
128
|
+
if (!ts.isExpression(node)) return void 0;
|
|
129
|
+
return literalJsonValue(node);
|
|
130
|
+
},
|
|
131
|
+
isIdentifier(node) {
|
|
132
|
+
return ts.isIdentifier(node);
|
|
133
|
+
},
|
|
134
|
+
getRange(node) {
|
|
135
|
+
return {
|
|
136
|
+
start: node.getStart(),
|
|
137
|
+
end: node.getEnd()
|
|
138
|
+
};
|
|
139
|
+
},
|
|
140
|
+
getLocation(node) {
|
|
141
|
+
const sourceFile = node.getSourceFile();
|
|
142
|
+
const start = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
|
|
143
|
+
const end = sourceFile.getLineAndCharacterOfPosition(node.getEnd());
|
|
144
|
+
return {
|
|
145
|
+
start: {
|
|
146
|
+
line: start.line + 1,
|
|
147
|
+
column: start.character + 1
|
|
148
|
+
},
|
|
149
|
+
end: {
|
|
150
|
+
line: end.line + 1,
|
|
151
|
+
column: end.character + 1
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
},
|
|
155
|
+
findObjectExpressionAtRange(root, range) {
|
|
156
|
+
let result;
|
|
157
|
+
this.walk(root, (node) => {
|
|
158
|
+
if (result === void 0 && ts.isObjectLiteralExpression(node) && node.getStart(root) === range.start && node.getEnd() === range.end) result = node;
|
|
159
|
+
});
|
|
160
|
+
return result;
|
|
161
|
+
},
|
|
162
|
+
getParent(_root, node) {
|
|
163
|
+
return node.parent;
|
|
164
|
+
},
|
|
165
|
+
nodeContains(root, target) {
|
|
166
|
+
let found = false;
|
|
167
|
+
const visit = (node) => {
|
|
168
|
+
if (found) return;
|
|
169
|
+
if (node === target) {
|
|
170
|
+
found = true;
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
ts.forEachChild(node, visit);
|
|
174
|
+
};
|
|
175
|
+
visit(root);
|
|
176
|
+
return found;
|
|
177
|
+
},
|
|
178
|
+
getVariableDeclaratorName(node) {
|
|
179
|
+
if (!ts.isVariableDeclaration(node)) return void 0;
|
|
180
|
+
return ts.isIdentifier(node.name) ? node.name.text : void 0;
|
|
181
|
+
},
|
|
182
|
+
getVariableDeclaratorInitializer(node) {
|
|
183
|
+
return ts.isVariableDeclaration(node) ? node.initializer : void 0;
|
|
184
|
+
},
|
|
185
|
+
isVariableDeclaratorInitializer(node, child) {
|
|
186
|
+
return ts.isVariableDeclaration(node) && node.initializer === child;
|
|
187
|
+
},
|
|
188
|
+
getPropertyOwnerName(node) {
|
|
189
|
+
return ts.isPropertyAssignment(node) ? this.getPropertyName(node) : void 0;
|
|
190
|
+
},
|
|
191
|
+
isPropertyValue(node, child) {
|
|
192
|
+
return ts.isPropertyAssignment(node) && node.initializer === child;
|
|
193
|
+
},
|
|
194
|
+
isJsxBoundary(node) {
|
|
195
|
+
return ts.isJsxExpression(node) || ts.isJsxAttribute(node);
|
|
196
|
+
},
|
|
197
|
+
firstImportEnd(root) {
|
|
198
|
+
let position = 0;
|
|
199
|
+
for (const statement of root.statements) {
|
|
200
|
+
if (!ts.isImportDeclaration(statement)) break;
|
|
201
|
+
position = statement.getEnd();
|
|
202
|
+
}
|
|
203
|
+
return position;
|
|
204
|
+
},
|
|
205
|
+
isPatchableLiteral(node) {
|
|
206
|
+
return ts.isExpression(node) && literalJsonValue(node) !== void 0;
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
function literalJsonValue(node) {
|
|
210
|
+
if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) return node.text;
|
|
211
|
+
if (ts.isNumericLiteral(node)) return Number(node.text);
|
|
212
|
+
if (ts.isPrefixUnaryExpression(node) && node.operator === ts.SyntaxKind.MinusToken && ts.isNumericLiteral(node.operand)) return -Number(node.operand.text);
|
|
213
|
+
if (node.kind === ts.SyntaxKind.TrueKeyword) return true;
|
|
214
|
+
if (node.kind === ts.SyntaxKind.FalseKeyword) return false;
|
|
215
|
+
if (node.kind === ts.SyntaxKind.NullKeyword) return null;
|
|
216
|
+
}
|
|
217
|
+
function createEstreePandaAstAdapter(root) {
|
|
218
|
+
const parents = /* @__PURE__ */ new Map();
|
|
219
|
+
const childrenOf = (node) => {
|
|
220
|
+
const children = [];
|
|
221
|
+
for (const [key, value] of Object.entries(node)) {
|
|
222
|
+
if (key === "loc" || key === "metadata" || key === "comments" || key === "leadingComments" || key === "trailingComments") continue;
|
|
223
|
+
if (isEstreeNode(value)) children.push(value);
|
|
224
|
+
else if (Array.isArray(value)) {
|
|
225
|
+
for (const item of value) if (isEstreeNode(item)) children.push(item);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return children;
|
|
229
|
+
};
|
|
230
|
+
const walkedForParents = /* @__PURE__ */ new Set();
|
|
231
|
+
const walkNode = (node, parent) => {
|
|
232
|
+
if (walkedForParents.has(node)) return;
|
|
233
|
+
walkedForParents.add(node);
|
|
234
|
+
parents.set(node, parent);
|
|
235
|
+
for (const child of childrenOf(node)) walkNode(child, node);
|
|
236
|
+
};
|
|
237
|
+
walkNode(root, void 0);
|
|
238
|
+
return {
|
|
239
|
+
astKind: "estree",
|
|
240
|
+
walk(nextRoot, visit) {
|
|
241
|
+
const seen = /* @__PURE__ */ new Set();
|
|
242
|
+
const visitNode = (node, parent) => {
|
|
243
|
+
if (seen.has(node)) return;
|
|
244
|
+
seen.add(node);
|
|
245
|
+
visit(node, parent);
|
|
246
|
+
for (const child of childrenOf(node)) visitNode(child, node);
|
|
247
|
+
};
|
|
248
|
+
visitNode(nextRoot, void 0);
|
|
249
|
+
},
|
|
250
|
+
topLevelStatements(nextRoot) {
|
|
251
|
+
return Array.isArray(nextRoot.body) ? nextRoot.body.filter(isEstreeNode) : [];
|
|
252
|
+
},
|
|
253
|
+
isImportDeclaration(node) {
|
|
254
|
+
return node.type === "ImportDeclaration";
|
|
255
|
+
},
|
|
256
|
+
getImportSource(node) {
|
|
257
|
+
if (node.type !== "ImportDeclaration") return void 0;
|
|
258
|
+
const source = node.source;
|
|
259
|
+
return isEstreeNode(source) && typeof source.value === "string" ? source.value : void 0;
|
|
260
|
+
},
|
|
261
|
+
getNamedImportBindings(node) {
|
|
262
|
+
if (node.type !== "ImportDeclaration" || !Array.isArray(node.specifiers)) return [];
|
|
263
|
+
return node.specifiers.filter(isEstreeNode).flatMap((specifier) => {
|
|
264
|
+
if (specifier.type !== "ImportSpecifier") return [];
|
|
265
|
+
const imported = specifier.imported;
|
|
266
|
+
const local = specifier.local;
|
|
267
|
+
if (!isEstreeNode(imported) || !isEstreeNode(local)) return [];
|
|
268
|
+
const importedName = identifierOrLiteralName(imported);
|
|
269
|
+
const localName = identifierOrLiteralName(local);
|
|
270
|
+
return importedName && localName ? [{
|
|
271
|
+
imported: importedName,
|
|
272
|
+
local: localName
|
|
273
|
+
}] : [];
|
|
274
|
+
});
|
|
275
|
+
},
|
|
276
|
+
isCallExpression(node) {
|
|
277
|
+
return node.type === "CallExpression";
|
|
278
|
+
},
|
|
279
|
+
getCallCalleeIdentifier(node) {
|
|
280
|
+
if (node.type !== "CallExpression" || !isEstreeNode(node.callee)) return void 0;
|
|
281
|
+
return node.callee.type === "Identifier" && typeof node.callee.name === "string" ? node.callee.name : void 0;
|
|
282
|
+
},
|
|
283
|
+
getCallArguments(node) {
|
|
284
|
+
return node.type === "CallExpression" && Array.isArray(node.arguments) ? node.arguments.filter(isEstreeNode) : [];
|
|
285
|
+
},
|
|
286
|
+
isObjectExpression(node) {
|
|
287
|
+
return node.type === "ObjectExpression";
|
|
288
|
+
},
|
|
289
|
+
getObjectProperties(node) {
|
|
290
|
+
return node.type === "ObjectExpression" && Array.isArray(node.properties) ? node.properties.filter(isEstreeNode) : [];
|
|
291
|
+
},
|
|
292
|
+
isPlainProperty(node) {
|
|
293
|
+
return node.type === "Property" && node.kind === "init" && node.method !== true;
|
|
294
|
+
},
|
|
295
|
+
isSpreadProperty(node) {
|
|
296
|
+
return node.type === "SpreadElement" || node.type === "SpreadProperty";
|
|
297
|
+
},
|
|
298
|
+
getPropertyName(node) {
|
|
299
|
+
if (node.type !== "Property" || node.computed === true || !isEstreeNode(node.key)) return;
|
|
300
|
+
return identifierOrLiteralName(node.key);
|
|
301
|
+
},
|
|
302
|
+
getPropertyValue(node) {
|
|
303
|
+
return node.type === "Property" && isEstreeNode(node.value) ? node.value : void 0;
|
|
304
|
+
},
|
|
305
|
+
getLiteralValue(node) {
|
|
306
|
+
if (node.type === "Literal") return isJsonLiteral(node.value) ? node.value : void 0;
|
|
307
|
+
if (node.type === "UnaryExpression" && node.operator === "-" && isEstreeNode(node.argument) && node.argument.type === "Literal" && typeof node.argument.value === "number") return -node.argument.value;
|
|
308
|
+
},
|
|
309
|
+
isIdentifier(node) {
|
|
310
|
+
return node.type === "Identifier";
|
|
311
|
+
},
|
|
312
|
+
getRange(node) {
|
|
313
|
+
return {
|
|
314
|
+
start: numericPosition(node.start),
|
|
315
|
+
end: numericPosition(node.end)
|
|
316
|
+
};
|
|
317
|
+
},
|
|
318
|
+
getLocation(node) {
|
|
319
|
+
const loc = node.loc;
|
|
320
|
+
if (!loc) return {
|
|
321
|
+
start: {
|
|
322
|
+
line: 1,
|
|
323
|
+
column: 1
|
|
324
|
+
},
|
|
325
|
+
end: {
|
|
326
|
+
line: 1,
|
|
327
|
+
column: 1
|
|
328
|
+
}
|
|
329
|
+
};
|
|
330
|
+
return {
|
|
331
|
+
start: {
|
|
332
|
+
line: loc.start.line,
|
|
333
|
+
column: loc.start.column + 1
|
|
334
|
+
},
|
|
335
|
+
end: {
|
|
336
|
+
line: loc.end.line,
|
|
337
|
+
column: loc.end.column + 1
|
|
338
|
+
}
|
|
339
|
+
};
|
|
340
|
+
},
|
|
341
|
+
findObjectExpressionAtRange(nextRoot, range) {
|
|
342
|
+
let result;
|
|
343
|
+
this.walk(nextRoot, (node) => {
|
|
344
|
+
if (result === void 0 && node.type === "ObjectExpression" && numericPosition(node.start) === range.start && numericPosition(node.end) === range.end) result = node;
|
|
345
|
+
});
|
|
346
|
+
return result;
|
|
347
|
+
},
|
|
348
|
+
getParent(_root, node) {
|
|
349
|
+
return parents.get(node);
|
|
350
|
+
},
|
|
351
|
+
nodeContains(nextRoot, target) {
|
|
352
|
+
let found = false;
|
|
353
|
+
this.walk(nextRoot, (node) => {
|
|
354
|
+
if (node === target) found = true;
|
|
355
|
+
});
|
|
356
|
+
return found;
|
|
357
|
+
},
|
|
358
|
+
getVariableDeclaratorName(node) {
|
|
359
|
+
if (node.type !== "VariableDeclarator" || !isEstreeNode(node.id)) return void 0;
|
|
360
|
+
return node.id.type === "Identifier" && typeof node.id.name === "string" ? node.id.name : void 0;
|
|
361
|
+
},
|
|
362
|
+
getVariableDeclaratorInitializer(node) {
|
|
363
|
+
return node.type === "VariableDeclarator" && isEstreeNode(node.init) ? node.init : void 0;
|
|
364
|
+
},
|
|
365
|
+
isVariableDeclaratorInitializer(node, child) {
|
|
366
|
+
return node.type === "VariableDeclarator" && node.init === child;
|
|
367
|
+
},
|
|
368
|
+
getPropertyOwnerName(node) {
|
|
369
|
+
return node.type === "Property" ? this.getPropertyName(node) : void 0;
|
|
370
|
+
},
|
|
371
|
+
isPropertyValue(node, child) {
|
|
372
|
+
return node.type === "Property" && node.value === child;
|
|
373
|
+
},
|
|
374
|
+
isJsxBoundary(node) {
|
|
375
|
+
return node.type === "JSXExpressionContainer" || node.type === "Attribute";
|
|
376
|
+
},
|
|
377
|
+
firstImportEnd(nextRoot) {
|
|
378
|
+
let position = 0;
|
|
379
|
+
for (const statement of this.topLevelStatements(nextRoot)) {
|
|
380
|
+
if (statement.type !== "ImportDeclaration") break;
|
|
381
|
+
position = numericPosition(statement.end);
|
|
382
|
+
}
|
|
383
|
+
return position;
|
|
384
|
+
},
|
|
385
|
+
isPatchableLiteral(node) {
|
|
386
|
+
return this.getLiteralValue(node) !== void 0;
|
|
387
|
+
}
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
function isEstreeNode(value) {
|
|
391
|
+
return typeof value === "object" && value !== null && typeof value.type === "string";
|
|
392
|
+
}
|
|
393
|
+
function identifierOrLiteralName(node) {
|
|
394
|
+
if (node.type === "Identifier" && typeof node.name === "string") return node.name;
|
|
395
|
+
if (node.type === "Literal") {
|
|
396
|
+
if (typeof node.value === "string") return node.value;
|
|
397
|
+
if (typeof node.value === "number") return String(node.value);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
function isJsonLiteral(value) {
|
|
401
|
+
return value === null || [
|
|
402
|
+
"string",
|
|
403
|
+
"number",
|
|
404
|
+
"boolean"
|
|
405
|
+
].includes(typeof value);
|
|
406
|
+
}
|
|
407
|
+
function numericPosition(value) {
|
|
408
|
+
return typeof value === "number" ? value : 0;
|
|
409
|
+
}
|
|
410
|
+
//#endregion
|
|
411
|
+
//#region src/analyzer/pandaCssAnalyzer.ts
|
|
412
|
+
const DEFAULT_CSS_IMPORT_SOURCES$1 = [
|
|
413
|
+
"../styled-system/css",
|
|
414
|
+
"@/styled-system/css",
|
|
415
|
+
"styled-system/css"
|
|
416
|
+
];
|
|
417
|
+
function analyzePandaCssSource(options) {
|
|
418
|
+
const source = parsePandaSource(options.sourceSyntax, {
|
|
419
|
+
filePath: options.filePath,
|
|
420
|
+
sourceText: options.sourceText
|
|
421
|
+
});
|
|
422
|
+
const cssImportSources = new Set(options.cssImportSources ?? DEFAULT_CSS_IMPORT_SOURCES$1);
|
|
423
|
+
const cssNames = /* @__PURE__ */ new Set();
|
|
424
|
+
for (const statement of source.ops.topLevelStatements(source.root)) {
|
|
425
|
+
if (!source.ops.isImportDeclaration(statement)) continue;
|
|
426
|
+
const importSource = source.ops.getImportSource(statement);
|
|
427
|
+
if (!importSource || !cssImportSources.has(importSource)) continue;
|
|
428
|
+
for (const binding of source.ops.getNamedImportBindings(statement)) if (binding.imported === "css") cssNames.add(binding.local);
|
|
429
|
+
}
|
|
430
|
+
if (cssNames.size === 0) return { entries: [] };
|
|
431
|
+
const entries = [];
|
|
432
|
+
let ordinal = 0;
|
|
433
|
+
source.ops.walk(source.root, (node) => {
|
|
434
|
+
const callee = source.ops.getCallCalleeIdentifier(node);
|
|
435
|
+
if (source.ops.isCallExpression(node) && callee && cssNames.has(callee)) entries.push(createEntry(source, options, node, ordinal++));
|
|
436
|
+
});
|
|
437
|
+
return { entries };
|
|
438
|
+
}
|
|
439
|
+
function createEntry(source, options, call, ordinal) {
|
|
440
|
+
const file = toRelativeProjectPath$1(options.filePath, options.projectRoot);
|
|
441
|
+
const callStart = source.ops.getRange(call).start;
|
|
442
|
+
const callPosition = positionAt(source.sourceText, callStart);
|
|
443
|
+
const id = `${file}:${callPosition.line}:${callPosition.column}#${ordinal}`;
|
|
444
|
+
const firstArg = source.ops.getCallArguments(call)[0];
|
|
445
|
+
if (!firstArg) return unsupportedEntry(source, options, call, id, "unsupported-source-shape", "css() has no style object");
|
|
446
|
+
if (!source.ops.isObjectExpression(firstArg)) return unsupportedEntry(source, options, call, id, source.ops.isIdentifier(firstArg) ? "variable-reference" : "dynamic-expression", "css() argument is not a local object literal");
|
|
447
|
+
const extracted = extractStyleObject(source, firstArg);
|
|
448
|
+
if (!extracted.ok) return unsupportedEntry(source, options, call, id, extracted.reason, extracted.description);
|
|
449
|
+
const range = source.ops.getRange(firstArg);
|
|
450
|
+
const name = cssSourceName(source, call);
|
|
451
|
+
return {
|
|
452
|
+
protocolVersion: 1,
|
|
453
|
+
id,
|
|
454
|
+
kind: "panda-css",
|
|
455
|
+
file,
|
|
456
|
+
absoluteFile: normalizePath$5(options.filePath),
|
|
457
|
+
range,
|
|
458
|
+
loc: source.ops.getLocation(firstArg),
|
|
459
|
+
sourceHash: hashSource(options.sourceText.slice(range.start, range.end)),
|
|
460
|
+
...name ? { name } : {},
|
|
461
|
+
confidence: "high",
|
|
462
|
+
reason: "static local css() object literal",
|
|
463
|
+
callee: source.ops.getCallCalleeIdentifier(call) ?? "css",
|
|
464
|
+
styleObject: extracted.styleObject,
|
|
465
|
+
dynamic: false
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
function cssSourceName(source, call) {
|
|
469
|
+
let current = call;
|
|
470
|
+
while (true) {
|
|
471
|
+
const parent = source.ops.getParent(source.root, current);
|
|
472
|
+
if (!parent) return void 0;
|
|
473
|
+
if (source.ops.isJsxBoundary(parent)) return;
|
|
474
|
+
if (source.ops.isPropertyValue(parent, current)) return source.ops.getPropertyOwnerName(parent);
|
|
475
|
+
if (source.ops.isVariableDeclaratorInitializer(parent, current)) return source.ops.getVariableDeclaratorName(parent);
|
|
476
|
+
const propertyName = source.ops.getPropertyOwnerName(parent);
|
|
477
|
+
const propertyValue = source.ops.getPropertyValue(parent);
|
|
478
|
+
if (propertyName && propertyValue && source.ops.nodeContains(propertyValue, call)) return propertyName;
|
|
479
|
+
const variableName = source.ops.getVariableDeclaratorName(parent);
|
|
480
|
+
const initializer = source.ops.getVariableDeclaratorInitializer(parent);
|
|
481
|
+
if (variableName && initializer && source.ops.nodeContains(initializer, call)) return variableName;
|
|
482
|
+
current = parent;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
function unsupportedEntry(source, options, call, id, reason, description) {
|
|
486
|
+
const file = toRelativeProjectPath$1(options.filePath, options.projectRoot);
|
|
487
|
+
const range = source.ops.getRange(call);
|
|
488
|
+
return {
|
|
489
|
+
protocolVersion: 1,
|
|
490
|
+
id,
|
|
491
|
+
kind: "dynamic",
|
|
492
|
+
file,
|
|
493
|
+
absoluteFile: normalizePath$5(options.filePath),
|
|
494
|
+
range,
|
|
495
|
+
loc: source.ops.getLocation(call),
|
|
496
|
+
sourceHash: hashSource(options.sourceText.slice(range.start, range.end)),
|
|
497
|
+
confidence: "low",
|
|
498
|
+
reason: description,
|
|
499
|
+
dynamic: true,
|
|
500
|
+
unsupportedReason: reason
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
function extractStyleObject(source, objectNode) {
|
|
504
|
+
const styleObject = {};
|
|
505
|
+
for (const property of source.ops.getObjectProperties(objectNode)) {
|
|
506
|
+
if (source.ops.isSpreadProperty(property)) return {
|
|
507
|
+
ok: false,
|
|
508
|
+
reason: "spread-value",
|
|
509
|
+
description: "Object spread can change which style keys exist at runtime"
|
|
510
|
+
};
|
|
511
|
+
if (!source.ops.isPlainProperty(property)) return {
|
|
512
|
+
ok: false,
|
|
513
|
+
reason: "unsupported-source-shape",
|
|
514
|
+
description: "Only plain property assignments are supported in css() objects"
|
|
515
|
+
};
|
|
516
|
+
const name = source.ops.getPropertyName(property);
|
|
517
|
+
if (name === void 0) return {
|
|
518
|
+
ok: false,
|
|
519
|
+
reason: "unsupported-source-shape",
|
|
520
|
+
description: "Computed style property names are not safe to map to source paths"
|
|
521
|
+
};
|
|
522
|
+
const propertyValue = source.ops.getPropertyValue(property);
|
|
523
|
+
if (!propertyValue) return {
|
|
524
|
+
ok: false,
|
|
525
|
+
reason: "unsupported-source-shape",
|
|
526
|
+
description: "Style property has no value that can be patched"
|
|
527
|
+
};
|
|
528
|
+
const value = extractStyleValue(source, propertyValue);
|
|
529
|
+
if (!value.ok) return value;
|
|
530
|
+
styleObject[name] = value.value;
|
|
531
|
+
}
|
|
532
|
+
return {
|
|
533
|
+
ok: true,
|
|
534
|
+
styleObject
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
function extractStyleValue(source, node) {
|
|
538
|
+
if (source.ops.isObjectExpression(node)) {
|
|
539
|
+
const object = extractStyleObject(source, node);
|
|
540
|
+
if (!object.ok) return object;
|
|
541
|
+
return {
|
|
542
|
+
ok: true,
|
|
543
|
+
value: object.styleObject
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
const literal = source.ops.getLiteralValue(node);
|
|
547
|
+
if (literal !== void 0) return {
|
|
548
|
+
ok: true,
|
|
549
|
+
value: {
|
|
550
|
+
kind: "literal",
|
|
551
|
+
value: literal,
|
|
552
|
+
range: source.ops.getRange(node),
|
|
553
|
+
loc: source.ops.getLocation(node)
|
|
554
|
+
}
|
|
555
|
+
};
|
|
556
|
+
return {
|
|
557
|
+
ok: false,
|
|
558
|
+
reason: source.ops.isIdentifier(node) ? "variable-reference" : "dynamic-expression",
|
|
559
|
+
description: "Style value is not a literal that can be patched without evaluating user code"
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
function positionAt(sourceText, position) {
|
|
563
|
+
let line = 1;
|
|
564
|
+
let lineStart = 0;
|
|
565
|
+
for (let index = 0; index < position; index += 1) if (sourceText.charCodeAt(index) === 10) {
|
|
566
|
+
line += 1;
|
|
567
|
+
lineStart = index + 1;
|
|
568
|
+
}
|
|
569
|
+
return {
|
|
570
|
+
line,
|
|
571
|
+
column: position - lineStart + 1
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
function toRelativeProjectPath$1(filePath, projectRoot) {
|
|
575
|
+
const file = normalizePath$5(filePath);
|
|
576
|
+
if (!projectRoot) return file;
|
|
577
|
+
const root = normalizePath$5(projectRoot).replace(/\/$/, "");
|
|
578
|
+
if (file === root) return file.split("/").at(-1) ?? file;
|
|
579
|
+
if (file.startsWith(`${root}/`)) return file.slice(root.length + 1);
|
|
580
|
+
return file;
|
|
581
|
+
}
|
|
582
|
+
function normalizePath$5(path) {
|
|
583
|
+
return path.replace(/\\/g, "/");
|
|
584
|
+
}
|
|
585
|
+
function hashSource(source) {
|
|
586
|
+
let hash = 5381;
|
|
587
|
+
for (let index = 0; index < source.length; index += 1) hash = hash * 33 ^ source.charCodeAt(index);
|
|
588
|
+
return (hash >>> 0).toString(16);
|
|
589
|
+
}
|
|
590
|
+
//#endregion
|
|
591
|
+
//#region src/patcher/inlineCssSourcePatcher.ts
|
|
592
|
+
const DEFAULT_INLINE_CSS_IMPORT_SOURCES = [
|
|
593
|
+
"../styled-system/css",
|
|
594
|
+
"@/styled-system/css",
|
|
595
|
+
"styled-system/css"
|
|
596
|
+
];
|
|
597
|
+
function createInlineCssSourcePatch(options) {
|
|
598
|
+
const { request, sourceText } = options;
|
|
599
|
+
const parsed = parseJsxSource(request.jsxSource);
|
|
600
|
+
if (!parsed.ok) return failure$3(request.editId, parsed.code, parsed.message, parsed.details);
|
|
601
|
+
const normalizedFile = normalizePath$4(options.filePath);
|
|
602
|
+
if (normalizedFile !== normalizePath$4(`${options.projectRoot.replace(/\/$/, "")}/${parsed.file}`)) return failure$3(request.editId, "stale-source", "JSX source location points to a different file.", {
|
|
603
|
+
filePath: normalizedFile,
|
|
604
|
+
jsxSource: request.jsxSource
|
|
605
|
+
});
|
|
606
|
+
let source;
|
|
607
|
+
try {
|
|
608
|
+
source = parsePandaSource(options.sourceSyntax, {
|
|
609
|
+
filePath: options.filePath,
|
|
610
|
+
sourceText
|
|
611
|
+
});
|
|
612
|
+
} catch (error) {
|
|
613
|
+
return failure$3(request.editId, "parse-error", "Source could not be parsed for writeback.", { message: error instanceof Error ? error.message : String(error) });
|
|
614
|
+
}
|
|
615
|
+
if (source.astKind !== "typescript") return failure$3(request.editId, "unsupported-source-shape", "Inline Panda source creation is not supported for ESTree-backed source yet.");
|
|
616
|
+
const sourceFile = source.root;
|
|
617
|
+
const position = positionForLineColumn$1(sourceFile, parsed.line, parsed.column);
|
|
618
|
+
if (position === void 0) return failure$3(request.editId, "stale-source", "JSX source location is outside the file.");
|
|
619
|
+
const element = findJsxElementAtStart$1(sourceFile, position);
|
|
620
|
+
if (!element) return failure$3(request.editId, "stale-source", "JSX source location no longer points to a JSX element.");
|
|
621
|
+
const existingImport = findCssImportBinding$1(sourceFile, options.cssImportSources);
|
|
622
|
+
const cssLocalName = existingImport?.localName ?? availableCssLocalName(sourceFile);
|
|
623
|
+
const replacements = [];
|
|
624
|
+
const attributeReplacement = inlineCssAttributeReplacement(sourceFile, sourceText, element, cssLocalName, existingImport?.localName);
|
|
625
|
+
if (!attributeReplacement.ok) return failure$3(request.editId, attributeReplacement.code, attributeReplacement.message, attributeReplacement.details);
|
|
626
|
+
replacements.push(attributeReplacement.replacement);
|
|
627
|
+
if (!existingImport) replacements.push({
|
|
628
|
+
range: {
|
|
629
|
+
start: importInsertionPosition$2(sourceFile),
|
|
630
|
+
end: importInsertionPosition$2(sourceFile)
|
|
631
|
+
},
|
|
632
|
+
text: cssImportText$1(cssLocalName, options.cssImportSources)
|
|
633
|
+
});
|
|
634
|
+
const nextSource = applyReplacements$3(sourceText, replacements);
|
|
635
|
+
const file = parsed.file;
|
|
636
|
+
return {
|
|
637
|
+
ok: true,
|
|
638
|
+
editId: request.editId,
|
|
639
|
+
file,
|
|
640
|
+
diff: focusedDiff$3(file, sourceText, nextSource, replacements),
|
|
641
|
+
nextSource,
|
|
642
|
+
written: false
|
|
643
|
+
};
|
|
644
|
+
}
|
|
645
|
+
function inlineCssAttributeReplacement(sourceFile, sourceText, element, cssLocalName, existingCssLocalName) {
|
|
646
|
+
let classAttribute;
|
|
647
|
+
for (const property of element.attributes.properties) {
|
|
648
|
+
if (!ts.isJsxAttribute(property) || !ts.isIdentifier(property.name)) continue;
|
|
649
|
+
if (property.name.text !== "class" && property.name.text !== "className") continue;
|
|
650
|
+
classAttribute = property;
|
|
651
|
+
const expression = property.initializer && jsxAttributeExpression$2(property.initializer);
|
|
652
|
+
if (expression && existingCssLocalName && containsInlineCssCall(sourceFile, expression, existingCssLocalName)) return {
|
|
653
|
+
ok: false,
|
|
654
|
+
code: "unsupported-operation",
|
|
655
|
+
message: "This JSX element already has an inline css() source."
|
|
656
|
+
};
|
|
657
|
+
break;
|
|
658
|
+
}
|
|
659
|
+
if (!classAttribute) {
|
|
660
|
+
const insertPosition = element.getEnd() - (ts.isJsxSelfClosingElement(element) ? 2 : 1);
|
|
661
|
+
return {
|
|
662
|
+
ok: true,
|
|
663
|
+
replacement: {
|
|
664
|
+
range: {
|
|
665
|
+
start: insertPosition,
|
|
666
|
+
end: insertPosition
|
|
667
|
+
},
|
|
668
|
+
text: ` class={${cssLocalName}({})}`
|
|
669
|
+
}
|
|
670
|
+
};
|
|
671
|
+
}
|
|
672
|
+
if (!ts.isIdentifier(classAttribute.name)) return {
|
|
673
|
+
ok: false,
|
|
674
|
+
code: "unsupported-source-shape",
|
|
675
|
+
message: "Namespaced JSX class attributes cannot receive inline css() sources."
|
|
676
|
+
};
|
|
677
|
+
const attributeName = classAttribute.name.text;
|
|
678
|
+
const inlineCssCall = `${cssLocalName}({})`;
|
|
679
|
+
if (!classAttribute.initializer) return {
|
|
680
|
+
ok: true,
|
|
681
|
+
replacement: {
|
|
682
|
+
range: {
|
|
683
|
+
start: classAttribute.getStart(sourceFile),
|
|
684
|
+
end: classAttribute.getEnd()
|
|
685
|
+
},
|
|
686
|
+
text: `${attributeName}={${inlineCssCall}}`
|
|
687
|
+
}
|
|
688
|
+
};
|
|
689
|
+
if (ts.isStringLiteral(classAttribute.initializer)) return {
|
|
690
|
+
ok: true,
|
|
691
|
+
replacement: {
|
|
692
|
+
range: {
|
|
693
|
+
start: classAttribute.getStart(sourceFile),
|
|
694
|
+
end: classAttribute.getEnd()
|
|
695
|
+
},
|
|
696
|
+
text: `${attributeName}={\`${escapeTemplateText$1(classAttribute.initializer.text)} \${${inlineCssCall}}\`}`
|
|
697
|
+
}
|
|
698
|
+
};
|
|
699
|
+
const expression = jsxAttributeExpression$2(classAttribute.initializer);
|
|
700
|
+
if (!expression) return {
|
|
701
|
+
ok: false,
|
|
702
|
+
code: "unsupported-source-shape",
|
|
703
|
+
message: "Only string and expression class attributes can receive inline css() sources."
|
|
704
|
+
};
|
|
705
|
+
const expressionText = sourceText.slice(expression.getStart(sourceFile), expression.getEnd());
|
|
706
|
+
return {
|
|
707
|
+
ok: true,
|
|
708
|
+
replacement: {
|
|
709
|
+
range: {
|
|
710
|
+
start: classAttribute.getStart(sourceFile),
|
|
711
|
+
end: classAttribute.getEnd()
|
|
712
|
+
},
|
|
713
|
+
text: `${attributeName}={[${expressionText}, ${inlineCssCall}].filter(Boolean).join(' ')}`
|
|
714
|
+
}
|
|
715
|
+
};
|
|
716
|
+
}
|
|
717
|
+
function jsxAttributeExpression$2(initializer) {
|
|
718
|
+
if (ts.isJsxExpression(initializer)) return initializer.expression;
|
|
719
|
+
}
|
|
720
|
+
function findJsxElementAtStart$1(sourceFile, position) {
|
|
721
|
+
let result;
|
|
722
|
+
const visit = (node) => {
|
|
723
|
+
if (result) return;
|
|
724
|
+
if ((ts.isJsxOpeningElement(node) || ts.isJsxSelfClosingElement(node)) && node.getStart(sourceFile) === position) {
|
|
725
|
+
result = node;
|
|
726
|
+
return;
|
|
727
|
+
}
|
|
728
|
+
ts.forEachChild(node, visit);
|
|
729
|
+
};
|
|
730
|
+
visit(sourceFile);
|
|
731
|
+
return result;
|
|
732
|
+
}
|
|
733
|
+
function containsInlineCssCall(sourceFile, expression, cssLocalName) {
|
|
734
|
+
let found = false;
|
|
735
|
+
const visit = (node) => {
|
|
736
|
+
if (found) return;
|
|
737
|
+
if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && node.expression.text === cssLocalName && ts.isObjectLiteralExpression(node.arguments[0])) {
|
|
738
|
+
found = true;
|
|
739
|
+
return;
|
|
740
|
+
}
|
|
741
|
+
ts.forEachChild(node, visit);
|
|
742
|
+
};
|
|
743
|
+
visit(expression);
|
|
744
|
+
return found;
|
|
745
|
+
}
|
|
746
|
+
function findCssImportBinding$1(sourceFile, cssImportSources) {
|
|
747
|
+
const importSources = new Set(cssImportSources ?? DEFAULT_INLINE_CSS_IMPORT_SOURCES);
|
|
748
|
+
for (const statement of sourceFile.statements) {
|
|
749
|
+
if (!ts.isImportDeclaration(statement)) continue;
|
|
750
|
+
if (!ts.isStringLiteral(statement.moduleSpecifier)) continue;
|
|
751
|
+
if (!importSources.has(statement.moduleSpecifier.text)) continue;
|
|
752
|
+
const namedBindings = statement.importClause?.namedBindings;
|
|
753
|
+
if (!namedBindings || !ts.isNamedImports(namedBindings)) continue;
|
|
754
|
+
for (const element of namedBindings.elements) if ((element.propertyName?.text ?? element.name.text) === "css") return {
|
|
755
|
+
localName: element.name.text,
|
|
756
|
+
moduleSpecifier: statement.moduleSpecifier.text
|
|
757
|
+
};
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
function availableCssLocalName(sourceFile) {
|
|
761
|
+
const identifiers = /* @__PURE__ */ new Set();
|
|
762
|
+
const visit = (node) => {
|
|
763
|
+
if (ts.isIdentifier(node)) identifiers.add(node.text);
|
|
764
|
+
ts.forEachChild(node, visit);
|
|
765
|
+
};
|
|
766
|
+
visit(sourceFile);
|
|
767
|
+
return identifiers.has("css") ? "pandaCss" : "css";
|
|
768
|
+
}
|
|
769
|
+
function cssImportText$1(cssLocalName, cssImportSources) {
|
|
770
|
+
const moduleSpecifier = cssImportSources?.[0] ?? DEFAULT_INLINE_CSS_IMPORT_SOURCES[0];
|
|
771
|
+
return `import { ${cssLocalName === "css" ? "css" : `css as ${cssLocalName}`} } from '${moduleSpecifier}'\n`;
|
|
772
|
+
}
|
|
773
|
+
function importInsertionPosition$2(sourceFile) {
|
|
774
|
+
let position = 0;
|
|
775
|
+
for (const statement of sourceFile.statements) {
|
|
776
|
+
if (!ts.isImportDeclaration(statement)) break;
|
|
777
|
+
position = statement.getEnd();
|
|
778
|
+
}
|
|
779
|
+
return position === 0 ? 0 : position + 1;
|
|
780
|
+
}
|
|
781
|
+
function parseJsxSource(source) {
|
|
782
|
+
const match = /^(.*):(\d+):(\d+)$/.exec(source.trim());
|
|
783
|
+
const file = match?.[1]?.trim();
|
|
784
|
+
const line = match?.[2] ? Number.parseInt(match[2], 10) : void 0;
|
|
785
|
+
const column = match?.[3] ? Number.parseInt(match[3], 10) : void 0;
|
|
786
|
+
if (!match || !file || line === void 0 || column === void 0 || line < 1 || column < 1) return {
|
|
787
|
+
ok: false,
|
|
788
|
+
code: "invalid-edit",
|
|
789
|
+
message: "Inline source creation requires a JSX source path with line and column.",
|
|
790
|
+
details: { source }
|
|
791
|
+
};
|
|
792
|
+
return {
|
|
793
|
+
ok: true,
|
|
794
|
+
file,
|
|
795
|
+
line,
|
|
796
|
+
column
|
|
797
|
+
};
|
|
798
|
+
}
|
|
799
|
+
function positionForLineColumn$1(sourceFile, line, column) {
|
|
800
|
+
const lineStart = sourceFile.getLineStarts()[line - 1];
|
|
801
|
+
if (lineStart === void 0) return void 0;
|
|
802
|
+
const position = lineStart + column - 1;
|
|
803
|
+
return position <= sourceFile.text.length ? position : void 0;
|
|
804
|
+
}
|
|
805
|
+
function escapeTemplateText$1(value) {
|
|
806
|
+
return value.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/g, "\\${");
|
|
807
|
+
}
|
|
808
|
+
function applyReplacements$3(sourceText, replacements) {
|
|
809
|
+
let nextSource = sourceText;
|
|
810
|
+
for (const replacement of [...replacements].sort((left, right) => right.range.start - left.range.start)) nextSource = `${nextSource.slice(0, replacement.range.start)}${replacement.text}${nextSource.slice(replacement.range.end)}`;
|
|
811
|
+
return nextSource;
|
|
812
|
+
}
|
|
813
|
+
function focusedDiff$3(file, sourceText, nextSource, replacements) {
|
|
814
|
+
const firstStart = Math.min(...replacements.map((replacement) => replacement.range.start));
|
|
815
|
+
const lastEnd = Math.max(...replacements.map((replacement) => replacement.range.end));
|
|
816
|
+
const oldStartLine = lineNumberAt$3(sourceText, firstStart);
|
|
817
|
+
const oldEndLine = lineNumberAt$3(sourceText, lastEnd);
|
|
818
|
+
const contextStartLine = Math.max(1, oldStartLine - 2);
|
|
819
|
+
const contextEndLine = oldEndLine + 2;
|
|
820
|
+
const oldLines = sourceText.split("\n");
|
|
821
|
+
const newLines = nextSource.split("\n");
|
|
822
|
+
const oldSlice = oldLines.slice(contextStartLine - 1, contextEndLine);
|
|
823
|
+
const newSlice = newLines.slice(contextStartLine - 1, contextEndLine + (newLines.length - oldLines.length));
|
|
824
|
+
return [
|
|
825
|
+
`--- a/${file}`,
|
|
826
|
+
`+++ b/${file}`,
|
|
827
|
+
`@@ -${contextStartLine},${oldSlice.length} +${contextStartLine},${newSlice.length} @@`,
|
|
828
|
+
...oldSlice.map((line) => `-${line}`),
|
|
829
|
+
...newSlice.map((line) => `+${line}`)
|
|
830
|
+
].join("\n");
|
|
831
|
+
}
|
|
832
|
+
function lineNumberAt$3(sourceText, position) {
|
|
833
|
+
let line = 1;
|
|
834
|
+
for (let index = 0; index < position; index += 1) if (sourceText.charCodeAt(index) === 10) line += 1;
|
|
835
|
+
return line;
|
|
836
|
+
}
|
|
837
|
+
function normalizePath$4(path) {
|
|
838
|
+
return path.replace(/\\/g, "/");
|
|
839
|
+
}
|
|
840
|
+
function failure$3(editId, code, message, details) {
|
|
841
|
+
return {
|
|
842
|
+
ok: false,
|
|
843
|
+
editId,
|
|
844
|
+
written: false,
|
|
845
|
+
error: {
|
|
846
|
+
code,
|
|
847
|
+
message,
|
|
848
|
+
details
|
|
849
|
+
}
|
|
850
|
+
};
|
|
851
|
+
}
|
|
852
|
+
//#endregion
|
|
853
|
+
//#region src/patcher/staticCssPatcher.ts
|
|
854
|
+
function createStaticCssPatch(options) {
|
|
855
|
+
const { manifest, request, sourceText } = options;
|
|
856
|
+
const collected = collectPatchReplacements({
|
|
857
|
+
manifest,
|
|
858
|
+
request,
|
|
859
|
+
sourceText,
|
|
860
|
+
filePath: options.filePath,
|
|
861
|
+
styledSystemPackageName: options.styledSystemPackageName,
|
|
862
|
+
sourceSyntax: options.sourceSyntax
|
|
863
|
+
});
|
|
864
|
+
if (!collected.ok) return collected;
|
|
865
|
+
const overlap = findOverlappingReplacement(collected.replacements);
|
|
866
|
+
if (overlap) return failure$2(request.editId, "ambiguous-target", "Multiple edits target overlapping source ranges.", { ranges: overlap });
|
|
867
|
+
const nextSource = applyReplacements$2(sourceText, collected.replacements);
|
|
868
|
+
return {
|
|
869
|
+
ok: true,
|
|
870
|
+
editId: request.editId,
|
|
871
|
+
file: collected.entry.file,
|
|
872
|
+
diff: focusedDiff$2(collected.entry.file, sourceText, nextSource, collected.replacements),
|
|
873
|
+
nextSource,
|
|
874
|
+
written: false
|
|
875
|
+
};
|
|
876
|
+
}
|
|
877
|
+
function createStaticCssBatchPatch(options) {
|
|
878
|
+
const { requests, sourceText } = options;
|
|
879
|
+
const firstRequest = requests[0];
|
|
880
|
+
if (!firstRequest) return failure$2("", "invalid-edit", "Batch patch requires at least one style edit request.");
|
|
881
|
+
const collectedPatches = requests.map((request) => ({
|
|
882
|
+
request,
|
|
883
|
+
patch: collectPatchReplacements({
|
|
884
|
+
...options,
|
|
885
|
+
request
|
|
886
|
+
})
|
|
887
|
+
}));
|
|
888
|
+
const failurePatch = collectedPatches.find((item) => !item.patch.ok);
|
|
889
|
+
if (failurePatch && !failurePatch.patch.ok) return failurePatch.patch;
|
|
890
|
+
const successfulPatches = collectedPatches.map((item) => item.patch);
|
|
891
|
+
const replacements = dedupeIdenticalReplacements(successfulPatches.flatMap((patch) => patch.replacements));
|
|
892
|
+
const overlap = findOverlappingReplacement(replacements);
|
|
893
|
+
if (overlap) return failure$2(firstRequest.editId, "ambiguous-target", "Multiple edits target overlapping source ranges.", { ranges: overlap });
|
|
894
|
+
const nextSource = applyReplacements$2(sourceText, replacements);
|
|
895
|
+
const file = successfulPatches[0]?.entry.file ?? "";
|
|
896
|
+
return {
|
|
897
|
+
ok: true,
|
|
898
|
+
editId: requests.map((request) => request.editId).join(" "),
|
|
899
|
+
file,
|
|
900
|
+
diff: focusedDiff$2(file, sourceText, nextSource, replacements),
|
|
901
|
+
nextSource,
|
|
902
|
+
written: false
|
|
903
|
+
};
|
|
904
|
+
}
|
|
905
|
+
function collectPatchReplacements(options) {
|
|
906
|
+
const { manifest, request, sourceText } = options;
|
|
907
|
+
const entry = manifest.entries.find((item) => item.id === request.editId);
|
|
908
|
+
if (!entry) return failure$2(request.editId, "manifest-entry-not-found", "No manifest entry exists for edit id.");
|
|
909
|
+
if (request.kind !== "panda-css" || entry.kind !== "panda-css") return failure$2(request.editId, "unsupported-source-shape", "Only Panda css() entries can be patched.");
|
|
910
|
+
const fileFailure = validateProjectFile$1(manifest, entry, options.filePath);
|
|
911
|
+
if (fileFailure) return failure$2(request.editId, fileFailure.code, fileFailure.message, fileFailure.details);
|
|
912
|
+
if (!entry.range) return failure$2(request.editId, "unsupported-source-shape", "Manifest entry has no source range.");
|
|
913
|
+
if (!entry.sourceHash || hashSource(sourceText.slice(entry.range.start, entry.range.end)) !== entry.sourceHash) return failure$2(request.editId, "stale-source", "Source text no longer matches the manifest entry hash.");
|
|
914
|
+
if (request.options?.expectedSourceHash && request.options.expectedSourceHash !== entry.sourceHash) return failure$2(request.editId, "stale-source", "Edit request was prepared against a different source hash.", {
|
|
915
|
+
expectedSourceHash: request.options.expectedSourceHash,
|
|
916
|
+
actualSourceHash: entry.sourceHash
|
|
917
|
+
});
|
|
918
|
+
let source;
|
|
919
|
+
try {
|
|
920
|
+
source = parsePandaSource(options.sourceSyntax, {
|
|
921
|
+
filePath: entry.absoluteFile ?? entry.file,
|
|
922
|
+
sourceText
|
|
923
|
+
});
|
|
924
|
+
} catch (error) {
|
|
925
|
+
return failure$2(request.editId, "parse-error", "Source could not be parsed for writeback.", { message: error instanceof Error ? error.message : String(error) });
|
|
926
|
+
}
|
|
927
|
+
const objectNode = source.ops.findObjectExpressionAtRange(source.root, entry.range);
|
|
928
|
+
if (!objectNode) return failure$2(request.editId, "stale-source", "Manifest range no longer points to a css() object literal.");
|
|
929
|
+
const parentCall = source.ops.getParent(source.root, objectNode);
|
|
930
|
+
if (!parentCall || !source.ops.isCallExpression(parentCall) || source.ops.getCallCalleeIdentifier(parentCall) !== entry.callee) return failure$2(request.editId, "stale-source", "Manifest range no longer belongs to the expected css() call.");
|
|
931
|
+
const replacements = [];
|
|
932
|
+
const sourceFile = source.astKind === "typescript" ? source.root : void 0;
|
|
933
|
+
const tokenVarImportLocalName = sourceFile && options.styledSystemPackageName ? tokenImportLocalName(sourceFile, options.styledSystemPackageName) : void 0;
|
|
934
|
+
for (const edit of request.edits) {
|
|
935
|
+
if (edit.op === "rename" || edit.op === "replace-object") return failure$2(request.editId, "unsupported-operation", `Unsupported edit operation: ${edit.op}.`);
|
|
936
|
+
const editFailure = validateStyleEdit(edit);
|
|
937
|
+
if (editFailure) return failure$2(request.editId, editFailure.code, editFailure.message, editFailure.details);
|
|
938
|
+
const replacement = replacementForEdit(source, objectNode, edit, sourceText, {
|
|
939
|
+
path: edit.path,
|
|
940
|
+
tokenVarImportLocalName
|
|
941
|
+
});
|
|
942
|
+
if (!replacement.ok) return failure$2(request.editId, replacement.code, replacement.message, replacement.details);
|
|
943
|
+
replacements.push(replacement);
|
|
944
|
+
}
|
|
945
|
+
if (sourceFile && tokenVarImportLocalName && replacements.some((replacement) => replacement.text.includes(`${tokenVarImportLocalName}.var(`))) {
|
|
946
|
+
const tokenImport = tokenVarImportReplacement(sourceFile, options.styledSystemPackageName, tokenVarImportLocalName);
|
|
947
|
+
if (tokenImport) replacements.push(tokenImport);
|
|
948
|
+
}
|
|
949
|
+
if (sourceFile && source.astKind === "typescript" && rootObjectWillBeEmptyAfterEdits(objectNode, request.edits)) {
|
|
950
|
+
const inlineCleanup = inlineEmptyCssCleanupReplacement(objectNode, sourceFile, sourceText);
|
|
951
|
+
if (inlineCleanup) return {
|
|
952
|
+
ok: true,
|
|
953
|
+
entry,
|
|
954
|
+
replacements: [inlineCleanup]
|
|
955
|
+
};
|
|
956
|
+
}
|
|
957
|
+
return {
|
|
958
|
+
ok: true,
|
|
959
|
+
entry,
|
|
960
|
+
replacements
|
|
961
|
+
};
|
|
962
|
+
}
|
|
963
|
+
function validateProjectFile$1(manifest, entry, filePath) {
|
|
964
|
+
const root = normalizePath$3(manifest.projectRoot).replace(/\/$/, "");
|
|
965
|
+
if (!root) return {
|
|
966
|
+
code: "path-outside-project",
|
|
967
|
+
message: "Manifest project root is empty."
|
|
968
|
+
};
|
|
969
|
+
const absoluteFile = normalizePath$3(entry.absoluteFile ?? `${root}/${entry.file}`);
|
|
970
|
+
if (absoluteFile !== root && !absoluteFile.startsWith(`${root}/`)) return {
|
|
971
|
+
code: "path-outside-project",
|
|
972
|
+
message: "Manifest entry points outside the project root.",
|
|
973
|
+
details: {
|
|
974
|
+
file: absoluteFile,
|
|
975
|
+
projectRoot: root
|
|
976
|
+
}
|
|
977
|
+
};
|
|
978
|
+
if (filePath && normalizePath$3(filePath) !== absoluteFile) return {
|
|
979
|
+
code: "stale-source",
|
|
980
|
+
message: "Provided source file does not match the manifest entry file.",
|
|
981
|
+
details: {
|
|
982
|
+
filePath: normalizePath$3(filePath),
|
|
983
|
+
manifestFile: absoluteFile
|
|
984
|
+
}
|
|
985
|
+
};
|
|
986
|
+
}
|
|
987
|
+
function validateStyleEdit(edit) {
|
|
988
|
+
if (edit.path.length === 0 || edit.path.some((part) => part.length === 0)) return {
|
|
989
|
+
code: "invalid-path",
|
|
990
|
+
message: `${edit.op} edits require a non-empty style path.`,
|
|
991
|
+
details: { path: [...edit.path] }
|
|
992
|
+
};
|
|
993
|
+
if (edit.op === "set" && !isJsonPrimitive$1(edit.value)) return {
|
|
994
|
+
code: "unsupported-operation",
|
|
995
|
+
message: "Patching only supports literal primitive values.",
|
|
996
|
+
details: { path: [...edit.path] }
|
|
997
|
+
};
|
|
998
|
+
}
|
|
999
|
+
function replacementForEdit(source, objectNode, edit, sourceText, literalContext) {
|
|
1000
|
+
let current = objectNode;
|
|
1001
|
+
const { path } = edit;
|
|
1002
|
+
for (let index = 0; index < path.length; index += 1) {
|
|
1003
|
+
const part = path[index];
|
|
1004
|
+
const located = locateProperty$1(source, current, part);
|
|
1005
|
+
if (!located.ok) {
|
|
1006
|
+
if (source.astKind === "typescript" && edit.op === "set" && located.reason.code === "invalid-path") {
|
|
1007
|
+
if (!isJsonPrimitive$1(edit.value)) return {
|
|
1008
|
+
ok: false,
|
|
1009
|
+
code: "unsupported-operation",
|
|
1010
|
+
message: "Patching only supports literal primitive values.",
|
|
1011
|
+
details: { path: [...edit.path] }
|
|
1012
|
+
};
|
|
1013
|
+
return insertionForMissingNestedProperty(current, path.slice(index), edit.value, source.root, sourceText, literalContext);
|
|
1014
|
+
}
|
|
1015
|
+
return {
|
|
1016
|
+
ok: false,
|
|
1017
|
+
...located.reason
|
|
1018
|
+
};
|
|
1019
|
+
}
|
|
1020
|
+
const initializer = source.ops.getPropertyValue(located.property);
|
|
1021
|
+
if (!initializer) return {
|
|
1022
|
+
ok: false,
|
|
1023
|
+
code: "unsupported-source-shape",
|
|
1024
|
+
message: "Target property has no patchable value.",
|
|
1025
|
+
details: { path: [...path] }
|
|
1026
|
+
};
|
|
1027
|
+
if (index === path.length - 1) {
|
|
1028
|
+
if (edit.op === "delete") {
|
|
1029
|
+
if (source.astKind !== "typescript") return {
|
|
1030
|
+
ok: false,
|
|
1031
|
+
code: "unsupported-operation",
|
|
1032
|
+
message: "Delete edits are not supported for ESTree-backed source yet.",
|
|
1033
|
+
details: { path: [...path] }
|
|
1034
|
+
};
|
|
1035
|
+
return {
|
|
1036
|
+
ok: true,
|
|
1037
|
+
range: deletionRangeForProperty(current, located.property, source.root, sourceText),
|
|
1038
|
+
text: ""
|
|
1039
|
+
};
|
|
1040
|
+
}
|
|
1041
|
+
if (!source.ops.isPatchableLiteral(initializer)) return {
|
|
1042
|
+
ok: false,
|
|
1043
|
+
code: "unsupported-source-shape",
|
|
1044
|
+
message: "Target path is not an existing literal value.",
|
|
1045
|
+
details: { path: [...path] }
|
|
1046
|
+
};
|
|
1047
|
+
if (!isJsonPrimitive$1(edit.value)) return {
|
|
1048
|
+
ok: false,
|
|
1049
|
+
code: "unsupported-operation",
|
|
1050
|
+
message: "Patching only supports literal primitive values.",
|
|
1051
|
+
details: { path: [...edit.path] }
|
|
1052
|
+
};
|
|
1053
|
+
return {
|
|
1054
|
+
ok: true,
|
|
1055
|
+
range: source.ops.getRange(initializer),
|
|
1056
|
+
text: literalSource$1(edit.value, literalContext)
|
|
1057
|
+
};
|
|
1058
|
+
}
|
|
1059
|
+
if (!source.ops.isObjectExpression(initializer)) return {
|
|
1060
|
+
ok: false,
|
|
1061
|
+
code: "invalid-path",
|
|
1062
|
+
message: "Target path descends through a non-object value.",
|
|
1063
|
+
details: { path: path.slice(0, index + 1) }
|
|
1064
|
+
};
|
|
1065
|
+
current = initializer;
|
|
1066
|
+
}
|
|
1067
|
+
return {
|
|
1068
|
+
ok: false,
|
|
1069
|
+
code: "invalid-path",
|
|
1070
|
+
message: `${edit.op} edits require a non-empty style path.`
|
|
1071
|
+
};
|
|
1072
|
+
}
|
|
1073
|
+
function insertionForMissingNestedProperty(objectNode, path, value, sourceFile, sourceText, literalContext) {
|
|
1074
|
+
const propertyName = path[0];
|
|
1075
|
+
if (!propertyName) return {
|
|
1076
|
+
ok: false,
|
|
1077
|
+
code: "invalid-path",
|
|
1078
|
+
message: "Missing nested property insertion requires a non-empty path."
|
|
1079
|
+
};
|
|
1080
|
+
return insertionForMissingProperty$1(objectNode, propertyName, nestedStyleObjectSource(path.slice(1), value, literalContext), sourceFile, sourceText, literalContext);
|
|
1081
|
+
}
|
|
1082
|
+
function insertionForMissingProperty$1(objectNode, propertyName, value, sourceFile, sourceText, literalContext) {
|
|
1083
|
+
const closeBrace = objectNode.getEnd() - 1;
|
|
1084
|
+
const multiline = sourceText.slice(objectNode.getStart(sourceFile), objectNode.getEnd()).includes("\n");
|
|
1085
|
+
const property = `${propertyNameSource$1(propertyName)}: ${valueSource(value, {
|
|
1086
|
+
...literalContext,
|
|
1087
|
+
path: [...literalContext.path.slice(0, -1), propertyName]
|
|
1088
|
+
})}`;
|
|
1089
|
+
if (objectNode.properties.length === 0) {
|
|
1090
|
+
if (!multiline) return {
|
|
1091
|
+
ok: true,
|
|
1092
|
+
range: {
|
|
1093
|
+
start: closeBrace,
|
|
1094
|
+
end: closeBrace
|
|
1095
|
+
},
|
|
1096
|
+
text: property
|
|
1097
|
+
};
|
|
1098
|
+
const parentIndent = indentationBefore$1(sourceText, objectNode.getStart(sourceFile));
|
|
1099
|
+
const childIndent = `${parentIndent} `;
|
|
1100
|
+
return {
|
|
1101
|
+
ok: true,
|
|
1102
|
+
range: {
|
|
1103
|
+
start: closeBrace,
|
|
1104
|
+
end: closeBrace
|
|
1105
|
+
},
|
|
1106
|
+
text: `\n${childIndent}${property},\n${parentIndent}`
|
|
1107
|
+
};
|
|
1108
|
+
}
|
|
1109
|
+
const lastProperty = objectNode.properties[objectNode.properties.length - 1];
|
|
1110
|
+
const trailingComma = commaAfterNode$1(lastProperty, objectNode, sourceText);
|
|
1111
|
+
if (!multiline) {
|
|
1112
|
+
const insertAt = trailingComma ?? lastProperty.getEnd();
|
|
1113
|
+
return {
|
|
1114
|
+
ok: true,
|
|
1115
|
+
range: {
|
|
1116
|
+
start: insertAt,
|
|
1117
|
+
end: insertAt
|
|
1118
|
+
},
|
|
1119
|
+
text: trailingComma === void 0 ? `, ${property}` : ` ${property}`
|
|
1120
|
+
};
|
|
1121
|
+
}
|
|
1122
|
+
const childIndent = indentationBefore$1(sourceText, lastProperty.getStart(sourceFile));
|
|
1123
|
+
if (trailingComma !== void 0) return {
|
|
1124
|
+
ok: true,
|
|
1125
|
+
range: {
|
|
1126
|
+
start: trailingComma + 1,
|
|
1127
|
+
end: trailingComma + 1
|
|
1128
|
+
},
|
|
1129
|
+
text: `\n${childIndent}${property},`
|
|
1130
|
+
};
|
|
1131
|
+
return {
|
|
1132
|
+
ok: true,
|
|
1133
|
+
range: {
|
|
1134
|
+
start: lastProperty.getEnd(),
|
|
1135
|
+
end: lastProperty.getEnd()
|
|
1136
|
+
},
|
|
1137
|
+
text: `,\n${childIndent}${property}`
|
|
1138
|
+
};
|
|
1139
|
+
}
|
|
1140
|
+
function nestedStyleObjectSource(path, value, literalContext) {
|
|
1141
|
+
if (path.length === 0) return {
|
|
1142
|
+
kind: "raw-source",
|
|
1143
|
+
text: literalSource$1(value, literalContext)
|
|
1144
|
+
};
|
|
1145
|
+
const [propertyName, ...rest] = path;
|
|
1146
|
+
return {
|
|
1147
|
+
kind: "raw-source",
|
|
1148
|
+
text: `{ ${propertyNameSource$1(propertyName)}: ${nestedStyleObjectSource(rest, value, {
|
|
1149
|
+
...literalContext,
|
|
1150
|
+
path: [...literalContext.path.slice(0, -rest.length - 1), propertyName]
|
|
1151
|
+
}).text} }`
|
|
1152
|
+
};
|
|
1153
|
+
}
|
|
1154
|
+
function valueSource(value, context) {
|
|
1155
|
+
if (isRawSourceValue(value)) return value.text;
|
|
1156
|
+
return literalSource$1(value, context);
|
|
1157
|
+
}
|
|
1158
|
+
function isRawSourceValue(value) {
|
|
1159
|
+
return typeof value === "object" && value !== null && "kind" in value && value.kind === "raw-source";
|
|
1160
|
+
}
|
|
1161
|
+
function commaAfterNode$1(node, objectNode, sourceText) {
|
|
1162
|
+
for (let index = node.getEnd(); index < objectNode.getEnd() - 1; index += 1) {
|
|
1163
|
+
const char = sourceText[index];
|
|
1164
|
+
if (char === ",") return index;
|
|
1165
|
+
if (!isWhitespace$1(char)) return void 0;
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
function isWhitespace$1(char) {
|
|
1169
|
+
return char === " " || char === " " || char === "\n" || char === "\r";
|
|
1170
|
+
}
|
|
1171
|
+
function indentationBefore$1(sourceText, position) {
|
|
1172
|
+
const lineStart = sourceText.lastIndexOf("\n", position - 1) + 1;
|
|
1173
|
+
return sourceText.slice(lineStart, position).match(/^[ \t]*/)?.[0] ?? "";
|
|
1174
|
+
}
|
|
1175
|
+
function propertyNameSource$1(propertyName) {
|
|
1176
|
+
return /^[$A-Z_a-z][$\w]*$/.test(propertyName) ? propertyName : JSON.stringify(propertyName);
|
|
1177
|
+
}
|
|
1178
|
+
function propertyNameText$2(name) {
|
|
1179
|
+
if (ts.isIdentifier(name) || ts.isStringLiteral(name) || ts.isNumericLiteral(name)) return name.text;
|
|
1180
|
+
}
|
|
1181
|
+
function deletionRangeForProperty(objectNode, property, sourceFile, sourceText) {
|
|
1182
|
+
const properties = objectNode.properties;
|
|
1183
|
+
const index = properties.findIndex((item) => item === property);
|
|
1184
|
+
const next = properties[index + 1];
|
|
1185
|
+
if (next) return {
|
|
1186
|
+
start: property.getStart(sourceFile),
|
|
1187
|
+
end: next.getStart(sourceFile)
|
|
1188
|
+
};
|
|
1189
|
+
const trailingComma = commaAfterNode$1(property, objectNode, sourceText);
|
|
1190
|
+
const previous = properties[index - 1];
|
|
1191
|
+
if (previous) return {
|
|
1192
|
+
start: previous.getEnd(),
|
|
1193
|
+
end: trailingComma === void 0 ? property.getEnd() : trailingComma + 1
|
|
1194
|
+
};
|
|
1195
|
+
return {
|
|
1196
|
+
start: property.getStart(sourceFile),
|
|
1197
|
+
end: trailingComma === void 0 ? property.getEnd() : trailingComma + 1
|
|
1198
|
+
};
|
|
1199
|
+
}
|
|
1200
|
+
function rootObjectWillBeEmptyAfterEdits(objectNode, edits) {
|
|
1201
|
+
if (objectNode.properties.length === 0) return true;
|
|
1202
|
+
if (edits.some((edit) => edit.op !== "delete" || edit.path.length !== 1)) return false;
|
|
1203
|
+
const deleted = new Set(edits.flatMap((edit) => edit.op === "delete" && edit.path.length === 1 ? [edit.path[0]] : []));
|
|
1204
|
+
for (const property of objectNode.properties) {
|
|
1205
|
+
if (!ts.isPropertyAssignment(property)) return false;
|
|
1206
|
+
const name = propertyNameText$2(property.name);
|
|
1207
|
+
if (name === void 0 || !deleted.has(name)) return false;
|
|
1208
|
+
}
|
|
1209
|
+
return true;
|
|
1210
|
+
}
|
|
1211
|
+
function inlineEmptyCssCleanupReplacement(objectNode, sourceFile, sourceText) {
|
|
1212
|
+
const call = objectNode.parent;
|
|
1213
|
+
if (!ts.isCallExpression(call)) return void 0;
|
|
1214
|
+
const classAttribute = jsxClassAttributeAncestor(call);
|
|
1215
|
+
if (!classAttribute || !ts.isIdentifier(classAttribute.name)) return void 0;
|
|
1216
|
+
const expression = classAttribute.initializer && jsxAttributeExpression$1(classAttribute.initializer);
|
|
1217
|
+
if (!expression) return removeJsxAttributeReplacement(classAttribute);
|
|
1218
|
+
if (unwrapParentheses(expression) === call) return removeJsxAttributeReplacement(classAttribute);
|
|
1219
|
+
const generatedArray = generatedClassJoinArray(expression);
|
|
1220
|
+
if (!generatedArray) return void 0;
|
|
1221
|
+
const keptElements = generatedArray.elements.filter((element) => unwrapParentheses(element) !== call);
|
|
1222
|
+
if (keptElements.length === generatedArray.elements.length) return void 0;
|
|
1223
|
+
if (keptElements.length === 0) return removeJsxAttributeReplacement(classAttribute);
|
|
1224
|
+
const attributeName = classAttribute.name.text;
|
|
1225
|
+
if (keptElements.length === 1) {
|
|
1226
|
+
const keptText = sourceText.slice(keptElements[0].getStart(sourceFile), keptElements[0].getEnd());
|
|
1227
|
+
return {
|
|
1228
|
+
range: {
|
|
1229
|
+
start: classAttribute.getStart(sourceFile),
|
|
1230
|
+
end: classAttribute.getEnd()
|
|
1231
|
+
},
|
|
1232
|
+
text: `${attributeName}={${keptText}}`
|
|
1233
|
+
};
|
|
1234
|
+
}
|
|
1235
|
+
const joinedText = keptElements.map((element) => sourceText.slice(element.getStart(sourceFile), element.getEnd())).join(", ");
|
|
1236
|
+
return {
|
|
1237
|
+
range: {
|
|
1238
|
+
start: classAttribute.getStart(sourceFile),
|
|
1239
|
+
end: classAttribute.getEnd()
|
|
1240
|
+
},
|
|
1241
|
+
text: `${attributeName}={[${joinedText}].filter(Boolean).join(' ')}`
|
|
1242
|
+
};
|
|
1243
|
+
}
|
|
1244
|
+
function jsxClassAttributeAncestor(node) {
|
|
1245
|
+
let current = node;
|
|
1246
|
+
while (current) {
|
|
1247
|
+
if (ts.isJsxAttribute(current) && ts.isIdentifier(current.name) && (current.name.text === "class" || current.name.text === "className")) return current;
|
|
1248
|
+
current = current.parent;
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
function jsxAttributeExpression$1(initializer) {
|
|
1252
|
+
if (ts.isJsxExpression(initializer)) return initializer.expression;
|
|
1253
|
+
}
|
|
1254
|
+
function removeJsxAttributeReplacement(attribute) {
|
|
1255
|
+
return {
|
|
1256
|
+
range: {
|
|
1257
|
+
start: attribute.getFullStart(),
|
|
1258
|
+
end: attribute.getEnd()
|
|
1259
|
+
},
|
|
1260
|
+
text: ""
|
|
1261
|
+
};
|
|
1262
|
+
}
|
|
1263
|
+
function generatedClassJoinArray(expression) {
|
|
1264
|
+
const joinCall = unwrapParentheses(expression);
|
|
1265
|
+
if (!ts.isCallExpression(joinCall)) return void 0;
|
|
1266
|
+
if (!ts.isPropertyAccessExpression(joinCall.expression)) return void 0;
|
|
1267
|
+
if (joinCall.expression.name.text !== "join") return void 0;
|
|
1268
|
+
const filterCall = unwrapParentheses(joinCall.expression.expression);
|
|
1269
|
+
if (!ts.isCallExpression(filterCall)) return void 0;
|
|
1270
|
+
if (!ts.isPropertyAccessExpression(filterCall.expression)) return void 0;
|
|
1271
|
+
if (filterCall.expression.name.text !== "filter") return void 0;
|
|
1272
|
+
const array = unwrapParentheses(filterCall.expression.expression);
|
|
1273
|
+
return ts.isArrayLiteralExpression(array) ? array : void 0;
|
|
1274
|
+
}
|
|
1275
|
+
function unwrapParentheses(expression) {
|
|
1276
|
+
let current = expression;
|
|
1277
|
+
while (ts.isParenthesizedExpression(current)) current = current.expression;
|
|
1278
|
+
return current;
|
|
1279
|
+
}
|
|
1280
|
+
function locateProperty$1(source, objectNode, propertyName) {
|
|
1281
|
+
const matches = [];
|
|
1282
|
+
for (const property of source.ops.getObjectProperties(objectNode)) {
|
|
1283
|
+
if (source.ops.isSpreadProperty(property)) return {
|
|
1284
|
+
ok: false,
|
|
1285
|
+
reason: {
|
|
1286
|
+
code: "unsupported-source-shape",
|
|
1287
|
+
message: "Object spread can change which style keys exist at runtime."
|
|
1288
|
+
}
|
|
1289
|
+
};
|
|
1290
|
+
if (!source.ops.isPlainProperty(property)) return {
|
|
1291
|
+
ok: false,
|
|
1292
|
+
reason: {
|
|
1293
|
+
code: "unsupported-source-shape",
|
|
1294
|
+
message: "Only plain property assignments can be patched."
|
|
1295
|
+
}
|
|
1296
|
+
};
|
|
1297
|
+
const name = source.ops.getPropertyName(property);
|
|
1298
|
+
if (name === void 0) return {
|
|
1299
|
+
ok: false,
|
|
1300
|
+
reason: {
|
|
1301
|
+
code: "unsupported-source-shape",
|
|
1302
|
+
message: "Computed property names are not safe to patch."
|
|
1303
|
+
}
|
|
1304
|
+
};
|
|
1305
|
+
if (name === propertyName) matches.push(property);
|
|
1306
|
+
}
|
|
1307
|
+
if (matches.length > 1) return {
|
|
1308
|
+
ok: false,
|
|
1309
|
+
reason: {
|
|
1310
|
+
code: "ambiguous-target",
|
|
1311
|
+
message: "Style object contains duplicate keys for the requested path.",
|
|
1312
|
+
details: { property: propertyName }
|
|
1313
|
+
}
|
|
1314
|
+
};
|
|
1315
|
+
const property = matches[0];
|
|
1316
|
+
if (!property) return {
|
|
1317
|
+
ok: false,
|
|
1318
|
+
reason: {
|
|
1319
|
+
code: "invalid-path",
|
|
1320
|
+
message: "Style object does not contain the requested path.",
|
|
1321
|
+
details: { property: propertyName }
|
|
1322
|
+
}
|
|
1323
|
+
};
|
|
1324
|
+
return {
|
|
1325
|
+
ok: true,
|
|
1326
|
+
property
|
|
1327
|
+
};
|
|
1328
|
+
}
|
|
1329
|
+
function isJsonPrimitive$1(value) {
|
|
1330
|
+
return value === null || [
|
|
1331
|
+
"string",
|
|
1332
|
+
"number",
|
|
1333
|
+
"boolean"
|
|
1334
|
+
].includes(typeof value);
|
|
1335
|
+
}
|
|
1336
|
+
function literalSource$1(value, context) {
|
|
1337
|
+
if (typeof value === "string") {
|
|
1338
|
+
const complexTokenSource = complexTokenValueSource(value, context);
|
|
1339
|
+
if (complexTokenSource) return complexTokenSource;
|
|
1340
|
+
return `'${value.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
|
|
1341
|
+
}
|
|
1342
|
+
if (typeof value === "number") return Number.isFinite(value) ? String(value) : "null";
|
|
1343
|
+
if (typeof value === "boolean") return value ? "true" : "false";
|
|
1344
|
+
return "null";
|
|
1345
|
+
}
|
|
1346
|
+
function complexTokenValueSource(value, context) {
|
|
1347
|
+
const property = context?.path.at(-1);
|
|
1348
|
+
if (property !== "border" && property !== "outline") return void 0;
|
|
1349
|
+
const parts = value.trim().split(/\s+/);
|
|
1350
|
+
if (parts.length < 2) return void 0;
|
|
1351
|
+
const colorToken = parts.at(-1);
|
|
1352
|
+
if (!colorToken || !looksLikeColorTokenPath(colorToken)) return void 0;
|
|
1353
|
+
const tokenPath = colorToken.startsWith("colors.") ? colorToken : `colors.${colorToken}`;
|
|
1354
|
+
const prefix = parts.slice(0, -1).join(" ");
|
|
1355
|
+
if (!context?.tokenVarImportLocalName) return `'${escapeSingleQuotedString(`${prefix} token(${tokenPath})`)}'`;
|
|
1356
|
+
return `\`${escapeTemplateText(`${prefix} `)}\${${context.tokenVarImportLocalName}.var(${JSON.stringify(tokenPath)})}\``;
|
|
1357
|
+
}
|
|
1358
|
+
function looksLikeColorTokenPath(value) {
|
|
1359
|
+
return /^(?:colors\.)?[A-Za-z_][\w-]*(?:\.[A-Za-z0-9_][\w-]*)+$/.test(value);
|
|
1360
|
+
}
|
|
1361
|
+
function escapeSingleQuotedString(value) {
|
|
1362
|
+
return value.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
1363
|
+
}
|
|
1364
|
+
function escapeTemplateText(value) {
|
|
1365
|
+
return value.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/g, "\\${");
|
|
1366
|
+
}
|
|
1367
|
+
function tokenImportLocalName(sourceFile, styledSystemPackageName) {
|
|
1368
|
+
const moduleSpecifier = `${styledSystemPackageName}/tokens`;
|
|
1369
|
+
for (const statement of sourceFile.statements) {
|
|
1370
|
+
if (!ts.isImportDeclaration(statement)) continue;
|
|
1371
|
+
if (!ts.isStringLiteral(statement.moduleSpecifier)) continue;
|
|
1372
|
+
if (statement.moduleSpecifier.text !== moduleSpecifier) continue;
|
|
1373
|
+
const namedBindings = statement.importClause?.namedBindings;
|
|
1374
|
+
if (!namedBindings || !ts.isNamedImports(namedBindings)) return void 0;
|
|
1375
|
+
for (const element of namedBindings.elements) if ((element.propertyName?.text ?? element.name.text) === "token") return element.name.text;
|
|
1376
|
+
}
|
|
1377
|
+
return tokenIdentifierAvailable(sourceFile) ? "token" : void 0;
|
|
1378
|
+
}
|
|
1379
|
+
function tokenIdentifierAvailable(sourceFile) {
|
|
1380
|
+
let available = true;
|
|
1381
|
+
const visit = (node) => {
|
|
1382
|
+
if (!available) return;
|
|
1383
|
+
if (ts.isIdentifier(node) && node.text === "token") {
|
|
1384
|
+
available = false;
|
|
1385
|
+
return;
|
|
1386
|
+
}
|
|
1387
|
+
ts.forEachChild(node, visit);
|
|
1388
|
+
};
|
|
1389
|
+
visit(sourceFile);
|
|
1390
|
+
return available;
|
|
1391
|
+
}
|
|
1392
|
+
function tokenVarImportReplacement(sourceFile, styledSystemPackageName, localName) {
|
|
1393
|
+
const moduleSpecifier = `${styledSystemPackageName}/tokens`;
|
|
1394
|
+
for (const statement of sourceFile.statements) {
|
|
1395
|
+
if (!ts.isImportDeclaration(statement)) continue;
|
|
1396
|
+
if (!ts.isStringLiteral(statement.moduleSpecifier)) continue;
|
|
1397
|
+
if (statement.moduleSpecifier.text !== moduleSpecifier) continue;
|
|
1398
|
+
const namedBindings = statement.importClause?.namedBindings;
|
|
1399
|
+
if (!namedBindings || !ts.isNamedImports(namedBindings)) return void 0;
|
|
1400
|
+
if (namedBindings.elements.some((element) => (element.propertyName?.text ?? element.name.text) === "token")) return;
|
|
1401
|
+
const insertAt = namedBindings.elements.end;
|
|
1402
|
+
return {
|
|
1403
|
+
range: {
|
|
1404
|
+
start: insertAt,
|
|
1405
|
+
end: insertAt
|
|
1406
|
+
},
|
|
1407
|
+
text: `, token${localName === "token" ? "" : ` as ${localName}`}`
|
|
1408
|
+
};
|
|
1409
|
+
}
|
|
1410
|
+
const insertAt = importInsertionPosition$1(sourceFile);
|
|
1411
|
+
return {
|
|
1412
|
+
range: {
|
|
1413
|
+
start: insertAt,
|
|
1414
|
+
end: insertAt
|
|
1415
|
+
},
|
|
1416
|
+
text: `import { token${localName === "token" ? "" : ` as ${localName}`} } from '${moduleSpecifier}'\n`
|
|
1417
|
+
};
|
|
1418
|
+
}
|
|
1419
|
+
function importInsertionPosition$1(sourceFile) {
|
|
1420
|
+
let position = 0;
|
|
1421
|
+
for (const statement of sourceFile.statements) {
|
|
1422
|
+
if (!ts.isImportDeclaration(statement)) break;
|
|
1423
|
+
position = statement.getEnd();
|
|
1424
|
+
}
|
|
1425
|
+
return position === 0 ? 0 : position + 1;
|
|
1426
|
+
}
|
|
1427
|
+
function findOverlappingReplacement(replacements) {
|
|
1428
|
+
const sorted = [...replacements].sort((left, right) => left.range.start - right.range.start);
|
|
1429
|
+
for (let index = 1; index < sorted.length; index += 1) if (sorted[index - 1].range.end > sorted[index].range.start) return [sorted[index - 1].range, sorted[index].range];
|
|
1430
|
+
}
|
|
1431
|
+
function dedupeIdenticalReplacements(replacements) {
|
|
1432
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1433
|
+
const deduped = [];
|
|
1434
|
+
for (const replacement of replacements) {
|
|
1435
|
+
const key = `${replacement.range.start}:${replacement.range.end}:${replacement.text}`;
|
|
1436
|
+
if (seen.has(key)) continue;
|
|
1437
|
+
seen.add(key);
|
|
1438
|
+
deduped.push(replacement);
|
|
1439
|
+
}
|
|
1440
|
+
return deduped;
|
|
1441
|
+
}
|
|
1442
|
+
function applyReplacements$2(sourceText, replacements) {
|
|
1443
|
+
let nextSource = sourceText;
|
|
1444
|
+
for (const replacement of [...replacements].sort((left, right) => right.range.start - left.range.start)) nextSource = `${nextSource.slice(0, replacement.range.start)}${replacement.text}${nextSource.slice(replacement.range.end)}`;
|
|
1445
|
+
return nextSource;
|
|
1446
|
+
}
|
|
1447
|
+
function focusedDiff$2(file, sourceText, nextSource, replacements) {
|
|
1448
|
+
const firstStart = Math.min(...replacements.map((replacement) => replacement.range.start));
|
|
1449
|
+
const lastEnd = Math.max(...replacements.map((replacement) => replacement.range.end));
|
|
1450
|
+
const oldStartLine = lineNumberAt$2(sourceText, firstStart);
|
|
1451
|
+
const oldEndLine = lineNumberAt$2(sourceText, lastEnd);
|
|
1452
|
+
const contextStartLine = Math.max(1, oldStartLine - 2);
|
|
1453
|
+
const contextEndLine = oldEndLine + 2;
|
|
1454
|
+
const oldLines = sourceText.split("\n");
|
|
1455
|
+
const newLines = nextSource.split("\n");
|
|
1456
|
+
const oldSlice = oldLines.slice(contextStartLine - 1, contextEndLine);
|
|
1457
|
+
const newSlice = newLines.slice(contextStartLine - 1, contextEndLine + (newLines.length - oldLines.length));
|
|
1458
|
+
return [
|
|
1459
|
+
`--- a/${file}`,
|
|
1460
|
+
`+++ b/${file}`,
|
|
1461
|
+
`@@ -${contextStartLine},${oldSlice.length} +${contextStartLine},${newSlice.length} @@`,
|
|
1462
|
+
...oldSlice.map((line) => `-${line}`),
|
|
1463
|
+
...newSlice.map((line) => `+${line}`)
|
|
1464
|
+
].join("\n");
|
|
1465
|
+
}
|
|
1466
|
+
function lineNumberAt$2(sourceText, position) {
|
|
1467
|
+
let line = 1;
|
|
1468
|
+
for (let index = 0; index < position; index += 1) if (sourceText.charCodeAt(index) === 10) line += 1;
|
|
1469
|
+
return line;
|
|
1470
|
+
}
|
|
1471
|
+
function normalizePath$3(path) {
|
|
1472
|
+
return path.replace(/\\/g, "/");
|
|
1473
|
+
}
|
|
1474
|
+
function failure$2(editId, code, message, details) {
|
|
1475
|
+
return {
|
|
1476
|
+
ok: false,
|
|
1477
|
+
editId,
|
|
1478
|
+
written: false,
|
|
1479
|
+
error: {
|
|
1480
|
+
code,
|
|
1481
|
+
message,
|
|
1482
|
+
details
|
|
1483
|
+
}
|
|
1484
|
+
};
|
|
1485
|
+
}
|
|
1486
|
+
//#endregion
|
|
1487
|
+
//#region src/vite/pathSafety.ts
|
|
1488
|
+
function trustedTokenSourceFilePath(projectRoot, tokenSource) {
|
|
1489
|
+
const root = normalizePath$2(projectRoot).replace(/\/$/, "");
|
|
1490
|
+
const file = normalizePath$2(tokenSource.absoluteFile ?? `${root}/${tokenSource.file}`);
|
|
1491
|
+
if (!root || file !== root && !file.startsWith(`${root}/`)) return {
|
|
1492
|
+
ok: false,
|
|
1493
|
+
code: "path-outside-project",
|
|
1494
|
+
message: "Token source points outside the project root.",
|
|
1495
|
+
details: {
|
|
1496
|
+
file,
|
|
1497
|
+
projectRoot: root
|
|
1498
|
+
}
|
|
1499
|
+
};
|
|
1500
|
+
return {
|
|
1501
|
+
ok: true,
|
|
1502
|
+
file
|
|
1503
|
+
};
|
|
1504
|
+
}
|
|
1505
|
+
function trustedManifestFilePath(manifest, entry) {
|
|
1506
|
+
const root = normalizePath$2(manifest.projectRoot).replace(/\/$/, "");
|
|
1507
|
+
const file = normalizePath$2(entry.absoluteFile ?? `${root}/${entry.file}`);
|
|
1508
|
+
if (!root || file !== root && !file.startsWith(`${root}/`)) return {
|
|
1509
|
+
ok: false,
|
|
1510
|
+
code: "path-outside-project",
|
|
1511
|
+
message: "Manifest entry points outside the project root.",
|
|
1512
|
+
details: {
|
|
1513
|
+
file,
|
|
1514
|
+
projectRoot: root
|
|
1515
|
+
}
|
|
1516
|
+
};
|
|
1517
|
+
return {
|
|
1518
|
+
ok: true,
|
|
1519
|
+
file
|
|
1520
|
+
};
|
|
1521
|
+
}
|
|
1522
|
+
function safeProjectSourcePath(projectRoot, filePath) {
|
|
1523
|
+
const root = normalizePath$2(projectRoot).replace(/\/$/, "");
|
|
1524
|
+
const file = normalizePath$2(resolve(root, filePath)).replace(/\/$/, "");
|
|
1525
|
+
if (!root || file === root || !file.startsWith(`${root}/`)) return {
|
|
1526
|
+
ok: false,
|
|
1527
|
+
code: "path-outside-project",
|
|
1528
|
+
message: "Source path is outside the project root.",
|
|
1529
|
+
details: {
|
|
1530
|
+
file,
|
|
1531
|
+
projectRoot: root
|
|
1532
|
+
}
|
|
1533
|
+
};
|
|
1534
|
+
return {
|
|
1535
|
+
ok: true,
|
|
1536
|
+
file
|
|
1537
|
+
};
|
|
1538
|
+
}
|
|
1539
|
+
function stripViteQuery(id) {
|
|
1540
|
+
return id.split("?")[0] ?? id;
|
|
1541
|
+
}
|
|
1542
|
+
function toRelativeProjectPath(filePath, projectRoot) {
|
|
1543
|
+
if (!projectRoot) return filePath;
|
|
1544
|
+
const root = normalizePath$2(projectRoot).replace(/\/$/, "");
|
|
1545
|
+
if (filePath === root) return filePath.split("/").at(-1) ?? filePath;
|
|
1546
|
+
if (filePath.startsWith(`${root}/`)) return filePath.slice(root.length + 1);
|
|
1547
|
+
return filePath;
|
|
1548
|
+
}
|
|
1549
|
+
function normalizePath$2(path) {
|
|
1550
|
+
return path.replace(/\\/g, "/");
|
|
1551
|
+
}
|
|
1552
|
+
//#endregion
|
|
1553
|
+
//#region src/patcher/styleModulePatcher.ts
|
|
1554
|
+
const DEFAULT_CSS_IMPORT_SOURCES = [
|
|
1555
|
+
"../styled-system/css",
|
|
1556
|
+
"@/styled-system/css",
|
|
1557
|
+
"styled-system/css"
|
|
1558
|
+
];
|
|
1559
|
+
function componentStyleModulePaths(options) {
|
|
1560
|
+
const parsed = parseSourceRef(options.componentSource, "Component style list requires a component source path with line and column.");
|
|
1561
|
+
if (!parsed.ok) return parsed;
|
|
1562
|
+
const root = normalizePath$1(options.projectRoot).replace(/\/$/, "");
|
|
1563
|
+
const componentFile = normalizePath$1(resolve(root, parsed.file));
|
|
1564
|
+
if (!root || componentFile !== root && !componentFile.startsWith(`${root}/`)) return {
|
|
1565
|
+
ok: false,
|
|
1566
|
+
code: "path-outside-project",
|
|
1567
|
+
message: "Component source path is outside the project root.",
|
|
1568
|
+
details: {
|
|
1569
|
+
componentSource: options.componentSource,
|
|
1570
|
+
projectRoot: root
|
|
1571
|
+
}
|
|
1572
|
+
};
|
|
1573
|
+
const extensionMatch = /\.(tsx|jsx|ts|js)$/.exec(componentFile);
|
|
1574
|
+
if (!extensionMatch) return {
|
|
1575
|
+
ok: false,
|
|
1576
|
+
code: "unsupported-source-shape",
|
|
1577
|
+
message: "Component style lists are only supported for TS/TSX/JS/JSX component modules.",
|
|
1578
|
+
details: { componentSource: options.componentSource }
|
|
1579
|
+
};
|
|
1580
|
+
const componentBase = componentFile.slice(0, -extensionMatch[0].length);
|
|
1581
|
+
const styleFile = `${componentBase}.style.ts`;
|
|
1582
|
+
return {
|
|
1583
|
+
ok: true,
|
|
1584
|
+
paths: {
|
|
1585
|
+
componentFile,
|
|
1586
|
+
styleFile,
|
|
1587
|
+
styleFileRelative: toRelativeProjectPath(styleFile, options.projectRoot),
|
|
1588
|
+
importPath: `./${componentBase.split("/").at(-1) ?? "styles"}.style`
|
|
1589
|
+
}
|
|
1590
|
+
};
|
|
1591
|
+
}
|
|
1592
|
+
function readComponentStyleModule(options) {
|
|
1593
|
+
const paths = componentStyleModulePaths(options);
|
|
1594
|
+
if (!paths.ok) return {
|
|
1595
|
+
ok: false,
|
|
1596
|
+
error: {
|
|
1597
|
+
code: paths.code,
|
|
1598
|
+
message: paths.message,
|
|
1599
|
+
details: paths.details
|
|
1600
|
+
}
|
|
1601
|
+
};
|
|
1602
|
+
if (options.sourceText === void 0) return {
|
|
1603
|
+
ok: true,
|
|
1604
|
+
componentFile: toRelativeProjectPath(paths.paths.componentFile, options.projectRoot),
|
|
1605
|
+
styleFile: paths.paths.styleFileRelative,
|
|
1606
|
+
importPath: paths.paths.importPath,
|
|
1607
|
+
exists: false,
|
|
1608
|
+
sources: []
|
|
1609
|
+
};
|
|
1610
|
+
const parsed = parseStyleModuleSource({
|
|
1611
|
+
projectRoot: options.projectRoot,
|
|
1612
|
+
filePath: paths.paths.styleFile,
|
|
1613
|
+
sourceText: options.sourceText,
|
|
1614
|
+
cssImportSources: options.cssImportSources
|
|
1615
|
+
});
|
|
1616
|
+
if (!parsed.ok) return {
|
|
1617
|
+
ok: false,
|
|
1618
|
+
error: {
|
|
1619
|
+
code: parsed.code,
|
|
1620
|
+
message: parsed.message,
|
|
1621
|
+
details: parsed.details
|
|
1622
|
+
}
|
|
1623
|
+
};
|
|
1624
|
+
return {
|
|
1625
|
+
ok: true,
|
|
1626
|
+
componentFile: toRelativeProjectPath(paths.paths.componentFile, options.projectRoot),
|
|
1627
|
+
styleFile: paths.paths.styleFileRelative,
|
|
1628
|
+
importPath: paths.paths.importPath,
|
|
1629
|
+
exists: true,
|
|
1630
|
+
sources: parsed.sources
|
|
1631
|
+
};
|
|
1632
|
+
}
|
|
1633
|
+
function createStyleModuleSourcePatch(options) {
|
|
1634
|
+
const nameFailure = validateStyleSourceName(options.request.name);
|
|
1635
|
+
if (nameFailure) return failure$1(options.request.editId, nameFailure.code, nameFailure.message);
|
|
1636
|
+
const initialStyleObject = styleObjectTextForSourceCreateEdits(options.request.edits);
|
|
1637
|
+
if (!initialStyleObject.ok) return failure$1(options.request.editId, initialStyleObject.code, initialStyleObject.message);
|
|
1638
|
+
if (options.sourceText === void 0) {
|
|
1639
|
+
const cssLocalName = "css";
|
|
1640
|
+
const nextSource = [
|
|
1641
|
+
cssImportText(cssLocalName, options.cssImportSources),
|
|
1642
|
+
"",
|
|
1643
|
+
"export default {",
|
|
1644
|
+
` ${options.request.name}: ${cssLocalName}(${initialStyleObject.text}),`,
|
|
1645
|
+
"}",
|
|
1646
|
+
""
|
|
1647
|
+
].join("\n");
|
|
1648
|
+
const file = toRelativeProjectPath(options.filePath, options.projectRoot);
|
|
1649
|
+
return {
|
|
1650
|
+
ok: true,
|
|
1651
|
+
editId: options.request.editId,
|
|
1652
|
+
file,
|
|
1653
|
+
diff: nextFileDiff(file, nextSource),
|
|
1654
|
+
nextSource,
|
|
1655
|
+
written: false
|
|
1656
|
+
};
|
|
1657
|
+
}
|
|
1658
|
+
const sourceFile = ts.createSourceFile(options.filePath, options.sourceText, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
|
|
1659
|
+
const exportObject = findDefaultExportObject(sourceFile);
|
|
1660
|
+
if (!exportObject) return failure$1(options.request.editId, "unsupported-source-shape", "Style module sources require a direct export default { ... } object.");
|
|
1661
|
+
if (objectHasProperty(exportObject, options.request.name)) return failure$1(options.request.editId, "invalid-edit", "A style source with that name already exists.");
|
|
1662
|
+
const cssImport = findCssImportBinding(sourceFile, options.cssImportSources);
|
|
1663
|
+
const cssLocalName = cssImport?.localName ?? "css";
|
|
1664
|
+
const replacements = [{
|
|
1665
|
+
range: {
|
|
1666
|
+
start: exportObject.getEnd() - 1,
|
|
1667
|
+
end: exportObject.getEnd() - 1
|
|
1668
|
+
},
|
|
1669
|
+
text: `${exportObject.properties.length === 0 ? "\n" : ""} ${options.request.name}: ${cssLocalName}(${initialStyleObject.text}),\n`
|
|
1670
|
+
}];
|
|
1671
|
+
if (!cssImport) replacements.push({
|
|
1672
|
+
range: {
|
|
1673
|
+
start: importInsertionPosition(sourceFile),
|
|
1674
|
+
end: importInsertionPosition(sourceFile)
|
|
1675
|
+
},
|
|
1676
|
+
text: cssImportText(cssLocalName, options.cssImportSources)
|
|
1677
|
+
});
|
|
1678
|
+
const nextSource = applyReplacements$1(options.sourceText, replacements);
|
|
1679
|
+
const file = toRelativeProjectPath(options.filePath, options.projectRoot);
|
|
1680
|
+
return {
|
|
1681
|
+
ok: true,
|
|
1682
|
+
editId: options.request.editId,
|
|
1683
|
+
file,
|
|
1684
|
+
diff: focusedDiff$1(file, options.sourceText, nextSource, replacements),
|
|
1685
|
+
nextSource,
|
|
1686
|
+
written: false
|
|
1687
|
+
};
|
|
1688
|
+
}
|
|
1689
|
+
function createStyleModuleAttachPatch(options) {
|
|
1690
|
+
const nameFailure = validateStyleSourceName(options.request.name);
|
|
1691
|
+
if (nameFailure) return failure$1(options.request.editId, nameFailure.code, nameFailure.message);
|
|
1692
|
+
const parsed = parseSourceRef(options.request.jsxSource, "Style source attachment requires a JSX source path with line and column.");
|
|
1693
|
+
if (!parsed.ok) return failure$1(options.request.editId, parsed.code, parsed.message, parsed.details);
|
|
1694
|
+
const normalizedFile = normalizePath$1(options.filePath);
|
|
1695
|
+
if (normalizedFile !== normalizePath$1(`${options.projectRoot.replace(/\/$/, "")}/${parsed.file}`)) return failure$1(options.request.editId, "stale-source", "JSX source location points to a different file.", {
|
|
1696
|
+
filePath: normalizedFile,
|
|
1697
|
+
jsxSource: options.request.jsxSource
|
|
1698
|
+
});
|
|
1699
|
+
const paths = componentStyleModulePaths({
|
|
1700
|
+
projectRoot: options.projectRoot,
|
|
1701
|
+
componentSource: options.request.componentSource
|
|
1702
|
+
});
|
|
1703
|
+
if (!paths.ok) return failure$1(options.request.editId, paths.code, paths.message, paths.details);
|
|
1704
|
+
const sourceFile = ts.createSourceFile(options.filePath, options.sourceText, ts.ScriptTarget.Latest, true, options.filePath.endsWith(".tsx") ? ts.ScriptKind.TSX : ts.ScriptKind.TS);
|
|
1705
|
+
const position = positionForLineColumn(sourceFile, parsed.line, parsed.column);
|
|
1706
|
+
if (position === void 0) return failure$1(options.request.editId, "stale-source", "JSX source location is outside the file.");
|
|
1707
|
+
const element = findJsxElementAtStart(sourceFile, position);
|
|
1708
|
+
if (!element) return failure$1(options.request.editId, "stale-source", "JSX source location no longer points to a JSX element.");
|
|
1709
|
+
const importState = stylesImportState(sourceFile, paths.paths.importPath);
|
|
1710
|
+
if (!importState.ok) return failure$1(options.request.editId, importState.code, importState.message, importState.details);
|
|
1711
|
+
const classReplacement = styleModuleAttributeReplacement(sourceFile, options.sourceText, element, options.request.name, options.styledSystemPackageName);
|
|
1712
|
+
if (!classReplacement.ok) return failure$1(options.request.editId, classReplacement.code, classReplacement.message, classReplacement.details);
|
|
1713
|
+
const replacements = [classReplacement.replacement, ...classReplacement.imports];
|
|
1714
|
+
if (!importState.exists) replacements.push({
|
|
1715
|
+
range: {
|
|
1716
|
+
start: importInsertionPosition(sourceFile),
|
|
1717
|
+
end: importInsertionPosition(sourceFile)
|
|
1718
|
+
},
|
|
1719
|
+
text: `import styles from '${paths.paths.importPath}'\n`
|
|
1720
|
+
});
|
|
1721
|
+
const nextSource = applyReplacements$1(options.sourceText, replacements);
|
|
1722
|
+
const file = toRelativeProjectPath(options.filePath, options.projectRoot);
|
|
1723
|
+
return {
|
|
1724
|
+
ok: true,
|
|
1725
|
+
editId: options.request.editId,
|
|
1726
|
+
file,
|
|
1727
|
+
diff: focusedDiff$1(file, options.sourceText, nextSource, replacements),
|
|
1728
|
+
nextSource,
|
|
1729
|
+
written: false
|
|
1730
|
+
};
|
|
1731
|
+
}
|
|
1732
|
+
function createStyleModuleDetachPatch(options) {
|
|
1733
|
+
const nameFailure = validateStyleSourceName(options.request.name);
|
|
1734
|
+
if (nameFailure) return failure$1(options.request.editId, nameFailure.code, nameFailure.message);
|
|
1735
|
+
const parsed = parseSourceRef(options.request.jsxSource, "Style source removal requires a JSX source path with line and column.");
|
|
1736
|
+
if (!parsed.ok) return failure$1(options.request.editId, parsed.code, parsed.message, parsed.details);
|
|
1737
|
+
const normalizedFile = normalizePath$1(options.filePath);
|
|
1738
|
+
if (normalizedFile !== normalizePath$1(`${options.projectRoot.replace(/\/$/, "")}/${parsed.file}`)) return failure$1(options.request.editId, "stale-source", "JSX source location points to a different file.", {
|
|
1739
|
+
filePath: normalizedFile,
|
|
1740
|
+
jsxSource: options.request.jsxSource
|
|
1741
|
+
});
|
|
1742
|
+
const sourceFile = ts.createSourceFile(options.filePath, options.sourceText, ts.ScriptTarget.Latest, true, options.filePath.endsWith(".tsx") ? ts.ScriptKind.TSX : ts.ScriptKind.TS);
|
|
1743
|
+
const position = positionForLineColumn(sourceFile, parsed.line, parsed.column);
|
|
1744
|
+
if (position === void 0) return failure$1(options.request.editId, "stale-source", "JSX source location is outside the file.");
|
|
1745
|
+
const element = findJsxElementAtStart(sourceFile, position);
|
|
1746
|
+
if (!element) return failure$1(options.request.editId, "stale-source", "JSX source location no longer points to a JSX element.");
|
|
1747
|
+
const replacement = styleModuleAttributeDetachReplacement(sourceFile, options.sourceText, element, options.request.name);
|
|
1748
|
+
if (!replacement) return failure$1(options.request.editId, "unsupported-operation", "The selected style source could not be removed from this class expression.");
|
|
1749
|
+
const nextSource = applyReplacements$1(options.sourceText, [replacement]);
|
|
1750
|
+
const file = toRelativeProjectPath(options.filePath, options.projectRoot);
|
|
1751
|
+
return {
|
|
1752
|
+
ok: true,
|
|
1753
|
+
editId: options.request.editId,
|
|
1754
|
+
file,
|
|
1755
|
+
diff: focusedDiff$1(file, options.sourceText, nextSource, [replacement]),
|
|
1756
|
+
nextSource,
|
|
1757
|
+
written: false
|
|
1758
|
+
};
|
|
1759
|
+
}
|
|
1760
|
+
function parseStyleModuleSource(options) {
|
|
1761
|
+
const exportObject = findDefaultExportObject(ts.createSourceFile(options.filePath, options.sourceText, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS));
|
|
1762
|
+
if (!exportObject) return {
|
|
1763
|
+
ok: false,
|
|
1764
|
+
code: "unsupported-source-shape",
|
|
1765
|
+
message: "Style module sources require a direct export default { ... } object."
|
|
1766
|
+
};
|
|
1767
|
+
const analysis = analyzePandaCssSource({
|
|
1768
|
+
projectRoot: options.projectRoot,
|
|
1769
|
+
filePath: options.filePath,
|
|
1770
|
+
sourceText: options.sourceText,
|
|
1771
|
+
cssImportSources: options.cssImportSources
|
|
1772
|
+
});
|
|
1773
|
+
const entryByName = new Map(analysis.entries.map((entry) => [entry.name, entry]));
|
|
1774
|
+
return {
|
|
1775
|
+
ok: true,
|
|
1776
|
+
sources: exportObject.properties.flatMap((property) => {
|
|
1777
|
+
if (!ts.isPropertyAssignment(property)) return [];
|
|
1778
|
+
const name = propertyNameText$1(property.name);
|
|
1779
|
+
if (!name) return [];
|
|
1780
|
+
if (!ts.isCallExpression(property.initializer)) return [];
|
|
1781
|
+
if (!ts.isObjectLiteralExpression(property.initializer.arguments[0])) return [];
|
|
1782
|
+
const entry = entryByName.get(name);
|
|
1783
|
+
return [{
|
|
1784
|
+
name,
|
|
1785
|
+
editId: entry?.id,
|
|
1786
|
+
file: toRelativeProjectPath(options.filePath, options.projectRoot),
|
|
1787
|
+
sourceHash: entry?.sourceHash,
|
|
1788
|
+
...entry?.kind === "panda-css" ? { styleObject: entry.styleObject } : {}
|
|
1789
|
+
}];
|
|
1790
|
+
})
|
|
1791
|
+
};
|
|
1792
|
+
}
|
|
1793
|
+
function findDefaultExportObject(sourceFile) {
|
|
1794
|
+
for (const statement of sourceFile.statements) {
|
|
1795
|
+
if (!ts.isExportAssignment(statement) || statement.isExportEquals) continue;
|
|
1796
|
+
if (ts.isObjectLiteralExpression(statement.expression)) return statement.expression;
|
|
1797
|
+
}
|
|
1798
|
+
}
|
|
1799
|
+
function objectHasProperty(objectNode, name) {
|
|
1800
|
+
return objectNode.properties.some((property) => {
|
|
1801
|
+
if (!ts.isPropertyAssignment(property)) return false;
|
|
1802
|
+
return propertyNameText$1(property.name) === name;
|
|
1803
|
+
});
|
|
1804
|
+
}
|
|
1805
|
+
function styleObjectTextForSourceCreateEdits(edits) {
|
|
1806
|
+
if (!edits || edits.length === 0) return {
|
|
1807
|
+
ok: true,
|
|
1808
|
+
text: "{}"
|
|
1809
|
+
};
|
|
1810
|
+
const properties = [];
|
|
1811
|
+
for (const edit of edits) {
|
|
1812
|
+
if (edit.op !== "set") return {
|
|
1813
|
+
ok: false,
|
|
1814
|
+
code: "invalid-edit",
|
|
1815
|
+
message: "New style sources only support setting initial properties."
|
|
1816
|
+
};
|
|
1817
|
+
if (edit.path.length !== 1) return {
|
|
1818
|
+
ok: false,
|
|
1819
|
+
code: "unsupported-operation",
|
|
1820
|
+
message: "New style sources only support top-level initial properties."
|
|
1821
|
+
};
|
|
1822
|
+
const property = edit.path[0];
|
|
1823
|
+
if (!property || !/^[A-Za-z_$][\w$]*$/.test(property)) return {
|
|
1824
|
+
ok: false,
|
|
1825
|
+
code: "invalid-edit",
|
|
1826
|
+
message: "New style source property names must be valid identifiers."
|
|
1827
|
+
};
|
|
1828
|
+
properties.push(`${property}: ${JSON.stringify(edit.value)}`);
|
|
1829
|
+
}
|
|
1830
|
+
return {
|
|
1831
|
+
ok: true,
|
|
1832
|
+
text: `{ ${properties.join(", ")} }`
|
|
1833
|
+
};
|
|
1834
|
+
}
|
|
1835
|
+
function propertyNameText$1(name) {
|
|
1836
|
+
if (ts.isIdentifier(name)) return name.text;
|
|
1837
|
+
if (ts.isStringLiteral(name) || ts.isNumericLiteral(name)) return name.text;
|
|
1838
|
+
}
|
|
1839
|
+
function styleModuleAttributeReplacement(sourceFile, sourceText, element, name, styledSystemPackageName) {
|
|
1840
|
+
let classAttribute;
|
|
1841
|
+
for (const property of element.attributes.properties) {
|
|
1842
|
+
if (!ts.isJsxAttribute(property) || !ts.isIdentifier(property.name)) continue;
|
|
1843
|
+
if (property.name.text !== "class" && property.name.text !== "className") continue;
|
|
1844
|
+
classAttribute = property;
|
|
1845
|
+
break;
|
|
1846
|
+
}
|
|
1847
|
+
const styleReference = `styles.${name}`;
|
|
1848
|
+
if (!classAttribute) {
|
|
1849
|
+
const insertPosition = element.getEnd() - (ts.isJsxSelfClosingElement(element) ? 2 : 1);
|
|
1850
|
+
return {
|
|
1851
|
+
ok: true,
|
|
1852
|
+
replacement: {
|
|
1853
|
+
range: {
|
|
1854
|
+
start: insertPosition,
|
|
1855
|
+
end: insertPosition
|
|
1856
|
+
},
|
|
1857
|
+
text: ` class={${styleReference}}`
|
|
1858
|
+
},
|
|
1859
|
+
imports: []
|
|
1860
|
+
};
|
|
1861
|
+
}
|
|
1862
|
+
const attributeName = ts.isIdentifier(classAttribute.name) ? classAttribute.name.text : "class";
|
|
1863
|
+
if (!classAttribute.initializer) return {
|
|
1864
|
+
ok: true,
|
|
1865
|
+
replacement: {
|
|
1866
|
+
range: {
|
|
1867
|
+
start: classAttribute.getStart(sourceFile),
|
|
1868
|
+
end: classAttribute.getEnd()
|
|
1869
|
+
},
|
|
1870
|
+
text: `${attributeName}={${styleReference}}`
|
|
1871
|
+
},
|
|
1872
|
+
imports: []
|
|
1873
|
+
};
|
|
1874
|
+
const cx = cxImportState(sourceFile, sourceText, styledSystemPackageName);
|
|
1875
|
+
if (!cx.ok) return cx;
|
|
1876
|
+
if (ts.isStringLiteral(classAttribute.initializer)) return {
|
|
1877
|
+
ok: true,
|
|
1878
|
+
replacement: {
|
|
1879
|
+
range: {
|
|
1880
|
+
start: classAttribute.getStart(sourceFile),
|
|
1881
|
+
end: classAttribute.getEnd()
|
|
1882
|
+
},
|
|
1883
|
+
text: `${attributeName}={${cx.localName}(${JSON.stringify(classAttribute.initializer.text)}, ${styleReference})}`
|
|
1884
|
+
},
|
|
1885
|
+
imports: cx.imports
|
|
1886
|
+
};
|
|
1887
|
+
const expression = jsxAttributeExpression(classAttribute.initializer);
|
|
1888
|
+
if (!expression) return {
|
|
1889
|
+
ok: true,
|
|
1890
|
+
replacement: {
|
|
1891
|
+
range: {
|
|
1892
|
+
start: classAttribute.getStart(sourceFile),
|
|
1893
|
+
end: classAttribute.getEnd()
|
|
1894
|
+
},
|
|
1895
|
+
text: `${attributeName}={${styleReference}}`
|
|
1896
|
+
},
|
|
1897
|
+
imports: []
|
|
1898
|
+
};
|
|
1899
|
+
const expressionText = sourceText.slice(expression.getStart(sourceFile), expression.getEnd());
|
|
1900
|
+
const cxArgs = cxCallArguments(sourceFile, sourceText, expression, cx.localName, styleReference);
|
|
1901
|
+
return {
|
|
1902
|
+
ok: true,
|
|
1903
|
+
replacement: {
|
|
1904
|
+
range: {
|
|
1905
|
+
start: classAttribute.getStart(sourceFile),
|
|
1906
|
+
end: classAttribute.getEnd()
|
|
1907
|
+
},
|
|
1908
|
+
text: `${attributeName}={${cx.localName}(${cxArgs ?? `${expressionText}, ${styleReference}`})}`
|
|
1909
|
+
},
|
|
1910
|
+
imports: cx.imports
|
|
1911
|
+
};
|
|
1912
|
+
}
|
|
1913
|
+
function styleModuleAttributeDetachReplacement(sourceFile, sourceText, element, name) {
|
|
1914
|
+
let classAttribute;
|
|
1915
|
+
for (const property of element.attributes.properties) {
|
|
1916
|
+
if (!ts.isJsxAttribute(property) || !ts.isIdentifier(property.name)) continue;
|
|
1917
|
+
if (property.name.text !== "class" && property.name.text !== "className") continue;
|
|
1918
|
+
classAttribute = property;
|
|
1919
|
+
break;
|
|
1920
|
+
}
|
|
1921
|
+
if (!classAttribute?.initializer) return void 0;
|
|
1922
|
+
const expression = jsxAttributeExpression(classAttribute.initializer);
|
|
1923
|
+
if (!expression) return void 0;
|
|
1924
|
+
const styleReference = `styles.${name}`;
|
|
1925
|
+
const attributeName = ts.isIdentifier(classAttribute.name) ? classAttribute.name.text : "class";
|
|
1926
|
+
const replacementText = classExpressionAfterStyleReferenceRemoval(sourceFile, sourceText, expression, styleReference);
|
|
1927
|
+
if (replacementText === void 0) return void 0;
|
|
1928
|
+
return {
|
|
1929
|
+
range: {
|
|
1930
|
+
start: classAttribute.getStart(sourceFile),
|
|
1931
|
+
end: classAttribute.getEnd()
|
|
1932
|
+
},
|
|
1933
|
+
text: replacementText === "" ? "" : `${attributeName}={${replacementText}}`
|
|
1934
|
+
};
|
|
1935
|
+
}
|
|
1936
|
+
function cxImportState(sourceFile, sourceText, styledSystemPackageName) {
|
|
1937
|
+
const moduleSpecifier = `${styledSystemPackageName ?? "styled-system"}/css`;
|
|
1938
|
+
for (const statement of sourceFile.statements) {
|
|
1939
|
+
if (!ts.isImportDeclaration(statement)) continue;
|
|
1940
|
+
if (!ts.isStringLiteral(statement.moduleSpecifier)) continue;
|
|
1941
|
+
if (statement.moduleSpecifier.text !== moduleSpecifier) continue;
|
|
1942
|
+
const namedBindings = statement.importClause?.namedBindings;
|
|
1943
|
+
if (!namedBindings || !ts.isNamedImports(namedBindings)) return {
|
|
1944
|
+
ok: false,
|
|
1945
|
+
code: "unsupported-operation",
|
|
1946
|
+
message: "The styled-system css import must use named imports before cx can be added.",
|
|
1947
|
+
details: { moduleSpecifier }
|
|
1948
|
+
};
|
|
1949
|
+
for (const element of namedBindings.elements) if ((element.propertyName?.text ?? element.name.text) === "cx") return {
|
|
1950
|
+
ok: true,
|
|
1951
|
+
localName: element.name.text,
|
|
1952
|
+
imports: []
|
|
1953
|
+
};
|
|
1954
|
+
return {
|
|
1955
|
+
ok: true,
|
|
1956
|
+
localName: "cx",
|
|
1957
|
+
imports: [{
|
|
1958
|
+
range: {
|
|
1959
|
+
start: namedBindings.elements.end,
|
|
1960
|
+
end: namedBindings.elements.end
|
|
1961
|
+
},
|
|
1962
|
+
text: `${namedBindings.elements.length === 0 ? "" : ","} cx`
|
|
1963
|
+
}]
|
|
1964
|
+
};
|
|
1965
|
+
}
|
|
1966
|
+
if (identifierExists(sourceFile, "cx")) return {
|
|
1967
|
+
ok: false,
|
|
1968
|
+
code: "unsupported-operation",
|
|
1969
|
+
message: "The component module already uses the cx name.",
|
|
1970
|
+
details: { moduleSpecifier }
|
|
1971
|
+
};
|
|
1972
|
+
return {
|
|
1973
|
+
ok: true,
|
|
1974
|
+
localName: "cx",
|
|
1975
|
+
imports: [{
|
|
1976
|
+
range: {
|
|
1977
|
+
start: importInsertionPosition(sourceFile),
|
|
1978
|
+
end: importInsertionPosition(sourceFile)
|
|
1979
|
+
},
|
|
1980
|
+
text: `import { cx } from '${moduleSpecifier}'\n`
|
|
1981
|
+
}]
|
|
1982
|
+
};
|
|
1983
|
+
}
|
|
1984
|
+
function cxCallArguments(sourceFile, sourceText, expression, cxLocalName, styleReference) {
|
|
1985
|
+
if (!ts.isCallExpression(expression)) return void 0;
|
|
1986
|
+
if (!ts.isIdentifier(expression.expression) || expression.expression.text !== cxLocalName) return;
|
|
1987
|
+
return [...expression.arguments.map((argument) => sourceTextForNode(sourceFile, sourceText, argument)), styleReference].join(", ");
|
|
1988
|
+
}
|
|
1989
|
+
function identifierExists(sourceFile, name) {
|
|
1990
|
+
let exists = false;
|
|
1991
|
+
const visit = (node) => {
|
|
1992
|
+
if (exists) return;
|
|
1993
|
+
if (ts.isIdentifier(node) && node.text === name) {
|
|
1994
|
+
exists = true;
|
|
1995
|
+
return;
|
|
1996
|
+
}
|
|
1997
|
+
ts.forEachChild(node, visit);
|
|
1998
|
+
};
|
|
1999
|
+
visit(sourceFile);
|
|
2000
|
+
return exists;
|
|
2001
|
+
}
|
|
2002
|
+
function classExpressionAfterStyleReferenceRemoval(sourceFile, sourceText, expression, styleReference) {
|
|
2003
|
+
if (sourceTextForNode(sourceFile, sourceText, expression) === styleReference) return "";
|
|
2004
|
+
if (ts.isCallExpression(expression) && ts.isIdentifier(expression.expression) && expression.expression.text === "cx") {
|
|
2005
|
+
const remaining = expression.arguments.filter((argument) => sourceTextForNode(sourceFile, sourceText, argument) !== styleReference);
|
|
2006
|
+
if (remaining.length === expression.arguments.length) return void 0;
|
|
2007
|
+
if (remaining.length === 0) return "";
|
|
2008
|
+
if (remaining.length === 1) return sourceTextForNode(sourceFile, sourceText, remaining[0]);
|
|
2009
|
+
const remainingText = remaining.map((argument) => sourceTextForNode(sourceFile, sourceText, argument)).join(", ");
|
|
2010
|
+
return `${expression.expression.text}(${remainingText})`;
|
|
2011
|
+
}
|
|
2012
|
+
const array = ts.isArrayLiteralExpression(expression) ? expression : arrayLiteralFromClassJoinExpression(expression);
|
|
2013
|
+
if (!array || !ts.isArrayLiteralExpression(array)) return void 0;
|
|
2014
|
+
const remaining = array.elements.filter((element) => sourceTextForNode(sourceFile, sourceText, element) !== styleReference);
|
|
2015
|
+
if (remaining.length === array.elements.length) return void 0;
|
|
2016
|
+
if (remaining.length === 0) return "";
|
|
2017
|
+
if (remaining.length === 1) return sourceTextForNode(sourceFile, sourceText, remaining[0]);
|
|
2018
|
+
return `[${remaining.map((element) => sourceTextForNode(sourceFile, sourceText, element)).join(", ")}].filter(Boolean).join(' ')`;
|
|
2019
|
+
}
|
|
2020
|
+
function arrayLiteralFromClassJoinExpression(expression) {
|
|
2021
|
+
if (!ts.isCallExpression(expression)) return void 0;
|
|
2022
|
+
if (!ts.isPropertyAccessExpression(expression.expression)) return void 0;
|
|
2023
|
+
if (expression.expression.name.text !== "join") return void 0;
|
|
2024
|
+
const filterCall = expression.expression.expression;
|
|
2025
|
+
if (!ts.isCallExpression(filterCall)) return void 0;
|
|
2026
|
+
if (!ts.isPropertyAccessExpression(filterCall.expression)) return void 0;
|
|
2027
|
+
if (filterCall.expression.name.text !== "filter") return void 0;
|
|
2028
|
+
if (!ts.isIdentifier(filterCall.arguments[0]) || filterCall.arguments[0].text !== "Boolean") return;
|
|
2029
|
+
const array = filterCall.expression.expression;
|
|
2030
|
+
return ts.isArrayLiteralExpression(array) ? array : void 0;
|
|
2031
|
+
}
|
|
2032
|
+
function sourceTextForNode(sourceFile, sourceText, node) {
|
|
2033
|
+
return sourceText.slice(node.getStart(sourceFile), node.getEnd());
|
|
2034
|
+
}
|
|
2035
|
+
function stylesImportState(sourceFile, importPath) {
|
|
2036
|
+
for (const statement of sourceFile.statements) {
|
|
2037
|
+
if (!ts.isImportDeclaration(statement)) continue;
|
|
2038
|
+
const defaultImport = statement.importClause?.name;
|
|
2039
|
+
if (!defaultImport || defaultImport.text !== "styles") continue;
|
|
2040
|
+
if (ts.isStringLiteral(statement.moduleSpecifier) && statement.moduleSpecifier.text === importPath) return {
|
|
2041
|
+
ok: true,
|
|
2042
|
+
exists: true
|
|
2043
|
+
};
|
|
2044
|
+
return {
|
|
2045
|
+
ok: false,
|
|
2046
|
+
code: "unsupported-operation",
|
|
2047
|
+
message: "The component module already uses the styles import name for another module.",
|
|
2048
|
+
details: { importPath }
|
|
2049
|
+
};
|
|
2050
|
+
}
|
|
2051
|
+
let hasStylesIdentifier = false;
|
|
2052
|
+
const visit = (node) => {
|
|
2053
|
+
if (hasStylesIdentifier) return;
|
|
2054
|
+
if (ts.isIdentifier(node) && node.text === "styles") {
|
|
2055
|
+
hasStylesIdentifier = true;
|
|
2056
|
+
return;
|
|
2057
|
+
}
|
|
2058
|
+
ts.forEachChild(node, visit);
|
|
2059
|
+
};
|
|
2060
|
+
visit(sourceFile);
|
|
2061
|
+
if (hasStylesIdentifier) return {
|
|
2062
|
+
ok: false,
|
|
2063
|
+
code: "unsupported-operation",
|
|
2064
|
+
message: "The component module already uses the styles name.",
|
|
2065
|
+
details: { importPath }
|
|
2066
|
+
};
|
|
2067
|
+
return {
|
|
2068
|
+
ok: true,
|
|
2069
|
+
exists: false
|
|
2070
|
+
};
|
|
2071
|
+
}
|
|
2072
|
+
function findCssImportBinding(sourceFile, cssImportSources) {
|
|
2073
|
+
const importSources = new Set(cssImportSources ?? DEFAULT_CSS_IMPORT_SOURCES);
|
|
2074
|
+
for (const statement of sourceFile.statements) {
|
|
2075
|
+
if (!ts.isImportDeclaration(statement)) continue;
|
|
2076
|
+
if (!ts.isStringLiteral(statement.moduleSpecifier)) continue;
|
|
2077
|
+
if (!importSources.has(statement.moduleSpecifier.text)) continue;
|
|
2078
|
+
const namedBindings = statement.importClause?.namedBindings;
|
|
2079
|
+
if (!namedBindings || !ts.isNamedImports(namedBindings)) continue;
|
|
2080
|
+
for (const element of namedBindings.elements) if ((element.propertyName?.text ?? element.name.text) === "css") return { localName: element.name.text };
|
|
2081
|
+
}
|
|
2082
|
+
}
|
|
2083
|
+
function jsxAttributeExpression(initializer) {
|
|
2084
|
+
if (ts.isJsxExpression(initializer)) return initializer.expression;
|
|
2085
|
+
}
|
|
2086
|
+
function findJsxElementAtStart(sourceFile, position) {
|
|
2087
|
+
let result;
|
|
2088
|
+
const visit = (node) => {
|
|
2089
|
+
if (result) return;
|
|
2090
|
+
if ((ts.isJsxOpeningElement(node) || ts.isJsxSelfClosingElement(node)) && node.getStart(sourceFile) === position) {
|
|
2091
|
+
result = node;
|
|
2092
|
+
return;
|
|
2093
|
+
}
|
|
2094
|
+
ts.forEachChild(node, visit);
|
|
2095
|
+
};
|
|
2096
|
+
visit(sourceFile);
|
|
2097
|
+
return result;
|
|
2098
|
+
}
|
|
2099
|
+
function importInsertionPosition(sourceFile) {
|
|
2100
|
+
let position = 0;
|
|
2101
|
+
for (const statement of sourceFile.statements) {
|
|
2102
|
+
if (!ts.isImportDeclaration(statement)) break;
|
|
2103
|
+
position = statement.getEnd();
|
|
2104
|
+
}
|
|
2105
|
+
return position === 0 ? 0 : position + 1;
|
|
2106
|
+
}
|
|
2107
|
+
function cssImportText(cssLocalName, cssImportSources) {
|
|
2108
|
+
const moduleSpecifier = cssImportSources?.[0] ?? DEFAULT_CSS_IMPORT_SOURCES[0];
|
|
2109
|
+
return `import { ${cssLocalName === "css" ? "css" : `css as ${cssLocalName}`} } from '${moduleSpecifier}'\n`;
|
|
2110
|
+
}
|
|
2111
|
+
function parseSourceRef(source, message) {
|
|
2112
|
+
const match = /^(.*):(\d+):(\d+)$/.exec(source.trim());
|
|
2113
|
+
const file = match?.[1]?.trim();
|
|
2114
|
+
const line = match?.[2] ? Number.parseInt(match[2], 10) : void 0;
|
|
2115
|
+
const column = match?.[3] ? Number.parseInt(match[3], 10) : void 0;
|
|
2116
|
+
if (!match || !file || line === void 0 || column === void 0 || line < 1 || column < 1) return {
|
|
2117
|
+
ok: false,
|
|
2118
|
+
code: "invalid-edit",
|
|
2119
|
+
message,
|
|
2120
|
+
details: { source }
|
|
2121
|
+
};
|
|
2122
|
+
return {
|
|
2123
|
+
ok: true,
|
|
2124
|
+
file,
|
|
2125
|
+
line,
|
|
2126
|
+
column
|
|
2127
|
+
};
|
|
2128
|
+
}
|
|
2129
|
+
function positionForLineColumn(sourceFile, line, column) {
|
|
2130
|
+
const lineStart = sourceFile.getLineStarts()[line - 1];
|
|
2131
|
+
if (lineStart === void 0) return void 0;
|
|
2132
|
+
const position = lineStart + column - 1;
|
|
2133
|
+
return position <= sourceFile.text.length ? position : void 0;
|
|
2134
|
+
}
|
|
2135
|
+
function validateStyleSourceName(name) {
|
|
2136
|
+
if (/^[A-Za-z_$][\w$]*$/.test(name)) return void 0;
|
|
2137
|
+
return {
|
|
2138
|
+
code: "invalid-edit",
|
|
2139
|
+
message: "Style source names must be valid JavaScript identifiers."
|
|
2140
|
+
};
|
|
2141
|
+
}
|
|
2142
|
+
function applyReplacements$1(sourceText, replacements) {
|
|
2143
|
+
let nextSource = sourceText;
|
|
2144
|
+
for (const replacement of [...replacements].sort((left, right) => right.range.start - left.range.start)) nextSource = `${nextSource.slice(0, replacement.range.start)}${replacement.text}${nextSource.slice(replacement.range.end)}`;
|
|
2145
|
+
return nextSource;
|
|
2146
|
+
}
|
|
2147
|
+
function nextFileDiff(file, nextSource) {
|
|
2148
|
+
return [
|
|
2149
|
+
`--- /dev/null`,
|
|
2150
|
+
`+++ b/${file}`,
|
|
2151
|
+
...nextSource.split("\n").map((line) => `+${line}`)
|
|
2152
|
+
].join("\n");
|
|
2153
|
+
}
|
|
2154
|
+
function focusedDiff$1(file, sourceText, nextSource, replacements) {
|
|
2155
|
+
const firstStart = Math.min(...replacements.map((replacement) => replacement.range.start));
|
|
2156
|
+
const lastEnd = Math.max(...replacements.map((replacement) => replacement.range.end));
|
|
2157
|
+
const oldStartLine = lineNumberAt$1(sourceText, firstStart);
|
|
2158
|
+
const oldEndLine = lineNumberAt$1(sourceText, lastEnd);
|
|
2159
|
+
const contextStartLine = Math.max(1, oldStartLine - 2);
|
|
2160
|
+
const contextEndLine = oldEndLine + 2;
|
|
2161
|
+
const oldLines = sourceText.split("\n");
|
|
2162
|
+
const newLines = nextSource.split("\n");
|
|
2163
|
+
const oldSlice = oldLines.slice(contextStartLine - 1, contextEndLine);
|
|
2164
|
+
const newSlice = newLines.slice(contextStartLine - 1, contextEndLine + (newLines.length - oldLines.length));
|
|
2165
|
+
return [
|
|
2166
|
+
`--- a/${file}`,
|
|
2167
|
+
`+++ b/${file}`,
|
|
2168
|
+
`@@ -${contextStartLine},${oldSlice.length} +${contextStartLine},${newSlice.length} @@`,
|
|
2169
|
+
...oldSlice.map((line) => `-${line}`),
|
|
2170
|
+
...newSlice.map((line) => `+${line}`)
|
|
2171
|
+
].join("\n");
|
|
2172
|
+
}
|
|
2173
|
+
function lineNumberAt$1(sourceText, position) {
|
|
2174
|
+
let line = 1;
|
|
2175
|
+
for (let index = 0; index < position; index += 1) if (sourceText.charCodeAt(index) === 10) line += 1;
|
|
2176
|
+
return line;
|
|
2177
|
+
}
|
|
2178
|
+
function normalizePath$1(path) {
|
|
2179
|
+
return path.replace(/\\/g, "/");
|
|
2180
|
+
}
|
|
2181
|
+
function failure$1(editId, code, message, details) {
|
|
2182
|
+
return {
|
|
2183
|
+
ok: false,
|
|
2184
|
+
editId,
|
|
2185
|
+
written: false,
|
|
2186
|
+
error: {
|
|
2187
|
+
code,
|
|
2188
|
+
message,
|
|
2189
|
+
details
|
|
2190
|
+
}
|
|
2191
|
+
};
|
|
2192
|
+
}
|
|
2193
|
+
//#endregion
|
|
2194
|
+
//#region src/patcher/tokenConfigPatcher.ts
|
|
2195
|
+
function createTokenConfigPatch(options) {
|
|
2196
|
+
const { request, sourceText, tokenSource } = options;
|
|
2197
|
+
const validation = validateTokenPatchRequest(options);
|
|
2198
|
+
if (validation) return failure(request.editId, validation.code, validation.message, validation.details);
|
|
2199
|
+
const range = tokenSource.range ?? rangeFromTokenSourceId(tokenSource.id);
|
|
2200
|
+
if (!range) return failure(request.editId, "unsupported-source-shape", "Token source target does not include a source range.");
|
|
2201
|
+
if (hashSource(sourceText.slice(range.start, range.end)) !== tokenSource.sourceHash) return failure(request.editId, "stale-source", "Source text no longer matches the token source hash.");
|
|
2202
|
+
if (request.options?.expectedSourceHash && request.options.expectedSourceHash !== tokenSource.sourceHash) return failure(request.editId, "stale-source", "Edit request was prepared against a different token source hash.", {
|
|
2203
|
+
expectedSourceHash: request.options.expectedSourceHash,
|
|
2204
|
+
actualSourceHash: tokenSource.sourceHash
|
|
2205
|
+
});
|
|
2206
|
+
const sourceFile = ts.createSourceFile(tokenSource.absoluteFile ?? tokenSource.file, sourceText, ts.ScriptTarget.Latest, true, tokenSource.file.endsWith(".tsx") ? ts.ScriptKind.TSX : ts.ScriptKind.TS);
|
|
2207
|
+
const objectNode = findObjectLiteralAtRange(sourceFile, range);
|
|
2208
|
+
if (!objectNode) return failure(request.editId, "stale-source", "Token source range no longer points to an object literal.");
|
|
2209
|
+
const safety = tokenObjectWriteSafety(objectNode);
|
|
2210
|
+
if (!safety.writable) return failure(request.editId, "unsupported-source-shape", safety.reason);
|
|
2211
|
+
const replacement = replacementForTokenPath(objectNode, request, sourceFile, sourceText);
|
|
2212
|
+
if (!replacement.ok) return failure(request.editId, replacement.reason.code, replacement.reason.message, replacement.reason.details);
|
|
2213
|
+
const nextSource = applyReplacements(sourceText, [replacement.replacement]);
|
|
2214
|
+
return {
|
|
2215
|
+
ok: true,
|
|
2216
|
+
editId: request.editId,
|
|
2217
|
+
file: tokenSource.file,
|
|
2218
|
+
diff: focusedDiff(tokenSource.file, sourceText, nextSource, [replacement.replacement]),
|
|
2219
|
+
nextSource,
|
|
2220
|
+
written: false
|
|
2221
|
+
};
|
|
2222
|
+
}
|
|
2223
|
+
function validateTokenPatchRequest(options) {
|
|
2224
|
+
const { projectRoot, request, tokenSource } = options;
|
|
2225
|
+
if (request.kind !== "panda-token-config") return {
|
|
2226
|
+
code: "unsupported-operation",
|
|
2227
|
+
message: "Only Panda token config edits can be patched."
|
|
2228
|
+
};
|
|
2229
|
+
if (request.sourceTargetId !== tokenSource.id) return {
|
|
2230
|
+
code: "manifest-entry-not-found",
|
|
2231
|
+
message: "Token edit request does not match the provided token source target.",
|
|
2232
|
+
details: { sourceTargetId: request.sourceTargetId }
|
|
2233
|
+
};
|
|
2234
|
+
if (!tokenSource.writable || !tokenSource.writableSections.includes(request.section)) return {
|
|
2235
|
+
code: "unsupported-source-shape",
|
|
2236
|
+
message: tokenSource.readonlyReason ?? "Token source is not writable."
|
|
2237
|
+
};
|
|
2238
|
+
if (request.path.length === 0 || request.path.some((part) => part.length === 0)) return {
|
|
2239
|
+
code: "invalid-path",
|
|
2240
|
+
message: "Token edits require a non-empty token path.",
|
|
2241
|
+
details: { path: [...request.path] }
|
|
2242
|
+
};
|
|
2243
|
+
if (!isJsonPrimitive(request.value)) return {
|
|
2244
|
+
code: "unsupported-operation",
|
|
2245
|
+
message: "Token config patching only supports literal primitive values.",
|
|
2246
|
+
details: { path: [...request.path] }
|
|
2247
|
+
};
|
|
2248
|
+
return validateProjectFile(projectRoot, tokenSource, options.filePath);
|
|
2249
|
+
}
|
|
2250
|
+
function validateProjectFile(projectRoot, tokenSource, filePath) {
|
|
2251
|
+
const root = normalizePath(projectRoot).replace(/\/$/, "");
|
|
2252
|
+
if (!root) return {
|
|
2253
|
+
code: "path-outside-project",
|
|
2254
|
+
message: "Project root is empty."
|
|
2255
|
+
};
|
|
2256
|
+
const absoluteFile = normalizePath(tokenSource.absoluteFile ?? `${root}/${tokenSource.file}`);
|
|
2257
|
+
if (absoluteFile !== root && !absoluteFile.startsWith(`${root}/`)) return {
|
|
2258
|
+
code: "path-outside-project",
|
|
2259
|
+
message: "Token source points outside the project root.",
|
|
2260
|
+
details: {
|
|
2261
|
+
file: absoluteFile,
|
|
2262
|
+
projectRoot: root
|
|
2263
|
+
}
|
|
2264
|
+
};
|
|
2265
|
+
if (filePath && normalizePath(filePath) !== absoluteFile) return {
|
|
2266
|
+
code: "stale-source",
|
|
2267
|
+
message: "Provided source file does not match the token source file.",
|
|
2268
|
+
details: {
|
|
2269
|
+
filePath: normalizePath(filePath),
|
|
2270
|
+
tokenSourceFile: absoluteFile
|
|
2271
|
+
}
|
|
2272
|
+
};
|
|
2273
|
+
}
|
|
2274
|
+
function replacementForTokenPath(objectNode, request, sourceFile, sourceText) {
|
|
2275
|
+
let current = objectNode;
|
|
2276
|
+
const { path } = request;
|
|
2277
|
+
for (let index = 0; index < path.length; index += 1) {
|
|
2278
|
+
const part = path[index];
|
|
2279
|
+
const located = locateProperty(current, part);
|
|
2280
|
+
if (!located.ok) {
|
|
2281
|
+
if (located.reason.code === "invalid-path") return {
|
|
2282
|
+
ok: true,
|
|
2283
|
+
replacement: insertionForMissingTokenPath(current, path.slice(index), request.value, sourceFile, sourceText)
|
|
2284
|
+
};
|
|
2285
|
+
return {
|
|
2286
|
+
ok: false,
|
|
2287
|
+
reason: located.reason
|
|
2288
|
+
};
|
|
2289
|
+
}
|
|
2290
|
+
const initializer = located.property.initializer;
|
|
2291
|
+
const tokenObject = objectLiteralExpression(initializer);
|
|
2292
|
+
if (!tokenObject) return {
|
|
2293
|
+
ok: false,
|
|
2294
|
+
reason: {
|
|
2295
|
+
code: "unsupported-source-shape",
|
|
2296
|
+
message: "Token path descends through a non-object value.",
|
|
2297
|
+
details: { path: path.slice(0, index + 1) }
|
|
2298
|
+
}
|
|
2299
|
+
};
|
|
2300
|
+
if (index === path.length - 1) {
|
|
2301
|
+
const valueProperty = locateProperty(tokenObject, "value");
|
|
2302
|
+
if (!valueProperty.ok) return {
|
|
2303
|
+
ok: true,
|
|
2304
|
+
replacement: insertionForMissingProperty(tokenObject, "value", literalSource(request.value), sourceFile, sourceText)
|
|
2305
|
+
};
|
|
2306
|
+
const valueInitializer = valueProperty.property.initializer;
|
|
2307
|
+
if (!isPatchableLiteral(valueInitializer)) return {
|
|
2308
|
+
ok: false,
|
|
2309
|
+
reason: {
|
|
2310
|
+
code: "unsupported-source-shape",
|
|
2311
|
+
message: "Existing token value is not a literal primitive.",
|
|
2312
|
+
details: { path: [...path] }
|
|
2313
|
+
}
|
|
2314
|
+
};
|
|
2315
|
+
return {
|
|
2316
|
+
ok: true,
|
|
2317
|
+
replacement: {
|
|
2318
|
+
range: {
|
|
2319
|
+
start: valueInitializer.getStart(sourceFile),
|
|
2320
|
+
end: valueInitializer.getEnd()
|
|
2321
|
+
},
|
|
2322
|
+
text: literalSource(request.value)
|
|
2323
|
+
}
|
|
2324
|
+
};
|
|
2325
|
+
}
|
|
2326
|
+
if (locateProperty(tokenObject, "value").ok) return {
|
|
2327
|
+
ok: false,
|
|
2328
|
+
reason: {
|
|
2329
|
+
code: "invalid-path",
|
|
2330
|
+
message: "Token path descends through an existing token leaf.",
|
|
2331
|
+
details: { path: path.slice(0, index + 1) }
|
|
2332
|
+
}
|
|
2333
|
+
};
|
|
2334
|
+
current = tokenObject;
|
|
2335
|
+
}
|
|
2336
|
+
return {
|
|
2337
|
+
ok: false,
|
|
2338
|
+
reason: {
|
|
2339
|
+
code: "invalid-path",
|
|
2340
|
+
message: "Token edits require a non-empty token path."
|
|
2341
|
+
}
|
|
2342
|
+
};
|
|
2343
|
+
}
|
|
2344
|
+
function insertionForMissingTokenPath(objectNode, missingPath, value, sourceFile, sourceText) {
|
|
2345
|
+
const multiline = sourceText.slice(objectNode.getStart(sourceFile), objectNode.getEnd()).includes("\n");
|
|
2346
|
+
const fallbackChildIndent = `${indentationBefore(sourceText, objectNode.getStart(sourceFile))} `;
|
|
2347
|
+
const lastProperty = objectNode.properties[objectNode.properties.length - 1];
|
|
2348
|
+
return insertionForMissingProperty(objectNode, tokenBranchSource(missingPath, value, multiline, lastProperty ? indentationBefore(sourceText, lastProperty.getStart(sourceFile)) : fallbackChildIndent), void 0, sourceFile, sourceText);
|
|
2349
|
+
}
|
|
2350
|
+
function insertionForMissingProperty(objectNode, propertyNameOrSource, valueSource, sourceFile, sourceText) {
|
|
2351
|
+
const closeBrace = objectNode.getEnd() - 1;
|
|
2352
|
+
const multiline = sourceText.slice(objectNode.getStart(sourceFile), objectNode.getEnd()).includes("\n");
|
|
2353
|
+
const property = valueSource ? `${propertyNameSource(propertyNameOrSource)}: ${valueSource}` : propertyNameOrSource;
|
|
2354
|
+
if (objectNode.properties.length === 0) {
|
|
2355
|
+
if (!multiline) return {
|
|
2356
|
+
range: {
|
|
2357
|
+
start: closeBrace,
|
|
2358
|
+
end: closeBrace
|
|
2359
|
+
},
|
|
2360
|
+
text: property
|
|
2361
|
+
};
|
|
2362
|
+
const parentIndent = indentationBefore(sourceText, objectNode.getStart(sourceFile));
|
|
2363
|
+
const childIndent = `${parentIndent} `;
|
|
2364
|
+
return {
|
|
2365
|
+
range: {
|
|
2366
|
+
start: closeBrace,
|
|
2367
|
+
end: closeBrace
|
|
2368
|
+
},
|
|
2369
|
+
text: `\n${childIndent}${property},\n${parentIndent}`
|
|
2370
|
+
};
|
|
2371
|
+
}
|
|
2372
|
+
const lastProperty = objectNode.properties[objectNode.properties.length - 1];
|
|
2373
|
+
const trailingComma = commaAfterNode(lastProperty, objectNode, sourceText);
|
|
2374
|
+
if (!multiline) {
|
|
2375
|
+
const insertAt = trailingComma ?? lastProperty.getEnd();
|
|
2376
|
+
return {
|
|
2377
|
+
range: {
|
|
2378
|
+
start: insertAt,
|
|
2379
|
+
end: insertAt
|
|
2380
|
+
},
|
|
2381
|
+
text: trailingComma === void 0 ? `, ${property}` : ` ${property}`
|
|
2382
|
+
};
|
|
2383
|
+
}
|
|
2384
|
+
const childIndent = indentationBefore(sourceText, lastProperty.getStart(sourceFile));
|
|
2385
|
+
if (trailingComma !== void 0) return {
|
|
2386
|
+
range: {
|
|
2387
|
+
start: trailingComma + 1,
|
|
2388
|
+
end: trailingComma + 1
|
|
2389
|
+
},
|
|
2390
|
+
text: `\n${childIndent}${property},`
|
|
2391
|
+
};
|
|
2392
|
+
return {
|
|
2393
|
+
range: {
|
|
2394
|
+
start: lastProperty.getEnd(),
|
|
2395
|
+
end: lastProperty.getEnd()
|
|
2396
|
+
},
|
|
2397
|
+
text: `,\n${childIndent}${property}`
|
|
2398
|
+
};
|
|
2399
|
+
}
|
|
2400
|
+
function tokenBranchSource(path, value, multiline, indent) {
|
|
2401
|
+
const [first, ...rest] = path;
|
|
2402
|
+
if (!first) return "";
|
|
2403
|
+
if (rest.length === 0) return `${propertyNameSource(first)}: { value: ${literalSource(value)} }`;
|
|
2404
|
+
if (!multiline) return `${propertyNameSource(first)}: { ${tokenBranchSource(rest, value, false, indent)} }`;
|
|
2405
|
+
const childIndent = `${indent} `;
|
|
2406
|
+
const nested = tokenBranchSource(rest, value, true, childIndent);
|
|
2407
|
+
return `${propertyNameSource(first)}: {\n${childIndent}${nested},\n${indent}}`;
|
|
2408
|
+
}
|
|
2409
|
+
function findObjectLiteralAtRange(sourceFile, range) {
|
|
2410
|
+
let result;
|
|
2411
|
+
const visit = (node) => {
|
|
2412
|
+
if (ts.isObjectLiteralExpression(node) && node.getStart(sourceFile) === range.start && node.getEnd() === range.end) {
|
|
2413
|
+
result = node;
|
|
2414
|
+
return;
|
|
2415
|
+
}
|
|
2416
|
+
ts.forEachChild(node, visit);
|
|
2417
|
+
};
|
|
2418
|
+
visit(sourceFile);
|
|
2419
|
+
return result;
|
|
2420
|
+
}
|
|
2421
|
+
function tokenObjectWriteSafety(object) {
|
|
2422
|
+
const keys = /* @__PURE__ */ new Set();
|
|
2423
|
+
for (const property of object.properties) {
|
|
2424
|
+
if (!ts.isPropertyAssignment(property)) return {
|
|
2425
|
+
writable: false,
|
|
2426
|
+
reason: "Token object contains a spread, shorthand, method, or accessor."
|
|
2427
|
+
};
|
|
2428
|
+
const key = propertyNameText(property.name);
|
|
2429
|
+
if (!key) return {
|
|
2430
|
+
writable: false,
|
|
2431
|
+
reason: "Token object contains a computed key."
|
|
2432
|
+
};
|
|
2433
|
+
if (keys.has(key)) return {
|
|
2434
|
+
writable: false,
|
|
2435
|
+
reason: "Token object contains duplicate keys."
|
|
2436
|
+
};
|
|
2437
|
+
keys.add(key);
|
|
2438
|
+
const valueObject = objectLiteralExpression(property.initializer);
|
|
2439
|
+
if (!valueObject) return {
|
|
2440
|
+
writable: false,
|
|
2441
|
+
reason: "Token object contains a non-object token branch."
|
|
2442
|
+
};
|
|
2443
|
+
if (!objectProperty(valueObject, "value")) {
|
|
2444
|
+
const nestedSafety = tokenObjectWriteSafety(valueObject);
|
|
2445
|
+
if (!nestedSafety.writable) return nestedSafety;
|
|
2446
|
+
}
|
|
2447
|
+
}
|
|
2448
|
+
return { writable: true };
|
|
2449
|
+
}
|
|
2450
|
+
function locateProperty(objectNode, propertyName) {
|
|
2451
|
+
const matches = [];
|
|
2452
|
+
for (const property of objectNode.properties) {
|
|
2453
|
+
if (!ts.isPropertyAssignment(property)) return {
|
|
2454
|
+
ok: false,
|
|
2455
|
+
reason: {
|
|
2456
|
+
code: "unsupported-source-shape",
|
|
2457
|
+
message: "Only plain property assignments can be patched."
|
|
2458
|
+
}
|
|
2459
|
+
};
|
|
2460
|
+
const name = propertyNameText(property.name);
|
|
2461
|
+
if (name === void 0) return {
|
|
2462
|
+
ok: false,
|
|
2463
|
+
reason: {
|
|
2464
|
+
code: "unsupported-source-shape",
|
|
2465
|
+
message: "Computed property names are not safe to patch."
|
|
2466
|
+
}
|
|
2467
|
+
};
|
|
2468
|
+
if (name === propertyName) matches.push(property);
|
|
2469
|
+
}
|
|
2470
|
+
if (matches.length > 1) return {
|
|
2471
|
+
ok: false,
|
|
2472
|
+
reason: {
|
|
2473
|
+
code: "ambiguous-target",
|
|
2474
|
+
message: "Token source contains duplicate keys for the requested path.",
|
|
2475
|
+
details: { property: propertyName }
|
|
2476
|
+
}
|
|
2477
|
+
};
|
|
2478
|
+
const property = matches[0];
|
|
2479
|
+
if (!property) return {
|
|
2480
|
+
ok: false,
|
|
2481
|
+
reason: {
|
|
2482
|
+
code: "invalid-path",
|
|
2483
|
+
message: "Token source does not contain the requested path.",
|
|
2484
|
+
details: { property: propertyName }
|
|
2485
|
+
}
|
|
2486
|
+
};
|
|
2487
|
+
return {
|
|
2488
|
+
ok: true,
|
|
2489
|
+
property
|
|
2490
|
+
};
|
|
2491
|
+
}
|
|
2492
|
+
function objectProperty(objectNode, propertyName) {
|
|
2493
|
+
for (const property of objectNode.properties) {
|
|
2494
|
+
if (!ts.isPropertyAssignment(property)) continue;
|
|
2495
|
+
if (propertyNameText(property.name) === propertyName) return property.initializer;
|
|
2496
|
+
}
|
|
2497
|
+
}
|
|
2498
|
+
function objectLiteralExpression(expression) {
|
|
2499
|
+
if (ts.isObjectLiteralExpression(expression)) return expression;
|
|
2500
|
+
if (ts.isSatisfiesExpression(expression) || ts.isAsExpression(expression)) return objectLiteralExpression(expression.expression);
|
|
2501
|
+
}
|
|
2502
|
+
function propertyNameText(name) {
|
|
2503
|
+
if (ts.isIdentifier(name) || ts.isStringLiteral(name) || ts.isNumericLiteral(name)) return name.text;
|
|
2504
|
+
}
|
|
2505
|
+
function isPatchableLiteral(node) {
|
|
2506
|
+
return ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node) || ts.isNumericLiteral(node) || node.kind === ts.SyntaxKind.TrueKeyword || node.kind === ts.SyntaxKind.FalseKeyword || node.kind === ts.SyntaxKind.NullKeyword || ts.isPrefixUnaryExpression(node) && node.operator === ts.SyntaxKind.MinusToken && ts.isNumericLiteral(node.operand);
|
|
2507
|
+
}
|
|
2508
|
+
function isJsonPrimitive(value) {
|
|
2509
|
+
return value === null || [
|
|
2510
|
+
"string",
|
|
2511
|
+
"number",
|
|
2512
|
+
"boolean"
|
|
2513
|
+
].includes(typeof value);
|
|
2514
|
+
}
|
|
2515
|
+
function literalSource(value) {
|
|
2516
|
+
if (typeof value === "string") return `'${value.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
|
|
2517
|
+
if (typeof value === "number") return Number.isFinite(value) ? String(value) : "null";
|
|
2518
|
+
if (typeof value === "boolean") return value ? "true" : "false";
|
|
2519
|
+
return "null";
|
|
2520
|
+
}
|
|
2521
|
+
function propertyNameSource(propertyName) {
|
|
2522
|
+
if (/^\d+$/.test(propertyName)) return propertyName;
|
|
2523
|
+
return /^[$A-Z_a-z][$\w]*$/.test(propertyName) ? propertyName : JSON.stringify(propertyName);
|
|
2524
|
+
}
|
|
2525
|
+
function commaAfterNode(node, objectNode, sourceText) {
|
|
2526
|
+
for (let index = node.getEnd(); index < objectNode.getEnd() - 1; index += 1) {
|
|
2527
|
+
const char = sourceText[index];
|
|
2528
|
+
if (char === ",") return index;
|
|
2529
|
+
if (!isWhitespace(char)) return void 0;
|
|
2530
|
+
}
|
|
2531
|
+
}
|
|
2532
|
+
function isWhitespace(char) {
|
|
2533
|
+
return char === " " || char === " " || char === "\n" || char === "\r";
|
|
2534
|
+
}
|
|
2535
|
+
function indentationBefore(sourceText, position) {
|
|
2536
|
+
const lineStart = sourceText.lastIndexOf("\n", position - 1) + 1;
|
|
2537
|
+
return sourceText.slice(lineStart, position).match(/^[ \t]*/)?.[0] ?? "";
|
|
2538
|
+
}
|
|
2539
|
+
function rangeFromTokenSourceId(id) {
|
|
2540
|
+
const parts = id.split("#");
|
|
2541
|
+
const start = Number(parts[parts.length - 2]);
|
|
2542
|
+
const end = Number(parts[parts.length - 1]);
|
|
2543
|
+
if (!Number.isInteger(start) || !Number.isInteger(end) || start < 0 || end < start) return;
|
|
2544
|
+
return {
|
|
2545
|
+
start,
|
|
2546
|
+
end
|
|
2547
|
+
};
|
|
2548
|
+
}
|
|
2549
|
+
function applyReplacements(sourceText, replacements) {
|
|
2550
|
+
let nextSource = sourceText;
|
|
2551
|
+
for (const replacement of [...replacements].sort((left, right) => right.range.start - left.range.start)) nextSource = `${nextSource.slice(0, replacement.range.start)}${replacement.text}${nextSource.slice(replacement.range.end)}`;
|
|
2552
|
+
return nextSource;
|
|
2553
|
+
}
|
|
2554
|
+
function focusedDiff(file, sourceText, nextSource, replacements) {
|
|
2555
|
+
const firstStart = Math.min(...replacements.map((replacement) => replacement.range.start));
|
|
2556
|
+
const lastEnd = Math.max(...replacements.map((replacement) => replacement.range.end));
|
|
2557
|
+
const oldStartLine = lineNumberAt(sourceText, firstStart);
|
|
2558
|
+
const oldEndLine = lineNumberAt(sourceText, lastEnd);
|
|
2559
|
+
const contextStartLine = Math.max(1, oldStartLine - 2);
|
|
2560
|
+
const contextEndLine = oldEndLine + 2;
|
|
2561
|
+
const oldLines = sourceText.split("\n");
|
|
2562
|
+
const newLines = nextSource.split("\n");
|
|
2563
|
+
const oldSlice = oldLines.slice(contextStartLine - 1, contextEndLine);
|
|
2564
|
+
const newSlice = newLines.slice(contextStartLine - 1, contextEndLine + (newLines.length - oldLines.length));
|
|
2565
|
+
return [
|
|
2566
|
+
`--- a/${file}`,
|
|
2567
|
+
`+++ b/${file}`,
|
|
2568
|
+
`@@ -${contextStartLine},${oldSlice.length} +${contextStartLine},${newSlice.length} @@`,
|
|
2569
|
+
...oldSlice.map((line) => `-${line}`),
|
|
2570
|
+
...newSlice.map((line) => `+${line}`)
|
|
2571
|
+
].join("\n");
|
|
2572
|
+
}
|
|
2573
|
+
function lineNumberAt(sourceText, position) {
|
|
2574
|
+
let line = 1;
|
|
2575
|
+
for (let index = 0; index < position; index += 1) if (sourceText.charCodeAt(index) === 10) line += 1;
|
|
2576
|
+
return line;
|
|
2577
|
+
}
|
|
2578
|
+
function normalizePath(path) {
|
|
2579
|
+
return path.replace(/\\/g, "/");
|
|
2580
|
+
}
|
|
2581
|
+
function failure(editId, code, message, details) {
|
|
2582
|
+
return {
|
|
2583
|
+
ok: false,
|
|
2584
|
+
editId,
|
|
2585
|
+
written: false,
|
|
2586
|
+
error: {
|
|
2587
|
+
code,
|
|
2588
|
+
message,
|
|
2589
|
+
details
|
|
2590
|
+
}
|
|
2591
|
+
};
|
|
2592
|
+
}
|
|
2593
|
+
//#endregion
|
|
2594
|
+
export { tsxSourceParserAdapter as C, tsrxSourceParserAdapter as S, typescriptPandaAstAdapter as T, hashSource as _, createStyleModuleSourcePatch as a, parseWithSourceParserAdapter as b, safeProjectSourcePath as c, trustedManifestFilePath as d, trustedTokenSourceFilePath as f, analyzePandaCssSource as g, createInlineCssSourcePatch as h, createStyleModuleDetachPatch as i, stripViteQuery as l, createStaticCssPatch as m, componentStyleModulePaths as n, readComponentStyleModule as o, createStaticCssBatchPatch as p, createStyleModuleAttachPatch as r, normalizePath$2 as s, createTokenConfigPatch as t, toRelativeProjectPath as u, createEstreePandaAstAdapter as v, tsxSourceSyntaxAdapter as w, resolveSourceParserAdapter as x, parsePandaSource as y };
|