@thigasdevelopment/luam 0.2.1 → 0.4.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/lua/async.lua +118 -118
- package/luam.mjs +395 -62
- package/package.json +1 -1
- package/template/README.md +0 -120
- package/template/config.lua +0 -14
- package/template/env +0 -11
- package/template/framework/bootstrap-client.luam +0 -9
- package/template/framework/bootstrap-server.luam +0 -9
- package/template/framework/command.luam +0 -54
- package/template/framework/core.luam +0 -58
- package/template/framework/event.luam +0 -39
- package/template/framework/listener.luam +0 -40
- package/template/framework/loader.luam +0 -73
- package/template/framework/thread-pool.luam +0 -27
- package/template/gitignore +0 -7
- package/template/src/client/commands/ping-command.luam +0 -8
- package/template/src/client/main.luam +0 -5
- package/template/src/server/commands/hello-command.luam +0 -9
- package/template/src/server/handlers/player-join-listener.luam +0 -9
- package/template/src/server/main.luam +0 -11
- package/template/src/shared/config.luam +0 -5
- package/template/src/shared/framework/command.luam +0 -54
- package/template/src/shared/framework/core.luam +0 -58
- package/template/src/shared/framework/event.luam +0 -39
- package/template/src/shared/framework/listener.luam +0 -40
- package/template/src/shared/framework/loader.luam +0 -73
- package/template/src/shared/framework/thread-pool.luam +0 -27
package/luam.mjs
CHANGED
|
@@ -190,7 +190,7 @@ function parseArguments(argv) {
|
|
|
190
190
|
continue;
|
|
191
191
|
}
|
|
192
192
|
if (parsed.command !== null) {
|
|
193
|
-
if (parsed.command === "trace" && parsed.operand === null) {
|
|
193
|
+
if ((parsed.command === "init" || parsed.command === "trace") && parsed.operand === null) {
|
|
194
194
|
parsed.operand = token;
|
|
195
195
|
continue;
|
|
196
196
|
}
|
|
@@ -212,12 +212,13 @@ var EXIT_DIAGNOSTICS = 1;
|
|
|
212
212
|
var EXIT_USAGE = 2;
|
|
213
213
|
|
|
214
214
|
// src/cli/usage.ts
|
|
215
|
-
var VERSION = true ? "0.
|
|
215
|
+
var VERSION = true ? "0.4.1" : "0.0.0-dev";
|
|
216
216
|
var USAGE = [
|
|
217
217
|
"luam \u2014 the Luam compiler for Multi Theft Auto resources.",
|
|
218
218
|
"",
|
|
219
219
|
"Usage:",
|
|
220
220
|
" luam <command> [options]",
|
|
221
|
+
" luam init [path] [options]",
|
|
221
222
|
"",
|
|
222
223
|
"Commands:",
|
|
223
224
|
" build Compile the project and write the resource to the output directory.",
|
|
@@ -225,16 +226,16 @@ var USAGE = [
|
|
|
225
226
|
" dev Build, sync, restart, watch, and stream resource development logs.",
|
|
226
227
|
" doctor Check the CLI, supported editors, and the Luam extension.",
|
|
227
228
|
" ensure Build, sync the resource into the MTA server, and restart it.",
|
|
228
|
-
" init
|
|
229
|
+
" init Interactively scaffold a new resource project.",
|
|
229
230
|
" setup Detect supported editors and install the Luam extension with consent.",
|
|
230
231
|
" trace Resolve generated Lua positions back to Luam source positions.",
|
|
231
232
|
"",
|
|
232
233
|
"Options:",
|
|
233
234
|
" --cwd <path> Project directory that contains luam.json. Defaults to the current directory.",
|
|
234
235
|
" --config <path> Configuration file to load instead of luam.json.",
|
|
235
|
-
" --name <name> Resource name for init. Defaults to the
|
|
236
|
+
" --name <name> Resource name for init. Defaults to the destination directory name.",
|
|
236
237
|
" --force Let init overwrite files that already exist.",
|
|
237
|
-
" -y, --yes
|
|
238
|
+
" -y, --yes Accept init defaults or install every detected editor without prompting.",
|
|
238
239
|
" --watch Keep ensure or dev running and rebuild on source changes.",
|
|
239
240
|
" --no-watch Run ensure or dev once and exit.",
|
|
240
241
|
" --bundle Build or ensure with bundled output.",
|
|
@@ -916,6 +917,15 @@ function createNamed(name) {
|
|
|
916
917
|
function createRecord(name, members, origin = null) {
|
|
917
918
|
return { kind: "record", name, origin, members };
|
|
918
919
|
}
|
|
920
|
+
function renameRecord(type, name) {
|
|
921
|
+
return type.kind === "record" ? createRecord(name, type.members, type.origin) : type;
|
|
922
|
+
}
|
|
923
|
+
function createObjectType(members) {
|
|
924
|
+
const keys = [...members].map(
|
|
925
|
+
([name, member]) => member.kind === "optional" ? `${name}?: ${typeToString(member.element)}` : `${name}: ${typeToString(member)}`
|
|
926
|
+
);
|
|
927
|
+
return createRecord(keys.length === 0 ? "{}" : `{ ${keys.join(", ")} }`, members);
|
|
928
|
+
}
|
|
919
929
|
function createFunction(parameters, returnType, minimumArguments2, isVariadic = false) {
|
|
920
930
|
return { kind: "function", parameters, returnType, minimumArguments: minimumArguments2 ?? parameters.length, isVariadic };
|
|
921
931
|
}
|
|
@@ -1022,6 +1032,21 @@ function isFunctionAssignable(source, target, options) {
|
|
|
1022
1032
|
}
|
|
1023
1033
|
return target.returnType.kind === "void" || isAssignable(source.returnType, target.returnType, options);
|
|
1024
1034
|
}
|
|
1035
|
+
function isRecordAssignable(source, target, options) {
|
|
1036
|
+
for (const [name, member] of target.members) {
|
|
1037
|
+
const value = source.members.get(name);
|
|
1038
|
+
if (value === void 0) {
|
|
1039
|
+
if (member.kind !== "optional") {
|
|
1040
|
+
return false;
|
|
1041
|
+
}
|
|
1042
|
+
continue;
|
|
1043
|
+
}
|
|
1044
|
+
if (!isAssignable(value, member, options)) {
|
|
1045
|
+
return false;
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
return true;
|
|
1049
|
+
}
|
|
1025
1050
|
function isAssignable(source, target, options = { allowNil: false }) {
|
|
1026
1051
|
if (source.kind === "any" || target.kind === "any" || target.kind === "unknown") {
|
|
1027
1052
|
return true;
|
|
@@ -1053,6 +1078,9 @@ function isAssignable(source, target, options = { allowNil: false }) {
|
|
|
1053
1078
|
if (target.kind === "table") {
|
|
1054
1079
|
return isTableLike(source) || source.kind === "record";
|
|
1055
1080
|
}
|
|
1081
|
+
if (target.kind === "record") {
|
|
1082
|
+
return source.kind === "record" ? isRecordAssignable(source, target, options) : source.kind === "table";
|
|
1083
|
+
}
|
|
1056
1084
|
if (target.kind === "array") {
|
|
1057
1085
|
return source.kind === "table" || source.kind === "array" && isAssignable(source.element, target.element, options);
|
|
1058
1086
|
}
|
|
@@ -1137,8 +1165,8 @@ var Binder = class {
|
|
|
1137
1165
|
isDeclaredInCurrentScope(name) {
|
|
1138
1166
|
return this.scopes[this.scopes.length - 1]?.has(name) ?? false;
|
|
1139
1167
|
}
|
|
1140
|
-
declareAlias(name, type) {
|
|
1141
|
-
this.aliases.set(name, type);
|
|
1168
|
+
declareAlias(name, type, parameters = []) {
|
|
1169
|
+
this.aliases.set(name, { type, parameters });
|
|
1142
1170
|
}
|
|
1143
1171
|
lookupAlias(name) {
|
|
1144
1172
|
return this.aliases.get(name) ?? null;
|
|
@@ -4831,6 +4859,14 @@ var DeclarationRegistry = class {
|
|
|
4831
4859
|
lookupInterface(name) {
|
|
4832
4860
|
return this.interfaces.get(name) ?? null;
|
|
4833
4861
|
}
|
|
4862
|
+
interfaceExtends(name, target, visited = /* @__PURE__ */ new Set()) {
|
|
4863
|
+
const current = this.lookupInterface(name);
|
|
4864
|
+
if (current === null || visited.has(name)) {
|
|
4865
|
+
return false;
|
|
4866
|
+
}
|
|
4867
|
+
visited.add(name);
|
|
4868
|
+
return current.superInterfaces.some((parent) => parent === target || this.interfaceExtends(parent, target, visited));
|
|
4869
|
+
}
|
|
4834
4870
|
declareEnum(info) {
|
|
4835
4871
|
this.enums.set(info.name, info);
|
|
4836
4872
|
}
|
|
@@ -4846,24 +4882,42 @@ var DeclarationRegistry = class {
|
|
|
4846
4882
|
allEnums() {
|
|
4847
4883
|
return [...this.enums.values()];
|
|
4848
4884
|
}
|
|
4849
|
-
|
|
4885
|
+
collectInterfaceMembers(name, collected, visited) {
|
|
4886
|
+
const current = this.lookupInterface(name);
|
|
4887
|
+
if (current === null || visited.has(current.name)) {
|
|
4888
|
+
return;
|
|
4889
|
+
}
|
|
4890
|
+
visited.add(current.name);
|
|
4891
|
+
for (const [memberName, member] of current.members) {
|
|
4892
|
+
if (!collected.has(memberName)) {
|
|
4893
|
+
collected.set(memberName, member);
|
|
4894
|
+
}
|
|
4895
|
+
}
|
|
4896
|
+
for (const parent of current.superInterfaces) {
|
|
4897
|
+
this.collectInterfaceMembers(parent, collected, visited);
|
|
4898
|
+
}
|
|
4899
|
+
}
|
|
4900
|
+
collectMembers(name) {
|
|
4850
4901
|
const collected = /* @__PURE__ */ new Map();
|
|
4851
4902
|
const visited = /* @__PURE__ */ new Set();
|
|
4852
|
-
let current = this.lookupClass(
|
|
4903
|
+
let current = this.lookupClass(name);
|
|
4853
4904
|
while (current !== null && !visited.has(current.name)) {
|
|
4854
|
-
for (const [
|
|
4855
|
-
if (!collected.has(
|
|
4856
|
-
collected.set(
|
|
4905
|
+
for (const [name2, member] of current.members) {
|
|
4906
|
+
if (!collected.has(name2)) {
|
|
4907
|
+
collected.set(name2, member);
|
|
4857
4908
|
}
|
|
4858
4909
|
}
|
|
4859
4910
|
visited.add(current.name);
|
|
4860
4911
|
current = current.superClass === null ? null : this.lookupClass(current.superClass);
|
|
4861
4912
|
}
|
|
4913
|
+
if (current === null && this.lookupClass(name) === null) {
|
|
4914
|
+
this.collectInterfaceMembers(name, collected, visited);
|
|
4915
|
+
}
|
|
4862
4916
|
return [...collected.values()];
|
|
4863
4917
|
}
|
|
4864
|
-
lookupMember(
|
|
4918
|
+
lookupMember(name, member) {
|
|
4865
4919
|
const visited = /* @__PURE__ */ new Set();
|
|
4866
|
-
let current = this.lookupClass(
|
|
4920
|
+
let current = this.lookupClass(name);
|
|
4867
4921
|
while (current !== null && !visited.has(current.name)) {
|
|
4868
4922
|
const found = current.members.get(member);
|
|
4869
4923
|
if (found !== void 0) {
|
|
@@ -4872,7 +4926,9 @@ var DeclarationRegistry = class {
|
|
|
4872
4926
|
visited.add(current.name);
|
|
4873
4927
|
current = current.superClass === null ? null : this.lookupClass(current.superClass);
|
|
4874
4928
|
}
|
|
4875
|
-
|
|
4929
|
+
const collected = /* @__PURE__ */ new Map();
|
|
4930
|
+
this.collectInterfaceMembers(name, collected, visited);
|
|
4931
|
+
return collected.get(member) ?? null;
|
|
4876
4932
|
}
|
|
4877
4933
|
};
|
|
4878
4934
|
|
|
@@ -4950,6 +5006,35 @@ function mtaMember(className, member) {
|
|
|
4950
5006
|
return mtaClassRegistry().lookupMember(className, member);
|
|
4951
5007
|
}
|
|
4952
5008
|
|
|
5009
|
+
// ../compiler/src/checker/type-substitution.ts
|
|
5010
|
+
function substituteType(type, substitutions) {
|
|
5011
|
+
if (type.kind === "named") {
|
|
5012
|
+
return substitutions.get(type.name) ?? type;
|
|
5013
|
+
}
|
|
5014
|
+
if (type.kind === "array") {
|
|
5015
|
+
return createArray(substituteType(type.element, substitutions));
|
|
5016
|
+
}
|
|
5017
|
+
if (type.kind === "optional") {
|
|
5018
|
+
return createOptional(substituteType(type.element, substitutions));
|
|
5019
|
+
}
|
|
5020
|
+
if (type.kind === "union") {
|
|
5021
|
+
return createUnion(type.options.map((option) => substituteType(option, substitutions)));
|
|
5022
|
+
}
|
|
5023
|
+
if (type.kind === "function") {
|
|
5024
|
+
const parameters = type.parameters.map((parameter) => substituteType(parameter, substitutions));
|
|
5025
|
+
const returnType = substituteType(type.returnType, substitutions);
|
|
5026
|
+
return createFunction(parameters, returnType, type.minimumArguments, type.isVariadic);
|
|
5027
|
+
}
|
|
5028
|
+
if (type.kind === "tuple") {
|
|
5029
|
+
return createTuple(type.elements.map((element) => substituteType(element, substitutions)));
|
|
5030
|
+
}
|
|
5031
|
+
if (type.kind === "record") {
|
|
5032
|
+
const members = new Map([...type.members].map(([name, member]) => [name, substituteType(member, substitutions)]));
|
|
5033
|
+
return createRecord(type.name, members, type.origin);
|
|
5034
|
+
}
|
|
5035
|
+
return type;
|
|
5036
|
+
}
|
|
5037
|
+
|
|
4953
5038
|
// ../compiler/src/checker/context.ts
|
|
4954
5039
|
var BUILTIN_TYPES = {
|
|
4955
5040
|
any: ANY_TYPE,
|
|
@@ -4987,6 +5072,7 @@ var CheckContext = class {
|
|
|
4987
5072
|
methodStack = [];
|
|
4988
5073
|
projectEnvironments = /* @__PURE__ */ new Map();
|
|
4989
5074
|
ambientClasses = /* @__PURE__ */ new Set();
|
|
5075
|
+
ambientInterfaces = /* @__PURE__ */ new Set();
|
|
4990
5076
|
constructor(mode, environment, ambient = EMPTY_AMBIENT, project = EMPTY_PROJECT_DECLARATIONS, oop = false) {
|
|
4991
5077
|
this.mode = mode;
|
|
4992
5078
|
this.environment = environment;
|
|
@@ -5008,6 +5094,9 @@ var CheckContext = class {
|
|
|
5008
5094
|
isAmbientClass(name) {
|
|
5009
5095
|
return this.ambientClasses.has(name);
|
|
5010
5096
|
}
|
|
5097
|
+
isAmbientInterface(name) {
|
|
5098
|
+
return this.ambientInterfaces.has(name);
|
|
5099
|
+
}
|
|
5011
5100
|
noteExternalReference(name, position2) {
|
|
5012
5101
|
if (!this.externalReferences.has(name)) {
|
|
5013
5102
|
this.externalReferences.set(name, position2);
|
|
@@ -5027,13 +5116,32 @@ var CheckContext = class {
|
|
|
5027
5116
|
return this.types.get(expression) ?? UNKNOWN_TYPE;
|
|
5028
5117
|
}
|
|
5029
5118
|
pushReturnType(type) {
|
|
5030
|
-
this.returnStack.push(type);
|
|
5119
|
+
this.returnStack.push({ expected: type, inferred: [] });
|
|
5031
5120
|
}
|
|
5032
5121
|
popReturnType() {
|
|
5033
|
-
this.returnStack.pop();
|
|
5122
|
+
const frame = this.returnStack.pop();
|
|
5123
|
+
if (frame === void 0 || frame.inferred.length === 0) {
|
|
5124
|
+
return VOID_TYPE;
|
|
5125
|
+
}
|
|
5126
|
+
const tuples = frame.inferred.filter((type) => type.kind === "tuple");
|
|
5127
|
+
const tupleSize = tuples[0]?.kind === "tuple" ? tuples[0].elements.length : null;
|
|
5128
|
+
const sameTupleSize = tuples.length === frame.inferred.length && tuples.every((type) => type.kind === "tuple" && type.elements.length === tupleSize);
|
|
5129
|
+
if (sameTupleSize) {
|
|
5130
|
+
const first = tuples[0];
|
|
5131
|
+
if (first?.kind === "tuple") {
|
|
5132
|
+
const elements = first.elements.map(
|
|
5133
|
+
(_, index) => createUnion(tuples.map((type) => type.kind === "tuple" ? type.elements[index] ?? ANY_TYPE : ANY_TYPE))
|
|
5134
|
+
);
|
|
5135
|
+
return createTuple(elements);
|
|
5136
|
+
}
|
|
5137
|
+
}
|
|
5138
|
+
return createUnion(frame.inferred);
|
|
5034
5139
|
}
|
|
5035
5140
|
currentReturnType() {
|
|
5036
|
-
return this.returnStack[this.returnStack.length - 1] ?? null;
|
|
5141
|
+
return this.returnStack[this.returnStack.length - 1]?.expected ?? null;
|
|
5142
|
+
}
|
|
5143
|
+
inferReturnType(type) {
|
|
5144
|
+
this.returnStack[this.returnStack.length - 1]?.inferred.push(type);
|
|
5037
5145
|
}
|
|
5038
5146
|
pushClassMethod(frame) {
|
|
5039
5147
|
this.methodStack.push(frame);
|
|
@@ -5057,13 +5165,23 @@ var CheckContext = class {
|
|
|
5057
5165
|
if (annotation.kind === "type-union") {
|
|
5058
5166
|
return createUnion(annotation.options.map((option) => this.resolveAnnotation(option)));
|
|
5059
5167
|
}
|
|
5168
|
+
if (annotation.kind === "type-object") {
|
|
5169
|
+
const members = /* @__PURE__ */ new Map();
|
|
5170
|
+
for (const member of annotation.members) {
|
|
5171
|
+
members.set(member.name, this.resolveAnnotation(member.annotation));
|
|
5172
|
+
}
|
|
5173
|
+
return createObjectType(members);
|
|
5174
|
+
}
|
|
5175
|
+
if (annotation.kind === "type-tuple") {
|
|
5176
|
+
return createTuple(annotation.elements.map((element) => this.resolveAnnotation(element)));
|
|
5177
|
+
}
|
|
5060
5178
|
if (annotation.kind === "type-function") {
|
|
5061
5179
|
const parameters = annotation.parameters.map((parameter) => this.resolveAnnotation(parameter));
|
|
5062
5180
|
const optional = parameters.findIndex((parameter) => parameter.kind === "optional");
|
|
5063
5181
|
const minimum = optional === -1 ? parameters.length : optional;
|
|
5064
5182
|
return createFunction(parameters, this.resolveAnnotation(annotation.returnType), minimum, annotation.isVariadic);
|
|
5065
5183
|
}
|
|
5066
|
-
return this.resolveNamedAnnotation(annotation.name);
|
|
5184
|
+
return this.resolveNamedAnnotation(annotation.name, annotation.typeArguments, annotation.position);
|
|
5067
5185
|
}
|
|
5068
5186
|
expectAssignable(source, target, position2, subject) {
|
|
5069
5187
|
if (isAssignable(source, target, { allowNil: this.allowNil })) {
|
|
@@ -5088,6 +5206,7 @@ var CheckContext = class {
|
|
|
5088
5206
|
this.binder.declareGlobal({ name: info.name, type: createNamed(info.name), isLocal: false, position: info.position });
|
|
5089
5207
|
}
|
|
5090
5208
|
for (const info of ambient.interfaces) {
|
|
5209
|
+
this.ambientInterfaces.add(info.name);
|
|
5091
5210
|
this.declarations.declareInterface(info);
|
|
5092
5211
|
}
|
|
5093
5212
|
for (const info of ambient.enums) {
|
|
@@ -5098,12 +5217,25 @@ var CheckContext = class {
|
|
|
5098
5217
|
this.binder.declareGlobal({ name: info.name, type: info.type, isLocal: false, position: info.position });
|
|
5099
5218
|
}
|
|
5100
5219
|
}
|
|
5101
|
-
resolveNamedAnnotation(name) {
|
|
5220
|
+
resolveNamedAnnotation(name, typeArguments, position2) {
|
|
5102
5221
|
const builtin = BUILTIN_TYPES[name];
|
|
5103
5222
|
if (builtin !== void 0) {
|
|
5223
|
+
if (typeArguments.length > 0) {
|
|
5224
|
+
this.report("check-generic-arity", `Type "${name}" does not accept type arguments.`, position2);
|
|
5225
|
+
}
|
|
5104
5226
|
return builtin;
|
|
5105
5227
|
}
|
|
5106
|
-
|
|
5228
|
+
const alias = this.binder.lookupAlias(name);
|
|
5229
|
+
if (alias === null) {
|
|
5230
|
+
return createNamed(name);
|
|
5231
|
+
}
|
|
5232
|
+
if (typeArguments.length !== alias.parameters.length) {
|
|
5233
|
+
const expected = alias.parameters.length === 1 ? "1 type argument" : `${alias.parameters.length} type arguments`;
|
|
5234
|
+
this.report("check-generic-arity", `Type "${name}" expects ${expected} but received ${typeArguments.length}.`, position2);
|
|
5235
|
+
}
|
|
5236
|
+
const resolved = typeArguments.map((argument) => this.resolveAnnotation(argument));
|
|
5237
|
+
const substitutions = new Map(alias.parameters.map((parameter, index) => [parameter, resolved[index] ?? ANY_TYPE]));
|
|
5238
|
+
return substituteType(alias.type, substitutions);
|
|
5107
5239
|
}
|
|
5108
5240
|
};
|
|
5109
5241
|
|
|
@@ -5387,7 +5519,7 @@ function expandClassDecorators(context, statement, fieldTypes) {
|
|
|
5387
5519
|
const occupied = new Map(statement.members.filter((member) => member.kind === "class-method").map((member) => [member.name, member.name]));
|
|
5388
5520
|
const classDecorators = validateDecorators(context, statement.decorators, "class");
|
|
5389
5521
|
for (const member of statement.members) {
|
|
5390
|
-
if (member.kind === "class-method") {
|
|
5522
|
+
if (member.kind === "class-method" || member.name === "constructor") {
|
|
5391
5523
|
validateDecorators(context, member.decorators, "method");
|
|
5392
5524
|
continue;
|
|
5393
5525
|
}
|
|
@@ -5969,7 +6101,11 @@ function resolveNamedMember(context, name, expression) {
|
|
|
5969
6101
|
return resolveMtaMember(context, name, expression.property, expression.position)?.type ?? ANY_TYPE;
|
|
5970
6102
|
}
|
|
5971
6103
|
const contract = context.declarations.lookupClass(name) === null ? context.declarations.lookupInterface(name) : null;
|
|
5972
|
-
|
|
6104
|
+
if (contract === null) {
|
|
6105
|
+
return null;
|
|
6106
|
+
}
|
|
6107
|
+
const members = new Map(context.declarations.collectMembers(name).map((member2) => [member2.name, member2]));
|
|
6108
|
+
return checkInterfaceMember(context, name, members, expression);
|
|
5973
6109
|
}
|
|
5974
6110
|
var NATIVE_CONSTRUCTOR = "new";
|
|
5975
6111
|
function nativeConstructor(context, name) {
|
|
@@ -6273,7 +6409,7 @@ function checkMultiValueExpression(context, expression) {
|
|
|
6273
6409
|
return checkTable(context, expression);
|
|
6274
6410
|
case "function-expression": {
|
|
6275
6411
|
const type = buildFunctionType(context, expression.parameters, expression.returnAnnotation);
|
|
6276
|
-
checkFunctionBody(context, expression.parameters, expression.returnAnnotation, expression.body, null);
|
|
6412
|
+
checkFunctionBody(context, expression.parameters, expression.returnAnnotation, expression.body, type, null);
|
|
6277
6413
|
return context.record(expression, type);
|
|
6278
6414
|
}
|
|
6279
6415
|
case "binary-expression": {
|
|
@@ -6334,8 +6470,16 @@ function registerMembers(context, info, statement) {
|
|
|
6334
6470
|
return generated;
|
|
6335
6471
|
}
|
|
6336
6472
|
function checkMethodBody(context, info, member) {
|
|
6473
|
+
const explicitSelf = member.parameters.find((parameter) => parameter.name === "self");
|
|
6474
|
+
if (explicitSelf !== void 0) {
|
|
6475
|
+
context.report("check-explicit-self-parameter", 'Class methods receive "self" automatically; remove it from the parameter list.', explicitSelf.position);
|
|
6476
|
+
}
|
|
6477
|
+
const signature = info.members.get(member.name)?.type;
|
|
6478
|
+
if (signature?.kind !== "function") {
|
|
6479
|
+
return;
|
|
6480
|
+
}
|
|
6337
6481
|
context.pushClassMethod({ className: info.name, methodName: member.name });
|
|
6338
|
-
checkFunctionBody(context, member.parameters, member.returnAnnotation, member.body, createNamed(info.name));
|
|
6482
|
+
checkFunctionBody(context, member.parameters, member.returnAnnotation, member.body, signature, createNamed(info.name));
|
|
6339
6483
|
context.popClassMethod();
|
|
6340
6484
|
}
|
|
6341
6485
|
function checkContract(context, info, contract, member) {
|
|
@@ -6360,7 +6504,7 @@ function checkInterfaces(context, info) {
|
|
|
6360
6504
|
context.report("check-unknown-interface", `Class "${info.name}" implements "${name}", which is not defined.`, info.position);
|
|
6361
6505
|
continue;
|
|
6362
6506
|
}
|
|
6363
|
-
for (const member of
|
|
6507
|
+
for (const member of context.declarations.collectMembers(contract.name)) {
|
|
6364
6508
|
checkContract(context, info, name, member);
|
|
6365
6509
|
}
|
|
6366
6510
|
}
|
|
@@ -6418,11 +6562,46 @@ function checkInterfaceDeclaration(context, statement) {
|
|
|
6418
6562
|
return;
|
|
6419
6563
|
}
|
|
6420
6564
|
const members = /* @__PURE__ */ new Map();
|
|
6565
|
+
for (const name of new Set(statement.superInterfaces)) {
|
|
6566
|
+
const parent = context.declarations.lookupInterface(name);
|
|
6567
|
+
if (name === statement.name || context.declarations.interfaceExtends(name, statement.name)) {
|
|
6568
|
+
context.report("check-interface-cycle", `Interface "${statement.name}" creates an inheritance cycle through "${name}".`, statement.position);
|
|
6569
|
+
} else if (parent === null) {
|
|
6570
|
+
context.noteExternalReference(name, statement.position);
|
|
6571
|
+
context.report("check-unknown-interface", `Interface "${statement.name}" extends "${name}", which is not defined.`, statement.position);
|
|
6572
|
+
} else if (context.isAmbientInterface(name)) {
|
|
6573
|
+
context.noteExternalReference(name, statement.position);
|
|
6574
|
+
}
|
|
6575
|
+
}
|
|
6576
|
+
if (new Set(statement.superInterfaces).size !== statement.superInterfaces.length) {
|
|
6577
|
+
context.report("check-duplicate-interface-parent", `Interface "${statement.name}" extends the same interface more than once.`, statement.position);
|
|
6578
|
+
}
|
|
6421
6579
|
for (const member of statement.members) {
|
|
6422
6580
|
const type = member.kind === "interface-field" ? context.resolveAnnotation(member.annotation) : buildFunctionType(context, member.parameters, member.returnAnnotation);
|
|
6423
|
-
|
|
6581
|
+
const info = { name: member.name, type, isMethod: member.kind === "interface-method", position: member.position };
|
|
6582
|
+
if (members.has(member.name)) {
|
|
6583
|
+
context.report("check-duplicate-interface-member", `Interface "${statement.name}" declares member "${member.name}" more than once.`, member.position);
|
|
6584
|
+
} else {
|
|
6585
|
+
members.set(member.name, info);
|
|
6586
|
+
}
|
|
6424
6587
|
}
|
|
6425
|
-
|
|
6588
|
+
const inherited = /* @__PURE__ */ new Map();
|
|
6589
|
+
for (const parent of statement.superInterfaces) {
|
|
6590
|
+
for (const member of context.declarations.collectMembers(parent)) {
|
|
6591
|
+
const existing = members.get(member.name) ?? inherited.get(member.name);
|
|
6592
|
+
if (existing !== void 0 && (existing.isMethod !== member.isMethod || typeToString(existing.type) !== typeToString(member.type))) {
|
|
6593
|
+
context.report("check-conflicting-interface-member", `Interface "${statement.name}" inherits conflicting declarations of "${member.name}".`, statement.position);
|
|
6594
|
+
} else if (existing === void 0) {
|
|
6595
|
+
inherited.set(member.name, member);
|
|
6596
|
+
}
|
|
6597
|
+
}
|
|
6598
|
+
}
|
|
6599
|
+
context.declarations.declareInterface({
|
|
6600
|
+
name: statement.name,
|
|
6601
|
+
superInterfaces: statement.superInterfaces,
|
|
6602
|
+
members,
|
|
6603
|
+
position: statement.position
|
|
6604
|
+
});
|
|
6426
6605
|
}
|
|
6427
6606
|
function checkEnumDeclaration(context, statement) {
|
|
6428
6607
|
if (context.declarations.lookupEnum(statement.name) !== null) {
|
|
@@ -6487,9 +6666,9 @@ function buildFunctionType(context, parameters, returnAnnotation) {
|
|
|
6487
6666
|
const types = parameters.filter((parameter) => !parameter.isVararg).map((parameter) => context.resolveAnnotation(parameter.annotation));
|
|
6488
6667
|
return createFunction(types, context.resolveAnnotation(returnAnnotation), minimumArguments(context, parameters), isVariadic);
|
|
6489
6668
|
}
|
|
6490
|
-
function checkFunctionBody(context, parameters, returnAnnotation, body, selfType) {
|
|
6669
|
+
function checkFunctionBody(context, parameters, returnAnnotation, body, signature, selfType) {
|
|
6491
6670
|
context.binder.pushScope();
|
|
6492
|
-
context.pushReturnType(context.resolveAnnotation(returnAnnotation));
|
|
6671
|
+
context.pushReturnType(returnAnnotation === null ? null : context.resolveAnnotation(returnAnnotation));
|
|
6493
6672
|
if (selfType !== null) {
|
|
6494
6673
|
context.binder.declare({ name: "self", type: selfType, isLocal: true, position: ORIGIN });
|
|
6495
6674
|
}
|
|
@@ -6498,7 +6677,10 @@ function checkFunctionBody(context, parameters, returnAnnotation, body, selfType
|
|
|
6498
6677
|
context.binder.declare({ name: parameter.name, type, isLocal: true, position: parameter.position });
|
|
6499
6678
|
}
|
|
6500
6679
|
checkStatements(context, body);
|
|
6501
|
-
context.popReturnType();
|
|
6680
|
+
const inferred = context.popReturnType();
|
|
6681
|
+
if (returnAnnotation === null) {
|
|
6682
|
+
signature.returnType = inferred;
|
|
6683
|
+
}
|
|
6502
6684
|
context.binder.popScope();
|
|
6503
6685
|
}
|
|
6504
6686
|
function valueAt(values, valueTypes, index) {
|
|
@@ -6568,12 +6750,16 @@ function checkFunctionDeclaration(context, statement) {
|
|
|
6568
6750
|
}
|
|
6569
6751
|
}
|
|
6570
6752
|
context.record(statement.name, type);
|
|
6571
|
-
checkFunctionBody(context, statement.parameters, statement.returnAnnotation, statement.body, statement.isMethod ? ANY_TYPE : null);
|
|
6753
|
+
checkFunctionBody(context, statement.parameters, statement.returnAnnotation, statement.body, type, statement.isMethod ? ANY_TYPE : null);
|
|
6572
6754
|
}
|
|
6573
6755
|
function checkReturn(context, statement) {
|
|
6574
6756
|
const valueTypes = checkValueList(context, statement.values);
|
|
6575
6757
|
const expected = context.currentReturnType();
|
|
6576
|
-
if (expected === null
|
|
6758
|
+
if (expected === null) {
|
|
6759
|
+
context.inferReturnType(createTuple(valueTypes));
|
|
6760
|
+
return;
|
|
6761
|
+
}
|
|
6762
|
+
if (expected.kind === "any") {
|
|
6577
6763
|
return;
|
|
6578
6764
|
}
|
|
6579
6765
|
if (expected.kind === "void") {
|
|
@@ -6582,6 +6768,23 @@ function checkReturn(context, statement) {
|
|
|
6582
6768
|
}
|
|
6583
6769
|
return;
|
|
6584
6770
|
}
|
|
6771
|
+
if (expected.kind === "tuple") {
|
|
6772
|
+
if (valueTypes.length !== expected.elements.length) {
|
|
6773
|
+
const message = `This function declares ${expected.elements.length} return values but returns ${valueTypes.length}.`;
|
|
6774
|
+
context.report("check-return-mismatch", message, statement.position);
|
|
6775
|
+
return;
|
|
6776
|
+
}
|
|
6777
|
+
valueTypes.forEach((type, index) => {
|
|
6778
|
+
const value2 = statement.values[Math.min(index, statement.values.length - 1)];
|
|
6779
|
+
context.expectAssignable(type, expected.elements[index] ?? ANY_TYPE, value2?.position ?? statement.position, `Return value ${index + 1}`);
|
|
6780
|
+
});
|
|
6781
|
+
return;
|
|
6782
|
+
}
|
|
6783
|
+
if (valueTypes.length > 1) {
|
|
6784
|
+
const message = `This function declares one return value of type "${typeToString(expected)}" but returns ${valueTypes.length} values.`;
|
|
6785
|
+
context.report("check-return-mismatch", message, statement.position);
|
|
6786
|
+
return;
|
|
6787
|
+
}
|
|
6585
6788
|
const first = valueTypes[0];
|
|
6586
6789
|
const value = statement.values[0];
|
|
6587
6790
|
if (first === void 0 || value === void 0) {
|
|
@@ -6628,9 +6831,12 @@ function checkStatement(context, statement) {
|
|
|
6628
6831
|
return checkNumericFor(context, statement);
|
|
6629
6832
|
case "generic-for-statement":
|
|
6630
6833
|
return checkGenericFor(context, statement);
|
|
6631
|
-
case "type-alias-statement":
|
|
6632
|
-
|
|
6834
|
+
case "type-alias-statement": {
|
|
6835
|
+
const resolved = context.resolveAnnotation(statement.annotation);
|
|
6836
|
+
const named2 = statement.annotation.kind === "type-object" ? renameRecord(resolved, statement.name) : resolved;
|
|
6837
|
+
context.binder.declareAlias(statement.name, named2, statement.typeParameters);
|
|
6633
6838
|
return;
|
|
6839
|
+
}
|
|
6634
6840
|
case "declare-statement":
|
|
6635
6841
|
return checkDeclareStatement(context, statement);
|
|
6636
6842
|
case "class-declaration":
|
|
@@ -7781,13 +7987,45 @@ function parseFunctionType(stream) {
|
|
|
7781
7987
|
return { kind: "type-function", parameters, isVariadic, returnType, position: position2 };
|
|
7782
7988
|
}
|
|
7783
7989
|
function parseGroupedType(stream) {
|
|
7784
|
-
stream.expect("punctuation", "(");
|
|
7990
|
+
const position2 = stream.expect("punctuation", "(").position;
|
|
7785
7991
|
const inner = parseTypeAnnotation(stream);
|
|
7786
|
-
if (stream.
|
|
7787
|
-
|
|
7992
|
+
if (!stream.match("punctuation", ",")) {
|
|
7993
|
+
stream.expect("punctuation", ")");
|
|
7994
|
+
return inner;
|
|
7788
7995
|
}
|
|
7996
|
+
const elements = [inner];
|
|
7997
|
+
do {
|
|
7998
|
+
elements.push(parseTypeAnnotation(stream));
|
|
7999
|
+
} while (stream.match("punctuation", ","));
|
|
7789
8000
|
stream.expect("punctuation", ")");
|
|
7790
|
-
return
|
|
8001
|
+
return { kind: "type-tuple", elements, position: position2 };
|
|
8002
|
+
}
|
|
8003
|
+
function parseObjectMember(stream) {
|
|
8004
|
+
const token = stream.expectName();
|
|
8005
|
+
const annotation = parseNamedAnnotation(stream, "field");
|
|
8006
|
+
if (annotation === null) {
|
|
8007
|
+
throw stream.error(`Key "${token.value}" of an object type requires a type annotation.`, "parse-invalid-type");
|
|
8008
|
+
}
|
|
8009
|
+
return { name: token.value, annotation, position: token.position };
|
|
8010
|
+
}
|
|
8011
|
+
function parseObjectType(stream) {
|
|
8012
|
+
const position2 = stream.expect("punctuation", "{").position;
|
|
8013
|
+
const members = [];
|
|
8014
|
+
const declared = /* @__PURE__ */ new Set();
|
|
8015
|
+
while (!stream.check("punctuation", "}") && !stream.isEof()) {
|
|
8016
|
+
if (stream.match("punctuation", ";") || stream.match("punctuation", ",")) {
|
|
8017
|
+
continue;
|
|
8018
|
+
}
|
|
8019
|
+
const member = parseObjectMember(stream);
|
|
8020
|
+
if (declared.has(member.name)) {
|
|
8021
|
+
stream.report("parse-duplicate-key", `Object type key "${member.name}" is declared more than once.`, member.position);
|
|
8022
|
+
continue;
|
|
8023
|
+
}
|
|
8024
|
+
declared.add(member.name);
|
|
8025
|
+
members.push(member);
|
|
8026
|
+
}
|
|
8027
|
+
stream.expect("punctuation", "}");
|
|
8028
|
+
return { kind: "type-object", members, position: position2 };
|
|
7791
8029
|
}
|
|
7792
8030
|
function parsePrimaryType(stream) {
|
|
7793
8031
|
if (stream.check("keyword", "function")) {
|
|
@@ -7796,6 +8034,9 @@ function parsePrimaryType(stream) {
|
|
|
7796
8034
|
if (stream.check("identifier", FUNCTION_TYPE)) {
|
|
7797
8035
|
return parseFunctionType(stream);
|
|
7798
8036
|
}
|
|
8037
|
+
if (stream.check("punctuation", "{")) {
|
|
8038
|
+
return parseObjectType(stream);
|
|
8039
|
+
}
|
|
7799
8040
|
return stream.check("punctuation", "(") ? parseGroupedType(stream) : parseTypeName(stream);
|
|
7800
8041
|
}
|
|
7801
8042
|
function parsePostfixType(stream) {
|
|
@@ -8125,16 +8366,24 @@ function parseForStatement(stream) {
|
|
|
8125
8366
|
}
|
|
8126
8367
|
|
|
8127
8368
|
// ../compiler/src/parser/recovery.ts
|
|
8128
|
-
|
|
8369
|
+
var BRACE_TERMINATORS = /* @__PURE__ */ new Set(["}"]);
|
|
8370
|
+
function isTerminator(stream, terminators) {
|
|
8371
|
+
const token = stream.current();
|
|
8372
|
+
if (token.kind !== "punctuation" && token.kind !== "keyword") {
|
|
8373
|
+
return false;
|
|
8374
|
+
}
|
|
8375
|
+
return terminators.has(token.value);
|
|
8376
|
+
}
|
|
8377
|
+
function recoverInBlock(stream, error, terminators = BRACE_TERMINATORS) {
|
|
8129
8378
|
stream.diagnostics.push(error.diagnostic);
|
|
8130
8379
|
if (stream.isEof()) {
|
|
8131
8380
|
return;
|
|
8132
8381
|
}
|
|
8133
8382
|
const start = stream.current();
|
|
8134
|
-
while (!stream.isEof() && !stream
|
|
8383
|
+
while (!stream.isEof() && !isTerminator(stream, terminators) && stream.current().position.line === start.position.line) {
|
|
8135
8384
|
stream.next();
|
|
8136
8385
|
}
|
|
8137
|
-
if (!stream.isEof() && !stream
|
|
8386
|
+
if (!stream.isEof() && !isTerminator(stream, terminators) && stream.current().position.offset === start.position.offset) {
|
|
8138
8387
|
stream.next();
|
|
8139
8388
|
}
|
|
8140
8389
|
}
|
|
@@ -8244,7 +8493,10 @@ function isDeclarationStart(stream) {
|
|
|
8244
8493
|
if (offset > 0 && token.value !== "class") {
|
|
8245
8494
|
return false;
|
|
8246
8495
|
}
|
|
8247
|
-
|
|
8496
|
+
if (token.value === "class") {
|
|
8497
|
+
return isClassHeader(stream, offset);
|
|
8498
|
+
}
|
|
8499
|
+
return token.value === "interface" ? stream.checkAhead(offset + 2, "punctuation", "{") || stream.checkAhead(offset + 2, "keyword", "extends") : stream.checkAhead(offset + 2, "punctuation", "{");
|
|
8248
8500
|
}
|
|
8249
8501
|
function skipDecoratorArguments(stream) {
|
|
8250
8502
|
let depth = 0;
|
|
@@ -8271,6 +8523,21 @@ function parseDecorators(stream) {
|
|
|
8271
8523
|
return decorators;
|
|
8272
8524
|
}
|
|
8273
8525
|
function parseClassMethod(stream, token, decorators) {
|
|
8526
|
+
stream.expect("operator", "=");
|
|
8527
|
+
const expression = parseFunctionExpression(stream);
|
|
8528
|
+
return {
|
|
8529
|
+
kind: "class-method",
|
|
8530
|
+
name: token.value,
|
|
8531
|
+
isConstructor: token.value === "constructor",
|
|
8532
|
+
isSynthetic: false,
|
|
8533
|
+
parameters: expression.parameters,
|
|
8534
|
+
returnAnnotation: expression.returnAnnotation,
|
|
8535
|
+
body: expression.body,
|
|
8536
|
+
decorators,
|
|
8537
|
+
position: token.position
|
|
8538
|
+
};
|
|
8539
|
+
}
|
|
8540
|
+
function parseLegacyClassMethod(stream, token, decorators) {
|
|
8274
8541
|
const parameters = parseParameters(stream);
|
|
8275
8542
|
const returnAnnotation = parseOptionalAnnotation(stream);
|
|
8276
8543
|
const body = parseBraceBlock(stream);
|
|
@@ -8289,14 +8556,26 @@ function parseClassMethod(stream, token, decorators) {
|
|
|
8289
8556
|
function parseFieldAnnotation(stream) {
|
|
8290
8557
|
return parseNamedAnnotation(stream, "field");
|
|
8291
8558
|
}
|
|
8559
|
+
function expectClassFieldBoundary(stream, token) {
|
|
8560
|
+
const current = stream.current();
|
|
8561
|
+
const delimiter = current.kind === "punctuation" && (current.value === "}" || current.value === ";" || current.value === ",");
|
|
8562
|
+
if (current.kind === "eof" || delimiter || current.position.line > token.position.line) {
|
|
8563
|
+
return;
|
|
8564
|
+
}
|
|
8565
|
+
throw stream.error(`Expected a line break or separator after class member "${token.value}".`, "parse-unexpected-token");
|
|
8566
|
+
}
|
|
8292
8567
|
function parseClassMember(stream) {
|
|
8293
8568
|
const decorators = parseDecorators(stream);
|
|
8294
8569
|
const token = stream.expectName();
|
|
8295
8570
|
if (stream.check("punctuation", "(")) {
|
|
8571
|
+
return parseLegacyClassMethod(stream, token, decorators);
|
|
8572
|
+
}
|
|
8573
|
+
if (stream.check("operator", "=") && stream.checkAhead(1, "keyword", "function")) {
|
|
8296
8574
|
return parseClassMethod(stream, token, decorators);
|
|
8297
8575
|
}
|
|
8298
8576
|
const annotation = parseFieldAnnotation(stream);
|
|
8299
8577
|
const value = stream.match("operator", "=") ? parseExpression(stream) : null;
|
|
8578
|
+
expectClassFieldBoundary(stream, token);
|
|
8300
8579
|
return { kind: "class-field", name: token.value, annotation, value, decorators, position: token.position };
|
|
8301
8580
|
}
|
|
8302
8581
|
function parseClassModifiers(stream, declaration) {
|
|
@@ -8348,7 +8627,13 @@ function parseInterfaceMember(stream) {
|
|
|
8348
8627
|
function parseInterfaceDeclaration(stream) {
|
|
8349
8628
|
const position2 = stream.next().position;
|
|
8350
8629
|
const name = stream.expect("identifier").value;
|
|
8630
|
+
const superInterfaces = [];
|
|
8351
8631
|
const members = [];
|
|
8632
|
+
if (stream.match("keyword", "extends")) {
|
|
8633
|
+
do {
|
|
8634
|
+
superInterfaces.push(stream.expect("identifier").value);
|
|
8635
|
+
} while (stream.match("punctuation", ","));
|
|
8636
|
+
}
|
|
8352
8637
|
stream.expect("punctuation", "{");
|
|
8353
8638
|
while (!stream.check("punctuation", "}") && !stream.isEof()) {
|
|
8354
8639
|
if (stream.match("punctuation", ";") || stream.match("punctuation", ",")) {
|
|
@@ -8364,7 +8649,7 @@ function parseInterfaceDeclaration(stream) {
|
|
|
8364
8649
|
}
|
|
8365
8650
|
}
|
|
8366
8651
|
stream.expect("punctuation", "}");
|
|
8367
|
-
return { kind: "interface-declaration", name, members, position: position2 };
|
|
8652
|
+
return { kind: "interface-declaration", name, superInterfaces, members, position: position2 };
|
|
8368
8653
|
}
|
|
8369
8654
|
function parseEnumDeclaration(stream) {
|
|
8370
8655
|
const position2 = stream.next().position;
|
|
@@ -8564,14 +8849,14 @@ function parseStatement(stream) {
|
|
|
8564
8849
|
}
|
|
8565
8850
|
return parseExpressionStatement(stream);
|
|
8566
8851
|
}
|
|
8567
|
-
function parseBlockStatement(stream) {
|
|
8852
|
+
function parseBlockStatement(stream, terminators = BRACE_TERMINATORS) {
|
|
8568
8853
|
try {
|
|
8569
8854
|
return parseStatement(stream);
|
|
8570
8855
|
} catch (error) {
|
|
8571
8856
|
if (!(error instanceof ParserError)) {
|
|
8572
8857
|
throw error;
|
|
8573
8858
|
}
|
|
8574
|
-
recoverInBlock(stream, error);
|
|
8859
|
+
recoverInBlock(stream, error, terminators);
|
|
8575
8860
|
return null;
|
|
8576
8861
|
}
|
|
8577
8862
|
}
|
|
@@ -8604,7 +8889,14 @@ function parseBlock(stream, terminators) {
|
|
|
8604
8889
|
if (stream.current().kind === "keyword" && stop.has(stream.current().value)) {
|
|
8605
8890
|
return body;
|
|
8606
8891
|
}
|
|
8607
|
-
const
|
|
8892
|
+
const offset = stream.current().position.offset;
|
|
8893
|
+
const statement = parseBlockStatement(stream, stop);
|
|
8894
|
+
if (statement === null) {
|
|
8895
|
+
if (stream.current().position.offset === offset) {
|
|
8896
|
+
return body;
|
|
8897
|
+
}
|
|
8898
|
+
continue;
|
|
8899
|
+
}
|
|
8608
8900
|
body.push(statement);
|
|
8609
8901
|
if (statement.kind === "return-statement") {
|
|
8610
8902
|
return body;
|
|
@@ -8751,7 +9043,7 @@ function classText(info) {
|
|
|
8751
9043
|
return `class ${info.name}:${info.superClass ?? ""}:${[...info.interfaces].sort(compareText).join(",")}{${membersText(info.members)}}`;
|
|
8752
9044
|
}
|
|
8753
9045
|
function interfaceText(info) {
|
|
8754
|
-
return `interface ${info.name}{${membersText(info.members)}}`;
|
|
9046
|
+
return `interface ${info.name}:${[...info.superInterfaces].sort(compareText).join(",")}{${membersText(info.members)}}`;
|
|
8755
9047
|
}
|
|
8756
9048
|
function enumText(info) {
|
|
8757
9049
|
return `enum ${info.name}{${info.members.join(",")}}`;
|
|
@@ -10792,6 +11084,28 @@ async function runDevCommand(context, options) {
|
|
|
10792
11084
|
// src/commands/init-command.ts
|
|
10793
11085
|
import { basename } from "node:path";
|
|
10794
11086
|
|
|
11087
|
+
// src/commands/init-prompt.ts
|
|
11088
|
+
import { createInterface } from "node:readline/promises";
|
|
11089
|
+
async function question(prompt, label2, fallback) {
|
|
11090
|
+
const answer = (await prompt.question(`${label2}${fallback === "" ? "" : ` (${fallback})`}: `)).trim();
|
|
11091
|
+
return answer === "" ? fallback : answer;
|
|
11092
|
+
}
|
|
11093
|
+
async function promptForProject(defaults, nameLocked) {
|
|
11094
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
11095
|
+
return defaults;
|
|
11096
|
+
}
|
|
11097
|
+
const prompt = createInterface({ input: process.stdin, output: process.stdout });
|
|
11098
|
+
try {
|
|
11099
|
+
const name = nameLocked ? defaults.name : await question(prompt, "Project name", defaults.name);
|
|
11100
|
+
const version = await question(prompt, "Version", defaults.version);
|
|
11101
|
+
const description = await question(prompt, "Description", defaults.description ?? "");
|
|
11102
|
+
const author = await question(prompt, "Author", defaults.author ?? "");
|
|
11103
|
+
return { name, version, description: description || null, author: author || null };
|
|
11104
|
+
} finally {
|
|
11105
|
+
prompt.close();
|
|
11106
|
+
}
|
|
11107
|
+
}
|
|
11108
|
+
|
|
10795
11109
|
// src/config/config-schema.ts
|
|
10796
11110
|
var CONFIG_FILE_NAME = "luam.json";
|
|
10797
11111
|
var DEFAULT_SOURCE_DIRS = ["src"];
|
|
@@ -10839,21 +11153,29 @@ function readTemplateSource(source) {
|
|
|
10839
11153
|
}
|
|
10840
11154
|
|
|
10841
11155
|
// src/scaffold/scaffold-plan.ts
|
|
10842
|
-
function renderConfig(source,
|
|
11156
|
+
function renderConfig(source, details) {
|
|
10843
11157
|
const parsed = JSON.parse(source);
|
|
10844
11158
|
const config = typeof parsed === "object" && parsed !== null ? parsed : {};
|
|
10845
|
-
|
|
11159
|
+
const { name: _name, version: _version, description: _description, author: _author, ...template } = config;
|
|
11160
|
+
const metadata = {
|
|
11161
|
+
...template,
|
|
11162
|
+
name: details.name,
|
|
11163
|
+
version: details.version,
|
|
11164
|
+
...details.description === null ? {} : { description: details.description },
|
|
11165
|
+
...details.author === null ? {} : { author: details.author }
|
|
11166
|
+
};
|
|
11167
|
+
return `${JSON.stringify(metadata, null, 4)}
|
|
10846
11168
|
`;
|
|
10847
11169
|
}
|
|
10848
|
-
function renderFile(file,
|
|
11170
|
+
function renderFile(file, details) {
|
|
10849
11171
|
const source = readTemplateSource(file.source);
|
|
10850
11172
|
if (file.path !== CONFIG_FILE_NAME) {
|
|
10851
11173
|
return { path: file.path, content: source };
|
|
10852
11174
|
}
|
|
10853
|
-
return { path: file.path, content: renderConfig(source,
|
|
11175
|
+
return { path: file.path, content: renderConfig(source, details) };
|
|
10854
11176
|
}
|
|
10855
|
-
function buildScaffoldPlan(
|
|
10856
|
-
return { name, files: TEMPLATE_FILES.map((file) => renderFile(file,
|
|
11177
|
+
function buildScaffoldPlan(details) {
|
|
11178
|
+
return { name: details.name, files: TEMPLATE_FILES.map((file) => renderFile(file, details)) };
|
|
10857
11179
|
}
|
|
10858
11180
|
|
|
10859
11181
|
// src/scaffold/scaffold-writer.ts
|
|
@@ -10884,21 +11206,31 @@ function resolveResourceName(root, requested) {
|
|
|
10884
11206
|
const folder = basename(root);
|
|
10885
11207
|
return isValidResourceName(folder) ? folder : FALLBACK_RESOURCE_NAME;
|
|
10886
11208
|
}
|
|
10887
|
-
function runInitCommand(root, logger, options) {
|
|
11209
|
+
async function runInitCommand(root, logger, options) {
|
|
10888
11210
|
if (options.name !== null && !isValidResourceName(options.name)) {
|
|
10889
11211
|
logger.error(`"--name" must be a valid MTA resource name but received "${options.name}".`);
|
|
10890
11212
|
return EXIT_USAGE;
|
|
10891
11213
|
}
|
|
10892
|
-
const
|
|
10893
|
-
|
|
11214
|
+
const defaults = {
|
|
11215
|
+
name: resolveResourceName(root, options.name),
|
|
11216
|
+
version: "1.0.0",
|
|
11217
|
+
description: null,
|
|
11218
|
+
author: null
|
|
11219
|
+
};
|
|
11220
|
+
const details = options.yes ? defaults : await options.prompt(defaults, options.name !== null);
|
|
11221
|
+
if (!isValidResourceName(details.name)) {
|
|
11222
|
+
logger.error(`"Project name" must be a valid MTA resource name but received "${details.name}".`);
|
|
11223
|
+
return EXIT_USAGE;
|
|
11224
|
+
}
|
|
11225
|
+
const result = writeScaffold(root, buildScaffoldPlan(details), options.force);
|
|
10894
11226
|
for (const path of result.skipped) {
|
|
10895
11227
|
logger.warn(`Kept the existing "${path}". Pass "--force" to overwrite it.`);
|
|
10896
11228
|
}
|
|
10897
11229
|
if (result.written.length === 0) {
|
|
10898
|
-
logger.info(`The project "${name}" already exists in "${root}". Nothing was written.`);
|
|
11230
|
+
logger.info(`The project "${details.name}" already exists in "${root}". Nothing was written.`);
|
|
10899
11231
|
return EXIT_OK;
|
|
10900
11232
|
}
|
|
10901
|
-
logger.info(`Scaffolded "${name}" into "${root}" (${result.written.length} files, ${result.skipped.length} kept).`);
|
|
11233
|
+
logger.info(`Scaffolded "${details.name}" into "${root}" (${result.written.length} files, ${result.skipped.length} kept).`);
|
|
10902
11234
|
logger.info('Next: run "luam check", then "luam build".');
|
|
10903
11235
|
return EXIT_OK;
|
|
10904
11236
|
}
|
|
@@ -11510,12 +11842,12 @@ function createEditorService() {
|
|
|
11510
11842
|
}
|
|
11511
11843
|
|
|
11512
11844
|
// src/editor/installation-prompt.ts
|
|
11513
|
-
import { createInterface } from "node:readline/promises";
|
|
11845
|
+
import { createInterface as createInterface2 } from "node:readline/promises";
|
|
11514
11846
|
async function promptForInstallation(editorName) {
|
|
11515
11847
|
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
11516
11848
|
return null;
|
|
11517
11849
|
}
|
|
11518
|
-
const prompt =
|
|
11850
|
+
const prompt = createInterface2({ input: process.stdin, output: process.stdout });
|
|
11519
11851
|
try {
|
|
11520
11852
|
const answer = (await prompt.question(`Install the Luam extension in ${editorName}? [Y/n] `)).trim().toLowerCase();
|
|
11521
11853
|
return answer === "" || answer === "y" || answer === "yes";
|
|
@@ -11638,7 +11970,8 @@ async function runCli(argv, overrides = {}) {
|
|
|
11638
11970
|
}
|
|
11639
11971
|
const root = resolve12(overrides.cwd ?? process.cwd(), parsed.cwd ?? ".");
|
|
11640
11972
|
if (parsed.command === "init") {
|
|
11641
|
-
|
|
11973
|
+
const target = parsed.operand === null ? root : resolve12(root, parsed.operand);
|
|
11974
|
+
return runInitCommand(target, logger, { name: parsed.name, force: parsed.force, yes: parsed.yes, prompt: overrides.initPrompt ?? promptForProject });
|
|
11642
11975
|
}
|
|
11643
11976
|
const editorService = overrides.editorService ?? createEditorService();
|
|
11644
11977
|
if (parsed.command === "doctor") {
|