cluaupp 1.5.2 → 2.0.0-beta.1
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/CHANGELOG.md +3 -0
- package/README.md +27 -40
- package/api/generated/bench-latest.json +43 -21
- package/api/generated/lsp-index.json +1 -1
- package/api/manifests/roblox-target.manifest.json +5 -5
- package/api/normalized/roblox-target.profile.json +8729 -267
- package/api/roblox/luau-builtins.d.luau +185 -0
- package/api/roblox/surface.json +1 -0
- package/api/roblox-api.lock.json +11 -2
- package/bin/cluaupp.js +38 -0
- package/editors/vscode/syntaxes/clpp.tmLanguage.json +211 -0
- package/examples/README.md +9 -9
- package/examples/game/cluaupp.config.json +2 -2
- package/examples/game/default.project.json +4 -15
- package/examples/game/out/ReplicatedStorage/Shared/GameInfo.luau +8 -0
- package/examples/game/out/ReplicatedStorage/Shared/GameInfo.luau.map.json +13 -0
- package/examples/game/out/ServerScriptService/Leaderstats.server.luau +26 -0
- package/examples/game/out/ServerScriptService/Leaderstats.server.luau.map.json +13 -0
- package/examples/game/out/ServerScriptService/Main.server.luau +15 -0
- package/examples/game/out/ServerScriptService/Main.server.luau.map.json +13 -0
- package/examples/game/out/StarterPlayer/StarterPlayerScripts/Main.client.luau +10 -0
- package/examples/game/out/StarterPlayer/StarterPlayerScripts/Main.client.luau.map.json +13 -0
- package/examples/game/src/ReplicatedStorage/Shared/GameInfo.clp +1 -0
- package/examples/game/src/ServerScriptService/Leaderstats.server.clp +18 -0
- package/examples/game/src/ServerScriptService/Main.server.clp +9 -0
- package/examples/game/src/StarterPlayer/StarterPlayerScripts/Main.client.clp +4 -0
- package/generated/api/build-profile.js +1 -1
- package/generated/api/loader.js +2 -2
- package/generated/api/surface/build.js +328 -0
- package/generated/api/surface/luau-defs.js +595 -0
- package/generated/api/surface/model.js +8 -0
- package/generated/clpp/contract.js +6 -3
- package/generated/clpp/handshake.js +36 -0
- package/generated/clpp/modules.js +20 -9
- package/generated/clpp/runner.js +62 -31
- package/generated/compile.js +18 -1
- package/generated/compiler/ast.js +3 -0
- package/generated/compiler/checker/calls.js +999 -0
- package/generated/compiler/checker/checker.js +1215 -0
- package/generated/compiler/checker/exprs.js +791 -0
- package/generated/compiler/checker/flow.js +168 -0
- package/generated/compiler/checker/members.js +763 -0
- package/generated/compiler/checker/statements.js +792 -0
- package/generated/compiler/checker/symbols.js +90 -0
- package/generated/compiler/checker/typeops.js +348 -0
- package/generated/compiler/checker/yields.js +115 -0
- package/generated/compiler/diagnostics.js +138 -0
- package/generated/compiler/emit/emitter.js +2356 -0
- package/generated/compiler/emit/luau-types.js +137 -0
- package/generated/compiler/emit/names.js +130 -0
- package/generated/compiler/emit/runtime.js +432 -0
- package/generated/compiler/lexer.js +403 -0
- package/generated/compiler/parser.js +1881 -0
- package/generated/compiler/project.js +332 -0
- package/generated/compiler/roblox/api.js +388 -0
- package/generated/compiler/roblox/context.js +100 -0
- package/generated/compiler/source.js +59 -0
- package/generated/compiler/stdlib.js +232 -0
- package/generated/compiler/types.js +250 -0
- package/generated/target/build-cache.js +4 -4
- package/generated/target/gen-rojo-tree.js +15 -0
- package/generated/target/source-map.js +61 -8
- package/generated/target/studio-bridge.js +1 -0
- package/generated/utils/project.js +83 -25
- package/native/bin/.gitkeep +1 -0
- package/package.json +17 -6
- package/plugins/CluauppNav/Plugin.luau +28 -4
- package/rust/cluaupp/Cargo.lock +795 -0
- package/rust/cluaupp/Cargo.toml +17 -0
- package/rust/cluaupp/src/main.rs +717 -0
- package/scripts/postinstall-native.js +56 -0
- package/skills/README.md +3 -3
- package/skills/clpp-hive-flare/SKILL.md +30 -26
- package/skills/clpp-language/SKILL.md +67 -74
- package/skills/clpp-language/compiler.md +24 -36
- package/skills/clpp-style/SKILL.md +31 -44
- package/src/api/build-profile.ts +1 -1
- package/src/api/loader.ts +2 -2
- package/src/api/surface/build.ts +374 -0
- package/src/api/surface/luau-defs.ts +635 -0
- package/src/api/surface/model.ts +96 -0
- package/src/clpp/contract.ts +20 -3
- package/src/clpp/handshake.ts +25 -0
- package/src/clpp/modules.ts +17 -10
- package/src/clpp/runner.ts +52 -34
- package/src/compile.ts +20 -2
- package/src/compiler/ast.ts +336 -0
- package/src/compiler/checker/calls.ts +1025 -0
- package/src/compiler/checker/checker.ts +1352 -0
- package/src/compiler/checker/exprs.ts +805 -0
- package/src/compiler/checker/flow.ts +174 -0
- package/src/compiler/checker/members.ts +793 -0
- package/src/compiler/checker/statements.ts +802 -0
- package/src/compiler/checker/symbols.ts +219 -0
- package/src/compiler/checker/typeops.ts +355 -0
- package/src/compiler/checker/yields.ts +120 -0
- package/src/compiler/diagnostics.ts +163 -0
- package/src/compiler/emit/emitter.ts +2486 -0
- package/src/compiler/emit/luau-types.ts +143 -0
- package/src/compiler/emit/names.ts +130 -0
- package/src/compiler/emit/runtime.ts +438 -0
- package/src/compiler/lexer.ts +444 -0
- package/src/compiler/parser.ts +1943 -0
- package/src/compiler/project.ts +354 -0
- package/src/compiler/roblox/api.ts +408 -0
- package/src/compiler/roblox/context.ts +108 -0
- package/src/compiler/source.ts +67 -0
- package/src/compiler/stdlib.ts +256 -0
- package/src/compiler/types.ts +313 -0
- package/src/target/build-cache.ts +5 -8
- package/src/target/gen-rojo-tree.ts +15 -0
- package/src/target/source-map.ts +65 -27
- package/src/target/studio-bridge.ts +3 -0
- package/src/types.ts +3 -0
- package/src/utils/project.ts +79 -31
- package/templates/README.md +7 -0
- package/templates/{game → game-modern}/cluaupp.config.json +2 -2
- package/templates/{game → game-modern}/package.json +1 -2
- package/templates/game-modern/rokit.toml +2 -0
- package/templates/game-modern/src/ReplicatedStorage/Shared/GameInfo.clp +1 -0
- package/templates/game-modern/src/ServerScriptService/Handlers/Guard.server.clp +4 -0
- package/templates/game-modern/src/ServerScriptService/Main.server.clp +8 -0
- package/templates/game-modern/src/StarterPlayer/StarterPlayerScripts/Main.client.clp +4 -0
- package/examples/game/out/ReplicatedStorage/Shared/Constants/Datas/TemplateData.luau +0 -29
- package/examples/game/out/ReplicatedStorage/Shared/Constants/Datas/TemplateData.luau.map.json +0 -13
- package/examples/game/out/ReplicatedStorage/Shared/Net/Net.luau +0 -83
- package/examples/game/out/ReplicatedStorage/Shared/Net/Net.luau.map.json +0 -13
- package/examples/game/out/ServerScriptService/Boot/DataBoot.server.luau +0 -18
- package/examples/game/out/ServerScriptService/Boot/DataBoot.server.luau.map.json +0 -13
- package/examples/game/out/ServerScriptService/Configuration/ServerConfig.luau +0 -14
- package/examples/game/out/ServerScriptService/Configuration/ServerConfig.luau.map.json +0 -13
- package/examples/game/out/ServerScriptService/Handlers/Guard.server.luau +0 -15
- package/examples/game/out/ServerScriptService/Handlers/Guard.server.luau.map.json +0 -13
- package/examples/game/out/ServerScriptService/Handlers/PlayerHandler.server.luau +0 -50
- package/examples/game/out/ServerScriptService/Handlers/PlayerHandler.server.luau.map.json +0 -13
- package/examples/game/out/StarterPlayer/StarterCharacterScripts/Character/CharacterReady.client.luau +0 -12
- package/examples/game/out/StarterPlayer/StarterCharacterScripts/Character/CharacterReady.client.luau.map.json +0 -13
- package/examples/game/out/StarterPlayer/StarterPlayerScripts/Controllers/DataController.client.luau +0 -16
- package/examples/game/out/StarterPlayer/StarterPlayerScripts/Controllers/DataController.client.luau.map.json +0 -13
- package/examples/game/src/Client/Controllers/DataController.client.clpp +0 -7
- package/examples/game/src/Client/Main.client.clpp +0 -4
- package/examples/game/src/Declarations/Net.flare +0 -5
- package/examples/game/src/Include/GameTypes.clh +0 -40
- package/examples/game/src/Modules/Core/Guard.server.clpp +0 -7
- package/examples/game/src/Modules/Core/PlayerHandler.server.clpp +0 -23
- package/examples/game/src/Server/Boot/DataBoot.server.clpp +0 -13
- package/examples/game/src/Server/Main.server.clpp +0 -4
- package/templates/game/.clangd +0 -25
- package/templates/game/.vscode/extensions.json +0 -8
- package/templates/game/.vscode/settings.json +0 -17
- package/templates/game/Packages/.gitkeep +0 -0
- package/templates/game/compile_flags.txt +0 -6
- package/templates/game/default.project.json +0 -41
- package/templates/game/include/clpp/datatypes.clh +0 -575
- package/templates/game/include/clpp/generated/enums.clh +0 -4920
- package/templates/game/include/clpp/generated/instances.clh +0 -87182
- package/templates/game/include/clpp/libs/arrayindexer.clh +0 -12
- package/templates/game/include/clpp/libs/axiom.clh +0 -154
- package/templates/game/include/clpp/libs/bloom.clh +0 -58
- package/templates/game/include/clpp/libs/coil.clh +0 -37
- package/templates/game/include/clpp/libs/crest.clh +0 -22
- package/templates/game/include/clpp/libs/echo.clh +0 -52
- package/templates/game/include/clpp/libs/ember.clh +0 -17
- package/templates/game/include/clpp/libs/flare.clh +0 -65
- package/templates/game/include/clpp/libs/gleam.clh +0 -111
- package/templates/game/include/clpp/libs/guide.clh +0 -42
- package/templates/game/include/clpp/libs/helm.clh +0 -31
- package/templates/game/include/clpp/libs/hive.clh +0 -20
- package/templates/game/include/clpp/libs/keep.clh +0 -158
- package/templates/game/include/clpp/libs/lens.clh +0 -24
- package/templates/game/include/clpp/libs/mint.clh +0 -28
- package/templates/game/include/clpp/libs/net.clh +0 -31
- package/templates/game/include/clpp/libs/occlude.clh +0 -15
- package/templates/game/include/clpp/libs/pin.clh +0 -28
- package/templates/game/include/clpp/libs/promise.clh +0 -24
- package/templates/game/include/clpp/libs/roster.clh +0 -123
- package/templates/game/include/clpp/libs/shift.clh +0 -22
- package/templates/game/include/clpp/libs/spark.clh +0 -28
- package/templates/game/include/clpp/libs/stage.clh +0 -23
- package/templates/game/include/clpp/libs/sweep.clh +0 -23
- package/templates/game/include/clpp/libs/trace.clh +0 -18
- package/templates/game/include/clpp/libs/ward.clh +0 -19
- package/templates/game/include/clpp/libs.clh +0 -31
- package/templates/game/include/clpp/roblox.clh +0 -41
- package/templates/game/rokit.toml +0 -5
- package/templates/game/src/Client/Controllers/DataController.client.clpp +0 -7
- package/templates/game/src/Client/Main.client.clpp +0 -4
- package/templates/game/src/Declarations/Net.flare +0 -5
- package/templates/game/src/Include/GameTypes.clh +0 -40
- package/templates/game/src/Modules/Core/Guard.server.clpp +0 -7
- package/templates/game/src/Modules/Core/PlayerHandler.server.clpp +0 -24
- package/templates/game/src/Server/Boot/DataBoot.server.clpp +0 -13
- package/templates/game/src/Server/Main.server.clpp +0 -4
- package/templates/game/stylua.toml +0 -9
- package/templates/game/wally.toml +0 -12
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/** Symbols, scopes and the per-module declaration registries shared by checker and emitter. */
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.Scope = void 0;
|
|
5
|
+
exports.emptyExports = emptyExports;
|
|
6
|
+
class Scope {
|
|
7
|
+
parent;
|
|
8
|
+
kind;
|
|
9
|
+
depth;
|
|
10
|
+
values = new Map();
|
|
11
|
+
types = new Map();
|
|
12
|
+
/** Narrowed types for paths (`sym#3`, `sym#3.Character`). */
|
|
13
|
+
narrowed = new Map();
|
|
14
|
+
constructor(parent, kind, depth) {
|
|
15
|
+
this.parent = parent;
|
|
16
|
+
this.kind = kind;
|
|
17
|
+
this.depth = depth;
|
|
18
|
+
}
|
|
19
|
+
lookup(name) {
|
|
20
|
+
let cur = this;
|
|
21
|
+
while (cur) {
|
|
22
|
+
const s = cur.values.get(name);
|
|
23
|
+
if (s) {
|
|
24
|
+
return s;
|
|
25
|
+
}
|
|
26
|
+
cur = cur.parent;
|
|
27
|
+
}
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
lookupType(name) {
|
|
31
|
+
let cur = this;
|
|
32
|
+
while (cur) {
|
|
33
|
+
const s = cur.types.get(name);
|
|
34
|
+
if (s) {
|
|
35
|
+
return s;
|
|
36
|
+
}
|
|
37
|
+
cur = cur.parent;
|
|
38
|
+
}
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
narrowedType(key) {
|
|
42
|
+
let cur = this;
|
|
43
|
+
while (cur) {
|
|
44
|
+
if (cur.narrowed.has(key)) {
|
|
45
|
+
return cur.narrowed.get(key);
|
|
46
|
+
}
|
|
47
|
+
cur = cur.parent;
|
|
48
|
+
}
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
/** Drop narrowings of a path and everything below it (after an assignment). */
|
|
52
|
+
invalidate(prefix) {
|
|
53
|
+
let cur = this;
|
|
54
|
+
while (cur) {
|
|
55
|
+
for (const key of [...cur.narrowed.keys()]) {
|
|
56
|
+
if (key === prefix || key.startsWith(`${prefix}.`)) {
|
|
57
|
+
cur.narrowed.delete(key);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
cur = cur.parent;
|
|
61
|
+
}
|
|
62
|
+
// Mark as explicitly unknown at this level so outer narrowings stop applying.
|
|
63
|
+
}
|
|
64
|
+
allValueNames() {
|
|
65
|
+
const out = [];
|
|
66
|
+
let cur = this;
|
|
67
|
+
while (cur) {
|
|
68
|
+
out.push(...cur.values.keys());
|
|
69
|
+
cur = cur.parent;
|
|
70
|
+
}
|
|
71
|
+
return out;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
exports.Scope = Scope;
|
|
75
|
+
function emptyExports(moduleId, file, kind, context) {
|
|
76
|
+
return {
|
|
77
|
+
moduleId,
|
|
78
|
+
file,
|
|
79
|
+
kind,
|
|
80
|
+
context,
|
|
81
|
+
funcs: new Map(),
|
|
82
|
+
structs: new Map(),
|
|
83
|
+
enums: new Map(),
|
|
84
|
+
variants: new Map(),
|
|
85
|
+
aliases: new Map(),
|
|
86
|
+
consts: new Map(),
|
|
87
|
+
namespaces: new Map(),
|
|
88
|
+
dmPath: null,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/** Assignability, joins and member lookups that do not need checker state. */
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.assignable = assignable;
|
|
5
|
+
exports.structExtends = structExtends;
|
|
6
|
+
exports.join = join;
|
|
7
|
+
exports.substitute = substitute;
|
|
8
|
+
exports.bindGenerics = bindGenerics;
|
|
9
|
+
const types_js_1 = require("../types.js");
|
|
10
|
+
/**
|
|
11
|
+
* Can a value of type `from` be stored where `to` is expected?
|
|
12
|
+
* "nullable": only differs by a possible null (`Part?` into `Part`) — error in strict, warning otherwise.
|
|
13
|
+
* "narrowing": float into int — always an error (would lose the fraction).
|
|
14
|
+
*/
|
|
15
|
+
function assignable(env, to, from) {
|
|
16
|
+
const a = (0, types_js_1.prune)(to);
|
|
17
|
+
const b = (0, types_js_1.prune)(from);
|
|
18
|
+
if (a === b || (0, types_js_1.isDynamic)(a) || (0, types_js_1.isDynamic)(b) || b.k === "never") {
|
|
19
|
+
return "ok";
|
|
20
|
+
}
|
|
21
|
+
if (a.k === "infer") {
|
|
22
|
+
a.resolved = b;
|
|
23
|
+
return "ok";
|
|
24
|
+
}
|
|
25
|
+
if (a.k === "param" || b.k === "param") {
|
|
26
|
+
return "ok";
|
|
27
|
+
}
|
|
28
|
+
if (b.k === "union") {
|
|
29
|
+
let worst = "ok";
|
|
30
|
+
for (const m of b.members) {
|
|
31
|
+
const r = assignable(env, a, m);
|
|
32
|
+
if (r === "no") {
|
|
33
|
+
return "no";
|
|
34
|
+
}
|
|
35
|
+
if (r !== "ok") {
|
|
36
|
+
worst = r;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return worst;
|
|
40
|
+
}
|
|
41
|
+
switch (a.k) {
|
|
42
|
+
case "void":
|
|
43
|
+
return b.k === "void" ? "ok" : "no";
|
|
44
|
+
case "null":
|
|
45
|
+
return b.k === "null" ? "ok" : "no";
|
|
46
|
+
case "option": {
|
|
47
|
+
if (b.k === "null") {
|
|
48
|
+
return "ok";
|
|
49
|
+
}
|
|
50
|
+
if (b.k === "option") {
|
|
51
|
+
return assignable(env, a.inner, b.inner) === "no" ? "no" : assignable(env, a.inner, b.inner);
|
|
52
|
+
}
|
|
53
|
+
return assignable(env, a.inner, b);
|
|
54
|
+
}
|
|
55
|
+
case "union": {
|
|
56
|
+
let best = "no";
|
|
57
|
+
for (const m of a.members) {
|
|
58
|
+
const r = assignable(env, m, b);
|
|
59
|
+
if (r === "ok") {
|
|
60
|
+
return "ok";
|
|
61
|
+
}
|
|
62
|
+
if (r !== "no") {
|
|
63
|
+
best = r;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (b.k === "null" && a.members.some((m) => (0, types_js_1.prune)(m).k === "null")) {
|
|
67
|
+
return "ok";
|
|
68
|
+
}
|
|
69
|
+
return best;
|
|
70
|
+
}
|
|
71
|
+
case "float":
|
|
72
|
+
return b.k === "float" || b.k === "int" || (b.k === "lit" && typeof b.value === "number") ? "ok" : nullableOr(env, a, b);
|
|
73
|
+
case "int":
|
|
74
|
+
if (b.k === "int" || (b.k === "lit" && typeof b.value === "number" && Number.isInteger(b.value))) {
|
|
75
|
+
return "ok";
|
|
76
|
+
}
|
|
77
|
+
if (b.k === "float" || (b.k === "lit" && typeof b.value === "number")) {
|
|
78
|
+
return "narrowing";
|
|
79
|
+
}
|
|
80
|
+
if (b.k === "enum") {
|
|
81
|
+
return "ok";
|
|
82
|
+
}
|
|
83
|
+
return nullableOr(env, a, b);
|
|
84
|
+
case "bool":
|
|
85
|
+
return b.k === "bool" || (b.k === "lit" && typeof b.value === "boolean") ? "ok" : nullableOr(env, a, b);
|
|
86
|
+
case "string":
|
|
87
|
+
return b.k === "string" || (b.k === "lit" && typeof b.value === "string") ? "ok" : nullableOr(env, a, b);
|
|
88
|
+
case "lit":
|
|
89
|
+
return b.k === "lit" && b.value === a.value ? "ok" : nullableOr(env, a, b);
|
|
90
|
+
case "list":
|
|
91
|
+
if (b.k === "list") {
|
|
92
|
+
if (!a.typed || !b.typed) {
|
|
93
|
+
return "ok";
|
|
94
|
+
}
|
|
95
|
+
const r = assignable(env, a.elem, b.elem);
|
|
96
|
+
return r === "ok" || r === "nullable" ? r : "no";
|
|
97
|
+
}
|
|
98
|
+
if (b.k === "tuple") {
|
|
99
|
+
return b.elems.every((e) => assignable(env, a.elem, e) === "ok") ? "ok" : "no";
|
|
100
|
+
}
|
|
101
|
+
return nullableOr(env, a, b);
|
|
102
|
+
case "dict":
|
|
103
|
+
if (b.k === "dict") {
|
|
104
|
+
return assignable(env, a.key, b.key) !== "no" && assignable(env, a.value, b.value) !== "no" ? "ok" : "no";
|
|
105
|
+
}
|
|
106
|
+
if (b.k === "table") {
|
|
107
|
+
return Object.values(b.fields).every((f) => assignable(env, a.value, f) !== "no") ? "ok" : "no";
|
|
108
|
+
}
|
|
109
|
+
return nullableOr(env, a, b);
|
|
110
|
+
case "table":
|
|
111
|
+
if (b.k === "table") {
|
|
112
|
+
for (const [k, v] of Object.entries(a.fields)) {
|
|
113
|
+
const bf = b.fields[k];
|
|
114
|
+
if (!bf) {
|
|
115
|
+
if ((0, types_js_1.prune)(v).k !== "option") {
|
|
116
|
+
return "no";
|
|
117
|
+
}
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
if (assignable(env, v, bf) === "no") {
|
|
121
|
+
return "no";
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return "ok";
|
|
125
|
+
}
|
|
126
|
+
if (b.k === "dict") {
|
|
127
|
+
return "ok";
|
|
128
|
+
}
|
|
129
|
+
return nullableOr(env, a, b);
|
|
130
|
+
case "struct":
|
|
131
|
+
if (b.k === "struct") {
|
|
132
|
+
if (a.id === b.id) {
|
|
133
|
+
return a.args.length === 0 || b.args.length === 0 || a.args.every((x, i) => (0, types_js_1.sameType)(x, b.args[i] ?? types_js_1.T.any)) ? "ok" : "no";
|
|
134
|
+
}
|
|
135
|
+
return structExtends(env, b.id, a.id) ? "ok" : "no";
|
|
136
|
+
}
|
|
137
|
+
return nullableOr(env, a, b);
|
|
138
|
+
case "enum":
|
|
139
|
+
case "variant":
|
|
140
|
+
if (b.k === a.k && b.id === a.id) {
|
|
141
|
+
return "ok";
|
|
142
|
+
}
|
|
143
|
+
if (a.k === "variant") {
|
|
144
|
+
return a.alternatives.some((alt) => assignable(env, alt, b) === "ok") ? "ok" : nullableOr(env, a, b);
|
|
145
|
+
}
|
|
146
|
+
return nullableOr(env, a, b);
|
|
147
|
+
case "result":
|
|
148
|
+
if (b.k === "result") {
|
|
149
|
+
const ok = assignable(env, a.ok, b.ok);
|
|
150
|
+
const err = assignable(env, a.err, b.err);
|
|
151
|
+
return ok !== "no" && err !== "no" ? "ok" : "no";
|
|
152
|
+
}
|
|
153
|
+
return nullableOr(env, a, b);
|
|
154
|
+
case "task":
|
|
155
|
+
return b.k === "task" ? "ok" : nullableOr(env, a, b);
|
|
156
|
+
case "func":
|
|
157
|
+
if (b.k === "func") {
|
|
158
|
+
return "ok";
|
|
159
|
+
}
|
|
160
|
+
return nullableOr(env, a, b);
|
|
161
|
+
case "instance":
|
|
162
|
+
if (b.k === "instance") {
|
|
163
|
+
return b.cls === a.cls || env.api.isA(b.cls, a.cls) ? "ok" : "no";
|
|
164
|
+
}
|
|
165
|
+
return nullableOr(env, a, b);
|
|
166
|
+
case "datatype":
|
|
167
|
+
if (b.k === "datatype") {
|
|
168
|
+
return b.name === a.name ? "ok" : "no";
|
|
169
|
+
}
|
|
170
|
+
return nullableOr(env, a, b);
|
|
171
|
+
case "rbxEnumItem":
|
|
172
|
+
if (b.k === "rbxEnumItem") {
|
|
173
|
+
return a.enumName === "*" || b.enumName === a.enumName || b.enumName === "*" ? "ok" : "no";
|
|
174
|
+
}
|
|
175
|
+
return nullableOr(env, a, b);
|
|
176
|
+
case "rbxEnum":
|
|
177
|
+
return b.k === "rbxEnum" && (a.enumName === "*" || a.enumName === b.enumName) ? "ok" : "no";
|
|
178
|
+
case "rbxSignal":
|
|
179
|
+
return b.k === "rbxSignal" ? "ok" : "no";
|
|
180
|
+
case "tuple":
|
|
181
|
+
return b.k === "tuple" && b.elems.length >= a.elems.length ? "ok" : "no";
|
|
182
|
+
case "thread":
|
|
183
|
+
case "buffer":
|
|
184
|
+
return b.k === a.k ? "ok" : nullableOr(env, a, b);
|
|
185
|
+
default:
|
|
186
|
+
return (0, types_js_1.sameType)(a, b) ? "ok" : "no";
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
function nullableOr(env, to, from) {
|
|
190
|
+
if (from.k === "option") {
|
|
191
|
+
return assignable(env, to, from.inner) === "ok" ? "nullable" : "no";
|
|
192
|
+
}
|
|
193
|
+
if (from.k === "null") {
|
|
194
|
+
return "nullable";
|
|
195
|
+
}
|
|
196
|
+
return "no";
|
|
197
|
+
}
|
|
198
|
+
function structExtends(env, childId, baseId) {
|
|
199
|
+
const seen = new Set();
|
|
200
|
+
const stack = [childId];
|
|
201
|
+
while (stack.length) {
|
|
202
|
+
const id = stack.pop();
|
|
203
|
+
if (id === baseId) {
|
|
204
|
+
return true;
|
|
205
|
+
}
|
|
206
|
+
if (seen.has(id)) {
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
seen.add(id);
|
|
210
|
+
const info = env.struct(id);
|
|
211
|
+
if (info) {
|
|
212
|
+
stack.push(...info.bases);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return false;
|
|
216
|
+
}
|
|
217
|
+
/** Least upper bound used for ternaries, list literals and inferred returns. */
|
|
218
|
+
function join(env, a, b) {
|
|
219
|
+
const x = (0, types_js_1.prune)(a);
|
|
220
|
+
const y = (0, types_js_1.prune)(b);
|
|
221
|
+
if ((0, types_js_1.isDynamic)(x)) {
|
|
222
|
+
return y;
|
|
223
|
+
}
|
|
224
|
+
if ((0, types_js_1.isDynamic)(y)) {
|
|
225
|
+
return x;
|
|
226
|
+
}
|
|
227
|
+
if (x.k === "never") {
|
|
228
|
+
return y;
|
|
229
|
+
}
|
|
230
|
+
if (y.k === "never") {
|
|
231
|
+
return x;
|
|
232
|
+
}
|
|
233
|
+
if ((0, types_js_1.sameType)(x, y)) {
|
|
234
|
+
return x;
|
|
235
|
+
}
|
|
236
|
+
if (x.k === "null") {
|
|
237
|
+
return types_js_1.T.option(y);
|
|
238
|
+
}
|
|
239
|
+
if (y.k === "null") {
|
|
240
|
+
return types_js_1.T.option(x);
|
|
241
|
+
}
|
|
242
|
+
if (x.k === "option" || y.k === "option") {
|
|
243
|
+
const inner = join(env, x.k === "option" ? x.inner : x, y.k === "option" ? y.inner : y);
|
|
244
|
+
return types_js_1.T.option(inner);
|
|
245
|
+
}
|
|
246
|
+
const numeric = (t) => t.k === "int" || t.k === "float" || (t.k === "lit" && typeof t.value === "number");
|
|
247
|
+
if (numeric(x) && numeric(y)) {
|
|
248
|
+
const isInt = (t) => t.k === "int" || (t.k === "lit" && Number.isInteger(t.value));
|
|
249
|
+
return isInt(x) && isInt(y) ? types_js_1.T.int : types_js_1.T.float;
|
|
250
|
+
}
|
|
251
|
+
if (x.k === "lit" && y.k === "lit" && typeof x.value === typeof y.value) {
|
|
252
|
+
return typeof x.value === "string" ? types_js_1.T.string : typeof x.value === "boolean" ? types_js_1.T.bool : types_js_1.T.float;
|
|
253
|
+
}
|
|
254
|
+
if ((x.k === "lit" && typeof x.value === "string" && y.k === "string") || (y.k === "lit" && typeof y.value === "string" && x.k === "string")) {
|
|
255
|
+
return types_js_1.T.string;
|
|
256
|
+
}
|
|
257
|
+
if (x.k === "instance" && y.k === "instance") {
|
|
258
|
+
return types_js_1.T.instance(commonClass(env, x.cls, y.cls));
|
|
259
|
+
}
|
|
260
|
+
if (x.k === "struct" && y.k === "struct") {
|
|
261
|
+
if (structExtends(env, x.id, y.id)) {
|
|
262
|
+
return y;
|
|
263
|
+
}
|
|
264
|
+
if (structExtends(env, y.id, x.id)) {
|
|
265
|
+
return x;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
if (x.k === "list" && y.k === "list") {
|
|
269
|
+
return types_js_1.T.list(join(env, x.elem, y.elem), x.typed && y.typed);
|
|
270
|
+
}
|
|
271
|
+
return { k: "union", members: [x, y] };
|
|
272
|
+
}
|
|
273
|
+
function commonClass(env, a, b) {
|
|
274
|
+
const chain = new Set();
|
|
275
|
+
let cur = a;
|
|
276
|
+
while (cur) {
|
|
277
|
+
chain.add(cur);
|
|
278
|
+
cur = env.api.classes[cur]?.superclass ?? null;
|
|
279
|
+
}
|
|
280
|
+
cur = b;
|
|
281
|
+
while (cur) {
|
|
282
|
+
if (chain.has(cur)) {
|
|
283
|
+
return cur;
|
|
284
|
+
}
|
|
285
|
+
cur = env.api.classes[cur]?.superclass ?? null;
|
|
286
|
+
}
|
|
287
|
+
return "Instance";
|
|
288
|
+
}
|
|
289
|
+
/** Replace generic parameters by their bindings. */
|
|
290
|
+
function substitute(t, bindings) {
|
|
291
|
+
if (bindings.size === 0) {
|
|
292
|
+
return t;
|
|
293
|
+
}
|
|
294
|
+
const p = (0, types_js_1.prune)(t);
|
|
295
|
+
switch (p.k) {
|
|
296
|
+
case "param":
|
|
297
|
+
return bindings.get(p.name) ?? p;
|
|
298
|
+
case "list":
|
|
299
|
+
return types_js_1.T.list(substitute(p.elem, bindings), p.typed);
|
|
300
|
+
case "dict":
|
|
301
|
+
return types_js_1.T.dict(substitute(p.key, bindings), substitute(p.value, bindings));
|
|
302
|
+
case "option":
|
|
303
|
+
return types_js_1.T.option(substitute(p.inner, bindings));
|
|
304
|
+
case "result":
|
|
305
|
+
return { k: "result", ok: substitute(p.ok, bindings), err: substitute(p.err, bindings) };
|
|
306
|
+
case "task":
|
|
307
|
+
return { k: "task", result: substitute(p.result, bindings) };
|
|
308
|
+
case "struct":
|
|
309
|
+
return p.args.length ? { ...p, args: p.args.map((x) => substitute(x, bindings)) } : p;
|
|
310
|
+
case "func":
|
|
311
|
+
return {
|
|
312
|
+
...p,
|
|
313
|
+
params: p.params.map((x) => ({ ...x, type: substitute(x.type, bindings) })),
|
|
314
|
+
ret: substitute(p.ret, bindings),
|
|
315
|
+
variadic: p.variadic ? substitute(p.variadic, bindings) : null,
|
|
316
|
+
};
|
|
317
|
+
case "union":
|
|
318
|
+
return { k: "union", members: p.members.map((m) => substitute(m, bindings)) };
|
|
319
|
+
case "tuple":
|
|
320
|
+
return { k: "tuple", elems: p.elems.map((m) => substitute(m, bindings)) };
|
|
321
|
+
default:
|
|
322
|
+
return p;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
/** Bind generic parameters by matching a declared type against an argument type. */
|
|
326
|
+
function bindGenerics(declared, actual, bindings) {
|
|
327
|
+
const d = (0, types_js_1.prune)(declared);
|
|
328
|
+
const a = (0, types_js_1.prune)(actual);
|
|
329
|
+
if (d.k === "param") {
|
|
330
|
+
if (!bindings.has(d.name) && !(0, types_js_1.isDynamic)(a)) {
|
|
331
|
+
bindings.set(d.name, a.k === "lit" ? (typeof a.value === "string" ? types_js_1.T.string : typeof a.value === "boolean" ? types_js_1.T.bool : Number.isInteger(a.value) ? types_js_1.T.int : types_js_1.T.float) : a);
|
|
332
|
+
}
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
if (d.k === "list" && a.k === "list") {
|
|
336
|
+
bindGenerics(d.elem, a.elem, bindings);
|
|
337
|
+
}
|
|
338
|
+
else if (d.k === "option") {
|
|
339
|
+
bindGenerics(d.inner, a.k === "option" ? a.inner : a, bindings);
|
|
340
|
+
}
|
|
341
|
+
else if (d.k === "dict" && a.k === "dict") {
|
|
342
|
+
bindGenerics(d.key, a.key, bindings);
|
|
343
|
+
bindGenerics(d.value, a.value, bindings);
|
|
344
|
+
}
|
|
345
|
+
else if (d.k === "struct" && a.k === "struct") {
|
|
346
|
+
d.args.forEach((x, i) => a.args[i] && bindGenerics(x, a.args[i], bindings));
|
|
347
|
+
}
|
|
348
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/** Can executing these statements yield the current thread (task.wait, WaitForChild, *Async, :Wait())? */
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.mayYield = mayYield;
|
|
5
|
+
const types_js_1 = require("../types.js");
|
|
6
|
+
const YIELDING_GLOBALS = new Set(["wait", "Wait"]);
|
|
7
|
+
const YIELDING_LIBRARY = new Set(["task.wait", "coroutine.yield"]);
|
|
8
|
+
function mayYield(c, body) {
|
|
9
|
+
let found = false;
|
|
10
|
+
const visitExpr = (e) => {
|
|
11
|
+
if (!e || found) {
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
if (exprYields(c, e)) {
|
|
15
|
+
found = true;
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
for (const [key, value] of Object.entries(e)) {
|
|
19
|
+
if (key === "ty" || key === "res" || key === "span" || key === "narrowedFrom") {
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
if (key === "body" && e.kind === "Function") {
|
|
23
|
+
continue; // a closure's body runs later
|
|
24
|
+
}
|
|
25
|
+
walk(value);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
const walk = (node) => {
|
|
29
|
+
if (found || !node || typeof node !== "object") {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
if (Array.isArray(node)) {
|
|
33
|
+
for (const n of node) {
|
|
34
|
+
walk(n);
|
|
35
|
+
}
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const rec = node;
|
|
39
|
+
if (rec.kind && isExprKind(rec.kind)) {
|
|
40
|
+
visitExpr(node);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
for (const [key, value] of Object.entries(node)) {
|
|
44
|
+
if (key === "ty" || key === "res" || key === "span" || key === "sym") {
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
walk(value);
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
walk(body);
|
|
51
|
+
return found;
|
|
52
|
+
}
|
|
53
|
+
const EXPR_KINDS = new Set([
|
|
54
|
+
"Number", "String", "Template", "Bool", "Null", "Name", "SelfField", "Member", "Scope", "Index", "Call", "New", "Unary",
|
|
55
|
+
"Binary", "Ternary", "Function", "Spawn", "Await", "Thread", "Join", "Move", "PCall", "Parallel", "Update", "Cast", "Error",
|
|
56
|
+
]);
|
|
57
|
+
function isExprKind(kind) {
|
|
58
|
+
return EXPR_KINDS.has(kind);
|
|
59
|
+
}
|
|
60
|
+
function exprYields(c, e) {
|
|
61
|
+
if (e.kind === "Await" || e.kind === "Join" || e.kind === "Parallel") {
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
if (e.kind !== "Call") {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
const callee = e.callee;
|
|
68
|
+
const res = callee.res;
|
|
69
|
+
if (!res) {
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
switch (res.kind) {
|
|
73
|
+
case "rbxMethod": {
|
|
74
|
+
const name = callee.kind === "Member" || callee.kind === "Scope" ? callee.name : "";
|
|
75
|
+
const hit = c.api.member(res.owner, name);
|
|
76
|
+
return Boolean(hit?.member.yields) || name === "WaitForChild" || /Async$/.test(name);
|
|
77
|
+
}
|
|
78
|
+
case "datatypeMember":
|
|
79
|
+
return res.datatype === "RBXScriptSignal" && res.member === "Wait";
|
|
80
|
+
case "global":
|
|
81
|
+
return YIELDING_GLOBALS.has(res.luau);
|
|
82
|
+
case "library":
|
|
83
|
+
return YIELDING_LIBRARY.has(`${res.lib}.${res.member}`);
|
|
84
|
+
case "func":
|
|
85
|
+
case "local": {
|
|
86
|
+
const decl = res.sym.decl;
|
|
87
|
+
if (decl) {
|
|
88
|
+
return decl.fn?.mayYield ?? true;
|
|
89
|
+
}
|
|
90
|
+
const t = (0, types_js_1.prune)(callee.ty ?? { k: "any" });
|
|
91
|
+
return t.k !== "func";
|
|
92
|
+
}
|
|
93
|
+
case "method": {
|
|
94
|
+
const info = c.structs.get(res.struct);
|
|
95
|
+
const m = info?.methods.get(res.name);
|
|
96
|
+
return m?.decl.fn?.mayYield ?? true;
|
|
97
|
+
}
|
|
98
|
+
case "moduleMember":
|
|
99
|
+
return true;
|
|
100
|
+
case "builtin":
|
|
101
|
+
case "stdlib":
|
|
102
|
+
case "datatypeStatic":
|
|
103
|
+
case "rbxProperty":
|
|
104
|
+
case "rbxEvent":
|
|
105
|
+
case "enumCase":
|
|
106
|
+
case "struct":
|
|
107
|
+
case "signal":
|
|
108
|
+
return false;
|
|
109
|
+
case "dynamic":
|
|
110
|
+
case "luauMethod":
|
|
111
|
+
return true;
|
|
112
|
+
default:
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DiagnosticBag = void 0;
|
|
4
|
+
exports.hasErrors = hasErrors;
|
|
5
|
+
exports.sortDiagnostics = sortDiagnostics;
|
|
6
|
+
exports.formatDiagnostic = formatDiagnostic;
|
|
7
|
+
exports.editDistance = editDistance;
|
|
8
|
+
exports.suggest = suggest;
|
|
9
|
+
class DiagnosticBag {
|
|
10
|
+
source;
|
|
11
|
+
items = [];
|
|
12
|
+
seen = new Set();
|
|
13
|
+
constructor(source) {
|
|
14
|
+
this.source = source;
|
|
15
|
+
}
|
|
16
|
+
add(code, severity, span, message, notes, fix) {
|
|
17
|
+
const start = this.source.position(span.start);
|
|
18
|
+
const end = this.source.position(Math.max(span.start, span.end));
|
|
19
|
+
const diagnostic = {
|
|
20
|
+
code,
|
|
21
|
+
severity,
|
|
22
|
+
message,
|
|
23
|
+
file: this.source.path,
|
|
24
|
+
span,
|
|
25
|
+
line: start.line,
|
|
26
|
+
column: start.column,
|
|
27
|
+
endLine: end.line,
|
|
28
|
+
endColumn: end.column,
|
|
29
|
+
};
|
|
30
|
+
if (notes && notes.length > 0) {
|
|
31
|
+
diagnostic.notes = notes;
|
|
32
|
+
}
|
|
33
|
+
if (fix) {
|
|
34
|
+
diagnostic.fix = fix;
|
|
35
|
+
}
|
|
36
|
+
const key = `${code}:${span.start}:${span.end}:${message}`;
|
|
37
|
+
if (!this.seen.has(key)) {
|
|
38
|
+
this.seen.add(key);
|
|
39
|
+
this.items.push(diagnostic);
|
|
40
|
+
}
|
|
41
|
+
return diagnostic;
|
|
42
|
+
}
|
|
43
|
+
error(code, span, message, notes, fix) {
|
|
44
|
+
return this.add(code, "error", span, message, notes, fix);
|
|
45
|
+
}
|
|
46
|
+
warning(code, span, message, notes, fix) {
|
|
47
|
+
return this.add(code, "warning", span, message, notes, fix);
|
|
48
|
+
}
|
|
49
|
+
info(code, span, message, notes) {
|
|
50
|
+
return this.add(code, "info", span, message, notes);
|
|
51
|
+
}
|
|
52
|
+
get errorCount() {
|
|
53
|
+
let n = 0;
|
|
54
|
+
for (const d of this.items) {
|
|
55
|
+
if (d.severity === "error") {
|
|
56
|
+
n++;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return n;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
exports.DiagnosticBag = DiagnosticBag;
|
|
63
|
+
function hasErrors(diagnostics) {
|
|
64
|
+
return diagnostics.some((d) => d.severity === "error");
|
|
65
|
+
}
|
|
66
|
+
function sortDiagnostics(diagnostics) {
|
|
67
|
+
return diagnostics.sort((a, b) => a.file.localeCompare(b.file) || a.span.start - b.span.start || a.code.localeCompare(b.code));
|
|
68
|
+
}
|
|
69
|
+
/** `file:line:col: error[CL3001]: message` plus a caret excerpt, like rustc/clang. */
|
|
70
|
+
function formatDiagnostic(d, source, color = false) {
|
|
71
|
+
const paint = (code, text) => (color ? `\x1b[${code}m${text}\x1b[0m` : text);
|
|
72
|
+
const label = d.severity === "error" ? paint("1;31", "error") : d.severity === "warning" ? paint("1;33", "warning") : paint("1;36", d.severity);
|
|
73
|
+
const lines = [`${d.file}:${d.line}:${d.column}: ${label}[${d.code}]: ${d.message}`];
|
|
74
|
+
if (source) {
|
|
75
|
+
const text = source.lineText(d.line).replace(/\t/g, " ");
|
|
76
|
+
const raw = source.lineText(d.line);
|
|
77
|
+
const prefix = raw.slice(0, d.column - 1).replace(/\t/g, " ");
|
|
78
|
+
const width = d.endLine === d.line ? Math.max(1, d.endColumn - d.column) : Math.max(1, raw.length - (d.column - 1));
|
|
79
|
+
const gutter = String(d.line).length;
|
|
80
|
+
lines.push(`${" ".repeat(gutter)} |`);
|
|
81
|
+
lines.push(`${d.line} | ${text}`);
|
|
82
|
+
lines.push(`${" ".repeat(gutter)} | ${" ".repeat(prefix.length)}${paint(d.severity === "error" ? "1;31" : "1;33", "^".repeat(Math.min(width, Math.max(1, text.length - prefix.length))))}`);
|
|
83
|
+
}
|
|
84
|
+
for (const note of d.notes || []) {
|
|
85
|
+
lines.push(` = ${note}`);
|
|
86
|
+
}
|
|
87
|
+
return lines.join("\n");
|
|
88
|
+
}
|
|
89
|
+
/** Damerau-free Levenshtein, capped; used for "did you mean" suggestions. */
|
|
90
|
+
function editDistance(a, b, cap = 4) {
|
|
91
|
+
if (Math.abs(a.length - b.length) > cap) {
|
|
92
|
+
return cap + 1;
|
|
93
|
+
}
|
|
94
|
+
const prev = new Array(b.length + 1);
|
|
95
|
+
const cur = new Array(b.length + 1);
|
|
96
|
+
for (let j = 0; j <= b.length; j++) {
|
|
97
|
+
prev[j] = j;
|
|
98
|
+
}
|
|
99
|
+
for (let i = 1; i <= a.length; i++) {
|
|
100
|
+
cur[0] = i;
|
|
101
|
+
let rowMin = cur[0];
|
|
102
|
+
const ca = a.charCodeAt(i - 1);
|
|
103
|
+
for (let j = 1; j <= b.length; j++) {
|
|
104
|
+
const cost = ca === b.charCodeAt(j - 1) ? 0 : 1;
|
|
105
|
+
cur[j] = Math.min(prev[j] + 1, cur[j - 1] + 1, prev[j - 1] + cost);
|
|
106
|
+
if (cur[j] < rowMin) {
|
|
107
|
+
rowMin = cur[j];
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (rowMin > cap) {
|
|
111
|
+
return cap + 1;
|
|
112
|
+
}
|
|
113
|
+
for (let j = 0; j <= b.length; j++) {
|
|
114
|
+
prev[j] = cur[j];
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return prev[b.length];
|
|
118
|
+
}
|
|
119
|
+
function suggest(name, candidates, max = 2) {
|
|
120
|
+
let best = null;
|
|
121
|
+
let bestScore = Infinity;
|
|
122
|
+
const lower = name.toLowerCase();
|
|
123
|
+
for (const candidate of candidates) {
|
|
124
|
+
if (candidate === name) {
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
if (candidate.toLowerCase() === lower) {
|
|
128
|
+
return candidate;
|
|
129
|
+
}
|
|
130
|
+
const limit = Math.min(max, Math.max(1, Math.floor(name.length / 3)));
|
|
131
|
+
const score = editDistance(lower, candidate.toLowerCase(), limit);
|
|
132
|
+
if (score <= limit && score < bestScore) {
|
|
133
|
+
best = candidate;
|
|
134
|
+
bestScore = score;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return best;
|
|
138
|
+
}
|