luaut-parser 1.2.0 → 2.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 +70 -28
- package/dist/index.cjs +414 -31
- package/dist/index.d.cts +92 -23
- package/dist/index.d.ts +92 -23
- package/dist/index.js +406 -21
- package/package.json +4 -2
- package/dist/luau.d.luaut +0 -245
- package/dist/roblox.d.luaut +0 -503
package/dist/index.js
CHANGED
|
@@ -3176,7 +3176,11 @@ function isAssignableInner(a, b) {
|
|
|
3176
3176
|
if (a.kind === "union") return a.types.every((t) => isAssignable(t, b));
|
|
3177
3177
|
if (b.kind === "union") return b.types.some((t) => isAssignable(a, t));
|
|
3178
3178
|
if (b.kind === "intersection") return b.types.every((t) => isAssignable(a, t));
|
|
3179
|
-
if (a.kind === "intersection")
|
|
3179
|
+
if (a.kind === "intersection") {
|
|
3180
|
+
if (a.types.some((t) => isAssignable(t, b))) return true;
|
|
3181
|
+
const merged = mergeObjectMembers(a.types);
|
|
3182
|
+
return merged !== void 0 && isAssignable(merged, b);
|
|
3183
|
+
}
|
|
3180
3184
|
if (a.kind === "literal") {
|
|
3181
3185
|
if (b.kind === "literal") return a.value === b.value;
|
|
3182
3186
|
if (b.kind === "primitive") return b.name === a.base;
|
|
@@ -3531,6 +3535,39 @@ var IDENT_KEY = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
|
3531
3535
|
function formatKey(k) {
|
|
3532
3536
|
return IDENT_KEY.test(k) ? k : JSON.stringify(k);
|
|
3533
3537
|
}
|
|
3538
|
+
function mergeObjectMembers(types) {
|
|
3539
|
+
const objects = [];
|
|
3540
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3541
|
+
const collect = (t) => {
|
|
3542
|
+
if (seen.has(t)) return true;
|
|
3543
|
+
seen.add(t);
|
|
3544
|
+
if (t.kind === "genericRef") {
|
|
3545
|
+
const expanded = expandAlias?.(t);
|
|
3546
|
+
return expanded !== void 0 && expanded !== t && collect(expanded);
|
|
3547
|
+
}
|
|
3548
|
+
if (t.kind === "intersection") return t.types.every(collect);
|
|
3549
|
+
if (t.kind === "object") {
|
|
3550
|
+
objects.push(t);
|
|
3551
|
+
return true;
|
|
3552
|
+
}
|
|
3553
|
+
return false;
|
|
3554
|
+
};
|
|
3555
|
+
if (!types.every(collect) || objects.length < 2) return void 0;
|
|
3556
|
+
const properties = /* @__PURE__ */ new Map();
|
|
3557
|
+
let indexer;
|
|
3558
|
+
for (const object of objects) {
|
|
3559
|
+
indexer ??= object.indexer;
|
|
3560
|
+
for (const [name, property] of object.properties) {
|
|
3561
|
+
const existing = properties.get(name);
|
|
3562
|
+
properties.set(name, existing ? {
|
|
3563
|
+
type: intersection([existing.type, property.type]),
|
|
3564
|
+
optional: existing.optional && property.optional,
|
|
3565
|
+
readonly: existing.readonly || property.readonly
|
|
3566
|
+
} : property);
|
|
3567
|
+
}
|
|
3568
|
+
}
|
|
3569
|
+
return objectType([...properties], indexer);
|
|
3570
|
+
}
|
|
3534
3571
|
|
|
3535
3572
|
// src/ast/analyzeTypes.ts
|
|
3536
3573
|
function analyzeTypes(program, scopes, options = {}) {
|
|
@@ -5823,22 +5860,368 @@ function briefType(t) {
|
|
|
5823
5860
|
return formatType(t);
|
|
5824
5861
|
}
|
|
5825
5862
|
|
|
5826
|
-
// src/
|
|
5827
|
-
import { readFileSync } from "fs";
|
|
5828
|
-
|
|
5829
|
-
|
|
5830
|
-
|
|
5831
|
-
|
|
5863
|
+
// src/project/host.ts
|
|
5864
|
+
import { readFileSync, statSync } from "fs";
|
|
5865
|
+
var nodeHost = {
|
|
5866
|
+
readFile(path) {
|
|
5867
|
+
try {
|
|
5868
|
+
return statSync(path).isFile() ? readFileSync(path, "utf8") : void 0;
|
|
5869
|
+
} catch {
|
|
5870
|
+
return void 0;
|
|
5871
|
+
}
|
|
5872
|
+
}
|
|
5873
|
+
};
|
|
5874
|
+
|
|
5875
|
+
// src/project/config.ts
|
|
5876
|
+
import { dirname, join, resolve } from "path";
|
|
5877
|
+
var CONFIG_FILE_NAMES = ["luaut.config.json", "luaut.config.jsonc"];
|
|
5878
|
+
function findConfig(file, host = nodeHost) {
|
|
5879
|
+
const searched = [];
|
|
5880
|
+
let directory = dirname(resolve(file));
|
|
5881
|
+
for (; ; ) {
|
|
5882
|
+
const found = [];
|
|
5883
|
+
for (const name of CONFIG_FILE_NAMES) {
|
|
5884
|
+
const path = join(directory, name);
|
|
5885
|
+
searched.push(path);
|
|
5886
|
+
if (host.readFile(path) !== void 0) found.push(path);
|
|
5887
|
+
}
|
|
5888
|
+
if (found.length > 1) {
|
|
5889
|
+
const message = `Only one luaut config may be in a folder, but both ${CONFIG_FILE_NAMES.join(" and ")} are in ${directory}`;
|
|
5890
|
+
return { searched, problems: found.map((path) => ({ file: path, message, line: 1, column: 1 })) };
|
|
5891
|
+
}
|
|
5892
|
+
if (found.length === 1) {
|
|
5893
|
+
const { config, problems } = loadConfig(found[0], host);
|
|
5894
|
+
return { config, problems, searched };
|
|
5895
|
+
}
|
|
5896
|
+
const parent = dirname(directory);
|
|
5897
|
+
if (parent === directory) return { searched, problems: [] };
|
|
5898
|
+
directory = parent;
|
|
5899
|
+
}
|
|
5900
|
+
}
|
|
5901
|
+
var OPTIONS = ["types", "paths", "baseUrl", "sourceMap"];
|
|
5902
|
+
function loadConfig(path, host = nodeHost) {
|
|
5903
|
+
const file = resolve(path);
|
|
5904
|
+
const source = host.readFile(file);
|
|
5905
|
+
if (source === void 0) return { problems: [{ file, message: "Cannot read the config file" }] };
|
|
5906
|
+
let raw;
|
|
5907
|
+
try {
|
|
5908
|
+
raw = JSON.parse(stripJsonComments(source));
|
|
5909
|
+
} catch (error) {
|
|
5910
|
+
const message = error.message;
|
|
5911
|
+
return { problems: [{ file, message: `Invalid JSON: ${message}`, ...jsonErrorPosition(source, message) }] };
|
|
5912
|
+
}
|
|
5913
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
5914
|
+
return { problems: [{ file, message: "The config must be a JSON object", line: 1, column: 1 }] };
|
|
5915
|
+
}
|
|
5916
|
+
const directory = dirname(file);
|
|
5917
|
+
const options = raw;
|
|
5918
|
+
const problems = [];
|
|
5919
|
+
const at = (key) => keyPosition(source, key);
|
|
5920
|
+
const problem = (key, message) => {
|
|
5921
|
+
problems.push({ file, message, ...at(key) });
|
|
5922
|
+
};
|
|
5923
|
+
for (const key of Object.keys(options)) {
|
|
5924
|
+
if (!OPTIONS.includes(key)) {
|
|
5925
|
+
problem(key, `Unknown option '${key}'. Options are: ${OPTIONS.join(", ")}`);
|
|
5926
|
+
}
|
|
5927
|
+
}
|
|
5928
|
+
let types = [];
|
|
5929
|
+
if (options.types !== void 0) {
|
|
5930
|
+
if (Array.isArray(options.types) && options.types.every((t) => typeof t === "string")) types = options.types;
|
|
5931
|
+
else problem("types", `'types' must be an array of strings, such as ["luau"]`);
|
|
5932
|
+
}
|
|
5933
|
+
const paths = {};
|
|
5934
|
+
if (options.paths !== void 0) {
|
|
5935
|
+
const value = options.paths;
|
|
5936
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
5937
|
+
for (const [pattern, targets] of Object.entries(value)) {
|
|
5938
|
+
if (Array.isArray(targets) && targets.every((t) => typeof t === "string")) paths[pattern] = targets;
|
|
5939
|
+
else problem(pattern, `'paths' entry '${pattern}' must be an array of strings`);
|
|
5940
|
+
if (pattern.split("*").length > 2) problem(pattern, `'paths' pattern '${pattern}' may contain at most one '*'`);
|
|
5941
|
+
}
|
|
5942
|
+
} else {
|
|
5943
|
+
problem("paths", `'paths' must be an object, such as { "@shared/*": ["src/shared/*"] }`);
|
|
5944
|
+
}
|
|
5945
|
+
}
|
|
5946
|
+
let baseUrl = directory;
|
|
5947
|
+
if (options.baseUrl !== void 0) {
|
|
5948
|
+
if (typeof options.baseUrl === "string") baseUrl = resolve(directory, options.baseUrl);
|
|
5949
|
+
else problem("baseUrl", "'baseUrl' must be a string");
|
|
5950
|
+
}
|
|
5951
|
+
let sourceMap = null;
|
|
5952
|
+
if (options.sourceMap !== void 0 && options.sourceMap !== null) {
|
|
5953
|
+
if (typeof options.sourceMap === "string") sourceMap = resolve(directory, options.sourceMap);
|
|
5954
|
+
else problem("sourceMap", "'sourceMap' must be a path string, or null for none");
|
|
5955
|
+
}
|
|
5956
|
+
return { config: { path: file, directory, source, types, paths, baseUrl, sourceMap }, problems };
|
|
5957
|
+
}
|
|
5958
|
+
function stripJsonComments(text) {
|
|
5959
|
+
const out = text.split("");
|
|
5960
|
+
let i = 0;
|
|
5961
|
+
let inString = false;
|
|
5962
|
+
while (i < text.length) {
|
|
5963
|
+
const ch = text[i];
|
|
5964
|
+
if (inString) {
|
|
5965
|
+
if (ch === "\\") i += 2;
|
|
5966
|
+
else {
|
|
5967
|
+
if (ch === '"') inString = false;
|
|
5968
|
+
i++;
|
|
5969
|
+
}
|
|
5970
|
+
continue;
|
|
5971
|
+
}
|
|
5972
|
+
if (ch === '"') {
|
|
5973
|
+
inString = true;
|
|
5974
|
+
i++;
|
|
5975
|
+
} else if (ch === "/" && text[i + 1] === "/") {
|
|
5976
|
+
while (i < text.length && text[i] !== "\n") out[i++] = " ";
|
|
5977
|
+
} else if (ch === "/" && text[i + 1] === "*") {
|
|
5978
|
+
out[i++] = " ";
|
|
5979
|
+
out[i++] = " ";
|
|
5980
|
+
while (i < text.length && !(text[i] === "*" && text[i + 1] === "/")) {
|
|
5981
|
+
if (text[i] !== "\n") out[i] = " ";
|
|
5982
|
+
i++;
|
|
5983
|
+
}
|
|
5984
|
+
if (i < text.length) {
|
|
5985
|
+
out[i++] = " ";
|
|
5986
|
+
out[i++] = " ";
|
|
5987
|
+
}
|
|
5988
|
+
} else if (ch === ",") {
|
|
5989
|
+
let j = i + 1;
|
|
5990
|
+
while (j < text.length && /\s/.test(text[j])) j++;
|
|
5991
|
+
if (text[j] === "}" || text[j] === "]") out[i] = " ";
|
|
5992
|
+
i++;
|
|
5993
|
+
} else {
|
|
5994
|
+
i++;
|
|
5995
|
+
}
|
|
5996
|
+
}
|
|
5997
|
+
return out.join("");
|
|
5998
|
+
}
|
|
5999
|
+
function jsonErrorPosition(source, message) {
|
|
6000
|
+
const lineColumn = /line (\d+) column (\d+)/.exec(message);
|
|
6001
|
+
if (lineColumn) return { line: Number(lineColumn[1]), column: Number(lineColumn[2]) };
|
|
6002
|
+
const position = /position (\d+)/.exec(message);
|
|
6003
|
+
return position ? offsetPosition(source, Number(position[1])) : { line: 1, column: 1 };
|
|
6004
|
+
}
|
|
6005
|
+
function keyPosition(source, key) {
|
|
6006
|
+
const offset = source.indexOf(JSON.stringify(key));
|
|
6007
|
+
return offset < 0 ? { line: 1, column: 1 } : offsetPosition(source, offset);
|
|
6008
|
+
}
|
|
6009
|
+
function offsetPosition(source, offset) {
|
|
6010
|
+
const before = source.slice(0, offset);
|
|
6011
|
+
const line = before.split("\n").length;
|
|
6012
|
+
return { line, column: offset - before.lastIndexOf("\n") };
|
|
6013
|
+
}
|
|
5832
6014
|
|
|
5833
|
-
// src/
|
|
5834
|
-
import {
|
|
5835
|
-
|
|
5836
|
-
|
|
5837
|
-
|
|
5838
|
-
|
|
6015
|
+
// src/project/libraries.ts
|
|
6016
|
+
import { dirname as dirname2, join as join2, resolve as resolve2 } from "path";
|
|
6017
|
+
function resolveTypeLibraries(config, host = nodeHost) {
|
|
6018
|
+
const files = [];
|
|
6019
|
+
const problems = [];
|
|
6020
|
+
const loaded = /* @__PURE__ */ new Set();
|
|
6021
|
+
const addFile = (file) => {
|
|
6022
|
+
const key = pathKey(file);
|
|
6023
|
+
if (loaded.has(key)) return;
|
|
6024
|
+
loaded.add(key);
|
|
6025
|
+
files.push(file);
|
|
6026
|
+
};
|
|
6027
|
+
const addPackage = (directory, entryFile, visiting) => {
|
|
6028
|
+
const key = pathKey(directory);
|
|
6029
|
+
if (visiting.has(key)) return;
|
|
6030
|
+
visiting.add(key);
|
|
6031
|
+
for (const dependency of dependencyNames(directory, host)) {
|
|
6032
|
+
const found = findPackage(dependency, directory, host);
|
|
6033
|
+
if (found) addPackage(found.directory, found.file, visiting);
|
|
6034
|
+
}
|
|
6035
|
+
addFile(entryFile);
|
|
6036
|
+
};
|
|
6037
|
+
for (const entry of config.types) {
|
|
6038
|
+
const relative = entry.startsWith("./") || entry.startsWith("../") || entry.startsWith("/") || /^[A-Za-z]:[\\/]/.test(entry);
|
|
6039
|
+
if (relative) {
|
|
6040
|
+
const target = resolve2(config.directory, entry);
|
|
6041
|
+
if (entry.endsWith(".luaut")) {
|
|
6042
|
+
if (host.readFile(target) !== void 0) addFile(target);
|
|
6043
|
+
else problems.push({ file: config.path, message: `Cannot find type library file '${entry}'`, ...entryPosition(config, entry) });
|
|
6044
|
+
continue;
|
|
6045
|
+
}
|
|
6046
|
+
const file = packageEntry(target, host);
|
|
6047
|
+
if (file) addPackage(target, file, /* @__PURE__ */ new Set());
|
|
6048
|
+
else problems.push({ file: config.path, message: `'${entry}' has no ${ENTRY_FILE} (or 'luaut.types' in its package.json)`, ...entryPosition(config, entry) });
|
|
6049
|
+
continue;
|
|
6050
|
+
}
|
|
6051
|
+
const names = entry.startsWith("@") || entry.includes("/") ? [entry] : [`@luaut/${entry}`, entry];
|
|
6052
|
+
const found = names.map((name) => findPackage(name, config.directory, host)).find(Boolean);
|
|
6053
|
+
if (found) addPackage(found.directory, found.file, /* @__PURE__ */ new Set());
|
|
6054
|
+
else {
|
|
6055
|
+
problems.push({
|
|
6056
|
+
file: config.path,
|
|
6057
|
+
message: `Cannot find type library '${entry}'. Install it with: npm i -D ${names[0]}`,
|
|
6058
|
+
...entryPosition(config, entry)
|
|
6059
|
+
});
|
|
6060
|
+
}
|
|
6061
|
+
}
|
|
6062
|
+
return { files, problems };
|
|
6063
|
+
}
|
|
6064
|
+
var ENTRY_FILE = "index.d.luaut";
|
|
6065
|
+
function packageEntry(directory, host) {
|
|
6066
|
+
const manifest = readJson(join2(directory, "package.json"), host);
|
|
6067
|
+
const declared = manifest?.luaut?.types;
|
|
6068
|
+
const file = resolve2(directory, typeof declared === "string" ? declared : ENTRY_FILE);
|
|
6069
|
+
return host.readFile(file) !== void 0 ? file : void 0;
|
|
6070
|
+
}
|
|
6071
|
+
function findPackage(name, from, host) {
|
|
6072
|
+
let directory = resolve2(from);
|
|
6073
|
+
for (; ; ) {
|
|
6074
|
+
const candidate = join2(directory, "node_modules", ...name.split("/"));
|
|
6075
|
+
const file = packageEntry(candidate, host);
|
|
6076
|
+
if (file) return { directory: candidate, file };
|
|
6077
|
+
const parent = dirname2(directory);
|
|
6078
|
+
if (parent === directory) return void 0;
|
|
6079
|
+
directory = parent;
|
|
6080
|
+
}
|
|
6081
|
+
}
|
|
6082
|
+
function dependencyNames(directory, host) {
|
|
6083
|
+
const manifest = readJson(join2(directory, "package.json"), host);
|
|
6084
|
+
const names = /* @__PURE__ */ new Set();
|
|
6085
|
+
for (const field of ["dependencies", "peerDependencies"]) {
|
|
6086
|
+
const deps = manifest?.[field];
|
|
6087
|
+
if (deps && typeof deps === "object") for (const name of Object.keys(deps)) names.add(name);
|
|
6088
|
+
}
|
|
6089
|
+
return [...names];
|
|
6090
|
+
}
|
|
6091
|
+
function readJson(path, host) {
|
|
6092
|
+
const text = host.readFile(path);
|
|
6093
|
+
if (text === void 0) return void 0;
|
|
6094
|
+
try {
|
|
6095
|
+
const value = JSON.parse(text);
|
|
6096
|
+
return value && typeof value === "object" ? value : void 0;
|
|
6097
|
+
} catch {
|
|
6098
|
+
return void 0;
|
|
6099
|
+
}
|
|
6100
|
+
}
|
|
6101
|
+
function entryPosition(config, entry) {
|
|
6102
|
+
return keyPosition(config.source, entry);
|
|
6103
|
+
}
|
|
6104
|
+
function pathKey(path) {
|
|
6105
|
+
const normalized = resolve2(path);
|
|
6106
|
+
return process.platform === "win32" ? normalized.toLowerCase() : normalized;
|
|
6107
|
+
}
|
|
6108
|
+
|
|
6109
|
+
// src/project/modules.ts
|
|
6110
|
+
import { dirname as dirname3, join as join3, resolve as resolve3 } from "path";
|
|
6111
|
+
function moduleCandidates(fromFile, specifier, config) {
|
|
6112
|
+
const bases = specifier.startsWith("./") || specifier.startsWith("../") ? [resolve3(dirname3(fromFile), specifier)] : config ? aliasTargets(config, specifier) : [];
|
|
6113
|
+
return bases.flatMap((base) => base.endsWith(".luaut") ? [base] : [`${base}.luaut`, `${base}.d.luaut`, join3(base, "index.luaut")]);
|
|
6114
|
+
}
|
|
6115
|
+
function resolveModulePath(fromFile, specifier, config, host = nodeHost) {
|
|
6116
|
+
return moduleCandidates(fromFile, specifier, config).find((path) => host.readFile(path) !== void 0);
|
|
6117
|
+
}
|
|
6118
|
+
function aliasTargets(config, specifier) {
|
|
6119
|
+
let match;
|
|
6120
|
+
let prefixLength = -1;
|
|
6121
|
+
for (const pattern2 of Object.keys(config.paths)) {
|
|
6122
|
+
const star = pattern2.indexOf("*");
|
|
6123
|
+
if (star < 0) {
|
|
6124
|
+
if (pattern2 === specifier) {
|
|
6125
|
+
match = { pattern: pattern2, wildcard: "" };
|
|
6126
|
+
break;
|
|
6127
|
+
}
|
|
6128
|
+
continue;
|
|
6129
|
+
}
|
|
6130
|
+
const prefix = pattern2.slice(0, star);
|
|
6131
|
+
const suffix = pattern2.slice(star + 1);
|
|
6132
|
+
const fits = specifier.length >= prefix.length + suffix.length && specifier.startsWith(prefix) && specifier.endsWith(suffix);
|
|
6133
|
+
if (fits && prefix.length > prefixLength) {
|
|
6134
|
+
prefixLength = prefix.length;
|
|
6135
|
+
match = { pattern: pattern2, wildcard: specifier.slice(prefix.length, specifier.length - suffix.length) };
|
|
6136
|
+
}
|
|
6137
|
+
}
|
|
6138
|
+
if (!match) return [];
|
|
6139
|
+
const { pattern, wildcard } = match;
|
|
6140
|
+
return config.paths[pattern].map((target) => resolve3(config.baseUrl, target.replace("*", wildcard)));
|
|
6141
|
+
}
|
|
6142
|
+
|
|
6143
|
+
// src/project/sourcemap.ts
|
|
6144
|
+
import { dirname as dirname4, extname, resolve as resolve4 } from "path";
|
|
6145
|
+
var IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
6146
|
+
var INSTANCE_MEMBERS = /* @__PURE__ */ new Set(["Name", "ClassName", "Parent", "Archivable"]);
|
|
6147
|
+
var SCRIPT_EXTENSIONS = /* @__PURE__ */ new Set([".luaut", ".luau", ".lua"]);
|
|
6148
|
+
function sourceMapTypes(text, path, options) {
|
|
6149
|
+
let root;
|
|
6150
|
+
try {
|
|
6151
|
+
root = JSON.parse(text);
|
|
6152
|
+
} catch (error) {
|
|
6153
|
+
return { problem: `Invalid sourcemap: ${error.message}` };
|
|
6154
|
+
}
|
|
6155
|
+
if (!isNode(root)) return { problem: "Invalid sourcemap: the root must be an object with 'name' and 'className'" };
|
|
6156
|
+
const directory = dirname4(resolve4(path));
|
|
6157
|
+
const lines = [];
|
|
6158
|
+
const aliasOfFile = /* @__PURE__ */ new Map();
|
|
6159
|
+
const used = /* @__PURE__ */ new Set();
|
|
6160
|
+
const canOmit = options.classes.has("Omit");
|
|
6161
|
+
const aliasOfNode = /* @__PURE__ */ new Map();
|
|
6162
|
+
const aliasFor = (segments) => {
|
|
6163
|
+
const base = `SourceMap_${segments.map((s) => s.replace(/[^A-Za-z0-9_]/g, "_")).join("_")}`;
|
|
6164
|
+
let alias = base;
|
|
6165
|
+
for (let n = 2; used.has(alias); n++) alias = `${base}_${n}`;
|
|
6166
|
+
used.add(alias);
|
|
6167
|
+
return alias;
|
|
6168
|
+
};
|
|
6169
|
+
const visit = (node, segments, parent) => {
|
|
6170
|
+
const alias = aliasFor(segments);
|
|
6171
|
+
aliasOfNode.set(node, alias);
|
|
6172
|
+
for (const filePath of node.filePaths ?? []) aliasOfFile.set(fileKey(resolve4(directory, filePath)), alias);
|
|
6173
|
+
const className = IDENTIFIER.test(node.className) && options.classes.has(node.className) ? node.className : "Instance";
|
|
6174
|
+
const taken = options.membersOf?.(className) ?? INSTANCE_MEMBERS;
|
|
6175
|
+
const members = [];
|
|
6176
|
+
if (parent && canOmit) members.push(`Parent: ${parent}`);
|
|
6177
|
+
const named = /* @__PURE__ */ new Set();
|
|
6178
|
+
for (const child of node.children ?? []) {
|
|
6179
|
+
if (!isNode(child)) continue;
|
|
6180
|
+
const childAlias = visit(child, [...segments, child.name], alias);
|
|
6181
|
+
if (!IDENTIFIER.test(child.name) || taken.has(child.name) || named.has(child.name)) continue;
|
|
6182
|
+
named.add(child.name);
|
|
6183
|
+
members.push(`${child.name}: ${childAlias}`);
|
|
6184
|
+
}
|
|
6185
|
+
const base = parent && canOmit ? `Omit<${className}, "Parent">` : className;
|
|
6186
|
+
lines.push(`type ${alias} = ${members.length ? `${base} & { ${members.join(", ")} }` : base}`);
|
|
6187
|
+
return alias;
|
|
6188
|
+
};
|
|
6189
|
+
const rootAlias = visit(root, [root.name], void 0);
|
|
6190
|
+
if (root.className === "DataModel") {
|
|
6191
|
+
lines.push(`declare game: ${rootAlias}`);
|
|
6192
|
+
const workspace = (root.children ?? []).find((child) => isNode(child) && child.className === "Workspace");
|
|
6193
|
+
const workspaceAlias = workspace && aliasOfNode.get(workspace);
|
|
6194
|
+
if (workspaceAlias) lines.push(`declare workspace: ${workspaceAlias}`);
|
|
6195
|
+
}
|
|
6196
|
+
let program;
|
|
6197
|
+
try {
|
|
6198
|
+
program = parse(lines.join("\n"));
|
|
6199
|
+
} catch (error) {
|
|
6200
|
+
return { problem: `Could not turn the sourcemap into types: ${error.message}` };
|
|
6201
|
+
}
|
|
6202
|
+
return {
|
|
6203
|
+
types: {
|
|
6204
|
+
program,
|
|
6205
|
+
scriptFor(file) {
|
|
6206
|
+
const alias = aliasOfFile.get(fileKey(file));
|
|
6207
|
+
return alias ? parse(`declare script: ${alias}`) : void 0;
|
|
6208
|
+
}
|
|
6209
|
+
}
|
|
6210
|
+
};
|
|
6211
|
+
}
|
|
6212
|
+
function isNode(value) {
|
|
6213
|
+
if (!value || typeof value !== "object") return false;
|
|
6214
|
+
const node = value;
|
|
6215
|
+
return typeof node.name === "string" && typeof node.className === "string";
|
|
6216
|
+
}
|
|
6217
|
+
function fileKey(path) {
|
|
6218
|
+
const extension = extname(path);
|
|
6219
|
+
const bare = SCRIPT_EXTENSIONS.has(extension) ? path.slice(0, -extension.length) : path;
|
|
6220
|
+
const normalized = resolve4(bare);
|
|
6221
|
+
return process.platform === "win32" ? normalized.toLowerCase() : normalized;
|
|
6222
|
+
}
|
|
5839
6223
|
|
|
5840
6224
|
// src/index.ts
|
|
5841
|
-
var defaultLibs = [luauLib, robloxLib];
|
|
5842
6225
|
var luautparser = {
|
|
5843
6226
|
tokenize,
|
|
5844
6227
|
parseTokens,
|
|
@@ -5854,6 +6237,7 @@ var luautparser = {
|
|
|
5854
6237
|
var index_default = luautparser;
|
|
5855
6238
|
export {
|
|
5856
6239
|
BinaryOperators,
|
|
6240
|
+
CONFIG_FILE_NAMES,
|
|
5857
6241
|
Keywords,
|
|
5858
6242
|
LexError,
|
|
5859
6243
|
Operators,
|
|
@@ -5868,10 +6252,10 @@ export {
|
|
|
5868
6252
|
bufferType,
|
|
5869
6253
|
containsTypeParam,
|
|
5870
6254
|
index_default as default,
|
|
5871
|
-
defaultLibs,
|
|
5872
6255
|
difference,
|
|
5873
6256
|
equalTypes,
|
|
5874
6257
|
falsyType,
|
|
6258
|
+
findConfig,
|
|
5875
6259
|
fn,
|
|
5876
6260
|
formatType,
|
|
5877
6261
|
getBinding,
|
|
@@ -5882,11 +6266,10 @@ export {
|
|
|
5882
6266
|
isPossiblyTruthy,
|
|
5883
6267
|
isUnassignedGlobal,
|
|
5884
6268
|
literal,
|
|
5885
|
-
|
|
5886
|
-
luauDefsPath,
|
|
5887
|
-
luauLib,
|
|
6269
|
+
loadConfig,
|
|
5888
6270
|
luautparser,
|
|
5889
6271
|
matchInfer,
|
|
6272
|
+
moduleCandidates,
|
|
5890
6273
|
moduleExports,
|
|
5891
6274
|
narrowExclude,
|
|
5892
6275
|
narrowFalsy,
|
|
@@ -5894,6 +6277,7 @@ export {
|
|
|
5894
6277
|
narrowTruthy,
|
|
5895
6278
|
neverType,
|
|
5896
6279
|
nilType,
|
|
6280
|
+
nodeHost,
|
|
5897
6281
|
numberType,
|
|
5898
6282
|
objectType,
|
|
5899
6283
|
optional,
|
|
@@ -5903,11 +6287,12 @@ export {
|
|
|
5903
6287
|
parseTokens,
|
|
5904
6288
|
parseWithRecovery,
|
|
5905
6289
|
primitive,
|
|
5906
|
-
|
|
5907
|
-
|
|
5908
|
-
robloxLib,
|
|
6290
|
+
resolveModulePath,
|
|
6291
|
+
resolveTypeLibraries,
|
|
5909
6292
|
setAliasExpander,
|
|
6293
|
+
sourceMapTypes,
|
|
5910
6294
|
stringType,
|
|
6295
|
+
stripJsonComments,
|
|
5911
6296
|
substitute,
|
|
5912
6297
|
templateMatches,
|
|
5913
6298
|
threadType,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "luaut-parser",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "luaut parser for roblox",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.js",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
}
|
|
24
24
|
},
|
|
25
25
|
"scripts": {
|
|
26
|
-
"test": "tsx scripts/test.ts",
|
|
26
|
+
"test": "tsx scripts/test.ts && tsx scripts/project.test.ts",
|
|
27
27
|
"build": "tsup",
|
|
28
28
|
"typecheck": "tsc --noEmit"
|
|
29
29
|
},
|
|
@@ -32,6 +32,8 @@
|
|
|
32
32
|
"license": "ISC",
|
|
33
33
|
"type": "module",
|
|
34
34
|
"devDependencies": {
|
|
35
|
+
"@luaut/luau": "^1.0.0",
|
|
36
|
+
"@luaut/roblox": "^1.0.0",
|
|
35
37
|
"@types/node": "^22.0.0",
|
|
36
38
|
"tsup": "^8.5.1",
|
|
37
39
|
"tsx": "^4.19.0",
|