@amxts/core 0.1.0 → 0.2.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.
Files changed (74) hide show
  1. package/README.md +18 -23
  2. package/README.ru.md +19 -22
  3. package/as/amxts.d.ts +63 -3
  4. package/as/constants.ts +684 -116
  5. package/as/entities.ts +1905 -1364
  6. package/as/events.ts +1694 -905
  7. package/as/facade.ts +1240 -220
  8. package/as/fetch.ts +1519 -0
  9. package/as/flags.ts +159 -145
  10. package/as/hlds.ts +1071 -0
  11. package/as/hooks.ts +5226 -5379
  12. package/as/kit.ts +15 -5
  13. package/as/lib/check.ts +1 -1
  14. package/as/natives.ts +2658 -2499
  15. package/as/remote.ts +39 -15
  16. package/as/tsconfig.json +6 -2
  17. package/as/vector.ts +12 -9
  18. package/includes/order.txt +22 -29
  19. package/includes/sources.json +0 -8
  20. package/node_modules/assemblyscript/std/assembly/index.d.ts +20 -11
  21. package/package.json +29 -7
  22. package/runtime/deps/assemblyscript/dist/asc.js +86 -16
  23. package/runtime/deps/assemblyscript/dist/assemblyscript.js +29 -29
  24. package/runtime/licenses/MPL-2.0.txt +373 -0
  25. package/runtime/licenses/README.md +5 -1
  26. package/runtime/licenses/curl-COPYING +22 -0
  27. package/runtime/licenses/libssh2-COPYING +43 -0
  28. package/runtime/licenses/mbedTLS-LICENSE +553 -0
  29. package/runtime/natives.txt +108 -105
  30. package/runtime/patches/assemblyscript-0.28.20-amxts.patch +752 -296
  31. package/runtime/patches/wamr-2.4.5-amxts.patch +148 -0
  32. package/scripts/apply-docs.ts +2 -2
  33. package/scripts/auto-imports.ts +2 -2
  34. package/scripts/build-identity.ts +115 -0
  35. package/scripts/build-wasm.ts +101 -50
  36. package/scripts/check.ts +17 -3
  37. package/scripts/client-messages.ts +246 -0
  38. package/scripts/compile-pool.ts +128 -0
  39. package/scripts/compile-worker.ts +23 -0
  40. package/scripts/compile.ts +156 -59
  41. package/scripts/docker-server.ts +13 -3
  42. package/scripts/docs/as/amxts.ts +86 -0
  43. package/scripts/docs/as/facade.ts +688 -188
  44. package/scripts/docs/as/fetch.ts +805 -0
  45. package/scripts/docs/entities.ts +43 -144
  46. package/scripts/docs/events.ts +15 -26
  47. package/scripts/docs/game.ts +68 -22
  48. package/scripts/docs/messages.ts +330 -16
  49. package/scripts/editor-api.ts +101 -0
  50. package/scripts/hlds-events.ts +370 -0
  51. package/scripts/includes.ts +12 -10
  52. package/scripts/player-fields.ts +16 -15
  53. package/scripts/prebuilt.ts +65 -23
  54. package/scripts/prepare.ts +39 -28
  55. package/scripts/project.ts +120 -20
  56. package/scripts/reapi-events.ts +98 -0
  57. package/scripts/shared-modules.ts +85 -23
  58. package/scripts/source-map.ts +242 -0
  59. package/scripts/system.ts +1 -1
  60. package/scripts/typed-commands.ts +263 -0
  61. package/scripts/typed-configs.ts +23 -12
  62. package/scripts/ui.ts +3 -1
  63. package/scripts/upgrade-menus.ts +274 -0
  64. package/scripts/upgrade-names.ts +384 -0
  65. package/scripts/upgrade.ts +830 -0
  66. package/src/cli-api.mjs +37 -4
  67. package/src/system.mjs +74 -3
  68. package/src/testing/compile.ts +8 -5
  69. package/src/testing/natives.ts +199 -5
  70. package/src/testing/network.ts +531 -0
  71. package/src/testing/server.ts +565 -47
  72. package/src/testing/tables.ts +25 -0
  73. package/as/modules/http.ts +0 -184
  74. package/scripts/docs/as/modules/http.ts +0 -66
@@ -67,7 +67,7 @@ type ObjectShape = Extract<Shape, { kind: 'object' }>;
67
67
  export type ImportReader = (from: string, spec: string) => { path: string; text: string } | null;
68
68
 
69
69
  /** A shape that cannot be read, at a node of the file it is in. */
70
- class ShapeError extends Error {
70
+ export class ShapeError extends Error {
71
71
  constructor(readonly node: ts.Node, readonly file: ts.SourceFile, message: string) {
72
72
  super(message);
73
73
  }
@@ -93,7 +93,7 @@ interface Context {
93
93
  file: ts.SourceFile;
94
94
  }
95
95
 
96
- class Shapes {
96
+ export class Shapes {
97
97
  private files = new Map<string, ts.SourceFile>();
98
98
 
99
99
  constructor(private imports: ImportReader) {}
@@ -190,7 +190,7 @@ class Shapes {
190
190
  return this.members(declaration.members, found.at, inside);
191
191
  }
192
192
 
193
- private declaration(name: ts.EntityName, at: Context): { node: ts.InterfaceDeclaration | ts.TypeAliasDeclaration; at: Context } | null {
193
+ declaration(name: ts.EntityName, at: Context): { node: ts.InterfaceDeclaration | ts.TypeAliasDeclaration; at: Context } | null {
194
194
  if (ts.isQualifiedName(name)) {
195
195
  if (!ts.isIdentifier(name.left)) return null;
196
196
  const namespace = this.importOf(name.left.text, at);
@@ -738,15 +738,26 @@ export function typedConfigs(path: string, display: string, text: string, import
738
738
  let out = text;
739
739
  for (const edit of edits.sort((a, b) => b.start - a.start)) out = out.slice(0, edit.start) + edit.with + out.slice(edit.end);
740
740
 
741
- // After the last line, so every line above keeps its number.
742
- const generated = [
743
- '',
744
- `// GENERATED by scripts/typed-configs.ts: the typed configs this file loads.`,
745
- `import * as ${NS} from "${HELPERS}";`,
746
- ...functions,
747
- '',
748
- ].join('\n');
749
- return { text: `${out}\n${generated}`, problems };
741
+ const code = ['// GENERATED by scripts/typed-configs.ts: the typed configs this file loads.', ...functions].join('\n');
742
+ return { text: withGenerated(path, out, code, `import * as ${NS} from "${HELPERS}";`), problems };
743
+ }
744
+
745
+ /**
746
+ * A file's text with code the build wrote for it: after its last line of its
747
+ * own, so every line above keeps its number, but before the imports that
748
+ * follow it - the auto-imports' line - with the code's own import after
749
+ * them. The build moves a file's trailing imports to its top
750
+ * (HoistImports), and the code would stop them being the last.
751
+ */
752
+ export function withGenerated(path: string, text: string, code: string, imports: string): string {
753
+ const file = ts.createSourceFile(path, text, ts.ScriptTarget.Latest, false, ts.ScriptKind.TS);
754
+ const statements = file.statements;
755
+ let first = statements.length;
756
+ while (first > 0 && ts.isImportDeclaration(statements[first - 1])) first--;
757
+ const at = first < statements.length ? statements[first].getStart(file) : text.length;
758
+ const head = text.slice(0, at);
759
+ const tail = text.slice(at);
760
+ return `${head}${head.endsWith('\n') ? '' : '\n'}${code}\n${tail}${tail.length === 0 || tail.endsWith('\n') ? '' : '\n'}${imports}\n`;
750
761
  }
751
762
 
752
763
  /** A load's shape and the type its function takes: the type argument, the defaults' declared type, or one made from the literal. */
package/scripts/ui.ts CHANGED
@@ -45,6 +45,9 @@ export function since(started: number): string {
45
45
  return `${((performance.now() - started) / 1000).toFixed(1)}s`;
46
46
  }
47
47
 
48
+ /** Whether a line is rewritten in place: a terminal, not CI's log. */
49
+ export const live = Boolean(process.stdout.isTTY) && !process.env.CI;
50
+
48
51
  /**
49
52
  * A step that takes a while, said as it starts - `◇ compiling hello` - and
50
53
  * ended with the line that says it is done, or with none when an error says
@@ -52,7 +55,6 @@ export function since(started: number): string {
52
55
  * (CI, a pipe) the start is a line of its own.
53
56
  */
54
57
  export function progress(text: string) {
55
- const live = Boolean(process.stdout.isTTY) && !process.env.CI;
56
58
  process.stdout.write(`${c.cyan('◇')} ${text}${live ? '' : '\n'}`);
57
59
  return {
58
60
  end(done?: string) {
@@ -0,0 +1,274 @@
1
+ // `amxts upgrade` for menu-core's code API: every function of a menu takes one
2
+ // object, the menu's context `{ player, target, row, menu }`, and an item is
3
+ // one object with its title in it.
4
+ //
5
+ // shop.addItem("Heal", { onSelect: heal }) shop.addItem({ title: "Heal", onSelect: ({ player }) => heal(player) })
6
+ // (player) => player.isAlive ({ player }) => player.isAlive
7
+ // (player, target) => new Player(target).name ({ target }) => target.name
8
+ // (player, target) => `${target}` ({ row: target }) => `${target}`
9
+ //
10
+ // A target was a number; in the context `target` is a Player and `row` the
11
+ // number it was. A parameter read only as `new Player(target)` becomes the
12
+ // context's `target`, one read as a number its `row` - so the code means what
13
+ // it meant. A function passed by its name and declared in the file is called
14
+ // from an arrow that hands it what it took; one declared elsewhere, a target
15
+ // given as a number and `menus.runActions` are listed. Conditions and
16
+ // condition filters keep their parameters. What is rewritten takes one object,
17
+ // so a second run changes nothing.
18
+ import type { Change, Edit, Left } from './upgrade';
19
+ import ts from 'typescript';
20
+ import { applyEdits } from './upgrade';
21
+
22
+ /**
23
+ * What each parameter of an old callback was, by its place: `number` is the
24
+ * target as a number (the context's `row`, or `target` where it was only made
25
+ * a Player), `menuName` the menu's name (the context's `menu.name`).
26
+ */
27
+ type Role = 'player' | 'target' | 'number' | 'name' | 'menuName';
28
+
29
+ const ITEM: Role[] = ['player', 'number'];
30
+ const OPEN: Role[] = ['player'];
31
+ const NAMED: Role[] = ['player', 'number', 'name'];
32
+ const RESTRICTION: Role[] = ['player', 'name', 'number'];
33
+ const ACTION_CHECK: Role[] = ['player', 'menuName', 'name'];
34
+ const FILTER: Role[] = ['target', 'player'];
35
+ const SOURCE: Role[] = ['player', 'menuName'];
36
+
37
+ /** The module's functions that take a callback: where it is among the arguments, and what it took. */
38
+ const MODULE_CALLBACKS: Record<string, { at: number; roles: Role[] }> = {
39
+ addAction: { at: 1, roles: NAMED },
40
+ addPlaceholder: { at: 1, roles: NAMED },
41
+ addRestriction: { at: 1, roles: RESTRICTION },
42
+ addActionCheck: { at: 2, roles: ACTION_CHECK },
43
+ setListSource: { at: 1, roles: SOURCE },
44
+ };
45
+ /** A menu's methods whose last argument is a callback, and what it took. */
46
+ const MENU_CALLBACKS: Record<string, Role[]> = { addFilter: FILTER, addPlaceholder: NAMED, setListSource: SOURCE };
47
+ /** An item's fields that are text or a test of the menu's context. */
48
+ const ITEM_FIELDS = new Set(['title', 'onSelect', 'visible', 'enabled', 'message']);
49
+ /** The module's functions that give a menu. */
50
+ const GIVE_MENU = new Set(['create', 'find', 'register', 'menuAt', 'activeMenu']);
51
+ const MENU_CORE = new Set(['@amxts/menu-core', '~/modules/menu-core']);
52
+
53
+ /** The context's field a role is read from, and the value it hands a function that took that role. */
54
+ const ARGUMENT: Record<Role, { key: string; value: string }> = {
55
+ player: { key: 'player', value: 'player' },
56
+ target: { key: 'target', value: 'target' },
57
+ number: { key: 'row', value: 'row' },
58
+ name: { key: 'name', value: 'name' },
59
+ menuName: { key: 'menu', value: 'menu.name' },
60
+ };
61
+
62
+ const isFunction = (node: ts.Node): node is ts.ArrowFunction | ts.FunctionExpression => ts.isArrowFunction(node) || ts.isFunctionExpression(node);
63
+
64
+ /** A file's menus brought to the context: the rewritten text, each change, and what is left to do by hand. */
65
+ export function upgradeMenus(file: string, text: string): { text: string; changes: Change[]; left: Left[] } {
66
+ if (!text.includes('menu') && !text.includes('addItem')) return { text, changes: [], left: [] };
67
+ const source = ts.createSourceFile(file, text, ts.ScriptTarget.Latest, true);
68
+ const edits: Edit[] = [];
69
+ const left: Left[] = [];
70
+ const lineOf = (node: ts.Node) => source.getLineAndCharacterOfPosition(node.getStart(source)).line + 1;
71
+ const slice = (node: ts.Node) => text.slice(node.getStart(source), node.getEnd());
72
+
73
+ // The names the module goes by here: `menus`, a namespace import, and its functions imported by name.
74
+ const namespaces = new Set(['menus']);
75
+ const imported = new Map<string, string>();
76
+ for (const statement of source.statements) {
77
+ if (!ts.isImportDeclaration(statement) || !ts.isStringLiteral(statement.moduleSpecifier) || !MENU_CORE.has(statement.moduleSpecifier.text)) continue;
78
+ const bindings = statement.importClause?.namedBindings;
79
+ if (bindings && ts.isNamespaceImport(bindings)) namespaces.add(bindings.name.text);
80
+ if (!bindings || !ts.isNamedImports(bindings)) continue;
81
+ for (const each of bindings.elements) imported.set(each.name.text, (each.propertyName ?? each.name).text);
82
+ }
83
+
84
+ /** The module's function a call names - `menus.create`, or `create` imported by name - or null. */
85
+ const moduleCall = (call: ts.CallExpression): string | null => {
86
+ const callee = call.expression;
87
+ if (ts.isPropertyAccessExpression(callee) && ts.isIdentifier(callee.expression) && namespaces.has(callee.expression.text)) return callee.name.text;
88
+ return ts.isIdentifier(callee) ? imported.get(callee.text) ?? null : null;
89
+ };
90
+
91
+ // The variables, parameters and functions a name may be, by name: enough to
92
+ // tell a menu and a function of the file's own.
93
+ const declared = new Map<string, ts.Node>();
94
+ const collect = (node: ts.Node) => {
95
+ if ((ts.isFunctionDeclaration(node) || ts.isVariableDeclaration(node) || ts.isParameter(node)) && node.name && ts.isIdentifier(node.name) && !declared.has(node.name.text)) declared.set(node.name.text, node);
96
+ ts.forEachChild(node, collect);
97
+ };
98
+ collect(source);
99
+
100
+ const unwrap = (node: ts.Expression): ts.Expression => (ts.isNonNullExpression(node) || ts.isParenthesizedExpression(node) || ts.isAsExpression(node) ? unwrap(node.expression) : node);
101
+
102
+ /** Whether an expression is a menu of menu-core: one the module gave, or a name of its `Menu` type. */
103
+ const isMenu = (node: ts.Expression): boolean => {
104
+ const bare = unwrap(node);
105
+ if (ts.isCallExpression(bare)) return GIVE_MENU.has(moduleCall(bare) ?? '');
106
+ if (!ts.isIdentifier(bare)) return false;
107
+ const declaration = declared.get(bare.text);
108
+ if (!declaration || !(ts.isVariableDeclaration(declaration) || ts.isParameter(declaration))) return false;
109
+ const type = declaration.type?.getText(source) ?? '';
110
+ if ([...namespaces].some(ns => type.startsWith(`${ns}.Menu`)) || imported.get(type.replace(/\s*\|.*$/, '')) === 'Menu') return true;
111
+ return !!declaration.initializer && isMenu(declaration.initializer);
112
+ };
113
+
114
+ /** The function a name is in this file, with its parameters; null for one declared elsewhere, or a value. */
115
+ const functionNamed = (name: string): ts.SignatureDeclaration | null => {
116
+ const declaration = declared.get(name);
117
+ if (!declaration) return null;
118
+ if (ts.isFunctionDeclaration(declaration)) return declaration;
119
+ if (ts.isVariableDeclaration(declaration) && declaration.initializer && isFunction(declaration.initializer)) return declaration.initializer;
120
+ return null;
121
+ };
122
+
123
+ /** Whether a function already takes the context: one object, or a parameter of a context's type. */
124
+ const takesContext = (fn: ts.SignatureDeclaration) => {
125
+ const first = fn.parameters[0];
126
+ return !first || !ts.isIdentifier(first.name) || /Context\b/.test(first.type?.getText(source) ?? '');
127
+ };
128
+
129
+ /** The reads of a parameter in a function's body, and those that only make it a Player: `new Player(target)`. */
130
+ const readsOf = (fn: ts.ArrowFunction | ts.FunctionExpression, name: string) => {
131
+ const reads: ts.Identifier[] = [];
132
+ const visit = (node: ts.Node) => {
133
+ if (ts.isIdentifier(node) && node.text === name && !(ts.isPropertyAccessExpression(node.parent) && node.parent.name === node) && !(ts.isPropertyAssignment(node.parent) && node.parent.name === node)) reads.push(node);
134
+ ts.forEachChild(node, visit);
135
+ };
136
+ visit(fn.body);
137
+ const asPlayer = reads.map(read => read.parent).filter((parent): parent is ts.NewExpression =>
138
+ ts.isNewExpression(parent) && ts.isIdentifier(parent.expression) && parent.expression.text === 'Player' && parent.arguments?.length === 1);
139
+ return { reads, asPlayer };
140
+ };
141
+
142
+ /**
143
+ * The edits that bring a callback - written in place, or passed by its
144
+ * name - to the context. A `text` may be a string: a name the file gives
145
+ * a value is one, and so is one it does not declare.
146
+ */
147
+ const callbackEdits = (node: ts.Expression, roles: Role[], text = false): Edit[] => {
148
+ if (isFunction(node)) return inlineEdits(node, roles);
149
+ if (!ts.isIdentifier(node) && !ts.isPropertyAccessExpression(node)) return [];
150
+ const name = slice(node);
151
+ const fn = ts.isIdentifier(node) ? functionNamed(node.text) : null;
152
+ if (!fn && (text || (ts.isIdentifier(node) && declared.has(node.text)))) return [];
153
+
154
+ if (!fn) {
155
+ const first = ARGUMENT[roles[0]];
156
+ left.push({ file, line: lineOf(node), why: `${name} is declared elsewhere: a function of the menu takes one object now - if it takes (${roles.join(', ')}), pass ({ ${first.key} }) => ${name}(${first.value}) and so on` });
157
+ return [];
158
+ }
159
+
160
+ if (fn.parameters.length === 0 || takesContext(fn)) return [];
161
+ const taken = roles.slice(0, fn.parameters.length).map(role => ARGUMENT[role]);
162
+ const keys = [...new Set(taken.map(each => each.key))];
163
+ const wrapper = `({ ${keys.join(', ')} }) => ${name}(${taken.map(each => each.value).join(', ')})`;
164
+ return [{ start: node.getStart(source), end: node.getEnd(), with: wrapper, from: name }];
165
+ };
166
+
167
+ /** A function written in place: its parameters become one object, the context's fields by the names it read them by. */
168
+ const inlineEdits = (fn: ts.ArrowFunction | ts.FunctionExpression, roles: Role[]): Edit[] => {
169
+ const params = fn.parameters;
170
+ if (params.length === 0 || !params.every(param => ts.isIdentifier(param.name))) return [];
171
+ const edits: Edit[] = [];
172
+ const fields: string[] = [];
173
+
174
+ params.slice(0, roles.length).forEach((param, at) => {
175
+ const name = (param.name as ts.Identifier).text;
176
+ const { reads, asPlayer } = readsOf(fn, name);
177
+ if (reads.length === 0) return;
178
+ let key = ARGUMENT[roles[at]].key;
179
+
180
+ // A target read only as `new Player(target)` is the context's Player.
181
+ if (roles[at] === 'number' && asPlayer.length === reads.length) {
182
+ key = 'target';
183
+ for (const made of asPlayer) edits.push({ start: made.getStart(source), end: made.getEnd(), with: name, from: slice(made) });
184
+ }
185
+
186
+ if (roles[at] === 'menuName') left.push({ file, line: lineOf(param), why: `${name} was the menu's name: the context's menu is the menu itself - read menu.name` });
187
+ fields.push(key === name ? key : `${key}: ${name}`);
188
+ });
189
+
190
+ const first = params[0];
191
+ const end = params[params.length - 1].getEnd();
192
+ const parenthesized = text.slice(fn.getStart(source), first.getStart(source)).includes('(');
193
+ const binding = fields.length ? `{ ${fields.join(', ')} }` : '';
194
+ const replaced = parenthesized ? binding : `(${binding})`;
195
+ edits.push({ start: first.getStart(source), end, with: replaced, from: text.slice(first.getStart(source), end) });
196
+ return edits;
197
+ };
198
+
199
+ /** The text a node becomes with edits inside it made. */
200
+ const rewritten = (node: ts.Node, inside: Edit[]) => {
201
+ const start = node.getStart(source);
202
+ let out = slice(node);
203
+ for (const edit of [...inside].sort((a, b) => b.start - a.start)) out = out.slice(0, edit.start - start) + edit.with + out.slice(edit.end - start);
204
+ return out;
205
+ };
206
+
207
+ /** The fields of an item's object literal - text and tests of the context - brought to it. */
208
+ const itemEdits = (item: ts.ObjectLiteralExpression, fields: Set<string>, roles: (field: string) => Role[]): Edit[] => item.properties.flatMap((property) => {
209
+ if (!ts.isPropertyAssignment(property) || !ts.isIdentifier(property.name) || !fields.has(property.name.text)) return [];
210
+ const value = property.initializer;
211
+ const field = property.name.text;
212
+ if (field !== 'enabled' || !ts.isArrayLiteralExpression(value)) return callbackEdits(value, roles(field), field === 'title' || field === 'message');
213
+ // A list of requirements: each `{ when, message }` is an item's test and text.
214
+ return value.elements.filter(ts.isObjectLiteralExpression).flatMap(requirement => itemEdits(requirement, new Set(['when', 'message']), () => ITEM));
215
+ });
216
+
217
+ /** `addItem(text, options)` - or `addFixedItem(slot, text, options)` - as one object with the title in it. */
218
+ const addItemEdits = (call: ts.CallExpression, at: number): Edit[] => {
219
+ const [label, options] = [call.arguments[at], call.arguments[at + 1]];
220
+ if (!label) return [];
221
+ if (ts.isObjectLiteralExpression(label) && !options) return itemEdits(label, ITEM_FIELDS, () => ITEM);
222
+
223
+ const title = rewritten(label, callbackEdits(label, ITEM, true));
224
+ if (!options) return [{ start: label.getStart(source), end: label.getEnd(), with: `{ title: ${title} }`, from: slice(label) }];
225
+ if (!ts.isObjectLiteralExpression(options)) return [{ start: label.getStart(source), end: options.getEnd(), with: `{ title: ${title}, ...${slice(options)} }`, from: `${slice(label)}, ${slice(options)}` }];
226
+
227
+ const brace = options.getStart(source) + 1;
228
+ const first = options.properties[0];
229
+ const multiline = first && text.slice(brace, first.getStart(source)).includes('\n');
230
+ const indent = multiline ? text.slice(brace, first.getStart(source)).replace(/^[^\n]*\n/, '') : '';
231
+ const opening = !first ? `{ title: ${title} ` : multiline ? `{\n${indent}title: ${title},` : `{ title: ${title},`;
232
+ return [
233
+ { start: label.getStart(source), end: brace, with: opening, from: `${slice(label)}, {` },
234
+ ...itemEdits(options, ITEM_FIELDS, () => ITEM),
235
+ ];
236
+ };
237
+
238
+ /** `show(player, { target: player.id })`: the target is the player himself. */
239
+ const showEdits = (options: ts.Expression | undefined): Edit[] => {
240
+ if (!options || !ts.isObjectLiteralExpression(options)) return [];
241
+ return options.properties.flatMap((property) => {
242
+ if (!ts.isPropertyAssignment(property) || !ts.isIdentifier(property.name) || property.name.text !== 'target') return [];
243
+ const value = property.initializer;
244
+ if (ts.isPropertyAccessExpression(value) && value.name.text === 'id') return [{ start: value.getStart(source), end: value.getEnd(), with: slice(value.expression), from: slice(value) }];
245
+ if (ts.isNumericLiteral(value)) left.push({ file, line: lineOf(value), why: 'show\'s target is a player now: pass the Player, not its number' });
246
+ return [];
247
+ });
248
+ };
249
+
250
+ const visit = (node: ts.Node) => {
251
+ ts.forEachChild(node, visit);
252
+ if (!ts.isCallExpression(node)) return;
253
+ const callee = node.expression;
254
+ const args = node.arguments;
255
+ const fn = moduleCall(node);
256
+
257
+ if (fn === 'create' && args[1] && ts.isObjectLiteralExpression(args[1])) edits.push(...itemEdits(args[1], new Set(['title', 'activeWhen']), field => (field === 'activeWhen' ? OPEN : ITEM)));
258
+ else if (fn === 'show') edits.push(...showEdits(args[2]));
259
+ else if (fn === 'runActions') left.push({ file, line: lineOf(node), why: 'runActions is a method of the menu now: menu.runActions(player, line, target), the target a Player' });
260
+ else if (fn && MODULE_CALLBACKS[fn] && args[MODULE_CALLBACKS[fn].at]) edits.push(...callbackEdits(args[MODULE_CALLBACKS[fn].at], MODULE_CALLBACKS[fn].roles));
261
+ if (fn || !ts.isPropertyAccessExpression(callee)) return;
262
+
263
+ const method = callee.name.text;
264
+ const menu = isMenu(callee.expression);
265
+ const oldItem = (at: number) => args[at] && (ts.isStringLiteralLike(args[at]) || ts.isTemplateExpression(args[at]) || isFunction(args[at]));
266
+
267
+ if (method === 'addItem' && (menu || oldItem(0))) edits.push(...addItemEdits(node, 0));
268
+ else if (method === 'addFixedItem' && (menu || oldItem(1))) edits.push(...addItemEdits(node, 1));
269
+ else if (menu && method === 'show') edits.push(...showEdits(args[1]));
270
+ else if (menu && MENU_CALLBACKS[method] && args.length >= 1) edits.push(...callbackEdits(args[method === 'addPlaceholder' ? 1 : 0], MENU_CALLBACKS[method]));
271
+ };
272
+ visit(source);
273
+ return { ...applyEdits(file, text, source, edits), left };
274
+ }