@codecanvas-ide/engine 0.2.0 → 0.3.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/README.md +2 -0
- package/dist/codecanvas-engine.js +144 -136
- package/dist/index.js +271 -104
- package/dist/index.js.map +4 -4
- package/dist/types/canvas.d.ts +1 -0
- package/dist/types/layoutRules.d.ts +1 -1
- package/dist/types/parse.d.ts +6 -1
- package/dist/types/react.d.ts +30 -0
- package/dist/types/styles.d.ts +1 -1
- package/dist/types/syntax.d.ts +3 -0
- package/dist/types/types.d.ts +15 -2
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -1,5 +1,101 @@
|
|
|
1
1
|
// src/parse.ts
|
|
2
|
+
import ts3 from "typescript";
|
|
3
|
+
|
|
4
|
+
// src/react.ts
|
|
5
|
+
import ts2 from "typescript";
|
|
6
|
+
|
|
7
|
+
// src/syntax.ts
|
|
2
8
|
import ts from "typescript";
|
|
9
|
+
function unwrap(e) {
|
|
10
|
+
while (ts.isParenthesizedExpression(e) || ts.isAsExpression(e) || ts.isSatisfiesExpression(e) || ts.isTypeAssertionExpression(e)) e = e.expression;
|
|
11
|
+
return e;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// src/react.ts
|
|
15
|
+
var REACT_MODULE = /^(react|react-dom|react-native)(\/|$)/;
|
|
16
|
+
function fileLanguageOf(path, sf) {
|
|
17
|
+
const script = /\.[cm]?jsx?$/.test(path) ? "js" : "ts";
|
|
18
|
+
const hasJsxExtension = /\.[jt]sx$/.test(path);
|
|
19
|
+
const mayHaveJsx = script === "js" && /<[A-Za-z>]/.test(sf.text);
|
|
20
|
+
const hasJsx = hasJsxExtension || mayHaveJsx && containsJsx(sf);
|
|
21
|
+
const isReact = hasJsx || importsReact(sf);
|
|
22
|
+
return { script, hasJsx, isReact, label: languageLabel(script, hasJsx, isReact) };
|
|
23
|
+
}
|
|
24
|
+
function languageLabel(script, hasJsx, isReact) {
|
|
25
|
+
if (hasJsx) return script === "js" ? "React JSX" : "React TSX";
|
|
26
|
+
if (isReact) return script === "js" ? "React JS" : "React TS";
|
|
27
|
+
return script === "js" ? "JS" : "TS";
|
|
28
|
+
}
|
|
29
|
+
function isJsx(node) {
|
|
30
|
+
return ts2.isJsxElement(node) || ts2.isJsxSelfClosingElement(node) || ts2.isJsxFragment(node);
|
|
31
|
+
}
|
|
32
|
+
function containsJsx(node) {
|
|
33
|
+
return isJsx(node) || !!ts2.forEachChild(node, (child) => containsJsx(child) || void 0);
|
|
34
|
+
}
|
|
35
|
+
function importsReact(sf) {
|
|
36
|
+
return sf.statements.some((statement) => {
|
|
37
|
+
const isImportOrReExport = ts2.isImportDeclaration(statement) || ts2.isExportDeclaration(statement);
|
|
38
|
+
if (isImportOrReExport) return !!statement.moduleSpecifier && ts2.isStringLiteral(statement.moduleSpecifier) && REACT_MODULE.test(statement.moduleSpecifier.text);
|
|
39
|
+
if (ts2.isImportEqualsDeclaration(statement)) return ts2.isExternalModuleReference(statement.moduleReference) && isReactModuleName(statement.moduleReference.expression);
|
|
40
|
+
if (ts2.isVariableStatement(statement)) return statement.declarationList.declarations.some((declaration) => !!declaration.initializer && isRequireOfReact(declaration.initializer));
|
|
41
|
+
return false;
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
function isRequireOfReact(expression) {
|
|
45
|
+
if (ts2.isPropertyAccessExpression(expression)) return isRequireOfReact(expression.expression);
|
|
46
|
+
const isRequireCall = ts2.isCallExpression(expression) && ts2.isIdentifier(expression.expression) && expression.expression.text === "require";
|
|
47
|
+
return isRequireCall && expression.arguments.length === 1 && isReactModuleName(expression.arguments[0]);
|
|
48
|
+
}
|
|
49
|
+
function isReactModuleName(expression) {
|
|
50
|
+
return ts2.isStringLiteral(expression) && REACT_MODULE.test(expression.text);
|
|
51
|
+
}
|
|
52
|
+
var COMPONENT_WRAPPERS = /* @__PURE__ */ new Set(["memo", "forwardRef", "React.memo", "React.forwardRef"]);
|
|
53
|
+
var COMPONENT_BASE_CLASSES = /* @__PURE__ */ new Set(["Component", "PureComponent", "React.Component", "React.PureComponent"]);
|
|
54
|
+
var isComponentName = (name) => /^[A-Z]/.test(name);
|
|
55
|
+
function returnsJsx(fn) {
|
|
56
|
+
if (!fn.body) return false;
|
|
57
|
+
if (!ts2.isBlock(fn.body)) return isJsxValue(fn.body);
|
|
58
|
+
const hasJsxReturn = (node) => {
|
|
59
|
+
if (ts2.isReturnStatement(node)) return !!node.expression && isJsxValue(node.expression);
|
|
60
|
+
if (ts2.isFunctionLike(node) || ts2.isClassLike(node)) return false;
|
|
61
|
+
return !!ts2.forEachChild(node, (child) => hasJsxReturn(child) || void 0);
|
|
62
|
+
};
|
|
63
|
+
return hasJsxReturn(fn.body);
|
|
64
|
+
}
|
|
65
|
+
function isJsxValue(expression) {
|
|
66
|
+
const value = unwrap(expression);
|
|
67
|
+
if (isJsx(value)) return true;
|
|
68
|
+
if (ts2.isConditionalExpression(value)) return isJsxValue(value.whenTrue) || isJsxValue(value.whenFalse);
|
|
69
|
+
if (ts2.isBinaryExpression(value)) return isJsxValue(value.left) || isJsxValue(value.right);
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
function componentFunctionOf(initializer) {
|
|
73
|
+
const value = unwrap(initializer);
|
|
74
|
+
if (ts2.isArrowFunction(value) || ts2.isFunctionExpression(value)) return returnsJsx(value) ? value : void 0;
|
|
75
|
+
const isWrapperCall = ts2.isCallExpression(value) && value.arguments.length > 0 && COMPONENT_WRAPPERS.has(value.expression.getText());
|
|
76
|
+
if (!isWrapperCall) return void 0;
|
|
77
|
+
const wrapped = unwrap(value.arguments[0]);
|
|
78
|
+
if (ts2.isArrowFunction(wrapped) || ts2.isFunctionExpression(wrapped)) return wrapped;
|
|
79
|
+
return componentFunctionOf(wrapped);
|
|
80
|
+
}
|
|
81
|
+
function isComponentClass(cls) {
|
|
82
|
+
const extendsClause = cls.heritageClauses?.find((clause) => clause.token === ts2.SyntaxKind.ExtendsKeyword);
|
|
83
|
+
const baseClass = extendsClause?.types[0]?.expression.getText();
|
|
84
|
+
return !!baseClass && COMPONENT_BASE_CLASSES.has(baseClass);
|
|
85
|
+
}
|
|
86
|
+
var STATE_HOOKS = /* @__PURE__ */ new Set(["useState", "useReducer", "React.useState", "React.useReducer"]);
|
|
87
|
+
function stateHookOf(declaration) {
|
|
88
|
+
const call = declaration.initializer && unwrap(declaration.initializer);
|
|
89
|
+
const isStateHookCall = !!call && ts2.isCallExpression(call) && STATE_HOOKS.has(call.expression.getText());
|
|
90
|
+
if (!isStateHookCall) return void 0;
|
|
91
|
+
if (ts2.isIdentifier(declaration.name)) return { call, value: declaration };
|
|
92
|
+
if (!ts2.isArrayBindingPattern(declaration.name)) return void 0;
|
|
93
|
+
const [value, setter] = declaration.name.elements.map((element) => ts2.isBindingElement(element) && ts2.isIdentifier(element.name) ? element : void 0);
|
|
94
|
+
return value || setter ? { call, value, setter } : void 0;
|
|
95
|
+
}
|
|
96
|
+
var isHookName = (name) => /^use[A-Z0-9]/.test(name);
|
|
97
|
+
|
|
98
|
+
// src/parse.ts
|
|
3
99
|
var PREVIEW_LINES = 6;
|
|
4
100
|
var CONTAINER_PREVIEW_LINES = 3;
|
|
5
101
|
var PREVIEW_WIDTH = 64;
|
|
@@ -15,11 +111,12 @@ function parseStructure(path, text, prev) {
|
|
|
15
111
|
let sf;
|
|
16
112
|
if (old && !old.reused) {
|
|
17
113
|
old.reused = true;
|
|
18
|
-
sf =
|
|
114
|
+
sf = ts3.updateSourceFile(old.sf, text, changeRange(prev.text, text));
|
|
19
115
|
} else {
|
|
20
|
-
sf =
|
|
116
|
+
sf = ts3.createSourceFile(path, text, ts3.ScriptTarget.Latest, true, kind);
|
|
21
117
|
}
|
|
22
|
-
const
|
|
118
|
+
const language = fileLanguageOf(path, sf);
|
|
119
|
+
const b = new Builder(sf, text, language.isReact);
|
|
23
120
|
b.collectStatements(sf.statements, null, 0);
|
|
24
121
|
const syntax = sf.parseDiagnostics ?? [];
|
|
25
122
|
const model = {
|
|
@@ -31,7 +128,8 @@ function parseStructure(path, text, prev) {
|
|
|
31
128
|
syntaxErrors: syntax.length,
|
|
32
129
|
firstErrorLine: -1,
|
|
33
130
|
diagnostics: [],
|
|
34
|
-
indentUnit: detectIndentUnit(text)
|
|
131
|
+
indentUnit: detectIndentUnit(text),
|
|
132
|
+
language
|
|
35
133
|
};
|
|
36
134
|
model.diagnostics = syntax.map((d) => toCodeDiagnostic(model, sf, d, "syntax"));
|
|
37
135
|
model.firstErrorLine = model.diagnostics[0]?.line ?? -1;
|
|
@@ -54,9 +152,9 @@ function toCodeDiagnostic(model, sf, d, source) {
|
|
|
54
152
|
start,
|
|
55
153
|
length: d.length ?? 0,
|
|
56
154
|
line: sf.getLineAndCharacterOfPosition(Math.min(start, sf.text.length)).line + 1,
|
|
57
|
-
message:
|
|
155
|
+
message: ts3.flattenDiagnosticMessageText(d.messageText, "\n"),
|
|
58
156
|
source,
|
|
59
|
-
severity: d.category ===
|
|
157
|
+
severity: d.category === ts3.DiagnosticCategory.Error ? "error" : "warning",
|
|
60
158
|
blockId: blockAt(model, start)?.id ?? null
|
|
61
159
|
};
|
|
62
160
|
}
|
|
@@ -77,29 +175,29 @@ function changeRange(a, b) {
|
|
|
77
175
|
while (s < n && a.charCodeAt(s) === b.charCodeAt(s)) s++;
|
|
78
176
|
let e = 0;
|
|
79
177
|
while (e < n - s && a.charCodeAt(a.length - 1 - e) === b.charCodeAt(b.length - 1 - e)) e++;
|
|
80
|
-
return
|
|
178
|
+
return ts3.createTextChangeRange(ts3.createTextSpan(s, a.length - e - s), b.length - e - s);
|
|
81
179
|
}
|
|
82
180
|
function commonJsExportedFunction(s) {
|
|
83
|
-
if (!
|
|
181
|
+
if (!ts3.isExpressionStatement(s) || !ts3.isBinaryExpression(s.expression) || s.expression.operatorToken.kind !== ts3.SyntaxKind.EqualsToken) return null;
|
|
84
182
|
const { left, right } = s.expression;
|
|
85
183
|
const fn = unwrap(right);
|
|
86
|
-
const isFunction =
|
|
87
|
-
if (!isFunction || !
|
|
184
|
+
const isFunction = ts3.isFunctionExpression(fn) || ts3.isArrowFunction(fn);
|
|
185
|
+
if (!isFunction || !ts3.isPropertyAccessExpression(left) || !ts3.isIdentifier(left.name)) return null;
|
|
88
186
|
const target = left.expression.getText();
|
|
89
187
|
const isExportsObject = target === "exports" || target === "module.exports";
|
|
90
188
|
return isExportsObject ? { assignment: s.expression, exportedName: left.name, fn } : null;
|
|
91
189
|
}
|
|
92
190
|
function scriptKindOf(path) {
|
|
93
|
-
if (/\.[cm]?jsx?$/.test(path)) return path.endsWith("x") ?
|
|
94
|
-
return path.endsWith("x") ?
|
|
191
|
+
if (/\.[cm]?jsx?$/.test(path)) return path.endsWith("x") ? ts3.ScriptKind.JSX : ts3.ScriptKind.JS;
|
|
192
|
+
return path.endsWith("x") ? ts3.ScriptKind.TSX : ts3.ScriptKind.TS;
|
|
95
193
|
}
|
|
96
194
|
function createProgram(sf) {
|
|
97
195
|
const options = {
|
|
98
196
|
noLib: true,
|
|
99
197
|
noResolve: true,
|
|
100
198
|
allowJs: true,
|
|
101
|
-
target:
|
|
102
|
-
jsx:
|
|
199
|
+
target: ts3.ScriptTarget.Latest,
|
|
200
|
+
jsx: ts3.JsxEmit.Preserve,
|
|
103
201
|
types: []
|
|
104
202
|
};
|
|
105
203
|
const host = {
|
|
@@ -115,7 +213,7 @@ function createProgram(sf) {
|
|
|
115
213
|
readFile: () => void 0,
|
|
116
214
|
getDirectories: () => []
|
|
117
215
|
};
|
|
118
|
-
return
|
|
216
|
+
return ts3.createProgram({ rootNames: [sf.fileName], options, host });
|
|
119
217
|
}
|
|
120
218
|
function detectIndentUnit(text) {
|
|
121
219
|
const m = /\n(\t+|[ ]{2,8})\S/.exec(text);
|
|
@@ -123,12 +221,14 @@ function detectIndentUnit(text) {
|
|
|
123
221
|
return m[1].startsWith(" ") ? " " : m[1].length >= 4 && m[1].length % 4 === 0 ? " " : " ";
|
|
124
222
|
}
|
|
125
223
|
var Builder = class {
|
|
126
|
-
constructor(sf, text) {
|
|
224
|
+
constructor(sf, text, isReact) {
|
|
127
225
|
this.sf = sf;
|
|
128
226
|
this.text = text;
|
|
227
|
+
this.isReact = isReact;
|
|
129
228
|
}
|
|
130
229
|
sf;
|
|
131
230
|
text;
|
|
231
|
+
isReact;
|
|
132
232
|
roots = [];
|
|
133
233
|
byId = /* @__PURE__ */ new Map();
|
|
134
234
|
/**
|
|
@@ -140,6 +240,8 @@ var Builder = class {
|
|
|
140
240
|
rangeToBlock = /* @__PURE__ */ new Map();
|
|
141
241
|
/** Every name a usage could resolve to. Other identifiers skip the (slow) checker. */
|
|
142
242
|
names = /* @__PURE__ */ new Set();
|
|
243
|
+
/** Setters of React state (`setCount`), keyed like `declToBlock`: using one writes the state. */
|
|
244
|
+
stateSetters = /* @__PURE__ */ new Set();
|
|
143
245
|
// ---------- structure ----------
|
|
144
246
|
collectStatements(stmts, parent, depth, declarationsOnly = false) {
|
|
145
247
|
let group = [];
|
|
@@ -168,21 +270,23 @@ var Builder = class {
|
|
|
168
270
|
};
|
|
169
271
|
for (let i = 0; i < stmts.length; i++) {
|
|
170
272
|
const s = stmts[i];
|
|
171
|
-
if (
|
|
273
|
+
if (ts3.isFunctionDeclaration(s)) {
|
|
172
274
|
flushGroup();
|
|
173
275
|
const overloads = [s];
|
|
174
276
|
while (!overloads[overloads.length - 1].body) {
|
|
175
277
|
const next = stmts[i + 1];
|
|
176
|
-
if (next &&
|
|
278
|
+
if (next && ts3.isFunctionDeclaration(next) && next.name?.text === s.name?.text) {
|
|
177
279
|
overloads.push(next);
|
|
178
280
|
i++;
|
|
179
281
|
} else break;
|
|
180
282
|
}
|
|
181
|
-
|
|
182
|
-
|
|
283
|
+
const name = s.name?.text ?? "default";
|
|
284
|
+
const isComponent = this.isReact && isComponentName(name) && returnsJsx(overloads[overloads.length - 1]);
|
|
285
|
+
this.addFunctionLike(overloads, name, parent, depth, { kind: isComponent ? "component" : "function" });
|
|
286
|
+
} else if (ts3.isClassDeclaration(s)) {
|
|
183
287
|
flushGroup();
|
|
184
288
|
this.addClass(s, s, s.name?.text ?? "default", parent, depth);
|
|
185
|
-
} else if (
|
|
289
|
+
} else if (ts3.isVariableStatement(s)) {
|
|
186
290
|
flushGroup();
|
|
187
291
|
this.addVariableStatement(s, parent, depth, declarationsOnly);
|
|
188
292
|
} else if (commonJsExportedFunction(s)) {
|
|
@@ -192,14 +296,14 @@ var Builder = class {
|
|
|
192
296
|
this.addFunctionLike([fn], exportedName.text, parent, depth, { rangeNodes: [s], declNodes, extraTags: ["export"], nameNode: exportedName });
|
|
193
297
|
} else if (declarationsOnly) {
|
|
194
298
|
continue;
|
|
195
|
-
} else if (
|
|
299
|
+
} else if (ts3.isImportDeclaration(s) || ts3.isImportEqualsDeclaration(s)) {
|
|
196
300
|
flushGroup();
|
|
197
301
|
this.addImport(s, parent, depth);
|
|
198
|
-
} else if (
|
|
302
|
+
} else if (ts3.isInterfaceDeclaration(s) || ts3.isTypeAliasDeclaration(s) || ts3.isEnumDeclaration(s)) {
|
|
199
303
|
flushGroup();
|
|
200
|
-
const keyword =
|
|
304
|
+
const keyword = ts3.isInterfaceDeclaration(s) ? "interface" : ts3.isEnumDeclaration(s) ? "enum" : "type";
|
|
201
305
|
const typeParams = "typeParameters" in s && s.typeParameters ? `<${s.typeParameters.map((t) => t.getText()).join(", ")}>` : "";
|
|
202
|
-
const bodyStart =
|
|
306
|
+
const bodyStart = ts3.isTypeAliasDeclaration(s) ? s.type.getStart(this.sf) : this.text.indexOf("{", s.name.end);
|
|
203
307
|
this.add(
|
|
204
308
|
{
|
|
205
309
|
kind: "type",
|
|
@@ -217,7 +321,7 @@ var Builder = class {
|
|
|
217
321
|
parent,
|
|
218
322
|
depth
|
|
219
323
|
);
|
|
220
|
-
} else if (
|
|
324
|
+
} else if (ts3.isModuleDeclaration(s) && s.body && ts3.isModuleBlock(s.body)) {
|
|
221
325
|
flushGroup();
|
|
222
326
|
const body = s.body;
|
|
223
327
|
this.add(
|
|
@@ -247,13 +351,13 @@ var Builder = class {
|
|
|
247
351
|
addImport(s, parent, depth) {
|
|
248
352
|
let spec;
|
|
249
353
|
let names = [];
|
|
250
|
-
if (
|
|
354
|
+
if (ts3.isImportDeclaration(s)) {
|
|
251
355
|
spec = s.moduleSpecifier.text;
|
|
252
356
|
const clause = s.importClause;
|
|
253
357
|
if (clause?.name) names.push(clause.name.text);
|
|
254
358
|
const nb = clause?.namedBindings;
|
|
255
|
-
if (nb &&
|
|
256
|
-
if (nb &&
|
|
359
|
+
if (nb && ts3.isNamespaceImport(nb)) names.push("* as " + nb.name.text);
|
|
360
|
+
if (nb && ts3.isNamedImports(nb)) names.push(...nb.elements.map((e) => e.name.text));
|
|
257
361
|
if (!clause) names = ["(side effect)"];
|
|
258
362
|
} else {
|
|
259
363
|
spec = s.name.text;
|
|
@@ -267,7 +371,7 @@ var Builder = class {
|
|
|
267
371
|
key: "import:" + spec,
|
|
268
372
|
name: spec,
|
|
269
373
|
detail: "",
|
|
270
|
-
tags:
|
|
374
|
+
tags: ts3.isImportDeclaration(s) && s.importClause?.isTypeOnly ? ["type"] : [],
|
|
271
375
|
rangeNodes: [s],
|
|
272
376
|
declNodes: [s],
|
|
273
377
|
previewRange: null,
|
|
@@ -286,11 +390,11 @@ var Builder = class {
|
|
|
286
390
|
let previewRange = null;
|
|
287
391
|
let insert = null;
|
|
288
392
|
let insertAt = -1;
|
|
289
|
-
if (body &&
|
|
393
|
+
if (body && ts3.isBlock(body)) {
|
|
290
394
|
previewRange = [body.getStart(this.sf) + 1, body.end - 1];
|
|
291
395
|
insert = "body";
|
|
292
396
|
const last = body.statements[body.statements.length - 1];
|
|
293
|
-
insertAt = last &&
|
|
397
|
+
insertAt = last && ts3.isReturnStatement(last) ? this.lineStartOf(last.getStart(this.sf)) : body.end - 1;
|
|
294
398
|
} else if (body) {
|
|
295
399
|
previewRange = [body.getStart(this.sf), body.end];
|
|
296
400
|
}
|
|
@@ -298,11 +402,12 @@ var Builder = class {
|
|
|
298
402
|
const ret = sig.type ? ": " + sig.type.getText() : "";
|
|
299
403
|
const detail = squash(`${typeParams}(${sig.parameters.map((p) => p.getText()).join(", ")})${ret}`);
|
|
300
404
|
const tags = [...opts.extraTags ?? [], ...modifierTags(nodes[0])];
|
|
405
|
+
if (this.isReact && isHookName(name)) tags.push("hook");
|
|
301
406
|
if (nodes.length > 1) tags.push(`${nodes.length - 1} overload${nodes.length > 2 ? "s" : ""}`);
|
|
302
407
|
if (impl.asteriskToken) tags.push("generator");
|
|
303
408
|
this.add(
|
|
304
409
|
{
|
|
305
|
-
kind: "function",
|
|
410
|
+
kind: opts.kind ?? "function",
|
|
306
411
|
key: opts.key ?? name,
|
|
307
412
|
name,
|
|
308
413
|
detail,
|
|
@@ -313,7 +418,7 @@ var Builder = class {
|
|
|
313
418
|
previewRange,
|
|
314
419
|
insert,
|
|
315
420
|
insertAt,
|
|
316
|
-
children: body &&
|
|
421
|
+
children: body && ts3.isBlock(body) ? (blk) => this.collectStatements(body.statements, blk, depth + 1, true) : void 0
|
|
317
422
|
},
|
|
318
423
|
parent,
|
|
319
424
|
depth
|
|
@@ -325,7 +430,7 @@ var Builder = class {
|
|
|
325
430
|
const typeParams = cls.typeParameters?.length ? `<${cls.typeParameters.map((t) => t.getText()).join(", ")}>` : "";
|
|
326
431
|
this.add(
|
|
327
432
|
{
|
|
328
|
-
kind: "class",
|
|
433
|
+
kind: this.isReact && isComponentClass(cls) ? "component" : "class",
|
|
329
434
|
key: name,
|
|
330
435
|
name,
|
|
331
436
|
detail: squash(typeParams + (heritage ? " " + heritage : "")),
|
|
@@ -345,26 +450,26 @@ var Builder = class {
|
|
|
345
450
|
collectMembers(members, parent, depth) {
|
|
346
451
|
for (let i = 0; i < members.length; i++) {
|
|
347
452
|
const m = members[i];
|
|
348
|
-
if (
|
|
349
|
-
const name =
|
|
453
|
+
if (ts3.isMethodDeclaration(m) || ts3.isConstructorDeclaration(m)) {
|
|
454
|
+
const name = ts3.isConstructorDeclaration(m) ? "constructor" : m.name.getText();
|
|
350
455
|
const overloads = [m];
|
|
351
456
|
while (!overloads[overloads.length - 1].body) {
|
|
352
457
|
const next = members[i + 1];
|
|
353
|
-
const nextName = next && (
|
|
458
|
+
const nextName = next && (ts3.isConstructorDeclaration(next) ? "constructor" : ts3.isMethodDeclaration(next) ? next.name.getText() : null);
|
|
354
459
|
if (nextName === name) {
|
|
355
460
|
overloads.push(next);
|
|
356
461
|
i++;
|
|
357
462
|
} else break;
|
|
358
463
|
}
|
|
359
464
|
this.addFunctionLike(overloads, name, parent, depth);
|
|
360
|
-
} else if (
|
|
465
|
+
} else if (ts3.isGetAccessor(m) || ts3.isSetAccessor(m)) {
|
|
361
466
|
const name = m.name.getText();
|
|
362
|
-
const word =
|
|
467
|
+
const word = ts3.isGetAccessor(m) ? "get" : "set";
|
|
363
468
|
this.addFunctionLike([m], name, parent, depth, { key: `${word} ${name}`, extraTags: [word] });
|
|
364
|
-
} else if (
|
|
469
|
+
} else if (ts3.isPropertyDeclaration(m)) {
|
|
365
470
|
const name = m.name.getText();
|
|
366
471
|
const init = m.initializer && unwrap(m.initializer);
|
|
367
|
-
if (init && (
|
|
472
|
+
if (init && (ts3.isArrowFunction(init) || ts3.isFunctionExpression(init))) {
|
|
368
473
|
this.addFunctionLike([init], name, parent, depth, { rangeNodes: [m], declNodes: [m], extraTags: modifierTags(m), nameNode: m.name });
|
|
369
474
|
} else {
|
|
370
475
|
this.addVariable(m, m, name, m.type ? ": " + m.type.getText() : "", modifierTags(m), parent, depth, m.initializer);
|
|
@@ -374,25 +479,61 @@ var Builder = class {
|
|
|
374
479
|
}
|
|
375
480
|
addVariableStatement(s, parent, depth, functionsOnly) {
|
|
376
481
|
const decls = s.declarationList.declarations;
|
|
377
|
-
const keyword = s.declarationList.flags &
|
|
482
|
+
const keyword = s.declarationList.flags & ts3.NodeFlags.Const ? "const" : s.declarationList.flags & ts3.NodeFlags.Let ? "let" : "var";
|
|
378
483
|
for (const d of decls) {
|
|
379
484
|
const rangeNode = decls.length === 1 ? s : d;
|
|
380
485
|
const name = d.name.getText();
|
|
381
486
|
const tags = [...modifierTags(s), keyword];
|
|
487
|
+
const stateHook = this.isReact ? stateHookOf(d) : void 0;
|
|
488
|
+
if (stateHook) {
|
|
489
|
+
this.addState(stateHook, rangeNode, tags, parent, depth);
|
|
490
|
+
continue;
|
|
491
|
+
}
|
|
382
492
|
const init = d.initializer && unwrap(d.initializer);
|
|
383
|
-
|
|
493
|
+
const componentFunction = this.isReact && init && isComponentName(name) ? componentFunctionOf(init) : void 0;
|
|
494
|
+
if (componentFunction) {
|
|
495
|
+
this.addFunctionLike([componentFunction], name, parent, depth, { kind: "component", rangeNodes: [rangeNode], declNodes: [d], extraTags: tags, nameNode: d.name });
|
|
496
|
+
} else if (init && (ts3.isArrowFunction(init) || ts3.isFunctionExpression(init))) {
|
|
384
497
|
this.addFunctionLike([init], name, parent, depth, { rangeNodes: [rangeNode], declNodes: [d], extraTags: tags, nameNode: d.name });
|
|
385
|
-
} else if (init &&
|
|
498
|
+
} else if (init && ts3.isClassExpression(init)) {
|
|
386
499
|
this.addClass(init, rangeNode, name, parent, depth, [d, init], tags);
|
|
387
500
|
} else if (!functionsOnly) {
|
|
388
501
|
this.addVariable(d, rangeNode, name, d.type ? ": " + d.type.getText() : "", tags, parent, depth, d.initializer);
|
|
389
502
|
}
|
|
390
503
|
}
|
|
391
504
|
}
|
|
505
|
+
/** `const [count, setCount] = useState(0)`: a state block named `count`; using `setCount` writes it. */
|
|
506
|
+
addState(hook, rangeNode, tags, parent, depth) {
|
|
507
|
+
const named = hook.value ?? hook.setter;
|
|
508
|
+
const setterName = hook.setter?.name.getText();
|
|
509
|
+
const [firstArg, ...otherArgs] = hook.call.arguments;
|
|
510
|
+
const args = firstArg ? truncate(squash(firstArg.getText()), 24) + (otherArgs.length ? ", \u2026" : "") : "";
|
|
511
|
+
if (hook.setter && setterName) {
|
|
512
|
+
this.stateSetters.add(nodeKey(hook.setter));
|
|
513
|
+
this.names.add(setterName);
|
|
514
|
+
}
|
|
515
|
+
this.add(
|
|
516
|
+
{
|
|
517
|
+
kind: "state",
|
|
518
|
+
key: named.name.getText(),
|
|
519
|
+
name: named.name.getText(),
|
|
520
|
+
detail: `${hook.call.expression.getText()}(${args})`,
|
|
521
|
+
tags: setterName && hook.value ? [...tags, setterName] : tags,
|
|
522
|
+
rangeNodes: [rangeNode],
|
|
523
|
+
declNodes: [hook.value, hook.setter].filter((declaration) => !!declaration),
|
|
524
|
+
nameNode: named.name,
|
|
525
|
+
previewRange: [hook.call.getStart(this.sf), hook.call.end],
|
|
526
|
+
insert: null,
|
|
527
|
+
insertAt: -1
|
|
528
|
+
},
|
|
529
|
+
parent,
|
|
530
|
+
depth
|
|
531
|
+
);
|
|
532
|
+
}
|
|
392
533
|
addVariable(decl, rangeNode, name, type, tags, parent, depth, initializer) {
|
|
393
534
|
const init = initializer && unwrap(initializer);
|
|
394
|
-
const objectChildren = init &&
|
|
395
|
-
(p) =>
|
|
535
|
+
const objectChildren = init && ts3.isObjectLiteralExpression(init) ? init.properties.filter(
|
|
536
|
+
(p) => ts3.isMethodDeclaration(p) || ts3.isPropertyAssignment(p) && (ts3.isArrowFunction(unwrap(p.initializer)) || ts3.isFunctionExpression(unwrap(p.initializer)))
|
|
396
537
|
) : [];
|
|
397
538
|
this.add(
|
|
398
539
|
{
|
|
@@ -410,7 +551,7 @@ var Builder = class {
|
|
|
410
551
|
children: objectChildren.length ? (blk) => {
|
|
411
552
|
for (const p of objectChildren) {
|
|
412
553
|
const pname = p.name.getText();
|
|
413
|
-
if (
|
|
554
|
+
if (ts3.isMethodDeclaration(p)) this.addFunctionLike([p], pname, blk, depth + 1);
|
|
414
555
|
else {
|
|
415
556
|
const fn = unwrap(p.initializer);
|
|
416
557
|
this.addFunctionLike([fn], pname, blk, depth + 1, { rangeNodes: [p], declNodes: [p], nameNode: p.name });
|
|
@@ -472,7 +613,7 @@ var Builder = class {
|
|
|
472
613
|
/** Include doc comments directly above the node (no blank line in between). */
|
|
473
614
|
startWithComments(node) {
|
|
474
615
|
let start = node.getStart(this.sf);
|
|
475
|
-
const comments =
|
|
616
|
+
const comments = ts3.getLeadingCommentRanges(this.text, node.pos) ?? [];
|
|
476
617
|
for (let i = comments.length - 1; i >= 0; i--) {
|
|
477
618
|
const gap = this.text.slice(comments[i].end, start);
|
|
478
619
|
if (/\S/.test(gap) || (gap.match(/\n/g)?.length ?? 0) > 1) break;
|
|
@@ -500,8 +641,8 @@ var Builder = class {
|
|
|
500
641
|
const visit = (node, current) => {
|
|
501
642
|
const blk = this.rangeToBlock.get(nodeKey(node));
|
|
502
643
|
if (blk) current = blk;
|
|
503
|
-
if (
|
|
504
|
-
|
|
644
|
+
if (ts3.isIdentifier(node) && current) this.recordUsage(node, current, checker, byPair, otherFiles);
|
|
645
|
+
ts3.forEachChild(node, (c) => visit(c, current));
|
|
505
646
|
};
|
|
506
647
|
visit(sf, null);
|
|
507
648
|
return [...byPair.values()];
|
|
@@ -510,29 +651,30 @@ var Builder = class {
|
|
|
510
651
|
const isKnownName = this.names.has(id.text) || !!otherFiles?.projectNames.has(id.text);
|
|
511
652
|
if (!isKnownName) return;
|
|
512
653
|
const parent = id.parent;
|
|
513
|
-
const isDeclarationName = !!parent && parent.name === id && !
|
|
654
|
+
const isDeclarationName = !!parent && parent.name === id && !ts3.isPropertyAccessExpression(parent) && !ts3.isShorthandPropertyAssignment(parent);
|
|
514
655
|
if (isDeclarationName) return;
|
|
515
656
|
let sym;
|
|
516
657
|
try {
|
|
517
|
-
sym = parent &&
|
|
658
|
+
sym = parent && ts3.isShorthandPropertyAssignment(parent) ? checker.getShorthandAssignmentValueSymbol(parent) : checker.getSymbolAtLocation(id);
|
|
518
659
|
} catch {
|
|
519
660
|
return;
|
|
520
661
|
}
|
|
521
662
|
if (!sym?.declarations) return;
|
|
522
|
-
|
|
663
|
+
let kind = usageKind(id);
|
|
523
664
|
let to;
|
|
524
665
|
let importBlockId;
|
|
525
666
|
for (const d of sym.declarations) {
|
|
526
667
|
to = this.declToBlock.get(nodeKey(d));
|
|
668
|
+
if (this.stateSetters.has(nodeKey(d))) kind = "write";
|
|
527
669
|
if (!to) {
|
|
528
670
|
let n = d;
|
|
529
|
-
while (n && !
|
|
530
|
-
if (n && !
|
|
671
|
+
while (n && !ts3.isImportDeclaration(n) && !ts3.isImportEqualsDeclaration(n) && !ts3.isSourceFile(n)) n = n.parent;
|
|
672
|
+
if (n && !ts3.isSourceFile(n)) importBlockId = to = this.declToBlock.get(nodeKey(n));
|
|
531
673
|
}
|
|
532
674
|
if (to) break;
|
|
533
675
|
}
|
|
534
676
|
if (otherFiles) {
|
|
535
|
-
const isImportedName = !!importBlockId && (sym.flags &
|
|
677
|
+
const isImportedName = !!importBlockId && (sym.flags & ts3.SymbolFlags.Alias) !== 0;
|
|
536
678
|
const target = isImportedName ? safeAliasedSymbol(checker, sym) : to ? void 0 : sym;
|
|
537
679
|
const declaredElsewhere = target?.declarations?.find((d) => d.getSourceFile() !== id.getSourceFile());
|
|
538
680
|
const external = declaredElsewhere && otherFiles.blockInOtherFile(declaredElsewhere);
|
|
@@ -549,6 +691,7 @@ function addUsage(out, usage) {
|
|
|
549
691
|
if (!existing) return void out.set(pairKey, usage);
|
|
550
692
|
existing.count++;
|
|
551
693
|
if (usage.kind === "call") existing.kind = "call";
|
|
694
|
+
else if (usage.kind === "write" && existing.kind === "read") existing.kind = "write";
|
|
552
695
|
}
|
|
553
696
|
function safeAliasedSymbol(checker, symbol) {
|
|
554
697
|
try {
|
|
@@ -561,23 +704,26 @@ function nodeKey(node) {
|
|
|
561
704
|
return `${node.kind}:${node.pos}:${node.end}`;
|
|
562
705
|
}
|
|
563
706
|
function usageKind(id) {
|
|
707
|
+
if (isJsxTagName(id)) return "render";
|
|
564
708
|
let n = id;
|
|
565
|
-
if (
|
|
709
|
+
if (ts3.isPropertyAccessExpression(n.parent) && n.parent.name === n) n = n.parent;
|
|
566
710
|
const p = n.parent;
|
|
567
|
-
if ((
|
|
568
|
-
if (
|
|
569
|
-
if (
|
|
570
|
-
if (
|
|
571
|
-
if ((
|
|
572
|
-
if (
|
|
711
|
+
if ((ts3.isCallExpression(p) || ts3.isNewExpression(p)) && p.expression === n) return "call";
|
|
712
|
+
if (ts3.isTaggedTemplateExpression(p) && p.tag === n) return "call";
|
|
713
|
+
if (ts3.isJsxOpeningLikeElement(p) && p.tagName === n) return "render";
|
|
714
|
+
if (ts3.isBinaryExpression(p) && p.left === n && p.operatorToken.kind >= ts3.SyntaxKind.FirstAssignment && p.operatorToken.kind <= ts3.SyntaxKind.LastAssignment) return "write";
|
|
715
|
+
if ((ts3.isPrefixUnaryExpression(p) || ts3.isPostfixUnaryExpression(p)) && (p.operator === ts3.SyntaxKind.PlusPlusToken || p.operator === ts3.SyntaxKind.MinusMinusToken)) return "write";
|
|
716
|
+
if (ts3.isTypeReferenceNode(p) || ts3.isExpressionWithTypeArguments(p) || ts3.isTypeQueryNode(p) || ts3.isQualifiedName(p)) return "type";
|
|
573
717
|
return "read";
|
|
574
718
|
}
|
|
575
|
-
function
|
|
576
|
-
|
|
577
|
-
|
|
719
|
+
function isJsxTagName(id) {
|
|
720
|
+
let tag = id;
|
|
721
|
+
while (ts3.isPropertyAccessExpression(tag.parent) && tag.parent.expression === tag) tag = tag.parent;
|
|
722
|
+
const element = tag.parent;
|
|
723
|
+
return (ts3.isJsxOpeningLikeElement(element) || ts3.isJsxClosingElement(element)) && element.tagName === tag;
|
|
578
724
|
}
|
|
579
725
|
function modifierTags(n) {
|
|
580
|
-
const mods =
|
|
726
|
+
const mods = ts3.canHaveModifiers(n) ? ts3.getModifiers(n) ?? [] : [];
|
|
581
727
|
return mods.map((m) => m.getText());
|
|
582
728
|
}
|
|
583
729
|
function squash(s) {
|
|
@@ -625,13 +771,13 @@ function makePreview(text, range, children, max) {
|
|
|
625
771
|
}
|
|
626
772
|
|
|
627
773
|
// src/project.ts
|
|
628
|
-
import
|
|
774
|
+
import ts5 from "typescript";
|
|
629
775
|
|
|
630
776
|
// src/workspace.ts
|
|
631
|
-
import
|
|
777
|
+
import ts4 from "typescript";
|
|
632
778
|
function parseConfig(config, projectPaths) {
|
|
633
779
|
const configFileName = "/" + config.path;
|
|
634
|
-
const json =
|
|
780
|
+
const json = ts4.parseConfigFileTextToJson(configFileName, config.text);
|
|
635
781
|
if (json.error || !json.config) return null;
|
|
636
782
|
const readConfig = (name) => name === configFileName ? config.text : config.extendedFiles?.[name.slice(1)];
|
|
637
783
|
const host = {
|
|
@@ -641,7 +787,7 @@ function parseConfig(config, projectPaths) {
|
|
|
641
787
|
readFile: readConfig
|
|
642
788
|
};
|
|
643
789
|
const folder = config.path.slice(0, config.path.lastIndexOf("/") + 1);
|
|
644
|
-
const parsed =
|
|
790
|
+
const parsed = ts4.parseJsonConfigFileContent(json.config, host, "/" + folder, void 0, configFileName);
|
|
645
791
|
const included = new Set(parsed.fileNames.map((name) => name.slice(1)));
|
|
646
792
|
return { path: config.path, folder, options: parsed.options, includes: (projectPath) => included.has(projectPath) };
|
|
647
793
|
}
|
|
@@ -743,7 +889,7 @@ var Project = class {
|
|
|
743
889
|
/** Language services by config path, created when a file of that config is first resolved. */
|
|
744
890
|
services = /* @__PURE__ */ new Map();
|
|
745
891
|
/** Shared by the services, so a file several programs include is parsed once. */
|
|
746
|
-
documentRegistry =
|
|
892
|
+
documentRegistry = ts5.createDocumentRegistry(true, "/");
|
|
747
893
|
/** Changes when files are added or removed, or the settings change: where imports point may change then. */
|
|
748
894
|
fileSetVersion = 0;
|
|
749
895
|
/** Load the standard library later, e.g. once it has been downloaded. Type errors show from then on. */
|
|
@@ -777,11 +923,11 @@ var Project = class {
|
|
|
777
923
|
compilerOptions(configOptions, packagePaths) {
|
|
778
924
|
const paths = { ...packagePaths, ...configOptions.paths };
|
|
779
925
|
return {
|
|
780
|
-
target:
|
|
781
|
-
module:
|
|
782
|
-
moduleResolution:
|
|
926
|
+
target: ts5.ScriptTarget.ES2022,
|
|
927
|
+
module: ts5.ModuleKind.ESNext,
|
|
928
|
+
moduleResolution: ts5.ModuleResolutionKind.Bundler,
|
|
783
929
|
strict: true,
|
|
784
|
-
jsx:
|
|
930
|
+
jsx: ts5.JsxEmit.Preserve,
|
|
785
931
|
skipLibCheck: true,
|
|
786
932
|
allowImportingTsExtensions: true,
|
|
787
933
|
...configOptions,
|
|
@@ -827,7 +973,7 @@ var Project = class {
|
|
|
827
973
|
serviceOfConfig(configPath) {
|
|
828
974
|
let service = this.services.get(configPath);
|
|
829
975
|
if (!service) {
|
|
830
|
-
service =
|
|
976
|
+
service = ts5.createLanguageService(this.createHost(configPath), this.documentRegistry);
|
|
831
977
|
this.services.set(configPath, service);
|
|
832
978
|
}
|
|
833
979
|
return service;
|
|
@@ -996,7 +1142,7 @@ var Project = class {
|
|
|
996
1142
|
const reExportedPaths = /* @__PURE__ */ new Set();
|
|
997
1143
|
const host = this.moduleResolutionHost();
|
|
998
1144
|
const resolve = (specifier) => {
|
|
999
|
-
const result =
|
|
1145
|
+
const result = ts5.resolveModuleName(specifier, toFileName(path), this.optionsOfFile(path), host).resolvedModule;
|
|
1000
1146
|
return result ? fromFileName(result.resolvedFileName) : null;
|
|
1001
1147
|
};
|
|
1002
1148
|
for (const block of file.model.roots) {
|
|
@@ -1050,7 +1196,7 @@ var Project = class {
|
|
|
1050
1196
|
getScriptSnapshot: (name) => {
|
|
1051
1197
|
if (name.startsWith(LIB_DIR)) {
|
|
1052
1198
|
const text = this.libFiles[name.slice(LIB_DIR.length)];
|
|
1053
|
-
return text === void 0 ? void 0 :
|
|
1199
|
+
return text === void 0 ? void 0 : ts5.ScriptSnapshot.fromString(text);
|
|
1054
1200
|
}
|
|
1055
1201
|
const path = fromFileName(name);
|
|
1056
1202
|
const file = path ? this.files.get(path) : void 0;
|
|
@@ -1064,7 +1210,7 @@ var Project = class {
|
|
|
1064
1210
|
const containingPath = fromFileName(containingFile);
|
|
1065
1211
|
const options = this.optionsOfFile(containingPath);
|
|
1066
1212
|
const host = this.moduleResolutionHost();
|
|
1067
|
-
return moduleLiterals.map((literal) =>
|
|
1213
|
+
return moduleLiterals.map((literal) => ts5.resolveModuleName(literal.text, containingFile, options, host));
|
|
1068
1214
|
},
|
|
1069
1215
|
getDefaultLibFileName: () => LIB_DIR + "lib.d.ts",
|
|
1070
1216
|
fileExists: (name) => this.fileExists(name),
|
|
@@ -1100,7 +1246,7 @@ function changedRegion(before, after) {
|
|
|
1100
1246
|
while (start < shorter && before.charCodeAt(start) === after.charCodeAt(start)) start++;
|
|
1101
1247
|
let fromEnd = 0;
|
|
1102
1248
|
while (fromEnd < shorter - start && before.charCodeAt(before.length - 1 - fromEnd) === after.charCodeAt(after.length - 1 - fromEnd)) fromEnd++;
|
|
1103
|
-
return
|
|
1249
|
+
return ts5.createTextChangeRange(ts5.createTextSpan(start, before.length - fromEnd - start), after.length - fromEnd - start);
|
|
1104
1250
|
}
|
|
1105
1251
|
function toFileName(path) {
|
|
1106
1252
|
return "/" + path;
|
|
@@ -1124,10 +1270,10 @@ function namesVisibleFrom(model, block) {
|
|
|
1124
1270
|
return names;
|
|
1125
1271
|
}
|
|
1126
1272
|
function isValidIdentifier(name) {
|
|
1127
|
-
const scanner =
|
|
1273
|
+
const scanner = ts5.createScanner(ts5.ScriptTarget.Latest, true, ts5.LanguageVariant.Standard, name);
|
|
1128
1274
|
const token = scanner.scan();
|
|
1129
1275
|
const isWholeText = scanner.getTokenEnd() === name.length && name.length > 0;
|
|
1130
|
-
const isNameToken = token ===
|
|
1276
|
+
const isNameToken = token === ts5.SyntaxKind.Identifier || token > ts5.SyntaxKind.LastReservedWord && token <= ts5.SyntaxKind.LastKeyword;
|
|
1131
1277
|
return isWholeText && isNameToken;
|
|
1132
1278
|
}
|
|
1133
1279
|
|
|
@@ -1257,7 +1403,7 @@ function connectProjectWorker(port) {
|
|
|
1257
1403
|
}
|
|
1258
1404
|
|
|
1259
1405
|
// src/edits.ts
|
|
1260
|
-
import
|
|
1406
|
+
import ts6 from "typescript";
|
|
1261
1407
|
function applyEdit(text, e) {
|
|
1262
1408
|
return text.slice(0, e.start) + e.text + text.slice(e.end);
|
|
1263
1409
|
}
|
|
@@ -1384,13 +1530,13 @@ function toTopLevel(code) {
|
|
|
1384
1530
|
function renameDeclaration(code, inClass, name) {
|
|
1385
1531
|
const prefix = inClass ? "class __cc__ {\n" : "";
|
|
1386
1532
|
const src = prefix + code + (inClass ? "\n}" : "");
|
|
1387
|
-
const sf =
|
|
1533
|
+
const sf = ts6.createSourceFile("snippet.ts", src, ts6.ScriptTarget.Latest, true);
|
|
1388
1534
|
let target;
|
|
1389
1535
|
const first = sf.statements[0];
|
|
1390
|
-
if (inClass && first &&
|
|
1391
|
-
else if (first && (
|
|
1536
|
+
if (inClass && first && ts6.isClassDeclaration(first)) target = first.members[0]?.name;
|
|
1537
|
+
else if (first && (ts6.isFunctionDeclaration(first) || ts6.isClassDeclaration(first) || ts6.isInterfaceDeclaration(first) || ts6.isTypeAliasDeclaration(first) || ts6.isEnumDeclaration(first)))
|
|
1392
1538
|
target = first.name;
|
|
1393
|
-
else if (first &&
|
|
1539
|
+
else if (first && ts6.isVariableStatement(first)) target = first.declarationList.declarations[0]?.name;
|
|
1394
1540
|
if (!target) return code;
|
|
1395
1541
|
const s = target.getStart(sf) - prefix.length;
|
|
1396
1542
|
return code.slice(0, s) + name + code.slice(target.end - prefix.length);
|
|
@@ -1402,26 +1548,26 @@ function must(model, id) {
|
|
|
1402
1548
|
}
|
|
1403
1549
|
function clipFromText(code) {
|
|
1404
1550
|
const text = code.replace(/\s+$/, "");
|
|
1405
|
-
const sf =
|
|
1551
|
+
const sf = ts6.createSourceFile("clip.ts", text, ts6.ScriptTarget.Latest, true);
|
|
1406
1552
|
const first = sf.statements[0];
|
|
1407
1553
|
const diags = sf.parseDiagnostics ?? [];
|
|
1408
1554
|
if (first && !diags.length) {
|
|
1409
1555
|
let name;
|
|
1410
1556
|
let kind = "statement";
|
|
1411
|
-
if (
|
|
1412
|
-
else if (
|
|
1413
|
-
else if (
|
|
1414
|
-
else if (
|
|
1557
|
+
if (ts6.isFunctionDeclaration(first)) name = first.name?.text, kind = "function";
|
|
1558
|
+
else if (ts6.isClassDeclaration(first)) name = first.name?.text, kind = "class";
|
|
1559
|
+
else if (ts6.isVariableStatement(first)) name = first.declarationList.declarations[0]?.name.getText(), kind = "variable";
|
|
1560
|
+
else if (ts6.isInterfaceDeclaration(first) || ts6.isTypeAliasDeclaration(first) || ts6.isEnumDeclaration(first)) name = first.name.text, kind = "type";
|
|
1415
1561
|
if (name) return { code: text, name, kind, fromClass: false };
|
|
1416
1562
|
}
|
|
1417
|
-
const wrapped =
|
|
1563
|
+
const wrapped = ts6.createSourceFile("clip.ts", `class __cc__ {
|
|
1418
1564
|
${text}
|
|
1419
|
-
}`,
|
|
1565
|
+
}`, ts6.ScriptTarget.Latest, true);
|
|
1420
1566
|
const cls = wrapped.statements[0];
|
|
1421
1567
|
const wd = wrapped.parseDiagnostics ?? [];
|
|
1422
|
-
if (cls &&
|
|
1568
|
+
if (cls && ts6.isClassDeclaration(cls) && !wd.length && cls.members[0]?.name) {
|
|
1423
1569
|
const m = cls.members[0];
|
|
1424
|
-
return { code: text, name: m.name.getText(wrapped), kind:
|
|
1570
|
+
return { code: text, name: m.name.getText(wrapped), kind: ts6.isPropertyDeclaration(m) ? "variable" : "function", fromClass: true };
|
|
1425
1571
|
}
|
|
1426
1572
|
return { code: text, name: "", kind: "statement", fromClass: false };
|
|
1427
1573
|
}
|
|
@@ -1579,7 +1725,7 @@ function resolveConflict(text, hunk, choice) {
|
|
|
1579
1725
|
var BAND_ORDER = ["data", "types", "main", "bottom"];
|
|
1580
1726
|
var ZONE_RULES = [
|
|
1581
1727
|
{ zone: "left", description: "Imports on the left", matches: (block) => block.kind === "import" },
|
|
1582
|
-
{ zone: "data", description: "Constants
|
|
1728
|
+
{ zone: "data", description: "Constants, other data and React state at the top", matches: (block) => block.kind === "variable" || block.kind === "state" },
|
|
1583
1729
|
{ zone: "types", description: "Then interfaces, types and enums", matches: (block) => block.kind === "type" },
|
|
1584
1730
|
{ zone: "bottom", description: "Code that runs when the file loads, at the bottom", matches: (block) => block.kind === "statement" },
|
|
1585
1731
|
{ zone: "main", description: "Functions, classes and the rest in between", matches: () => true }
|
|
@@ -1669,7 +1815,7 @@ var PLACEMENT_RULES = [
|
|
|
1669
1815
|
];
|
|
1670
1816
|
var zoneOf = (block) => ZONE_RULES.find((rule) => rule.matches(block)).zone;
|
|
1671
1817
|
var NEWSPAPER_DEPTH = 3;
|
|
1672
|
-
var isFunctionLike = (block) => block.kind === "function";
|
|
1818
|
+
var isFunctionLike = (block) => block.kind === "function" || block.kind === "component";
|
|
1673
1819
|
function accessedName(block, accessor) {
|
|
1674
1820
|
if (block.kind !== "function") return void 0;
|
|
1675
1821
|
if (block.tags.includes(accessor)) return block.name;
|
|
@@ -1823,6 +1969,7 @@ function orderByRelatedness(units, factsOf) {
|
|
|
1823
1969
|
function siblingFacts(blocks, text) {
|
|
1824
1970
|
const idsByName = /* @__PURE__ */ new Map();
|
|
1825
1971
|
for (const block of blocks) idsByName.set(block.name, [...idsByName.get(block.name) ?? [], block.id]);
|
|
1972
|
+
const componentIds = new Set(blocks.filter((block) => block.kind === "component").map((block) => block.id));
|
|
1826
1973
|
const facts = /* @__PURE__ */ new Map();
|
|
1827
1974
|
for (const block of blocks) {
|
|
1828
1975
|
const code = codeWithoutChildren(text, [block.start, block.end], block.children);
|
|
@@ -1832,9 +1979,11 @@ function siblingFacts(blocks, text) {
|
|
|
1832
1979
|
const ids = idsByName.get(match[0])?.filter((id) => id !== block.id);
|
|
1833
1980
|
if (!ids?.length) continue;
|
|
1834
1981
|
const isCall = /^\s*\(/.test(code.slice(match.index + match[0].length, match.index + match[0].length + 40));
|
|
1982
|
+
const isJsxTag = /<\/?$/.test(code.slice(Math.max(0, match.index - 2), match.index));
|
|
1835
1983
|
for (const id of ids) {
|
|
1836
1984
|
uses.add(id);
|
|
1837
|
-
|
|
1985
|
+
const isRender = isJsxTag && componentIds.has(id);
|
|
1986
|
+
if ((isCall || isRender) && !calls.includes(id)) calls.push(id);
|
|
1838
1987
|
}
|
|
1839
1988
|
}
|
|
1840
1989
|
facts.set(block.id, { calls, uses });
|
|
@@ -2171,6 +2320,8 @@ var CSS = (
|
|
|
2171
2320
|
--k-import: #6b7280; --k-import-head: #3b3f46; --k-import-bg: #25272c;
|
|
2172
2321
|
--k-class: #34b27a; --k-class-head: #1d6b4a; --k-class-bg: rgba(52,178,122,.08);
|
|
2173
2322
|
--k-function: #ec5fa5; --k-function-head: #8f2f62; --k-function-bg: rgba(236,95,165,.09);
|
|
2323
|
+
--k-component: #22b8cf; --k-component-head: #146c7a; --k-component-bg: rgba(34,184,207,.09);
|
|
2324
|
+
--k-state: #f0883e; --k-state-head: #8a4614; --k-state-bg: rgba(240,136,62,.09);
|
|
2174
2325
|
--k-variable: #4b8ef7; --k-variable-head: #23508f; --k-variable-bg: rgba(75,142,247,.09);
|
|
2175
2326
|
--k-type: #d4a92a; --k-type-head: #6e5714; --k-type-bg: rgba(212,169,42,.08);
|
|
2176
2327
|
--k-namespace: #9b8cf8; --k-namespace-head: #4d4291; --k-namespace-bg: rgba(155,140,248,.08);
|
|
@@ -2193,6 +2344,8 @@ var CSS = (
|
|
|
2193
2344
|
--k-import: #6b7280; --k-import-head: #4b5058; --k-import-bg: #eceef1;
|
|
2194
2345
|
--k-class: #1f9963; --k-class-head: #1f9963; --k-class-bg: rgba(31,153,99,.07);
|
|
2195
2346
|
--k-function: #d63e8c; --k-function-head: #d63e8c; --k-function-bg: rgba(214,62,140,.07);
|
|
2347
|
+
--k-component: #0f8fa3; --k-component-head: #0f8fa3; --k-component-bg: rgba(15,143,163,.07);
|
|
2348
|
+
--k-state: #d4691c; --k-state-head: #d4691c; --k-state-bg: rgba(212,105,28,.07);
|
|
2196
2349
|
--k-variable: #2f6fe0; --k-variable-head: #2f6fe0; --k-variable-bg: rgba(47,111,224,.07);
|
|
2197
2350
|
--k-type: #b58a0c; --k-type-head: #a87f08; --k-type-bg: rgba(181,138,12,.08);
|
|
2198
2351
|
--k-namespace: #6f5ce6; --k-namespace-head: #6f5ce6; --k-namespace-bg: rgba(111,92,230,.07);
|
|
@@ -2212,6 +2365,8 @@ var CSS = (
|
|
|
2212
2365
|
.cc-block.k-import { --k: var(--k-import); --kh: var(--k-import-head); --kbg: var(--k-import-bg); }
|
|
2213
2366
|
.cc-block.k-class { --k: var(--k-class); --kh: var(--k-class-head); --kbg: var(--k-class-bg); }
|
|
2214
2367
|
.cc-block.k-function { --k: var(--k-function); --kh: var(--k-function-head); --kbg: var(--k-function-bg); }
|
|
2368
|
+
.cc-block.k-component { --k: var(--k-component); --kh: var(--k-component-head); --kbg: var(--k-component-bg); }
|
|
2369
|
+
.cc-block.k-state { --k: var(--k-state); --kh: var(--k-state-head); --kbg: var(--k-state-bg); }
|
|
2215
2370
|
.cc-block.k-variable { --k: var(--k-variable); --kh: var(--k-variable-head); --kbg: var(--k-variable-bg); }
|
|
2216
2371
|
.cc-block.k-type { --k: var(--k-type); --kh: var(--k-type-head); --kbg: var(--k-type-bg); }
|
|
2217
2372
|
.cc-block.k-namespace { --k: var(--k-namespace); --kh: var(--k-namespace-head); --kbg: var(--k-namespace-bg); }
|
|
@@ -2241,6 +2396,7 @@ var CSS = (
|
|
|
2241
2396
|
.cc-mini { position: relative; margin: 6px 12px 0; height: 34px; opacity: .45; }
|
|
2242
2397
|
.cc-mini > i { position: absolute; border-radius: 2px; border: 1px solid var(--cc-faint); background: rgba(255,255,255,.06); }
|
|
2243
2398
|
.cc-mini > i.k-class { border-color: var(--k-class); } .cc-mini > i.k-function { border-color: var(--k-function); }
|
|
2399
|
+
.cc-mini > i.k-component { border-color: var(--k-component); } .cc-mini > i.k-state { border-color: var(--k-state); }
|
|
2244
2400
|
.cc-mini > i.k-variable { border-color: var(--k-variable); } .cc-mini > i.k-type { border-color: var(--k-type); }
|
|
2245
2401
|
|
|
2246
2402
|
.cc-err {
|
|
@@ -2336,6 +2492,7 @@ var CSS = (
|
|
|
2336
2492
|
.cc-btn:hover { background: rgba(127,127,127,.16); }
|
|
2337
2493
|
.cc-btn.danger { color: var(--cc-danger); }
|
|
2338
2494
|
.cc-btn.primary { background: var(--cc-accent); color: #fff; }
|
|
2495
|
+
.cc-language { padding: 0 8px; font: 600 11px/30px var(--cc-font, "JetBrains Mono", ui-monospace, Menlo, monospace); color: var(--cc-muted); white-space: nowrap; }
|
|
2339
2496
|
.cc-sep { width: 1px; height: 20px; background: var(--cc-panel-border); margin: 0 2px; flex: none; }
|
|
2340
2497
|
.cc-refs {
|
|
2341
2498
|
position: absolute; left: 50%; bottom: 64px; transform: translateX(-50%); z-index: 21; width: min(360px, calc(100% - 24px)); max-height: 45%;
|
|
@@ -2385,6 +2542,8 @@ var KIND_LABEL = {
|
|
|
2385
2542
|
import: "import",
|
|
2386
2543
|
class: "class",
|
|
2387
2544
|
function: "fn",
|
|
2545
|
+
component: "component",
|
|
2546
|
+
state: "state",
|
|
2388
2547
|
variable: "var",
|
|
2389
2548
|
type: "type",
|
|
2390
2549
|
namespace: "ns",
|
|
@@ -2414,7 +2573,7 @@ var CodeCanvas = class {
|
|
|
2414
2573
|
this.blocksEl = el("div", "cc-blocks");
|
|
2415
2574
|
this.svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
2416
2575
|
this.svg.classList.add("cc-edges");
|
|
2417
|
-
setHTML(this.svg, `<defs>${["import", "class", "function", "variable", "type", "namespace", "statement"].map(
|
|
2576
|
+
setHTML(this.svg, `<defs>${["import", "class", "function", "component", "state", "variable", "type", "namespace", "statement"].map(
|
|
2418
2577
|
(k) => `<marker id="cc-arrow-${k}" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0 0L10 5L0 10z" fill="var(--k-${k})"/></marker>`
|
|
2419
2578
|
).join("")}</defs>`);
|
|
2420
2579
|
this.world.append(this.frameEl, this.blocksEl, this.svg);
|
|
@@ -3676,7 +3835,8 @@ var CodeCanvas = class {
|
|
|
3676
3835
|
el("div", "cc-sep"),
|
|
3677
3836
|
...firstHunk ? [keep("Keep current", "current", firstHunk.currentLabel), keep("Keep incoming", "incoming", firstHunk.incomingLabel), keep("Keep both", "both"), el("div", "cc-sep")] : [],
|
|
3678
3837
|
button("Edit", () => this.openEditor(b.id)),
|
|
3679
|
-
button("Copy", () => this.copy(b.id))
|
|
3838
|
+
button("Copy", () => this.copy(b.id)),
|
|
3839
|
+
...this.languageLabelParts()
|
|
3680
3840
|
);
|
|
3681
3841
|
return this.renderRefs();
|
|
3682
3842
|
}
|
|
@@ -3707,8 +3867,15 @@ var CodeCanvas = class {
|
|
|
3707
3867
|
button("+", () => this.zoomBy(1.25))
|
|
3708
3868
|
);
|
|
3709
3869
|
}
|
|
3870
|
+
bar.append(...this.languageLabelParts());
|
|
3710
3871
|
this.renderRefs();
|
|
3711
3872
|
}
|
|
3873
|
+
languageLabelParts() {
|
|
3874
|
+
if (!this.view) return [];
|
|
3875
|
+
const label = el("span", "cc-language");
|
|
3876
|
+
label.textContent = this.view.language.label;
|
|
3877
|
+
return [el("div", "cc-sep"), label];
|
|
3878
|
+
}
|
|
3712
3879
|
toggleRefs() {
|
|
3713
3880
|
this.refsOpen = !this.refsOpen;
|
|
3714
3881
|
this.renderBar();
|
|
@@ -3748,7 +3915,7 @@ var CodeCanvas = class {
|
|
|
3748
3915
|
btn.style.setProperty("--k", `var(--k-${block?.kind ?? "statement"})`);
|
|
3749
3916
|
const name = block && model ? displayName(model, block) : blockId;
|
|
3750
3917
|
const label = isOtherFile ? `<span class="cc-file">${esc(fileName(path))} \u203A</span> ${esc(name)}` : esc(name);
|
|
3751
|
-
setHTML(btn, `<i></i><span>${label}</span><span>${usage.kind}${usage.count > 1 ? " \xD7" + usage.count : ""}</span>`);
|
|
3918
|
+
setHTML(btn, `<i></i><span>${label}</span><span>${usage.kind === "render" ? "renders" : usage.kind}${usage.count > 1 ? " \xD7" + usage.count : ""}</span>`);
|
|
3752
3919
|
btn.onclick = () => isOtherFile ? this.opts.onOpenBlock?.(path, blockId) : this.reveal(blockId);
|
|
3753
3920
|
btn.disabled = isOtherFile && !this.opts.onOpenBlock;
|
|
3754
3921
|
return btn;
|