@tilua/compiler 1.1.0 → 2.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/README.md +17 -5
- package/dist/{chunk-2IPPO4V3.js → chunk-TBSJRVKA.js} +205 -60
- package/dist/cli.cjs +204 -59
- package/dist/cli.js +1 -1
- package/dist/index.cjs +204 -59
- package/dist/index.d.cts +3 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -65,10 +65,14 @@ JavaScript module the build loads.
|
|
|
65
65
|
| `function f(n = 1, { x })` | `function f(n, arg) if n == nil then n = 1 end local x = arg.x ...` |
|
|
66
66
|
| `const` / `let` | `local` |
|
|
67
67
|
| `class C { … }` | one table for the class, one per instance — see below |
|
|
68
|
-
| `
|
|
69
|
-
| `
|
|
68
|
+
| `return [a, b]` | `return { a, b }` — several results are an array |
|
|
69
|
+
| `for (const item in xs)` | `for _, item in xs do … end` — an array or a table walked for its values |
|
|
70
|
+
| `for (const item in step)` | `for item in step do … end` — an iterator function |
|
|
71
|
+
| `for (const [k, v] in pairs(t))` | `for item in unpack(pairs(t), 1, 3) do local k, v = item[1], item[2] … end` — an iteration is `[step, state, first]` |
|
|
72
|
+
| `pcall(f)`, and other globals Luau answers several values from | the type library's own function, answering one — see below |
|
|
70
73
|
| `function f(a, ...rest)` | `function f(a, ...) local rest = {...}` |
|
|
71
|
-
| `
|
|
74
|
+
| `scriptArgs` | the chunk's own `...`, as `local scriptArgs = { ... }` |
|
|
75
|
+
| `f(a, ...xs)` | `table.unpack(xs)` in the last position |
|
|
72
76
|
| `f(...xs, a)` | the list built first, then `table.unpack` of it |
|
|
73
77
|
| `x as T`, `x satisfies T`, types, `declare` | removed |
|
|
74
78
|
| `names:filter(f)` | whatever the type library that declared `filter` says — see below |
|
|
@@ -113,8 +117,16 @@ tilua_accessors(Dog)
|
|
|
113
117
|
|
|
114
118
|
`__init` is what `super(...)` calls: it runs the constructor on an instance
|
|
115
119
|
that already exists, so a derived class builds one table, not one per level.
|
|
116
|
-
`new` is a real function on the class table
|
|
117
|
-
|
|
120
|
+
`new` is a real function on the class table, and `Dog.new(x)` is how an
|
|
121
|
+
instance is built — the way Luau builds one. An `abstract class` has no `new`,
|
|
122
|
+
and an `abstract` method no function: both exist only to be checked.
|
|
123
|
+
|
|
124
|
+
A method named for a metamethod — `__add`, `__tostring`, `__call`, … — is an
|
|
125
|
+
ordinary function on the class table, and since the class table is its
|
|
126
|
+
instances' metatable, it works as one. Luau reads metamethods off the
|
|
127
|
+
metatable with `rawget`, never through its `__index`, so a class would not
|
|
128
|
+
inherit its base's: `tilua_class` copies them into each class that extends
|
|
129
|
+
another, and one the class writes itself replaces the copy.
|
|
118
130
|
|
|
119
131
|
Two links come with every class, and they cost an instance nothing because
|
|
120
132
|
they live on the class table: `ClassObject`, which an instance reads through
|
|
@@ -180,14 +180,18 @@ var Lowerer = class {
|
|
|
180
180
|
const body = this.options.module ? this.module(this.options.module) : this.topLevel(this.source.body.statements);
|
|
181
181
|
const helpers = this.helperDefinitions();
|
|
182
182
|
const captured = [...this.builtins].map(([global, local2]) => local([local2], [identifier(global)]));
|
|
183
|
+
const scriptArgs = this.usesScriptArgs && !this.options.module ? [local(["scriptArgs"], [table([{ type: "TableFieldPositional", value: vararg() }])])] : [];
|
|
183
184
|
return {
|
|
184
|
-
statements: [...captured, ...helpers, ...this.classRuntimeStatements(), ...body],
|
|
185
|
+
statements: [...scriptArgs, ...captured, ...helpers, ...this.classRuntimeStatements(), ...body],
|
|
185
186
|
exportsName: this.exportsName,
|
|
186
187
|
module: this.info,
|
|
187
188
|
diagnostics: this.diagnostics,
|
|
188
|
-
origins: this.origins
|
|
189
|
+
origins: this.origins,
|
|
190
|
+
usesScriptArgs: this.usesScriptArgs
|
|
189
191
|
};
|
|
190
192
|
}
|
|
193
|
+
/** Whether the file reads `scriptArgs`, the language's own global. */
|
|
194
|
+
usesScriptArgs = false;
|
|
191
195
|
report(node, message) {
|
|
192
196
|
this.diagnostics.push({ message, line: node.line.start, column: node.column.start });
|
|
193
197
|
}
|
|
@@ -312,6 +316,10 @@ var Lowerer = class {
|
|
|
312
316
|
/** The name a global is read by at `node`, or `undefined` when `node` is
|
|
313
317
|
* not a global — a local of the same name is the author's. */
|
|
314
318
|
globalName(node) {
|
|
319
|
+
if (node.type === "MemberExpression" && !node.optional) {
|
|
320
|
+
const base = this.globalName(node.object);
|
|
321
|
+
return base === void 0 ? void 0 : `${base}.${node.property.name}`;
|
|
322
|
+
}
|
|
315
323
|
if (node.type !== "Identifier") return void 0;
|
|
316
324
|
const id = this.scopes.bindingOf.get(node);
|
|
317
325
|
if (id === void 0) return node.name;
|
|
@@ -608,7 +616,7 @@ var Lowerer = class {
|
|
|
608
616
|
break;
|
|
609
617
|
}
|
|
610
618
|
case "ReturnStatement":
|
|
611
|
-
if (declaration.
|
|
619
|
+
if (declaration.argument) this.report(declaration, "A module cannot return a value; export it instead");
|
|
612
620
|
body.push(returns([]));
|
|
613
621
|
break;
|
|
614
622
|
case "ImportStatement":
|
|
@@ -787,7 +795,7 @@ var Lowerer = class {
|
|
|
787
795
|
case "CallStatement": {
|
|
788
796
|
const chain = optionalChain(node.expression);
|
|
789
797
|
if (chain) return [this.optionalCallStatement(chain)];
|
|
790
|
-
const expression = this.
|
|
798
|
+
const expression = this.callValues(node.expression);
|
|
791
799
|
if (expression.type !== "CallExpression" && expression.type !== "MethodCallExpression") {
|
|
792
800
|
this.report(node, "A statement must be a call");
|
|
793
801
|
return [];
|
|
@@ -840,23 +848,26 @@ var Lowerer = class {
|
|
|
840
848
|
}];
|
|
841
849
|
case "GenericForStatement": {
|
|
842
850
|
const prelude = [];
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
if (variables.length === 1 && this.options.types?.iteratesValues.has(node)) {
|
|
850
|
-
variables.unshift(this.names.fresh("_"));
|
|
851
|
+
let item;
|
|
852
|
+
if (node.variable.type === "IdentifierPattern") {
|
|
853
|
+
item = this.name(node.variable.name);
|
|
854
|
+
} else {
|
|
855
|
+
item = this.names.fresh("item");
|
|
856
|
+
prelude.push(...this.destructure(node.variable, identifier(item), "declare"));
|
|
851
857
|
}
|
|
858
|
+
const form = this.options.types?.loops.get(node) ?? { walks: "values", viaIter: false };
|
|
859
|
+
let source = this.expression(node.iterator);
|
|
860
|
+
if (form.viaIter) source = methodCall(source, "__iter", []);
|
|
861
|
+
const variables = form.walks === "values" ? [this.names.fresh("_"), item] : [item];
|
|
862
|
+
const iterators = form.walks === "iteration" ? [call(this.builtin("unpack"), [source, number(1), number(3)])] : [source];
|
|
852
863
|
return [genericFor(
|
|
853
864
|
variables,
|
|
854
|
-
|
|
865
|
+
iterators,
|
|
855
866
|
this.loopBody51(this.block(node.body, prelude)).statements
|
|
856
867
|
)];
|
|
857
868
|
}
|
|
858
869
|
case "ReturnStatement":
|
|
859
|
-
return [returns(this.
|
|
870
|
+
return [returns(node.argument ? [this.expression(node.argument)] : [])];
|
|
860
871
|
case "BreakStatement":
|
|
861
872
|
return [{ type: "BreakStatement", ...spanOf(node) }];
|
|
862
873
|
case "ContinueStatement":
|
|
@@ -900,7 +911,7 @@ var Lowerer = class {
|
|
|
900
911
|
// end
|
|
901
912
|
// function Dog.speak(this) ... end
|
|
902
913
|
//
|
|
903
|
-
// `new
|
|
914
|
+
// `Dog.new(x)` builds one, `dog:speak()` passes the instance
|
|
904
915
|
// as `this`, and `super.speak()` is `Animal.speak(this)` — the
|
|
905
916
|
// base's own function, run on this instance.
|
|
906
917
|
// ============================================================
|
|
@@ -926,6 +937,7 @@ var Lowerer = class {
|
|
|
926
937
|
classRuntimeStatements() {
|
|
927
938
|
if (!this.classHelpers) return [];
|
|
928
939
|
const { build, accessors } = this.classHelpers;
|
|
940
|
+
const metamethods = `{ ${CLASS_METAMETHODS.map((name) => `"${name}"`).join(", ")} }`;
|
|
929
941
|
return parseLuau(`
|
|
930
942
|
local function ${build}(base)
|
|
931
943
|
local class = { __getters = {}, __setters = {} }
|
|
@@ -936,6 +948,9 @@ local function ${build}(base)
|
|
|
936
948
|
setmetatable(class, { __index = base })
|
|
937
949
|
setmetatable(class.__getters, { __index = base.__getters })
|
|
938
950
|
setmetatable(class.__setters, { __index = base.__setters })
|
|
951
|
+
for _, key in ipairs(${metamethods}) do
|
|
952
|
+
class[key] = base[key]
|
|
953
|
+
end
|
|
939
954
|
end
|
|
940
955
|
return class
|
|
941
956
|
end
|
|
@@ -998,6 +1013,7 @@ end
|
|
|
998
1013
|
}
|
|
999
1014
|
break;
|
|
1000
1015
|
case "ClassMethod":
|
|
1016
|
+
if (member2.isAbstract) break;
|
|
1001
1017
|
out.push(this.functionStatement(
|
|
1002
1018
|
member(self, member2.name.name),
|
|
1003
1019
|
member2,
|
|
@@ -1019,7 +1035,7 @@ end
|
|
|
1019
1035
|
}
|
|
1020
1036
|
}
|
|
1021
1037
|
out.push(this.classInit(node, self));
|
|
1022
|
-
out.push(this.classNew(node, self));
|
|
1038
|
+
if (!node.isAbstract) out.push(this.classNew(node, self));
|
|
1023
1039
|
out.push(callStatement(call(identifier(accessors), [self])));
|
|
1024
1040
|
} finally {
|
|
1025
1041
|
this.classContext = previous;
|
|
@@ -1297,6 +1313,10 @@ end
|
|
|
1297
1313
|
}
|
|
1298
1314
|
switch (node.type) {
|
|
1299
1315
|
case "Identifier":
|
|
1316
|
+
if (node.name === "scriptArgs" && this.isLanguageGlobal(node)) {
|
|
1317
|
+
this.usesScriptArgs = true;
|
|
1318
|
+
return identifier("scriptArgs");
|
|
1319
|
+
}
|
|
1300
1320
|
return this.loweredGlobalValue(node) ?? this.reference(node);
|
|
1301
1321
|
case "NilLiteral":
|
|
1302
1322
|
return nil();
|
|
@@ -1306,8 +1326,6 @@ end
|
|
|
1306
1326
|
return number(node.value, this.numberRaw(node));
|
|
1307
1327
|
case "StringLiteral":
|
|
1308
1328
|
return string(node.value);
|
|
1309
|
-
case "VarargExpression":
|
|
1310
|
-
return vararg();
|
|
1311
1329
|
// Only a recovering parse makes one, and a syntax error stops the build.
|
|
1312
1330
|
case "ErrorExpression":
|
|
1313
1331
|
this.report(node, "Syntax error");
|
|
@@ -1328,11 +1346,6 @@ end
|
|
|
1328
1346
|
}
|
|
1329
1347
|
case "UnaryExpression":
|
|
1330
1348
|
return unary(node.operator, this.expression(node.argument));
|
|
1331
|
-
case "NewExpression":
|
|
1332
|
-
return call(
|
|
1333
|
-
member(this.expression(node.callee), "new"),
|
|
1334
|
-
this.values(node.arguments)
|
|
1335
|
-
);
|
|
1336
1349
|
case "SuperExpression":
|
|
1337
1350
|
return this.superClassReference(node);
|
|
1338
1351
|
case "SpreadElement":
|
|
@@ -1340,17 +1353,16 @@ end
|
|
|
1340
1353
|
return nil();
|
|
1341
1354
|
case "ClassExpression":
|
|
1342
1355
|
return this.classExpression(node);
|
|
1343
|
-
case "MemberExpression":
|
|
1344
|
-
case "IndexExpression":
|
|
1345
1356
|
case "CallExpression":
|
|
1346
|
-
case "MethodCallExpression":
|
|
1357
|
+
case "MethodCallExpression":
|
|
1358
|
+
return this.callValues(node);
|
|
1359
|
+
case "MemberExpression":
|
|
1360
|
+
case "IndexExpression": {
|
|
1347
1361
|
const chain = optionalChain(node);
|
|
1348
1362
|
if (chain) return this.optionalChainExpression(chain);
|
|
1349
|
-
const
|
|
1350
|
-
if (
|
|
1351
|
-
|
|
1352
|
-
}
|
|
1353
|
-
return this.link(node, this.expression(object));
|
|
1363
|
+
const global = node.type === "MemberExpression" ? this.loweredGlobalValue(node) : void 0;
|
|
1364
|
+
if (global) return global;
|
|
1365
|
+
return this.link(node, this.expression(linkObject(node)));
|
|
1354
1366
|
}
|
|
1355
1367
|
case "ParenthesizedExpression":
|
|
1356
1368
|
return parenthesized(this.expression(node.expression));
|
|
@@ -1369,14 +1381,47 @@ end
|
|
|
1369
1381
|
};
|
|
1370
1382
|
}
|
|
1371
1383
|
}
|
|
1384
|
+
/** A call. */
|
|
1385
|
+
callValues(node) {
|
|
1386
|
+
const chain = optionalChain(node);
|
|
1387
|
+
if (chain) return this.optionalChainExpression(chain);
|
|
1388
|
+
const object = linkObject(node);
|
|
1389
|
+
if (node.type === "CallExpression" && object.type === "Identifier") {
|
|
1390
|
+
return this.link(node, this.reference(object));
|
|
1391
|
+
}
|
|
1392
|
+
return this.link(node, this.expression(object));
|
|
1393
|
+
}
|
|
1394
|
+
/** A call is one value in tilua, and Luau would hand on every value one
|
|
1395
|
+
* in the last place of an argument list or an array returns. Types say
|
|
1396
|
+
* how many a call returns; where a type says nothing — `any`, `unknown`
|
|
1397
|
+
* — the call is kept to one: `f((g()))`. */
|
|
1398
|
+
keptToOne(nodes, values) {
|
|
1399
|
+
const node = nodes[nodes.length - 1];
|
|
1400
|
+
const last = values[values.length - 1];
|
|
1401
|
+
if (!node || !last || !expandsToMany(last)) return values;
|
|
1402
|
+
const type = this.options.types?.typeOf.get(node);
|
|
1403
|
+
if (type && type.kind !== "any" && type.kind !== "unknown") return values;
|
|
1404
|
+
return [...values.slice(0, -1), parenthesized(last)];
|
|
1405
|
+
}
|
|
1406
|
+
/** Is this `scriptArgs` the language's own, not a name the file declared? */
|
|
1407
|
+
isLanguageGlobal(node) {
|
|
1408
|
+
const id = this.bindingIdOf(node);
|
|
1409
|
+
return id === void 0 || this.scopes.bindings.get(id)?.kind === "global";
|
|
1410
|
+
}
|
|
1372
1411
|
/** A list of values — a call's arguments, a `return`'s, a declaration's,
|
|
1373
1412
|
* an assignment's. `f(a, ...xs)` is Lua's own last-value expansion,
|
|
1374
1413
|
* `f(a, table.unpack(xs))`; a spread anywhere else cannot be, since only
|
|
1375
1414
|
* the last value of a list expands, so the whole list is built as an
|
|
1376
|
-
* array first and that is what expands.
|
|
1377
|
-
|
|
1415
|
+
* array first and that is what expands.
|
|
1416
|
+
*
|
|
1417
|
+
* `arguments` is set for a call's arguments, where Luau hands on every
|
|
1418
|
+
* value the last one returns — see `keptToOne`. */
|
|
1419
|
+
values(list, asArguments = false) {
|
|
1378
1420
|
const spreads = list.filter((a) => a.type === "SpreadElement");
|
|
1379
|
-
if (!spreads.length)
|
|
1421
|
+
if (!spreads.length) {
|
|
1422
|
+
const lowered = list.map((a) => this.expression(a));
|
|
1423
|
+
return asArguments ? this.keptToOne(list, lowered) : lowered;
|
|
1424
|
+
}
|
|
1380
1425
|
const unpack = (value) => call(member(this.builtin("table"), "unpack"), [value]);
|
|
1381
1426
|
const last = list[list.length - 1];
|
|
1382
1427
|
if (spreads.length === 1 && last.type === "SpreadElement") {
|
|
@@ -1397,13 +1442,13 @@ end
|
|
|
1397
1442
|
return index(object, this.expression(node.index));
|
|
1398
1443
|
case "CallExpression": {
|
|
1399
1444
|
const lowered = this.loweredGlobalCall(node);
|
|
1400
|
-
const args = this.values(node.arguments);
|
|
1401
|
-
if (lowered) return call(
|
|
1402
|
-
return call(
|
|
1445
|
+
const args = this.values(node.arguments, true);
|
|
1446
|
+
if (!lowered) return call(object, args);
|
|
1447
|
+
return call(calleePath(lowered.callee), [...lowered.prepend, ...args]);
|
|
1403
1448
|
}
|
|
1404
1449
|
case "MethodCallExpression": {
|
|
1405
1450
|
const lowered = this.loweredMethodCall(node);
|
|
1406
|
-
const args = this.values(node.arguments);
|
|
1451
|
+
const args = this.values(node.arguments, true);
|
|
1407
1452
|
if (lowered) {
|
|
1408
1453
|
return call(calleePath(lowered.callee), [
|
|
1409
1454
|
...lowered.prepend,
|
|
@@ -1637,7 +1682,8 @@ end
|
|
|
1637
1682
|
* runs of plain elements and the spread arrays are joined by `concat`. */
|
|
1638
1683
|
arrayExpression(node) {
|
|
1639
1684
|
if (!node.elements.some((e) => e.type === "SpreadElement")) {
|
|
1640
|
-
|
|
1685
|
+
const elements = node.elements;
|
|
1686
|
+
return table(this.keptToOne(elements, elements.map((e) => this.expression(e))).map((value) => ({ type: "TableFieldPositional", value })));
|
|
1641
1687
|
}
|
|
1642
1688
|
const parts = [];
|
|
1643
1689
|
let current = [];
|
|
@@ -1836,6 +1882,24 @@ function allNodes(root) {
|
|
|
1836
1882
|
visit(root);
|
|
1837
1883
|
return out;
|
|
1838
1884
|
}
|
|
1885
|
+
var CLASS_METAMETHODS = [
|
|
1886
|
+
"__add",
|
|
1887
|
+
"__sub",
|
|
1888
|
+
"__mul",
|
|
1889
|
+
"__div",
|
|
1890
|
+
"__idiv",
|
|
1891
|
+
"__mod",
|
|
1892
|
+
"__pow",
|
|
1893
|
+
"__unm",
|
|
1894
|
+
"__concat",
|
|
1895
|
+
"__len",
|
|
1896
|
+
"__eq",
|
|
1897
|
+
"__lt",
|
|
1898
|
+
"__le",
|
|
1899
|
+
"__call",
|
|
1900
|
+
"__tostring",
|
|
1901
|
+
"__iter"
|
|
1902
|
+
];
|
|
1839
1903
|
|
|
1840
1904
|
// src/lowering.ts
|
|
1841
1905
|
import { pathToFileURL } from "url";
|
|
@@ -1865,7 +1929,7 @@ async function loadLowerings(modules) {
|
|
|
1865
1929
|
}
|
|
1866
1930
|
|
|
1867
1931
|
// src/bundle.ts
|
|
1868
|
-
import { readFileSync } from "fs";
|
|
1932
|
+
import { readFileSync, statSync } from "fs";
|
|
1869
1933
|
import { dirname, relative, resolve as resolve2 } from "path";
|
|
1870
1934
|
import {
|
|
1871
1935
|
parse,
|
|
@@ -1993,7 +2057,8 @@ async function bundle(options) {
|
|
|
1993
2057
|
name: source.name,
|
|
1994
2058
|
statements: lowered.statements,
|
|
1995
2059
|
exportsName: lowered.exportsName,
|
|
1996
|
-
info: lowered.module
|
|
2060
|
+
info: lowered.module,
|
|
2061
|
+
usesScriptArgs: lowered.usesScriptArgs
|
|
1997
2062
|
});
|
|
1998
2063
|
}
|
|
1999
2064
|
for (const [file, fileDirectives] of directives) {
|
|
@@ -2018,6 +2083,9 @@ async function bundle(options) {
|
|
|
2018
2083
|
}
|
|
2019
2084
|
const program2 = parseLuau2(runtime(G));
|
|
2020
2085
|
const assignment = program2.body.statements[1];
|
|
2086
|
+
if ([...modules.values()].some((m) => m.usesScriptArgs)) {
|
|
2087
|
+
program2.body.statements.unshift(local(["scriptArgs"], [table([{ type: "TableFieldPositional", value: { type: "VarargExpression" } }])]));
|
|
2088
|
+
}
|
|
2021
2089
|
const modulesTable = assignment.type === "AssignmentStatement" && assignment.values[0].type === "TableExpression" ? assignment.values[0].fields.find((f) => f.type === "TableFieldNamed" && f.name.name === "modules")?.value : void 0;
|
|
2022
2090
|
if (!modulesTable) throw new Error("@tilua/compiler: the bundle runtime has no modules table");
|
|
2023
2091
|
for (const module of modules.values()) {
|
|
@@ -2154,7 +2222,22 @@ ${G} = {
|
|
|
2154
2222
|
}
|
|
2155
2223
|
`;
|
|
2156
2224
|
}
|
|
2225
|
+
function stampOf(file) {
|
|
2226
|
+
try {
|
|
2227
|
+
const at = statSync(file);
|
|
2228
|
+
return `${at.size}:${at.mtimeMs}`;
|
|
2229
|
+
} catch {
|
|
2230
|
+
return void 0;
|
|
2231
|
+
}
|
|
2232
|
+
}
|
|
2233
|
+
var parsedFiles = /* @__PURE__ */ new Map();
|
|
2157
2234
|
function parseFile(file, diagnostics, directives) {
|
|
2235
|
+
const stamp = stampOf(file);
|
|
2236
|
+
const cached = stamp !== void 0 ? parsedFiles.get(file) : void 0;
|
|
2237
|
+
if (cached && cached.stamp === stamp) {
|
|
2238
|
+
directives.set(file, cached.directives);
|
|
2239
|
+
return { program: cached.program, text: cached.text };
|
|
2240
|
+
}
|
|
2158
2241
|
let text;
|
|
2159
2242
|
try {
|
|
2160
2243
|
text = readFileSync(file, "utf8");
|
|
@@ -2164,7 +2247,9 @@ function parseFile(file, diagnostics, directives) {
|
|
|
2164
2247
|
}
|
|
2165
2248
|
try {
|
|
2166
2249
|
const program2 = parse(text);
|
|
2167
|
-
|
|
2250
|
+
const found = directivesOf(text);
|
|
2251
|
+
directives.set(file, found);
|
|
2252
|
+
if (stamp !== void 0) parsedFiles.set(file, { stamp, program: program2, text, directives: found });
|
|
2168
2253
|
return { program: program2, text };
|
|
2169
2254
|
} catch (error) {
|
|
2170
2255
|
if (error instanceof ParseError || error instanceof LexError) {
|
|
@@ -2178,22 +2263,93 @@ function parseFile(file, diagnostics, directives) {
|
|
|
2178
2263
|
function importedSpecifiers(program2) {
|
|
2179
2264
|
return program2.body.statements.flatMap((s) => s.type === "ImportStatement" || s.type === "ExportAllStatement" || s.type === "ExportNamedStatement" && s.source ? [s.source.value] : []);
|
|
2180
2265
|
}
|
|
2266
|
+
var libraryCache = /* @__PURE__ */ new Map();
|
|
2267
|
+
function parsedLibrary(file) {
|
|
2268
|
+
let stamp;
|
|
2269
|
+
try {
|
|
2270
|
+
const at = statSync(file);
|
|
2271
|
+
stamp = `${at.size}:${at.mtimeMs}`;
|
|
2272
|
+
} catch {
|
|
2273
|
+
stamp = "";
|
|
2274
|
+
}
|
|
2275
|
+
const cached = libraryCache.get(file);
|
|
2276
|
+
if (cached && cached.stamp === stamp) return cached.program;
|
|
2277
|
+
const program2 = parse(readFileSync(file, "utf8"));
|
|
2278
|
+
libraryCache.set(file, { stamp, program: program2 });
|
|
2279
|
+
return program2;
|
|
2280
|
+
}
|
|
2281
|
+
var moduleAnalyses = /* @__PURE__ */ new Map();
|
|
2181
2282
|
function analyzeModules(modules, config) {
|
|
2182
2283
|
const out = [];
|
|
2183
2284
|
const analyses = /* @__PURE__ */ new Map();
|
|
2184
2285
|
const libraries = config ? resolveTypeLibraries(config) : { files: [], lowerings: [], problems: [] };
|
|
2185
2286
|
for (const p of libraries.problems) out.push({ file: p.file, message: p.message, line: p.line ?? 1, column: p.column ?? 1, category: "config" });
|
|
2186
|
-
const libs = libraries.files.map(
|
|
2287
|
+
const libs = libraries.files.map(parsedLibrary);
|
|
2187
2288
|
const globals = libs.flatMap((lib) => lib.body.statements.flatMap((s) => s.type === "DeclareStatement" ? [s.name] : []));
|
|
2188
2289
|
const byFile = new Map(modules.map((m) => [m.file, m]));
|
|
2189
2290
|
const exportsCache = /* @__PURE__ */ new Map();
|
|
2190
2291
|
const inProgress = /* @__PURE__ */ new Set();
|
|
2292
|
+
const analyzed = /* @__PURE__ */ new Map();
|
|
2293
|
+
let partials = 0;
|
|
2294
|
+
const readWhile = /* @__PURE__ */ new Map();
|
|
2295
|
+
const settingFor = `${libraries.files.join("|")}#${config?.directory ?? ""}#${JSON.stringify(config?.paths ?? {})}`;
|
|
2296
|
+
const analyzeModule = (file, program2) => {
|
|
2297
|
+
const done = analyzed.get(file);
|
|
2298
|
+
if (done) return done;
|
|
2299
|
+
const remembered = moduleAnalyses.get(file);
|
|
2300
|
+
if (remembered && remembered.setting === settingFor && libs.length === remembered.libs.length && remembered.libs.every((lib, i) => lib === libs[i]) && remembered.program === program2 && [...remembered.read].every(([read, stamp]) => stampOf(read) === stamp)) {
|
|
2301
|
+
analyzed.set(file, remembered.result);
|
|
2302
|
+
readWhile.set(file, remembered.read);
|
|
2303
|
+
return remembered.result;
|
|
2304
|
+
}
|
|
2305
|
+
const scopes = analyzeScopes(program2, { builtinGlobals: globals, reportUndeclared: libs.length > 0 });
|
|
2306
|
+
const said = [];
|
|
2307
|
+
for (const d of scopes.diagnostics) {
|
|
2308
|
+
if (d.kind !== "undeclared") continue;
|
|
2309
|
+
said.push({ file, message: d.message, line: d.node.line.start, column: d.node.column.start, category: "type" });
|
|
2310
|
+
}
|
|
2311
|
+
const before = partials;
|
|
2312
|
+
const types = analyzeTypes(program2, scopes, {
|
|
2313
|
+
libs,
|
|
2314
|
+
resolveModule: resolverFor(file),
|
|
2315
|
+
reportUnknownTypes: libs.length > 0
|
|
2316
|
+
});
|
|
2317
|
+
for (const d of types.diagnostics) {
|
|
2318
|
+
const at = d.node;
|
|
2319
|
+
said.push({ file, message: d.message, line: at.line.start, column: at.column.start, category: "type" });
|
|
2320
|
+
}
|
|
2321
|
+
const result = { types, diagnostics: said };
|
|
2322
|
+
if (partials === before) {
|
|
2323
|
+
analyzed.set(file, result);
|
|
2324
|
+
const read = readWhile.get(file) ?? /* @__PURE__ */ new Map();
|
|
2325
|
+
const own = stampOf(file);
|
|
2326
|
+
if (own !== void 0) read.set(file, own);
|
|
2327
|
+
readWhile.set(file, read);
|
|
2328
|
+
if ([...read.values()].every((stamp) => stamp !== void 0)) {
|
|
2329
|
+
moduleAnalyses.set(file, { setting: settingFor, libs: [...libs], program: program2, read, result });
|
|
2330
|
+
}
|
|
2331
|
+
}
|
|
2332
|
+
return result;
|
|
2333
|
+
};
|
|
2191
2334
|
const resolverFor = (file) => (specifier) => {
|
|
2192
2335
|
const target = resolveModulePath(file, specifier, config);
|
|
2193
2336
|
if (!target) return void 0;
|
|
2194
|
-
|
|
2337
|
+
const noteRead = () => {
|
|
2338
|
+
const read = readWhile.get(file) ?? /* @__PURE__ */ new Map();
|
|
2339
|
+
readWhile.set(file, read);
|
|
2340
|
+
const stamp = stampOf(target);
|
|
2341
|
+
if (stamp !== void 0) read.set(target, stamp);
|
|
2342
|
+
for (const [below, belowStamp] of readWhile.get(target) ?? []) read.set(below, belowStamp);
|
|
2343
|
+
};
|
|
2344
|
+
if (inProgress.has(target)) {
|
|
2345
|
+
partials++;
|
|
2346
|
+
return { values: /* @__PURE__ */ new Map(), types: /* @__PURE__ */ new Map(), partial: true };
|
|
2347
|
+
}
|
|
2195
2348
|
const cached = exportsCache.get(target);
|
|
2196
|
-
if (cached)
|
|
2349
|
+
if (cached) {
|
|
2350
|
+
noteRead();
|
|
2351
|
+
return cached;
|
|
2352
|
+
}
|
|
2197
2353
|
let program2 = byFile.get(target)?.program;
|
|
2198
2354
|
if (!program2) {
|
|
2199
2355
|
try {
|
|
@@ -2205,30 +2361,19 @@ function analyzeModules(modules, config) {
|
|
|
2205
2361
|
inProgress.add(target);
|
|
2206
2362
|
try {
|
|
2207
2363
|
const scopes = analyzeScopes(program2, { builtinGlobals: globals });
|
|
2208
|
-
const types = analyzeTypes(program2, scopes, { libs, resolveModule: resolverFor(target), diagnostics: false });
|
|
2364
|
+
const types = byFile.has(target) ? analyzeModule(target, program2).types : analyzeTypes(program2, scopes, { libs, resolveModule: resolverFor(target), diagnostics: false });
|
|
2209
2365
|
const exports = moduleExports(program2, scopes, types, resolverFor(target));
|
|
2210
2366
|
exportsCache.set(target, exports);
|
|
2367
|
+
noteRead();
|
|
2211
2368
|
return exports;
|
|
2212
2369
|
} finally {
|
|
2213
2370
|
inProgress.delete(target);
|
|
2214
2371
|
}
|
|
2215
2372
|
};
|
|
2216
2373
|
for (const module of modules) {
|
|
2217
|
-
const
|
|
2218
|
-
for (const d of scopes.diagnostics) {
|
|
2219
|
-
if (d.kind !== "undeclared") continue;
|
|
2220
|
-
out.push({ file: module.file, message: d.message, line: d.node.line.start, column: d.node.column.start, category: "type" });
|
|
2221
|
-
}
|
|
2222
|
-
const types = analyzeTypes(module.program, scopes, {
|
|
2223
|
-
libs,
|
|
2224
|
-
resolveModule: resolverFor(module.file),
|
|
2225
|
-
reportUnknownTypes: libs.length > 0
|
|
2226
|
-
});
|
|
2374
|
+
const { types, diagnostics } = analyzeModule(module.file, module.program);
|
|
2227
2375
|
analyses.set(module.file, types);
|
|
2228
|
-
|
|
2229
|
-
const at = d.node;
|
|
2230
|
-
out.push({ file: module.file, message: d.message, line: at.line.start, column: at.column.start, category: "type" });
|
|
2231
|
-
}
|
|
2376
|
+
out.push(...diagnostics);
|
|
2232
2377
|
}
|
|
2233
2378
|
return { types: analyses, diagnostics: out, lowerings: [...libraries.lowerings] };
|
|
2234
2379
|
}
|