@tilua/parser 3.0.1 → 4.0.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 +29 -10
- package/dist/index.cjs +515 -93
- package/dist/index.d.cts +84 -5
- package/dist/index.d.ts +84 -5
- package/dist/index.js +504 -92
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -596,6 +596,8 @@ var UNUSED_EXPECT_ERROR = "Unused '@tilua-expect-error' directive";
|
|
|
596
596
|
|
|
597
597
|
// src/ast/builders.ts
|
|
598
598
|
var NO_DOTS = "tilua has no bare '...': take a rest parameter, '...args: T[]', or 'scriptArgs' for the script's own";
|
|
599
|
+
var NO_MEMBER_NAME = "Expected a name after '.'";
|
|
600
|
+
var NO_TYPEOF_CALL = "'typeof' is an operator, not a function: write 'typeof v'";
|
|
599
601
|
var ACCESSIBILITIES = /* @__PURE__ */ new Set(["public", "private", "protected"]);
|
|
600
602
|
var CLASS_MODIFIERS = /* @__PURE__ */ new Set([...ACCESSIBILITIES, "static", "abstract", "override", "readonly"]);
|
|
601
603
|
function bindThis(func, at) {
|
|
@@ -603,7 +605,7 @@ function bindThis(func, at) {
|
|
|
603
605
|
func.isMethod = true;
|
|
604
606
|
}
|
|
605
607
|
function bindThisParam(params, at) {
|
|
606
|
-
params.unshift({ type: "FunctionParameter", name: "this", ...spanFrom(at, at) });
|
|
608
|
+
params.unshift({ type: "FunctionParameter", name: "this", implicit: true, ...spanFrom(at, at) });
|
|
607
609
|
}
|
|
608
610
|
var ParseError = class extends Error {
|
|
609
611
|
constructor(message, line, column) {
|
|
@@ -1660,7 +1662,7 @@ var Parser = class {
|
|
|
1660
1662
|
};
|
|
1661
1663
|
}
|
|
1662
1664
|
if (isMethod) {
|
|
1663
|
-
func.params.unshift({ type: "FunctionParameter", name: "self", ...spanFrom(target, target) });
|
|
1665
|
+
func.params.unshift({ type: "FunctionParameter", name: "self", implicit: true, ...spanFrom(target, target) });
|
|
1664
1666
|
func.isMethod = true;
|
|
1665
1667
|
}
|
|
1666
1668
|
return {
|
|
@@ -1854,47 +1856,7 @@ var Parser = class {
|
|
|
1854
1856
|
const isStatic = modifiers.has("static");
|
|
1855
1857
|
if (this.checkKeyword("function")) {
|
|
1856
1858
|
this.advance();
|
|
1857
|
-
|
|
1858
|
-
if (modifiers.has("abstract")) {
|
|
1859
|
-
const head = this.parseFunctionHead();
|
|
1860
|
-
const func = {
|
|
1861
|
-
type: "FunctionBody",
|
|
1862
|
-
generics: head.generics,
|
|
1863
|
-
params: head.params,
|
|
1864
|
-
hasVarargs: head.hasVarargs,
|
|
1865
|
-
returnType: head.returnType,
|
|
1866
|
-
predicate: head.predicate,
|
|
1867
|
-
body: { type: "Block", statements: [], ...spanFrom(head.start, this.previous()) },
|
|
1868
|
-
...spanFrom(head.start, this.previous())
|
|
1869
|
-
};
|
|
1870
|
-
if (!isStatic) bindThis(func, start);
|
|
1871
|
-
if (!head.returnType) this.problem("An abstract method has no body to infer from: write its return type");
|
|
1872
|
-
if (this.checkPunctuator("{")) this.problem("An abstract method has no body");
|
|
1873
|
-
return { type: "ClassMethod", name: memberName, isStatic, func, isAbstract: true, ...spanFrom(start, this.previous()) };
|
|
1874
|
-
}
|
|
1875
|
-
const signatures = [];
|
|
1876
|
-
let written = memberName;
|
|
1877
|
-
while (true) {
|
|
1878
|
-
const head = this.parseFunctionHead();
|
|
1879
|
-
if (this.isClassOverloadContinuation(memberName.name, isStatic)) {
|
|
1880
|
-
if (!isStatic) bindThisParam(head.params, start);
|
|
1881
|
-
signatures.push({ ...this.headToSignature(head), name: written });
|
|
1882
|
-
if (isStatic) this.advance();
|
|
1883
|
-
this.expectKeyword("function");
|
|
1884
|
-
written = this.parseIdentifier();
|
|
1885
|
-
continue;
|
|
1886
|
-
}
|
|
1887
|
-
const func = this.headToBody(head, start);
|
|
1888
|
-
if (!isStatic) bindThis(func, start);
|
|
1889
|
-
return {
|
|
1890
|
-
type: "ClassMethod",
|
|
1891
|
-
name: memberName,
|
|
1892
|
-
isStatic,
|
|
1893
|
-
func,
|
|
1894
|
-
signatures: signatures.length ? signatures : void 0,
|
|
1895
|
-
...spanFrom(start, this.previous())
|
|
1896
|
-
};
|
|
1897
|
-
}
|
|
1859
|
+
return this.parseClassMethod(start, modifiers);
|
|
1898
1860
|
}
|
|
1899
1861
|
if (!isStatic && this.checkIdentifierValue("constructor") && this.punctuatorAt(1, "(")) {
|
|
1900
1862
|
this.advance();
|
|
@@ -1904,6 +1866,9 @@ var Parser = class {
|
|
|
1904
1866
|
bindThis(func, start);
|
|
1905
1867
|
return { type: "ClassConstructor", func, ...spanFrom(start, this.previous()) };
|
|
1906
1868
|
}
|
|
1869
|
+
if (this.startsMethodShorthand(0)) {
|
|
1870
|
+
return this.parseClassMethod(start, modifiers);
|
|
1871
|
+
}
|
|
1907
1872
|
if ((this.checkIdentifierValue("get") || this.checkIdentifierValue("set")) && this.peek(1).type === "Identifier" && this.punctuatorAt(2, "(")) {
|
|
1908
1873
|
const kind = this.advance().value;
|
|
1909
1874
|
const memberName = this.parseIdentifier();
|
|
@@ -1933,25 +1898,90 @@ var Parser = class {
|
|
|
1933
1898
|
return { type: "ClassField", name: memberName, isStatic, typeAnnotation, init, ...spanFrom(start, this.previous()) };
|
|
1934
1899
|
}
|
|
1935
1900
|
if (this.recover) {
|
|
1936
|
-
this.softError("Expected a class member: a field,
|
|
1901
|
+
this.softError("Expected a class member: a field, a method, 'constructor', 'get' or 'set'");
|
|
1937
1902
|
this.advance();
|
|
1938
1903
|
return void 0;
|
|
1939
1904
|
}
|
|
1940
|
-
this.error("Expected a class member: a field,
|
|
1905
|
+
this.error("Expected a class member: a field, a method, 'constructor', 'get' or 'set'");
|
|
1906
|
+
}
|
|
1907
|
+
/** A name at `offset` opening a method without `function`: `name(` or
|
|
1908
|
+
* `name<T>(`. A field's name is followed by `:` or `=` instead. */
|
|
1909
|
+
startsMethodShorthand(offset) {
|
|
1910
|
+
if (this.peek(offset).type !== "Identifier") return false;
|
|
1911
|
+
const next = this.peek(offset + 1);
|
|
1912
|
+
return (next.type === "Punctuator" || next.type === "Operator") && (next.value === "(" || next.value === "<");
|
|
1913
|
+
}
|
|
1914
|
+
/** A method, from its name on: `function` (if written) and the modifiers
|
|
1915
|
+
* are behind. */
|
|
1916
|
+
parseClassMethod(start, modifiers) {
|
|
1917
|
+
const isStatic = modifiers.has("static");
|
|
1918
|
+
const memberName = this.parseIdentifier();
|
|
1919
|
+
if (modifiers.has("abstract")) {
|
|
1920
|
+
const head = this.parseFunctionHead();
|
|
1921
|
+
const func = {
|
|
1922
|
+
type: "FunctionBody",
|
|
1923
|
+
generics: head.generics,
|
|
1924
|
+
params: head.params,
|
|
1925
|
+
hasVarargs: head.hasVarargs,
|
|
1926
|
+
returnType: head.returnType,
|
|
1927
|
+
predicate: head.predicate,
|
|
1928
|
+
body: { type: "Block", statements: [], ...spanFrom(head.start, this.previous()) },
|
|
1929
|
+
...spanFrom(head.start, this.previous())
|
|
1930
|
+
};
|
|
1931
|
+
if (!isStatic) bindThis(func, start);
|
|
1932
|
+
if (!head.returnType) this.problem("An abstract method has no body to infer from: write its return type");
|
|
1933
|
+
if (this.checkPunctuator("{")) this.problem("An abstract method has no body");
|
|
1934
|
+
return { type: "ClassMethod", name: memberName, isStatic, func, isAbstract: true, ...spanFrom(start, this.previous()) };
|
|
1935
|
+
}
|
|
1936
|
+
const signatures = [];
|
|
1937
|
+
let written = memberName;
|
|
1938
|
+
while (true) {
|
|
1939
|
+
const head = this.parseFunctionHead();
|
|
1940
|
+
const skip = this.classOverloadContinuation(memberName.name, isStatic);
|
|
1941
|
+
if (skip !== void 0) {
|
|
1942
|
+
if (!isStatic) bindThisParam(head.params, start);
|
|
1943
|
+
signatures.push({ ...this.headToSignature(head), name: written });
|
|
1944
|
+
for (let i = 0; i < skip; i++) this.advance();
|
|
1945
|
+
written = this.parseIdentifier();
|
|
1946
|
+
continue;
|
|
1947
|
+
}
|
|
1948
|
+
const func = this.headToBody(head, start);
|
|
1949
|
+
if (!isStatic) bindThis(func, start);
|
|
1950
|
+
return {
|
|
1951
|
+
type: "ClassMethod",
|
|
1952
|
+
name: memberName,
|
|
1953
|
+
isStatic,
|
|
1954
|
+
func,
|
|
1955
|
+
signatures: signatures.length ? signatures : void 0,
|
|
1956
|
+
...spanFrom(start, this.previous())
|
|
1957
|
+
};
|
|
1958
|
+
}
|
|
1941
1959
|
}
|
|
1942
|
-
/** Give a class method its `this`: a real first parameter, the way
|
|
1943
|
-
* `function T:m()` gets a real `self`. Every later pass — scopes, types,
|
|
1944
|
-
* arity, lowering — then sees an ordinary parameter and needs to know
|
|
1945
|
-
* nothing about classes. */
|
|
1946
1960
|
/** After a bodyless head inside a class body, does another declaration of
|
|
1947
|
-
* the same member follow? Then the head was an overload signature
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1961
|
+
* the same member follow? Then the head was an overload signature, and
|
|
1962
|
+
* this is how many tokens stand before the next one's name: a `;` that
|
|
1963
|
+
* ends the head, as TypeScript writes it, the modifiers, and `function`
|
|
1964
|
+
* when it is written. `undefined` when no overload follows. Each head may
|
|
1965
|
+
* be written either way, with `function` or without. */
|
|
1966
|
+
classOverloadContinuation(name, isStatic) {
|
|
1967
|
+
let offset = 0;
|
|
1968
|
+
if (this.punctuatorAt(0, ";")) offset++;
|
|
1969
|
+
let sawStatic = false;
|
|
1970
|
+
while (true) {
|
|
1971
|
+
const t = this.peek(offset);
|
|
1972
|
+
const word = t.type === "Identifier" ? t.value : void 0;
|
|
1973
|
+
if (word === void 0 || !CLASS_MODIFIERS.has(word) || this.startsMethodShorthand(offset)) break;
|
|
1974
|
+
if (word === "static") sawStatic = true;
|
|
1975
|
+
offset++;
|
|
1976
|
+
}
|
|
1977
|
+
if (sawStatic !== isStatic) return void 0;
|
|
1951
1978
|
const keyword = this.peek(offset);
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1979
|
+
const hasKeyword = keyword.type === "Keyword" && keyword.value === "function";
|
|
1980
|
+
if (hasKeyword) offset++;
|
|
1981
|
+
const named = this.peek(offset);
|
|
1982
|
+
if (!(named.type === "Identifier" && named.value === name)) return void 0;
|
|
1983
|
+
if (!hasKeyword && !this.startsMethodShorthand(offset)) return void 0;
|
|
1984
|
+
return offset;
|
|
1955
1985
|
}
|
|
1956
1986
|
operatorAt(ahead, value) {
|
|
1957
1987
|
const token = this.peek(ahead);
|
|
@@ -2105,6 +2135,7 @@ var Parser = class {
|
|
|
2105
2135
|
* `:` Identifier followed by one of Lua's call forms. */
|
|
2106
2136
|
startsMethodCall(offset = 0) {
|
|
2107
2137
|
if (this.peek(offset + 1).type !== "Identifier") return false;
|
|
2138
|
+
if (this.startsOwnLine(this.peek(offset), this.peek(offset + 1))) return false;
|
|
2108
2139
|
const after = this.peek(offset + 2);
|
|
2109
2140
|
if (after.type === "Punctuator") {
|
|
2110
2141
|
const v = String(after.value);
|
|
@@ -2130,6 +2161,23 @@ var Parser = class {
|
|
|
2130
2161
|
const isString = t.type === "InterpolatedString" || t.type === "Literal" && t.kind === "string";
|
|
2131
2162
|
return isString && this.punctuatorAt(1, ":") && this.startsMethodCall(1);
|
|
2132
2163
|
}
|
|
2164
|
+
/** Is there no member name at `offset`, after the `.` or `:` before it? */
|
|
2165
|
+
namelessAfter(offset) {
|
|
2166
|
+
const name = this.peek(offset);
|
|
2167
|
+
if (name.type !== "Identifier") return true;
|
|
2168
|
+
const before = offset === 0 ? this.previous() : this.peek(offset - 1);
|
|
2169
|
+
return before !== void 0 && this.startsOwnLine(before, name);
|
|
2170
|
+
}
|
|
2171
|
+
/** Recovery only. `obj.` may go on to its name on a later line, as in
|
|
2172
|
+
* JavaScript — but while `obj.` is still being typed, the line below is
|
|
2173
|
+
* whatever statement was already there, and reading it as the name turns
|
|
2174
|
+
* one unfinished line into errors about the next. So, as a missing `}`
|
|
2175
|
+
* is placed, the indentation says which: a line that carries the access
|
|
2176
|
+
* on is indented deeper than the line it carries on from. */
|
|
2177
|
+
startsOwnLine(before, name) {
|
|
2178
|
+
if (!this.recover || name.line.start <= before.line.end) return false;
|
|
2179
|
+
return this.indentOf(name) <= this.indentOf(before);
|
|
2180
|
+
}
|
|
2133
2181
|
/** Does the next token start right where the current one ends? */
|
|
2134
2182
|
touchesNext() {
|
|
2135
2183
|
const current = this.current();
|
|
@@ -2171,6 +2219,7 @@ var Parser = class {
|
|
|
2171
2219
|
const t = this.current();
|
|
2172
2220
|
if (t.type === "Keyword" && t.value === "not") return "not";
|
|
2173
2221
|
if (t.type === "Operator" && (t.value === "-" || t.value === "#")) return t.value;
|
|
2222
|
+
if (t.type === "Identifier" && t.value === "typeof") return "typeof";
|
|
2174
2223
|
return null;
|
|
2175
2224
|
}
|
|
2176
2225
|
/** Runs `fn` inside a bracket, where a `:` can only ever be a method
|
|
@@ -2270,6 +2319,10 @@ var Parser = class {
|
|
|
2270
2319
|
const op = this.isUnaryOperator();
|
|
2271
2320
|
if (op) {
|
|
2272
2321
|
const opTok = this.advance();
|
|
2322
|
+
const next = this.current();
|
|
2323
|
+
if (op === "typeof" && this.checkPunctuator("(") && next.line.start === opTok.line.end && next.column.start === opTok.column.end) {
|
|
2324
|
+
this.problemAt(next, NO_TYPEOF_CALL);
|
|
2325
|
+
}
|
|
2273
2326
|
const argument = this.parseExpression(UNARY_PRECEDENCE);
|
|
2274
2327
|
return {
|
|
2275
2328
|
type: "UnaryExpression",
|
|
@@ -2439,6 +2492,13 @@ var Parser = class {
|
|
|
2439
2492
|
if (this.checkPunctuator("?") && this.touchesNext()) {
|
|
2440
2493
|
const next = this.peek(1);
|
|
2441
2494
|
const punct = next.type === "Punctuator" ? String(next.value) : void 0;
|
|
2495
|
+
if (punct === "." && this.recover && this.namelessAfter(2) && !this.punctuatorAt(2, "[") && !this.punctuatorAt(2, "(")) {
|
|
2496
|
+
this.advance();
|
|
2497
|
+
this.advance();
|
|
2498
|
+
this.problemAt(this.previous(), NO_MEMBER_NAME);
|
|
2499
|
+
base = { type: "ErrorExpression", expression: base, ...spanFrom(base, this.previous()) };
|
|
2500
|
+
break;
|
|
2501
|
+
}
|
|
2442
2502
|
if (punct === "." && this.peek(2).type === "Identifier") {
|
|
2443
2503
|
this.advance();
|
|
2444
2504
|
this.advance();
|
|
@@ -2493,9 +2553,9 @@ var Parser = class {
|
|
|
2493
2553
|
}
|
|
2494
2554
|
}
|
|
2495
2555
|
if (this.matchPunctuator(".")) {
|
|
2496
|
-
if (this.
|
|
2497
|
-
this.
|
|
2498
|
-
base = { type: "ErrorExpression", ...spanFrom(base, this.previous()) };
|
|
2556
|
+
if (this.namelessAfter(0)) {
|
|
2557
|
+
this.problemAt(this.previous(), NO_MEMBER_NAME);
|
|
2558
|
+
base = { type: "ErrorExpression", expression: base, ...spanFrom(base, this.previous()) };
|
|
2499
2559
|
break;
|
|
2500
2560
|
}
|
|
2501
2561
|
const prop = this.parseIdentifier();
|
|
@@ -2508,6 +2568,12 @@ var Parser = class {
|
|
|
2508
2568
|
base = { type: "IndexExpression", object: base, index, ...spanFrom(base, this.previous()) };
|
|
2509
2569
|
continue;
|
|
2510
2570
|
}
|
|
2571
|
+
if (this.recover && !this.ternaryColons && this.checkPunctuator(":") && this.peek(1).line.start > this.current().line.end && !this.startsMethodCall()) {
|
|
2572
|
+
this.advance();
|
|
2573
|
+
this.problemAt(this.previous(), "Expected a name after ':'");
|
|
2574
|
+
base = { type: "ErrorExpression", expression: base, ...spanFrom(base, this.previous()) };
|
|
2575
|
+
break;
|
|
2576
|
+
}
|
|
2511
2577
|
if (this.checkPunctuator(":") && this.startsMethodCall() && this.takeMethodColon()) {
|
|
2512
2578
|
this.advance();
|
|
2513
2579
|
const method = this.parseIdentifier();
|
|
@@ -3351,6 +3417,7 @@ var Parser = class {
|
|
|
3351
3417
|
return { type: "TupleTypeNode", elements, ...rest ? { rest } : {}, ...spanFrom(t, this.previous()) };
|
|
3352
3418
|
}
|
|
3353
3419
|
if (t.type === "Identifier" && t.value === "typeof" && this.peek(1).type === "Punctuator" && this.peek(1).value === "(") {
|
|
3420
|
+
this.problemAt(this.peek(1), NO_TYPEOF_CALL);
|
|
3354
3421
|
this.advance();
|
|
3355
3422
|
this.advance();
|
|
3356
3423
|
const expression = this.parseExpression();
|
|
@@ -3731,7 +3798,7 @@ var BinaryOperators = [
|
|
|
3731
3798
|
"and",
|
|
3732
3799
|
"or"
|
|
3733
3800
|
];
|
|
3734
|
-
var UnaryOperators = ["-", "not", "#"];
|
|
3801
|
+
var UnaryOperators = ["-", "not", "#", "typeof"];
|
|
3735
3802
|
|
|
3736
3803
|
// src/ast/analyzeScopes.ts
|
|
3737
3804
|
function getBinding(analysis, id) {
|
|
@@ -4358,7 +4425,9 @@ var Analyzer = class {
|
|
|
4358
4425
|
case "BooleanLiteral":
|
|
4359
4426
|
case "NumberLiteral":
|
|
4360
4427
|
case "StringLiteral":
|
|
4428
|
+
return;
|
|
4361
4429
|
case "ErrorExpression":
|
|
4430
|
+
if (expr.expression) this.visitExpression(expr.expression, scope);
|
|
4362
4431
|
return;
|
|
4363
4432
|
case "InterpolatedStringExpression":
|
|
4364
4433
|
for (const part of expr.parts) {
|
|
@@ -4571,11 +4640,34 @@ declare metatable<T> T[]: {
|
|
|
4571
4640
|
-- \`nil\` when it is missing; an indexer's keys follow the named ones. A
|
|
4572
4641
|
-- member of the table's own with one of these names is what a call reaches,
|
|
4573
4642
|
-- and a class instance, which has a metatable of its own, has none of them.
|
|
4643
|
+
--
|
|
4644
|
+
-- The rest are JavaScript's \`Object\` functions, written as methods of the
|
|
4645
|
+
-- table they take first:
|
|
4646
|
+
--
|
|
4647
|
+
-- point:hasOwn("x") -- Object.hasOwn(point, "x")
|
|
4648
|
+
-- point:assign({ z: 3 }) -- Object.assign(point, { z: 3 })
|
|
4649
|
+
-- point:freeze() -- Object.freeze(point)
|
|
4650
|
+
--
|
|
4651
|
+
-- \`hasOwn\` asks the table itself, not what its metatable's \`__index\`
|
|
4652
|
+
-- lends it. \`assign\` copies each source's members onto the table, later ones
|
|
4653
|
+
-- over earlier ones, skipping a \`nil\` source, and answers the table. \`freeze\`
|
|
4654
|
+
-- is Luau's \`table.freeze\`; under Lua 5.1, which cannot freeze a table, the
|
|
4655
|
+
-- table is answered as it is and \`isFrozen\` is false.
|
|
4574
4656
|
declare metatable<T extends {}> T: {
|
|
4575
4657
|
__index: {
|
|
4576
4658
|
keys: (self: T) => ObjectKeys<T>,
|
|
4577
4659
|
values: (self: T) => ObjectValues<T>,
|
|
4578
4660
|
entries: (self: T) => ObjectEntries<T>,
|
|
4661
|
+
|
|
4662
|
+
hasOwn: (self: T, key: unknown) => boolean,
|
|
4663
|
+
hasOwnProperty: (self: T, key: unknown) => boolean,
|
|
4664
|
+
assign: (<U>(self: T, source: U) => T & U)
|
|
4665
|
+
& (<U, V>(self: T, a: U, b: V) => T & U & V)
|
|
4666
|
+
& (<U, V, W>(self: T, a: U, b: V, c: W) => T & U & V & W)
|
|
4667
|
+
-- Generic like the rest: a plain signature would be tried first.
|
|
4668
|
+
& (<S extends {}>(self: T, ...sources: S[]) => T),
|
|
4669
|
+
freeze: (self: T) => Readonly<T>,
|
|
4670
|
+
isFrozen: (self: T) => boolean,
|
|
4579
4671
|
},
|
|
4580
4672
|
}
|
|
4581
4673
|
`;
|
|
@@ -5641,6 +5733,13 @@ function mergeEnv(a, b, base) {
|
|
|
5641
5733
|
}
|
|
5642
5734
|
return out;
|
|
5643
5735
|
}
|
|
5736
|
+
function joinNarrowings(into, a, aFrom, b, bFrom) {
|
|
5737
|
+
for (const [key, left] of a) {
|
|
5738
|
+
const right = b.get(key);
|
|
5739
|
+
if (right === void 0 || left === aFrom.get(key) || right === bFrom.get(key)) continue;
|
|
5740
|
+
into.set(key, union([left, right]));
|
|
5741
|
+
}
|
|
5742
|
+
}
|
|
5644
5743
|
function mergeIndexer(a, b) {
|
|
5645
5744
|
return a ? { key: union([a.key, b.key]), value: union([a.value, b.value]) } : b;
|
|
5646
5745
|
}
|
|
@@ -5920,6 +6019,10 @@ function unwrapParens(e) {
|
|
|
5920
6019
|
while (e.type === "ParenthesizedExpression") e = e.expression;
|
|
5921
6020
|
return e;
|
|
5922
6021
|
}
|
|
6022
|
+
function memberLabel(label, key) {
|
|
6023
|
+
if (label === void 0 || key === void 0) return void 0;
|
|
6024
|
+
return typeof key === "number" ? `${label}[${key}]` : `${label}.${key}`;
|
|
6025
|
+
}
|
|
5923
6026
|
function expressionLabel(e, depth = 0) {
|
|
5924
6027
|
if (depth > 6) return void 0;
|
|
5925
6028
|
const args = (list) => {
|
|
@@ -5991,6 +6094,7 @@ var TypeAnalyzer = class {
|
|
|
5991
6094
|
metatables = [];
|
|
5992
6095
|
metatableCache = /* @__PURE__ */ new WeakMap();
|
|
5993
6096
|
methodSources = /* @__PURE__ */ new Map();
|
|
6097
|
+
typeofTables = /* @__PURE__ */ new Map();
|
|
5994
6098
|
/** Public: each alias resolved once (generic aliases keep their params as
|
|
5995
6099
|
* `typeParam` nodes in the body). */
|
|
5996
6100
|
aliases = new LazyMap();
|
|
@@ -6069,7 +6173,7 @@ var TypeAnalyzer = class {
|
|
|
6069
6173
|
const given = this.options.globalTypes?.[name];
|
|
6070
6174
|
if (given) this.bindingType.set(id, given);
|
|
6071
6175
|
else if (this.libGlobalTypes.has(name)) this.bindingType.defer(id, () => this.libGlobalTypes.get(name) ?? anyType);
|
|
6072
|
-
else if (LANGUAGE_GLOBALS.includes(name)) this.bindingType.set(id, arrayOf(
|
|
6176
|
+
else if (LANGUAGE_GLOBALS.includes(name)) this.bindingType.set(id, arrayOf(declaredUnknownType));
|
|
6073
6177
|
else this.bindingType.set(id, anyType);
|
|
6074
6178
|
}
|
|
6075
6179
|
setAliasExpander((t) => this.expand(t));
|
|
@@ -6092,6 +6196,7 @@ var TypeAnalyzer = class {
|
|
|
6092
6196
|
aliases: this.resolveDeferredAliases(),
|
|
6093
6197
|
loops: this.loops,
|
|
6094
6198
|
methodSources: this.methodSources,
|
|
6199
|
+
typeofTables: this.typeofTables,
|
|
6095
6200
|
metatableMembers: (type) => this.metatableMembers(type),
|
|
6096
6201
|
diagnostics: this.diagnostics
|
|
6097
6202
|
};
|
|
@@ -6942,7 +7047,12 @@ var TypeAnalyzer = class {
|
|
|
6942
7047
|
this.aliases.defer(name, () => this.classType(cls));
|
|
6943
7048
|
continue;
|
|
6944
7049
|
}
|
|
6945
|
-
if (this.dependsOnTypeQuery(name))
|
|
7050
|
+
if (this.dependsOnTypeQuery(name)) {
|
|
7051
|
+
if (def.runtimeClass) {
|
|
7052
|
+
this.aliases.defer(name, () => this.withTypeParams(def.params, () => this.resolveDef(def)));
|
|
7053
|
+
}
|
|
7054
|
+
continue;
|
|
7055
|
+
}
|
|
6946
7056
|
this.withTypeParams(def.params, () => {
|
|
6947
7057
|
this.aliases.set(name, this.resolveDef(def));
|
|
6948
7058
|
});
|
|
@@ -6950,14 +7060,16 @@ var TypeAnalyzer = class {
|
|
|
6950
7060
|
}
|
|
6951
7061
|
typeQueryDependents = /* @__PURE__ */ new Map();
|
|
6952
7062
|
/** Does alias `name` contain a `typeof`, itself or through an alias it
|
|
6953
|
-
* names?
|
|
7063
|
+
* names? Its parameters count: `class C<K extends Config["key"]>`
|
|
7064
|
+
* resolves the bound along with the class. */
|
|
6954
7065
|
dependsOnTypeQuery(name, visiting = /* @__PURE__ */ new Set()) {
|
|
6955
7066
|
const known = this.typeQueryDependents.get(name);
|
|
6956
7067
|
if (known !== void 0) return known;
|
|
6957
7068
|
const def = this.aliasDefs.get(name);
|
|
6958
7069
|
if (!def || def.class || visiting.has(name)) return false;
|
|
6959
7070
|
visiting.add(name);
|
|
6960
|
-
const
|
|
7071
|
+
const nodes = [def.node, ...def.params.flatMap((p) => [p.constraint, p.default])];
|
|
7072
|
+
const result = nodes.some(containsTypeQuery) || referencedTypeNames(nodes).some((other) => other !== name && this.dependsOnTypeQuery(other, visiting));
|
|
6961
7073
|
visiting.delete(name);
|
|
6962
7074
|
this.typeQueryDependents.set(name, result);
|
|
6963
7075
|
return result;
|
|
@@ -7682,7 +7794,7 @@ var TypeAnalyzer = class {
|
|
|
7682
7794
|
}
|
|
7683
7795
|
}
|
|
7684
7796
|
const mode = this.initIsAsConst(source) ? "asconst" : !isFreshLiteralExpr(source) ? "keep" : stmt.kind === "const" ? "const" : "widen";
|
|
7685
|
-
this.bindPattern(target, inferred, env, mode);
|
|
7797
|
+
this.bindPattern(target, inferred, env, mode, source && expressionLabel(source));
|
|
7686
7798
|
if (stmt.kind === "const") {
|
|
7687
7799
|
this.correlateDestructuring(target, inferred, env);
|
|
7688
7800
|
this.correlateIndexed(target, source, env);
|
|
@@ -7775,9 +7887,10 @@ var TypeAnalyzer = class {
|
|
|
7775
7887
|
} else if (target.type === "MemberExpression" || target.type === "IndexExpression") {
|
|
7776
7888
|
this.infer(target, env);
|
|
7777
7889
|
this.checkReadonlyAssign(target, env);
|
|
7890
|
+
this.checkMemberAssign(stmt, target, vt, env);
|
|
7778
7891
|
this.assignToRef(target, isFreshLiteralExpr(source) ? widen(vt) : vt, env);
|
|
7779
7892
|
} else if (target.type === "ObjectPattern" || target.type === "ArrayPattern") {
|
|
7780
|
-
this.reassignPattern(target, vt, env);
|
|
7893
|
+
this.reassignPattern(target, vt, env, expressionLabel(source));
|
|
7781
7894
|
}
|
|
7782
7895
|
return;
|
|
7783
7896
|
}
|
|
@@ -9143,7 +9256,8 @@ var TypeAnalyzer = class {
|
|
|
9143
9256
|
}
|
|
9144
9257
|
/** Like `bindPattern`, but the leaves are *existing* bindings (a `{a} = t`
|
|
9145
9258
|
* assignment). Updates their flow type; keeps a declared annotation. */
|
|
9146
|
-
reassignPattern(target, valueType, env) {
|
|
9259
|
+
reassignPattern(target, valueType, env, label) {
|
|
9260
|
+
if (target.type === "ObjectPattern" || target.type === "ArrayPattern") valueType = this.destructured(target, valueType, label);
|
|
9147
9261
|
switch (target.type) {
|
|
9148
9262
|
case "IdentifierPattern": {
|
|
9149
9263
|
const id = this.scopes.bindingOf.get(target);
|
|
@@ -9168,21 +9282,43 @@ var TypeAnalyzer = class {
|
|
|
9168
9282
|
for (const p of target.properties) {
|
|
9169
9283
|
const key = !p.computed && p.key.type === "Identifier" ? p.key.name : !p.computed && p.key.type === "StringLiteral" ? p.key.value : void 0;
|
|
9170
9284
|
const pt = key !== void 0 ? this.propertyType(valueType, key) : unknownType;
|
|
9171
|
-
this.reassignPattern(p.value, this.withDefault(pt, p.default, env), env);
|
|
9285
|
+
this.reassignPattern(p.value, this.withDefault(pt, p.default, env), env, memberLabel(label, key));
|
|
9172
9286
|
}
|
|
9173
9287
|
if (target.rest) this.reassignPattern(target.rest, this.withoutKeys(valueType, target.properties), env);
|
|
9174
9288
|
return;
|
|
9175
9289
|
}
|
|
9176
9290
|
case "ArrayPattern": {
|
|
9177
9291
|
target.elements.forEach((el, i) => {
|
|
9178
|
-
if (el) this.reassignPattern(el.value, this.withDefault(this.elementType(valueType, i), el.default, env), env);
|
|
9292
|
+
if (el) this.reassignPattern(el.value, this.withDefault(this.elementType(valueType, i), el.default, env), env, memberLabel(label, i + 1));
|
|
9179
9293
|
});
|
|
9180
9294
|
if (target.rest) this.reassignPattern(target.rest, this.restAfter(valueType, target.elements.length), env);
|
|
9181
9295
|
return;
|
|
9182
9296
|
}
|
|
9183
9297
|
}
|
|
9184
9298
|
}
|
|
9185
|
-
|
|
9299
|
+
destructuredReported = /* @__PURE__ */ new WeakSet();
|
|
9300
|
+
/** What a pattern reads from. `const { a } = v` is `v.a`, and fails where
|
|
9301
|
+
* that would: on a `v` that may be nil, and on one that is `unknown`.
|
|
9302
|
+
* `label` is the value as it would be written — `args[1]` for the inner
|
|
9303
|
+
* pattern of `const [{ a }] = args`. */
|
|
9304
|
+
destructured(pattern, raw, label) {
|
|
9305
|
+
let type = raw;
|
|
9306
|
+
const report = (message) => {
|
|
9307
|
+
if (!this.emitDiagnostics || this.destructuredReported.has(pattern)) return;
|
|
9308
|
+
this.destructuredReported.add(pattern);
|
|
9309
|
+
this.diagnostics.push({ node: pattern, message: `${label === void 0 ? "Object" : `'${label}'`} ${message}` });
|
|
9310
|
+
};
|
|
9311
|
+
if (this.includesNil(type)) {
|
|
9312
|
+
const t = this.expand(type);
|
|
9313
|
+
report(t.kind === "primitive" ? "is nil" : "is possibly nil. Check it first, or give it a default");
|
|
9314
|
+
type = withoutNil(t);
|
|
9315
|
+
}
|
|
9316
|
+
const read = this.expand(type);
|
|
9317
|
+
if (read.kind === "unknown" && read.declared) report("is of type 'unknown'");
|
|
9318
|
+
return type;
|
|
9319
|
+
}
|
|
9320
|
+
bindPattern(target, valueType, env, mode, label) {
|
|
9321
|
+
if (target.type === "ObjectPattern" || target.type === "ArrayPattern") valueType = this.destructured(target, valueType, label);
|
|
9186
9322
|
switch (target.type) {
|
|
9187
9323
|
case "IdentifierPattern": {
|
|
9188
9324
|
const id = this.bindingIdByName(target.name, target);
|
|
@@ -9203,7 +9339,7 @@ var TypeAnalyzer = class {
|
|
|
9203
9339
|
for (const p of target.properties) {
|
|
9204
9340
|
const key = !p.computed && p.key.type === "Identifier" ? p.key.name : !p.computed && p.key.type === "StringLiteral" ? p.key.value : void 0;
|
|
9205
9341
|
const propType = key !== void 0 ? this.propertyType(valueType, key) : unknownType;
|
|
9206
|
-
this.bindPattern(p.value, this.withDefault(propType, p.default, env), env, mode);
|
|
9342
|
+
this.bindPattern(p.value, this.withDefault(propType, p.default, env), env, mode, memberLabel(label, key));
|
|
9207
9343
|
}
|
|
9208
9344
|
if (target.rest) this.bindPattern(target.rest, this.withoutKeys(valueType, target.properties), env, mode);
|
|
9209
9345
|
return;
|
|
@@ -9211,7 +9347,7 @@ var TypeAnalyzer = class {
|
|
|
9211
9347
|
case "ArrayPattern": {
|
|
9212
9348
|
target.elements.forEach((el, i) => {
|
|
9213
9349
|
if (!el) return;
|
|
9214
|
-
this.bindPattern(el.value, this.withDefault(this.elementType(valueType, i), el.default, env), env, mode);
|
|
9350
|
+
this.bindPattern(el.value, this.withDefault(this.elementType(valueType, i), el.default, env), env, mode, memberLabel(label, i + 1));
|
|
9215
9351
|
});
|
|
9216
9352
|
if (target.rest) this.bindPattern(target.rest, this.restAfter(valueType, target.elements.length), env, mode);
|
|
9217
9353
|
return;
|
|
@@ -9776,6 +9912,7 @@ var TypeAnalyzer = class {
|
|
|
9776
9912
|
}
|
|
9777
9913
|
// Broken syntax is reported by the parser; nothing more to say.
|
|
9778
9914
|
case "ErrorExpression":
|
|
9915
|
+
if (expr.expression) this.infer(expr.expression, env);
|
|
9779
9916
|
return anyType;
|
|
9780
9917
|
case "Identifier": {
|
|
9781
9918
|
const id = this.bindingIdOf(expr);
|
|
@@ -9839,6 +9976,14 @@ var TypeAnalyzer = class {
|
|
|
9839
9976
|
this.checkOperands(expr, "#", arg);
|
|
9840
9977
|
return numberType;
|
|
9841
9978
|
}
|
|
9979
|
+
case "typeof": {
|
|
9980
|
+
const names = this.typeofNamesOf(arg);
|
|
9981
|
+
const tables = names && [...names].filter((name) => name === "array" || name === "object");
|
|
9982
|
+
if (tables?.length === 1) this.typeofTables.set(expr, tables[0]);
|
|
9983
|
+
else this.typeofTables.delete(expr);
|
|
9984
|
+
const answers = names?.size ? [...names] : [...this.typeofTypes().keys()];
|
|
9985
|
+
return union(answers.map((name) => literal(name)));
|
|
9986
|
+
}
|
|
9842
9987
|
}
|
|
9843
9988
|
return arg;
|
|
9844
9989
|
}
|
|
@@ -9881,7 +10026,9 @@ var TypeAnalyzer = class {
|
|
|
9881
10026
|
return booleanType;
|
|
9882
10027
|
case "==":
|
|
9883
10028
|
case "~=":
|
|
9884
|
-
this.
|
|
10029
|
+
if (!this.checkTypeofName(expr, expr.left, expr.right) && !this.checkTypeofName(expr, expr.right, expr.left)) {
|
|
10030
|
+
this.checkOverlap(expr, op, l, r);
|
|
10031
|
+
}
|
|
9885
10032
|
return booleanType;
|
|
9886
10033
|
case "+":
|
|
9887
10034
|
case "-":
|
|
@@ -10082,6 +10229,7 @@ var TypeAnalyzer = class {
|
|
|
10082
10229
|
const fromMetatable = this.receiverMetatableMember(objType, expr.method.name);
|
|
10083
10230
|
if (fromMetatable) this.methodSources.set(expr, fromMetatable.source);
|
|
10084
10231
|
const method = fromMetatable?.type ?? this.propertyType(objType, expr.method.name);
|
|
10232
|
+
this.typeOf.set(expr.method, method);
|
|
10085
10233
|
const united = this.unionSignatures(method);
|
|
10086
10234
|
const fns = united ?? this.overloadsOf(method);
|
|
10087
10235
|
const explicit = this.explicitTypeArguments(expr, fns);
|
|
@@ -10222,6 +10370,43 @@ var TypeAnalyzer = class {
|
|
|
10222
10370
|
message: `Cannot assign to '${name}' because it is a read-only property`
|
|
10223
10371
|
});
|
|
10224
10372
|
}
|
|
10373
|
+
/** `t.x = v` against what `x` was declared as. Only a property the
|
|
10374
|
+
* table's type names is checked: a key it does not name is the Lua
|
|
10375
|
+
* table growing, which `checkMissingMember` is not about. */
|
|
10376
|
+
checkMemberAssign(stmt, target, value, env) {
|
|
10377
|
+
if (!this.emitDiagnostics) return;
|
|
10378
|
+
let name;
|
|
10379
|
+
if (target.type === "MemberExpression") {
|
|
10380
|
+
name = target.property.name;
|
|
10381
|
+
} else {
|
|
10382
|
+
const index = this.expand(this.infer(target.index, env));
|
|
10383
|
+
if (index.kind !== "literal" || typeof index.value !== "string") return;
|
|
10384
|
+
name = index.value;
|
|
10385
|
+
}
|
|
10386
|
+
if (target.object.type === "Identifier") {
|
|
10387
|
+
const id = this.bindingIdOf(target.object);
|
|
10388
|
+
if (id !== void 0 && this.scopes.bindings.get(id)?.declaredBy === "namespace") return;
|
|
10389
|
+
}
|
|
10390
|
+
const declared = this.declaredProperty(this.expand(this.infer(target.object, env)), name);
|
|
10391
|
+
if (!declared || declared.kind === "any" || declared.kind === "unknown") return;
|
|
10392
|
+
if (this.fitsAnnotation(stmt.value, declared, value, env)) return;
|
|
10393
|
+
this.diagnostics.push({
|
|
10394
|
+
node: stmt,
|
|
10395
|
+
message: `Type '${briefType(value)}' is not assignable to '${briefType(declared)}'` + this.explainMismatch(value, declared)
|
|
10396
|
+
});
|
|
10397
|
+
}
|
|
10398
|
+
/** The type a property was declared with, where the type names it. */
|
|
10399
|
+
declaredProperty(t, name) {
|
|
10400
|
+
if (t.kind === "object") {
|
|
10401
|
+
const p = t.properties.get(name);
|
|
10402
|
+
return p && (p.optional ? optional(p.type) : p.type);
|
|
10403
|
+
}
|
|
10404
|
+
if (t.kind === "intersection") {
|
|
10405
|
+
const parts = t.types.flatMap((m) => this.declaredProperty(this.expand(m), name) ?? []);
|
|
10406
|
+
return parts.length ? intersection(parts) : void 0;
|
|
10407
|
+
}
|
|
10408
|
+
return void 0;
|
|
10409
|
+
}
|
|
10225
10410
|
isReadonlyProperty(t, name) {
|
|
10226
10411
|
if (t.kind === "object") return t.properties.get(name)?.readonly === true;
|
|
10227
10412
|
if (t.kind === "intersection" || t.kind === "union") {
|
|
@@ -10617,13 +10802,23 @@ var TypeAnalyzer = class {
|
|
|
10617
10802
|
if (cond.type === "BinaryExpression") {
|
|
10618
10803
|
const { operator: op, left, right } = cond;
|
|
10619
10804
|
if (op === "and") {
|
|
10620
|
-
|
|
10621
|
-
|
|
10805
|
+
const before = forkEnv(env);
|
|
10806
|
+
const leftFailed = forkEnv(env);
|
|
10807
|
+
this.applyNarrowing(left, env, t, leftFailed);
|
|
10808
|
+
const between = forkEnv(t);
|
|
10809
|
+
const rightFailed = forkEnv(t);
|
|
10810
|
+
this.applyNarrowing(right, t, t, rightFailed);
|
|
10811
|
+
joinNarrowings(f, leftFailed, before, rightFailed, between);
|
|
10622
10812
|
return;
|
|
10623
10813
|
}
|
|
10624
10814
|
if (op === "or") {
|
|
10625
|
-
|
|
10626
|
-
|
|
10815
|
+
const before = forkEnv(env);
|
|
10816
|
+
const leftHeld = forkEnv(env);
|
|
10817
|
+
this.applyNarrowing(left, env, leftHeld, f);
|
|
10818
|
+
const between = forkEnv(f);
|
|
10819
|
+
const rightHeld = forkEnv(f);
|
|
10820
|
+
this.applyNarrowing(right, f, rightHeld, f);
|
|
10821
|
+
joinNarrowings(t, leftHeld, before, rightHeld, between);
|
|
10627
10822
|
return;
|
|
10628
10823
|
}
|
|
10629
10824
|
if (op === "==" || op === "~=") {
|
|
@@ -10642,11 +10837,13 @@ var TypeAnalyzer = class {
|
|
|
10642
10837
|
this.narrowOptionalLinks(cond, env, t);
|
|
10643
10838
|
}
|
|
10644
10839
|
/** `a == b` / `a ~= b`. Handles, in order: a declaration-driven
|
|
10645
|
-
* `typeof
|
|
10840
|
+
* `typeof x == "..."` test, a literal/`nil` comparison against a
|
|
10646
10841
|
* reference, and a reference-to-reference comparison. */
|
|
10647
10842
|
narrowByComparison(left, right, eq, env, t, f) {
|
|
10648
10843
|
const yes = eq ? t : f;
|
|
10649
10844
|
const no = eq ? f : t;
|
|
10845
|
+
if (this.narrowByTypeof(left, right, yes, no, env)) return;
|
|
10846
|
+
if (this.narrowByTypeof(right, left, yes, no, env)) return;
|
|
10650
10847
|
if (this.narrowByCallResult(left, right, yes, no, env)) return;
|
|
10651
10848
|
if (this.narrowByCallResult(right, left, yes, no, env)) return;
|
|
10652
10849
|
const litOf = (e) => {
|
|
@@ -10673,18 +10870,149 @@ var TypeAnalyzer = class {
|
|
|
10673
10870
|
this.narrowRef(right, env, yes, no, (cur) => ({ yes: narrowTo(cur, lt), no: cur }));
|
|
10674
10871
|
}
|
|
10675
10872
|
}
|
|
10676
|
-
|
|
10677
|
-
|
|
10678
|
-
|
|
10679
|
-
|
|
10680
|
-
|
|
10681
|
-
|
|
10682
|
-
|
|
10683
|
-
|
|
10873
|
+
// --------------------------------------------------------
|
|
10874
|
+
// typeof
|
|
10875
|
+
// --------------------------------------------------------
|
|
10876
|
+
//
|
|
10877
|
+
// `typeof v` answers what Luau's `typeof` does, except for a table: that
|
|
10878
|
+
// is an `"array"` or an `"object"`. The names of a runtime's own values —
|
|
10879
|
+
// Roblox's `"Instance"`, `"Vector3"` — are a library's to give: it
|
|
10880
|
+
// declares `type TypeofNames = { Vector3: Vector3, Instance: Instance }`,
|
|
10881
|
+
// each property the name `typeof` answers for a value of that type. A
|
|
10882
|
+
// value several of them fit is the first one's.
|
|
10883
|
+
typeofTypesCache;
|
|
10884
|
+
/** Every name `typeof` answers, and what a value it answered that for
|
|
10885
|
+
* is — `unknown` where nothing more is known. */
|
|
10886
|
+
typeofTypes() {
|
|
10887
|
+
if (this.typeofTypesCache) return this.typeofTypesCache;
|
|
10888
|
+
const types = /* @__PURE__ */ new Map([
|
|
10889
|
+
["nil", nilType],
|
|
10890
|
+
["boolean", booleanType],
|
|
10891
|
+
["number", numberType],
|
|
10892
|
+
["string", stringType],
|
|
10893
|
+
["function", fn([], declaredUnknownType, declaredUnknownType)],
|
|
10894
|
+
["array", arrayOf(declaredUnknownType)],
|
|
10895
|
+
["object", objectType([], { key: declaredUnknownType, value: declaredUnknownType })],
|
|
10896
|
+
["thread", threadType],
|
|
10897
|
+
["buffer", bufferType],
|
|
10898
|
+
["userdata", unknownType]
|
|
10899
|
+
]);
|
|
10900
|
+
for (const [name, type] of this.typeofLibraryNames()) if (!types.has(name)) types.set(name, type);
|
|
10901
|
+
return this.typeofTypesCache = types;
|
|
10902
|
+
}
|
|
10903
|
+
typeofLibraryCache;
|
|
10904
|
+
typeofLibraryNames() {
|
|
10905
|
+
if (this.typeofLibraryCache) return this.typeofLibraryCache;
|
|
10906
|
+
const declared = this.aliases.get("TypeofNames");
|
|
10907
|
+
const names = declared && this.expand(declared);
|
|
10908
|
+
return this.typeofLibraryCache = names?.kind === "object" ? [...names.properties].map(([name, property]) => [name, property.type]) : [];
|
|
10909
|
+
}
|
|
10910
|
+
/** The names `typeof` can answer for a value of type `raw`, or
|
|
10911
|
+
* `undefined` where the type does not say. */
|
|
10912
|
+
typeofNamesOf(raw, depth = 0) {
|
|
10913
|
+
if (depth > 8) return void 0;
|
|
10914
|
+
const t = this.expand(raw);
|
|
10915
|
+
switch (t.kind) {
|
|
10916
|
+
case "never":
|
|
10917
|
+
return /* @__PURE__ */ new Set();
|
|
10918
|
+
case "primitive":
|
|
10919
|
+
return /* @__PURE__ */ new Set([t.name]);
|
|
10920
|
+
case "literal":
|
|
10921
|
+
return /* @__PURE__ */ new Set([t.base]);
|
|
10922
|
+
case "templateLiteral":
|
|
10923
|
+
return /* @__PURE__ */ new Set(["string"]);
|
|
10924
|
+
case "array":
|
|
10925
|
+
case "tuple":
|
|
10926
|
+
return /* @__PURE__ */ new Set(["array"]);
|
|
10927
|
+
case "function":
|
|
10928
|
+
return /* @__PURE__ */ new Set(["function"]);
|
|
10929
|
+
case "object": {
|
|
10930
|
+
const named = t.class && this.typeofLibraryNames().find(([, type]) => isAssignable(t, type));
|
|
10931
|
+
return /* @__PURE__ */ new Set([named ? named[0] : "object"]);
|
|
10932
|
+
}
|
|
10933
|
+
case "union": {
|
|
10934
|
+
const all = /* @__PURE__ */ new Set();
|
|
10935
|
+
for (const member of t.types) {
|
|
10936
|
+
const names = this.typeofNamesOf(member, depth + 1);
|
|
10937
|
+
if (!names) return void 0;
|
|
10938
|
+
for (const name of names) all.add(name);
|
|
10939
|
+
}
|
|
10940
|
+
return all;
|
|
10941
|
+
}
|
|
10942
|
+
case "intersection": {
|
|
10943
|
+
const known = t.types.map((part) => this.typeofNamesOf(part, depth + 1)).filter((names) => names !== void 0);
|
|
10944
|
+
if (!known.length) return void 0;
|
|
10945
|
+
const common = [...known[0]].filter((name) => known.every((names) => names.has(name)));
|
|
10946
|
+
return new Set(common.length ? common : known[0]);
|
|
10947
|
+
}
|
|
10948
|
+
case "typeParam":
|
|
10949
|
+
return t.constraint ? this.typeofNamesOf(t.constraint, depth + 1) : void 0;
|
|
10950
|
+
case "difference":
|
|
10951
|
+
return this.typeofNamesOf(t.base, depth + 1);
|
|
10952
|
+
default:
|
|
10953
|
+
return void 0;
|
|
10954
|
+
}
|
|
10955
|
+
}
|
|
10956
|
+
/** `typeof v == "table"`: a name `typeof` never answers. Returns true if
|
|
10957
|
+
* the comparison needs no further checking. */
|
|
10958
|
+
checkTypeofName(node, typeofSide, nameSide) {
|
|
10959
|
+
const test = unwrapParens(typeofSide);
|
|
10960
|
+
if (test.type !== "UnaryExpression" || test.operator !== "typeof") return false;
|
|
10961
|
+
const name = this.asLiteral(unwrapParens(nameSide));
|
|
10962
|
+
if (typeof name !== "string") return false;
|
|
10963
|
+
if (name === "nil") return true;
|
|
10964
|
+
if (this.typeofTypes().has(name)) return false;
|
|
10965
|
+
if (this.emitDiagnostics) {
|
|
10966
|
+
this.diagnostics.push({
|
|
10967
|
+
node,
|
|
10968
|
+
message: name === "table" ? `'typeof' never answers "table": a table is an "array" or an "object"` : `'typeof' never answers "${name}"`
|
|
10969
|
+
});
|
|
10970
|
+
}
|
|
10971
|
+
return true;
|
|
10972
|
+
}
|
|
10973
|
+
/** `typeof x == "number"`: `x` is what answers that name where the test
|
|
10974
|
+
* holds, and what does not where it fails. Returns true if `testSide` is
|
|
10975
|
+
* a `typeof` compared with a name. */
|
|
10976
|
+
narrowByTypeof(testSide, nameSide, yes, no, env) {
|
|
10977
|
+
const test = unwrapParens(testSide);
|
|
10978
|
+
if (test.type !== "UnaryExpression" || test.operator !== "typeof") return false;
|
|
10979
|
+
const name = this.asLiteral(unwrapParens(nameSide));
|
|
10980
|
+
if (typeof name !== "string") return false;
|
|
10981
|
+
const answered = this.typeofTypes().get(name);
|
|
10982
|
+
if (!answered) return true;
|
|
10983
|
+
const opaque = answered.kind !== "unknown";
|
|
10984
|
+
const subtracts = opaque && name !== "array" && name !== "object";
|
|
10985
|
+
const members = (cur) => {
|
|
10986
|
+
const t = this.expand(cur);
|
|
10987
|
+
return t.kind === "union" ? t.types.flatMap(members) : [t];
|
|
10988
|
+
};
|
|
10989
|
+
if (this.refKeyOf(test.argument) !== void 0) {
|
|
10990
|
+
this.narrowRef(test.argument, env, yes, no, (cur) => {
|
|
10991
|
+
const kept = [];
|
|
10992
|
+
const dropped = [];
|
|
10993
|
+
for (const member of members(cur)) {
|
|
10994
|
+
const names = this.typeofNamesOf(member);
|
|
10995
|
+
if (!names) {
|
|
10996
|
+
kept.push(opaque ? narrowTo(member, answered) : member);
|
|
10997
|
+
dropped.push(subtracts ? narrowExclude(member, answered) : member);
|
|
10998
|
+
continue;
|
|
10999
|
+
}
|
|
11000
|
+
if (names.has(name)) kept.push(member);
|
|
11001
|
+
if (!(names.size === 1 && names.has(name))) dropped.push(member);
|
|
11002
|
+
}
|
|
11003
|
+
return { yes: union(kept), no: union(dropped) };
|
|
11004
|
+
});
|
|
11005
|
+
}
|
|
11006
|
+
this.narrowOptionalLinks(test.argument, env, name === "nil" ? no : yes);
|
|
11007
|
+
return true;
|
|
11008
|
+
}
|
|
11009
|
+
/** Declaration-driven `kind(x) == "number"`.
|
|
10684
11010
|
*
|
|
10685
|
-
*
|
|
10686
|
-
*
|
|
10687
|
-
*
|
|
11011
|
+
* A function declared as an overload set whose members return
|
|
11012
|
+
* *string-literal* types (`(value: number) => "number"`, ...) says by its
|
|
11013
|
+
* answer what it was given. So for `f(x) == "lit"` we keep the parameter
|
|
11014
|
+
* types of the overloads that can return `"lit"`, and that union is what
|
|
11015
|
+
* `x` narrows to. Returns true if it handled the comparison. */
|
|
10688
11016
|
narrowByCallResult(callSide, litSide, yes, no, env) {
|
|
10689
11017
|
if (callSide.type !== "CallExpression" || callSide.arguments.length !== 1) return false;
|
|
10690
11018
|
const name = this.asLiteral(litSide);
|
|
@@ -11761,7 +12089,53 @@ function aliasTargets(config, specifier) {
|
|
|
11761
12089
|
|
|
11762
12090
|
// src/project/sourcemap.ts
|
|
11763
12091
|
import { dirname as dirname4, extname, resolve as resolve4 } from "path";
|
|
12092
|
+
|
|
12093
|
+
// src/luau.ts
|
|
12094
|
+
var LUAU_KEYWORDS = /* @__PURE__ */ new Set([
|
|
12095
|
+
"and",
|
|
12096
|
+
"break",
|
|
12097
|
+
"do",
|
|
12098
|
+
"else",
|
|
12099
|
+
"elseif",
|
|
12100
|
+
"end",
|
|
12101
|
+
"false",
|
|
12102
|
+
"for",
|
|
12103
|
+
"function",
|
|
12104
|
+
"if",
|
|
12105
|
+
"in",
|
|
12106
|
+
"local",
|
|
12107
|
+
"nil",
|
|
12108
|
+
"not",
|
|
12109
|
+
"or",
|
|
12110
|
+
"repeat",
|
|
12111
|
+
"return",
|
|
12112
|
+
"then",
|
|
12113
|
+
"true",
|
|
12114
|
+
"until",
|
|
12115
|
+
"while"
|
|
12116
|
+
]);
|
|
11764
12117
|
var IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
12118
|
+
function isIdentifier(name) {
|
|
12119
|
+
return IDENTIFIER.test(name);
|
|
12120
|
+
}
|
|
12121
|
+
function isLuauName(name) {
|
|
12122
|
+
return IDENTIFIER.test(name) && !LUAU_KEYWORDS.has(name);
|
|
12123
|
+
}
|
|
12124
|
+
var NAMED_ESCAPES = {
|
|
12125
|
+
"\\": "\\\\",
|
|
12126
|
+
'"': '\\"',
|
|
12127
|
+
"\n": "\\n",
|
|
12128
|
+
"\r": "\\r",
|
|
12129
|
+
" ": "\\t"
|
|
12130
|
+
};
|
|
12131
|
+
function escapeLuauString(text) {
|
|
12132
|
+
return text.replace(/[\\"\x00-\x1f\x7f]/g, (c) => NAMED_ESCAPES[c] ?? `\\${c.charCodeAt(0).toString().padStart(3, "0")}`);
|
|
12133
|
+
}
|
|
12134
|
+
function luauString(text) {
|
|
12135
|
+
return `"${escapeLuauString(text)}"`;
|
|
12136
|
+
}
|
|
12137
|
+
|
|
12138
|
+
// src/project/sourcemap.ts
|
|
11765
12139
|
var INSTANCE_MEMBERS = /* @__PURE__ */ new Set(["Name", "ClassName", "Parent", "Archivable"]);
|
|
11766
12140
|
var SCRIPT_EXTENSIONS = /* @__PURE__ */ new Set([".tilua", ".luau", ".lua"]);
|
|
11767
12141
|
function sourceMapTypes(text, path, options) {
|
|
@@ -11788,7 +12162,7 @@ function sourceMapTypes(text, path, options) {
|
|
|
11788
12162
|
const alias = aliasFor(segments);
|
|
11789
12163
|
aliasOfNode.set(node, alias);
|
|
11790
12164
|
for (const filePath of node.filePaths ?? []) aliasOfFile.set(fileKey(resolve4(directory, filePath)), alias);
|
|
11791
|
-
const className =
|
|
12165
|
+
const className = isIdentifier(node.className) && options.classes.has(node.className) ? node.className : "Instance";
|
|
11792
12166
|
const taken = options.membersOf?.(className) ?? INSTANCE_MEMBERS;
|
|
11793
12167
|
const members = [];
|
|
11794
12168
|
if (parent) members.push(`Parent: ${parent}`);
|
|
@@ -11797,7 +12171,7 @@ function sourceMapTypes(text, path, options) {
|
|
|
11797
12171
|
if (!isNode(child)) continue;
|
|
11798
12172
|
const childAlias = visit(child, [...segments, child.name], alias);
|
|
11799
12173
|
const service = child.name === child.className && options.classes.has(child.className);
|
|
11800
|
-
if (!
|
|
12174
|
+
if (!isIdentifier(child.name) || taken.has(child.name) && !service || named.has(child.name)) continue;
|
|
11801
12175
|
named.add(child.name);
|
|
11802
12176
|
members.push(`${child.name}: ${childAlias}`);
|
|
11803
12177
|
}
|
|
@@ -11839,6 +12213,34 @@ function fileKey(path) {
|
|
|
11839
12213
|
return process.platform === "win32" ? normalized.toLowerCase() : normalized;
|
|
11840
12214
|
}
|
|
11841
12215
|
|
|
12216
|
+
// src/loweringHelpers.ts
|
|
12217
|
+
function withoutNil2(type) {
|
|
12218
|
+
if (!type) return [];
|
|
12219
|
+
const members = type.kind === "union" ? type.types : [type];
|
|
12220
|
+
return members.filter((m) => !(m.kind === "primitive" && m.name === "nil"));
|
|
12221
|
+
}
|
|
12222
|
+
function isFunctionType(type) {
|
|
12223
|
+
const members = withoutNil2(type);
|
|
12224
|
+
return members.length > 0 && members.every((m) => m.kind === "function" || m.kind === "intersection" && m.types.every(isFunctionType));
|
|
12225
|
+
}
|
|
12226
|
+
function everyObject(type, test) {
|
|
12227
|
+
const members = withoutNil2(type);
|
|
12228
|
+
return members.length > 0 && members.every((m) => m.kind === "object" && test(m));
|
|
12229
|
+
}
|
|
12230
|
+
function isInstanceOf(type, className) {
|
|
12231
|
+
return everyObject(type, (m) => m.class !== void 0 && m.class.ancestors.includes(className));
|
|
12232
|
+
}
|
|
12233
|
+
function hasMembers(type, names) {
|
|
12234
|
+
return everyObject(type, (m) => names.every((name) => m.properties.has(name)));
|
|
12235
|
+
}
|
|
12236
|
+
function returnsTuple(receiver, method) {
|
|
12237
|
+
return everyObject(receiver, (m) => {
|
|
12238
|
+
const found = m.properties.get(method)?.type;
|
|
12239
|
+
const signatures = !found ? [] : found.kind === "intersection" ? found.types : [found];
|
|
12240
|
+
return signatures.length > 0 && signatures.every((f) => f.kind === "function" && f.returns.kind === "tuple");
|
|
12241
|
+
});
|
|
12242
|
+
}
|
|
12243
|
+
|
|
11842
12244
|
// src/index.ts
|
|
11843
12245
|
var tilua = {
|
|
11844
12246
|
tokenize,
|
|
@@ -11858,6 +12260,7 @@ export {
|
|
|
11858
12260
|
CONFIG_FILE_NAMES,
|
|
11859
12261
|
Keywords,
|
|
11860
12262
|
LANGUAGE_GLOBALS,
|
|
12263
|
+
LUAU_KEYWORDS,
|
|
11861
12264
|
LexError,
|
|
11862
12265
|
Operators,
|
|
11863
12266
|
PRELUDE_SOURCE,
|
|
@@ -11879,20 +12282,27 @@ export {
|
|
|
11879
12282
|
difference,
|
|
11880
12283
|
directivesOf,
|
|
11881
12284
|
equalTypes,
|
|
12285
|
+
escapeLuauString,
|
|
11882
12286
|
falsyType,
|
|
11883
12287
|
findConfig,
|
|
11884
12288
|
fn,
|
|
11885
12289
|
formatType,
|
|
11886
12290
|
getBinding,
|
|
12291
|
+
hasMembers,
|
|
11887
12292
|
intersection,
|
|
11888
12293
|
isAssignable,
|
|
11889
12294
|
isClassType,
|
|
12295
|
+
isFunctionType,
|
|
11890
12296
|
isGlobal,
|
|
12297
|
+
isIdentifier,
|
|
12298
|
+
isInstanceOf,
|
|
12299
|
+
isLuauName,
|
|
11891
12300
|
isPossiblyFalsy,
|
|
11892
12301
|
isPossiblyTruthy,
|
|
11893
12302
|
isUnassignedGlobal,
|
|
11894
12303
|
literal,
|
|
11895
12304
|
loadConfig,
|
|
12305
|
+
luauString,
|
|
11896
12306
|
matchInfer,
|
|
11897
12307
|
moduleCandidates,
|
|
11898
12308
|
moduleExports,
|
|
@@ -11915,6 +12325,7 @@ export {
|
|
|
11915
12325
|
readDirectives,
|
|
11916
12326
|
resolveModulePath,
|
|
11917
12327
|
resolveTypeLibraries,
|
|
12328
|
+
returnsTuple,
|
|
11918
12329
|
setAliasExpander,
|
|
11919
12330
|
setDeferredBound,
|
|
11920
12331
|
sourceMapTypes,
|
|
@@ -11933,5 +12344,6 @@ export {
|
|
|
11933
12344
|
unknownType,
|
|
11934
12345
|
widen,
|
|
11935
12346
|
withAliasName,
|
|
11936
|
-
withoutAliasName
|
|
12347
|
+
withoutAliasName,
|
|
12348
|
+
withoutNil2 as withoutNil
|
|
11937
12349
|
};
|