@markw65/monkeyc-optimizer 1.0.6 → 1.0.9

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/build/api.cjs CHANGED
@@ -40,12 +40,12 @@ __webpack_require__.r(__webpack_exports__);
40
40
 
41
41
  // EXPORTS
42
42
  __webpack_require__.d(__webpack_exports__, {
43
- "LiteralIntegerRe": () => (/* binding */ LiteralIntegerRe),
44
- "collectNamespaces": () => (/* binding */ collectNamespaces),
43
+ "LiteralIntegerRe": () => (/* binding */ api_LiteralIntegerRe),
44
+ "collectNamespaces": () => (/* binding */ api_collectNamespaces),
45
45
  "formatAst": () => (/* binding */ formatAst),
46
- "getApiMapping": () => (/* binding */ getApiMapping),
47
- "hasProperty": () => (/* binding */ hasProperty),
48
- "traverseAst": () => (/* binding */ traverseAst)
46
+ "getApiMapping": () => (/* binding */ api_getApiMapping),
47
+ "hasProperty": () => (/* binding */ api_hasProperty),
48
+ "traverseAst": () => (/* binding */ api_traverseAst)
49
49
  });
50
50
 
51
51
  ;// CONCATENATED MODULE: external "@markw65/prettier-plugin-monkeyc"
@@ -54,8 +54,757 @@ const prettier_plugin_monkeyc_namespaceObject = require("@markw65/prettier-plugi
54
54
  const promises_namespaceObject = require("fs/promises");
55
55
  ;// CONCATENATED MODULE: external "prettier/standalone.js"
56
56
  const standalone_js_namespaceObject = require("prettier/standalone.js");
57
+ ;// CONCATENATED MODULE: external "./api.cjs"
58
+ const external_api_cjs_namespaceObject = require("./api.cjs");
57
59
  ;// CONCATENATED MODULE: external "./util.cjs"
58
60
  const external_util_cjs_namespaceObject = require("./util.cjs");
61
+ ;// CONCATENATED MODULE: ./src/mc-rewrite.js
62
+
63
+
64
+
65
+
66
+
67
+ function processImports(allImports, lookup) {
68
+ allImports.forEach(({ node, stack }) => {
69
+ const [name, module] = lookup(node.id, node.as && node.as.name, stack);
70
+ if (name && module) {
71
+ const [parent] = stack.slice(-1);
72
+ if (!parent.decls) parent.decls = {};
73
+ if (!hasProperty(parent.decls, name)) parent.decls[name] = [];
74
+ module.forEach((m) => {
75
+ if (m.type == "ModuleDeclaration") {
76
+ pushUnique(parent.decls[name], m);
77
+ }
78
+ });
79
+ }
80
+ });
81
+ }
82
+
83
+ function collectClassInfo(state) {
84
+ state.allClasses.forEach((elm) => {
85
+ if (elm.node.superClass) {
86
+ const [, classes] = state.lookup(elm.node.superClass, null, elm.stack);
87
+ if (classes) {
88
+ elm.superClass = classes.filter((c) => c.type == "ClassDeclaration");
89
+ }
90
+ // set it "true" if there is a superClass, but we can't find it.
91
+ if (!elm.superClass || !elm.superClass.length) elm.superClass = true;
92
+ }
93
+ });
94
+
95
+ const markOverrides = (cls, scls) => {
96
+ if (scls === true) return;
97
+ scls.forEach((c) => {
98
+ c.decls &&
99
+ Object.values(c.decls).forEach((f) => {
100
+ if (f.type == "FunctionDeclaration") {
101
+ if (hasProperty(cls.decls, f.name)) {
102
+ f.hasOverride = true;
103
+ }
104
+ }
105
+ });
106
+ if (c.superClass) markOverrides(cls, c.superClass);
107
+ });
108
+ };
109
+
110
+ state.allClasses.forEach((elm) => {
111
+ if (elm.superClass) markOverrides(elm, elm.superClass);
112
+ });
113
+ }
114
+
115
+ async function analyze(fileNames, buildConfig) {
116
+ const excludeAnnotations =
117
+ buildConfig && buildConfig.excludeAnnotations
118
+ ? Object.fromEntries(buildConfig.excludeAnnotations.map((a) => [a, true]))
119
+ : {};
120
+
121
+ const allImports = [];
122
+ const state = {
123
+ allFunctions: [],
124
+ allClasses: [],
125
+ shouldExclude(node) {
126
+ if (node.attrs && node.attrs.attrs) {
127
+ if (
128
+ node.attrs.attrs.filter((attr) => {
129
+ if (attr.type != "UnaryExpression") return false;
130
+ if (attr.argument.type != "Identifier") return false;
131
+ return hasProperty(excludeAnnotations, attr.argument.name);
132
+ }).length
133
+ ) {
134
+ return true;
135
+ }
136
+ }
137
+ },
138
+ post(node) {
139
+ switch (node.type) {
140
+ case "FunctionDeclaration":
141
+ case "ClassDeclaration": {
142
+ const [scope] = state.stack.slice(-1);
143
+ const stack = state.stack.slice(0, -1);
144
+ scope.stack = stack;
145
+ (node.type == "FunctionDeclaration"
146
+ ? state.allFunctions
147
+ : state.allClasses
148
+ ).push(scope);
149
+ return;
150
+ }
151
+ case "Using":
152
+ case "ImportModule":
153
+ allImports.push({ node, stack: state.stack.slice() });
154
+ return;
155
+ }
156
+ },
157
+ };
158
+
159
+ await getApiMapping(state);
160
+
161
+ // Mark all functions from api.mir as "special" by
162
+ // setting their bodies to null. In api.mir, they're
163
+ // all empty, which makes it look like they're
164
+ // do-nothing functions.
165
+ const markApi = (node) => {
166
+ if (node.type == "FunctionDeclaration") {
167
+ node.node.body = null;
168
+ }
169
+ if (node.decls) {
170
+ Object.values(node.decls).forEach(markApi);
171
+ }
172
+ };
173
+ markApi(state.stack[0]);
174
+
175
+ const files = await Promise.all(
176
+ fileNames.map(async (name) => ({
177
+ name,
178
+ monkeyCSource: (await fs.readFile(name))
179
+ .toString()
180
+ .replace(/\r\n/g, "\n"),
181
+ }))
182
+ );
183
+
184
+ files.forEach((f) => {
185
+ f.ast = MonkeyC.parsers.monkeyc.parse(f.monkeyCSource, {
186
+ grammarSource: f.name,
187
+ });
188
+ f.ast.source = f.name;
189
+ f.ast.monkeyCSource = f.monkeyCSource;
190
+ delete f.monkeyCSource;
191
+ collectNamespaces(f.ast, state);
192
+ });
193
+
194
+ delete state.shouldExclude;
195
+ delete state.post;
196
+
197
+ processImports(allImports, state.lookup);
198
+ collectClassInfo(state);
199
+
200
+ return { files, state };
201
+ }
202
+
203
+ function getLiteralNode(node) {
204
+ if (Array.isArray(node)) {
205
+ if (!node.length) return null;
206
+ if (node.length === 1) return getLiteralNode(node[0]);
207
+ let result;
208
+ if (
209
+ node.every((n) => {
210
+ const lit = getLiteralNode(n);
211
+ if (!lit) return false;
212
+ if (!result) {
213
+ result = lit;
214
+ } else {
215
+ if (lit.value !== result.value) return false;
216
+ }
217
+ return true;
218
+ })
219
+ ) {
220
+ return result;
221
+ }
222
+ return null;
223
+ }
224
+ if (node.type == "Literal") return node;
225
+ if (node.type == "BinaryExpression" && node.operator == "as") {
226
+ return getLiteralNode(node.left) && node;
227
+ }
228
+ if (node.type == "UnaryExpression") {
229
+ if (node.argument.type != "Literal") return null;
230
+ switch (node.operator) {
231
+ case "-":
232
+ if (typeof node.argument.value == "number") {
233
+ return {
234
+ ...node.argument,
235
+ value: -node.argument.value,
236
+ raw: "-" + node.argument.value,
237
+ enumType: node.enumType,
238
+ };
239
+ }
240
+ }
241
+ }
242
+ }
243
+
244
+ function getNodeValue(node) {
245
+ if (
246
+ node.type == "BinaryExpression" &&
247
+ node.operator == "as" &&
248
+ node.right.type == "TypeSpecList" &&
249
+ node.right.ts.length == 1 &&
250
+ typeof node.right.ts[0] == "string"
251
+ ) {
252
+ // this is a cast we inserted to retain the type of an enum
253
+ // any arithmetic on it will revert to "Number", or "Long",
254
+ // so just ignore it.
255
+ return getNodeValue(node.left);
256
+ }
257
+ if (node.type != "Literal") {
258
+ return [null, null];
259
+ }
260
+ let type = node.value === null ? "Null" : typeof node.value;
261
+ if (type === "number") {
262
+ const match = LiteralIntegerRe.exec(node.raw);
263
+ if (match) {
264
+ type = match[2] == "l" ? "Long" : "Number";
265
+ } else if (node.raw.endsWith("d")) {
266
+ type = "Double";
267
+ } else {
268
+ type = "Float";
269
+ }
270
+ } else if (type === "string") {
271
+ type = "String";
272
+ } else if (type === "boolean") {
273
+ type = "Boolean";
274
+ } else {
275
+ type = "Unknown";
276
+ }
277
+ return [node, type];
278
+ }
279
+
280
+ function optimizeNode(node) {
281
+ switch (node.type) {
282
+ case "UnaryExpression": {
283
+ const [arg, type] = getNodeValue(node.argument);
284
+ if (arg === null) break;
285
+ switch (node.operator) {
286
+ case "+":
287
+ if (type === "Number" || type === "Long") {
288
+ return arg;
289
+ }
290
+ break;
291
+ case "-":
292
+ if (type === "Number" || type === "Long") {
293
+ return {
294
+ ...arg,
295
+ value: -arg.value,
296
+ raw: (-arg.value).toString() + (type === "Long" ? "l" : ""),
297
+ };
298
+ }
299
+ break;
300
+ case "!":
301
+ case "~":
302
+ {
303
+ let value;
304
+ if (type === "Number" || type === "Long") {
305
+ value = -arg.value - 1;
306
+ } else if (type === "Boolean" && node.operator == "!") {
307
+ value = !arg.value;
308
+ }
309
+ if (value !== undefined) {
310
+ return {
311
+ ...arg,
312
+ value,
313
+ raw: value.toString() + (type === "Long" ? "l" : ""),
314
+ };
315
+ }
316
+ }
317
+ break;
318
+ }
319
+ break;
320
+ }
321
+ case "BinaryExpression": {
322
+ const operators = {
323
+ "+": (left, right) => left + right,
324
+ "-": (left, right) => left - right,
325
+ "*": (left, right) => left * right,
326
+ "/": (left, right) => Math.trunc(left / right),
327
+ "%": (left, right) => left % right,
328
+ "&": (left, right, type) => (type === "Number" ? left & right : null),
329
+ "|": (left, right, type) => (type === "Number" ? left | right : null),
330
+ "<<": (left, right, type) => (type === "Number" ? left << right : null),
331
+ ">>": (left, right, type) => (type === "Number" ? left >> right : null),
332
+ };
333
+ const op = operators[node.operator];
334
+ if (op) {
335
+ const [left, left_type] = getNodeValue(node.left);
336
+ const [right, right_type] = getNodeValue(node.right);
337
+ if (!left || !right) break;
338
+ if (
339
+ left_type != right_type ||
340
+ (left_type != "Number" && left_type != "Long")
341
+ ) {
342
+ break;
343
+ }
344
+ const value = op(left.value, right.value, left_type);
345
+ if (value === null) break;
346
+ return {
347
+ ...left,
348
+ value,
349
+ raw: value.toString() + (left_type === "Long" ? "l" : ""),
350
+ };
351
+ }
352
+ break;
353
+ }
354
+ case "FunctionDeclaration":
355
+ if (node.body && evaluateFunction(node, null) !== false) {
356
+ node.optimizable = true;
357
+ }
358
+ break;
359
+ }
360
+ }
361
+
362
+ function evaluateFunction(func, args) {
363
+ if (args && args.length != func.params.length) {
364
+ return false;
365
+ }
366
+ const paramValues =
367
+ args && Object.fromEntries(func.params.map((p, i) => [p.name, args[i]]));
368
+ let ret = null;
369
+ const body = args ? JSON.parse(JSON.stringify(func.body)) : func.body;
370
+ try {
371
+ traverseAst(
372
+ body,
373
+ (node) => {
374
+ switch (node.type) {
375
+ case "BlockStatement":
376
+ case "ReturnStatement":
377
+ case "UnaryExpression":
378
+ case "BinaryExpression":
379
+ case "Literal":
380
+ case "Identifier":
381
+ return;
382
+ default:
383
+ throw new Error("Bad node type");
384
+ }
385
+ },
386
+ args &&
387
+ ((node) => {
388
+ switch (node.type) {
389
+ case "ReturnStatement":
390
+ ret = node.argument;
391
+ return;
392
+ case "BlockStatement":
393
+ case "Literal":
394
+ return;
395
+ case "Identifier":
396
+ if (hasProperty(paramValues, node.name)) {
397
+ return paramValues[node.name];
398
+ }
399
+ // fall through;
400
+ default: {
401
+ const repl = optimizeNode(node);
402
+ if (repl && repl.type === "Literal") return repl;
403
+ throw new Error("Didn't optimize");
404
+ }
405
+ }
406
+ })
407
+ );
408
+ return ret;
409
+ } catch (e) {
410
+ return false;
411
+ }
412
+ }
413
+
414
+ async function optimizeMonkeyC(fileNames, buildConfig) {
415
+ const { files, state } = await analyze(fileNames, buildConfig);
416
+ const replace = (node, obj) => {
417
+ for (const k of Object.keys(node)) {
418
+ delete node[k];
419
+ }
420
+ if (obj.enumType) {
421
+ obj = {
422
+ type: "BinaryExpression",
423
+ operator: "as",
424
+ left: obj,
425
+ right: { type: "TypeSpecList", ts: [obj.enumType] },
426
+ };
427
+ }
428
+ for (const [k, v] of Object.entries(obj)) {
429
+ node[k] = v;
430
+ }
431
+ };
432
+ const lookupAndReplace = (node) => {
433
+ const [, objects] = state.lookup(node);
434
+ if (!objects) {
435
+ return false;
436
+ }
437
+ const obj = getLiteralNode(objects);
438
+ if (!obj) {
439
+ return false;
440
+ }
441
+ replace(node, obj);
442
+ return true;
443
+ };
444
+
445
+ /*
446
+ * Might this function be called from somewhere, including
447
+ * callbacks from the api (eg getSettingsView, etc).
448
+ */
449
+ const maybeCalled = (func) => {
450
+ if (!func.body) {
451
+ // this is an api.mir function. It can be called
452
+ return true;
453
+ }
454
+ if (hasProperty(state.exposed, func.id.name)) return true;
455
+ if (hasProperty(state.calledFunctions, func.id.name)) {
456
+ return (
457
+ state.calledFunctions[func.id.name].find((f) => f === func) !== null
458
+ );
459
+ }
460
+ };
461
+ /*
462
+ * Does elm (a class) have a maybeCalled function called name,
463
+ * anywhere in its superClass chain.
464
+ */
465
+ const checkInherited = (elm, name) =>
466
+ elm.superClass === true ||
467
+ elm.superClass.some(
468
+ (sc) =>
469
+ (hasProperty(sc.decls, name) &&
470
+ sc.decls[name].some(
471
+ (f) => f.type == "FunctionDeclaration" && maybeCalled(f)
472
+ )) ||
473
+ (sc.superClass && checkInherited(sc, name))
474
+ );
475
+
476
+ state.localsStack = [{}];
477
+ state.exposed = {};
478
+ state.calledFunctions = {};
479
+ state.pre = (node) => {
480
+ switch (node.type) {
481
+ case "ConditionalExpression":
482
+ case "IfStatement":
483
+ case "DoWhileStatement":
484
+ case "WhileStatement":
485
+ state.traverse(node.test);
486
+ const [value, type] = getNodeValue(node.test);
487
+ if (value) {
488
+ let result = null;
489
+ if (type === "Null") {
490
+ result = false;
491
+ } else if (
492
+ type === "Boolean" ||
493
+ type === "Number" ||
494
+ type === "Long"
495
+ ) {
496
+ result = !!value.value;
497
+ }
498
+ if (result !== null) {
499
+ if (
500
+ node.type === "IfStatement" ||
501
+ node.type === "ConditionalExpression"
502
+ ) {
503
+ if (result === false) {
504
+ node.consequent = null;
505
+ } else {
506
+ node.alternate = null;
507
+ }
508
+ node.test = result;
509
+ } else if (node.type === "WhileStatement") {
510
+ if (result === false) {
511
+ node.body = null;
512
+ }
513
+ } else if (node.type === "DoWhileStatement") {
514
+ if (result === false) {
515
+ node.test = null;
516
+ }
517
+ } else {
518
+ throw new Error("Unexpected Node type");
519
+ }
520
+ }
521
+ }
522
+ return;
523
+
524
+ case "EnumDeclaration":
525
+ return false;
526
+ case "ForStatement": {
527
+ const map = state.localsStack.slice(-1).pop().map;
528
+ if (map) {
529
+ state.localsStack.push({ node, map: { ...map } });
530
+ }
531
+ break;
532
+ }
533
+ case "VariableDeclarator": {
534
+ const locals = state.localsStack.slice(-1).pop();
535
+ const { map } = locals;
536
+ if (map) {
537
+ if (hasProperty(map, node.id.name)) {
538
+ // We already have a variable with this name in scope
539
+ // Recent monkeyc compilers complain, so rename it
540
+ let suffix = 0;
541
+ let node_name = node.id.name;
542
+ const match = node_name.match(/^pmcr_(.*)_(\d+)$/);
543
+ if (match) {
544
+ node_name = match[1];
545
+ suffix = parseInt(match[2], 10) + 1;
546
+ }
547
+ if (!locals.inners) {
548
+ // find all the names declared in this scope, to avoid
549
+ // more conflicts
550
+ locals.inners = {};
551
+ traverseAst(locals.node, (node) => {
552
+ if (node.type === "VariableDeclarator") {
553
+ locals.inners[node.id.name] = true;
554
+ }
555
+ });
556
+ }
557
+ let name;
558
+ while (true) {
559
+ name = `pmcr_${node_name}_${suffix}`;
560
+ if (
561
+ !hasProperty(map, name) &&
562
+ !hasProperty(locals.inners, name)
563
+ ) {
564
+ // we also need to ensure that we don't hide the name of
565
+ // an outer module, class, function, enum or variable,
566
+ // since someone might want to access it from this scope.
567
+ let ok = false;
568
+ let i;
569
+ for (i = state.stack.length; i--; ) {
570
+ const elm = state.stack[i];
571
+ if (ok) {
572
+ if (hasProperty(elm.decls, name)) {
573
+ break;
574
+ }
575
+ } else if (elm.node.type === "FunctionDeclaration") {
576
+ ok = true;
577
+ }
578
+ }
579
+ if (i < 0) {
580
+ break;
581
+ }
582
+ }
583
+ suffix++;
584
+ }
585
+ map[node.id.name] = name;
586
+ map[name] = true;
587
+ node.id.name = name;
588
+ } else {
589
+ map[node.id.name] = true;
590
+ }
591
+ }
592
+ return ["init"];
593
+ }
594
+ case "UnaryExpression":
595
+ if (node.operator == ":") {
596
+ // If we produce a Symbol, for a given name,
597
+ // its possible that someone uses that symbol
598
+ // indirectly, so we can't remove any enums or
599
+ // constants with that name (we can still replace
600
+ // uses of those constants though).
601
+ state.exposed[node.argument.name] = true;
602
+ // In any case, we can't replace *this* use of the
603
+ // symbol with its value...
604
+ return false;
605
+ }
606
+ break;
607
+ case "Identifier": {
608
+ const map = state.localsStack.slice(-1).pop().map;
609
+ if (map) {
610
+ if (hasProperty(map, node.name)) {
611
+ const name = map[node.name];
612
+ if (name !== true) {
613
+ node.name = name;
614
+ }
615
+ }
616
+ }
617
+ if (hasProperty(state.index, node.name)) {
618
+ if (!lookupAndReplace(node)) {
619
+ state.exposed[node.name] = true;
620
+ }
621
+ }
622
+ return false;
623
+ }
624
+ case "MemberExpression":
625
+ if (node.property.type === "Identifier" && !node.computed) {
626
+ if (hasProperty(state.index, node.property.name)) {
627
+ if (lookupAndReplace(node)) {
628
+ return false;
629
+ } else {
630
+ state.exposed[node.property.name] = true;
631
+ }
632
+ }
633
+ // Don't optimize the property.
634
+ return ["object"];
635
+ }
636
+ break;
637
+ case "BlockStatement": {
638
+ const map = state.localsStack.slice(-1).pop().map;
639
+ if (map) {
640
+ state.localsStack.push({
641
+ node,
642
+ map: { ...map },
643
+ });
644
+ }
645
+ break;
646
+ }
647
+ case "FunctionDeclaration": {
648
+ const map = {};
649
+ node.params && node.params.forEach((p) => (map[p.name] = true));
650
+ state.localsStack.push({ node, map });
651
+ const [parent] = state.stack.slice(-2);
652
+ if (parent.type == "ClassDeclaration" && !maybeCalled(node)) {
653
+ let used = false;
654
+ if (node.id.name == "initialize") {
655
+ used = true;
656
+ } else if (parent.superClass) {
657
+ used = checkInherited(parent, node.id.name);
658
+ }
659
+ if (used) {
660
+ if (!hasProperty(state.calledFunctions, node.id.name)) {
661
+ state.calledFunctions[node.id.name] = [];
662
+ }
663
+ state.calledFunctions[node.id.name].push(node);
664
+ }
665
+ }
666
+ }
667
+ }
668
+ };
669
+ state.post = (node) => {
670
+ if (state.localsStack.slice(-1).pop().node === node) {
671
+ state.localsStack.pop();
672
+ }
673
+ const opt = optimizeNode(node);
674
+ if (opt) {
675
+ replace(node, opt);
676
+ return;
677
+ }
678
+ switch (node.type) {
679
+ case "ConditionalExpression":
680
+ case "IfStatement":
681
+ if (typeof node.test === "boolean") {
682
+ const rep = node.test ? node.consequent : node.alternate;
683
+ if (!rep) return false;
684
+ replace(node, rep);
685
+ }
686
+ break;
687
+ case "WhileStatement":
688
+ if (!node.body) return false;
689
+ break;
690
+ case "DoWhileStatement":
691
+ if (!node.test) return node.body;
692
+ break;
693
+
694
+ case "CallExpression": {
695
+ const [name, callees] = state.lookup(node.callee);
696
+ if (!callees || !callees.length) {
697
+ const n =
698
+ name ||
699
+ node.callee.name ||
700
+ (node.callee.property && node.callee.property.name);
701
+ if (n) {
702
+ state.exposed[n] = true;
703
+ } else {
704
+ // There are unnamed CallExpressions, such as new [size]
705
+ // So there's nothing to do here.
706
+ }
707
+ return;
708
+ }
709
+ if (callees.length == 1) {
710
+ const callee = callees[0].node;
711
+ if (
712
+ callee.optimizable &&
713
+ !callee.hasOverride &&
714
+ node.arguments.every((n) => getNodeValue(n)[0] !== null)
715
+ ) {
716
+ const ret = evaluateFunction(callee, node.arguments);
717
+ if (ret) {
718
+ replace(node, ret);
719
+ return;
720
+ }
721
+ }
722
+ }
723
+ if (!hasProperty(state.calledFunctions, name)) {
724
+ state.calledFunctions[name] = [];
725
+ }
726
+ callees.forEach((c) => state.calledFunctions[name].push(c.node));
727
+ break;
728
+ }
729
+ }
730
+ };
731
+ files.forEach((f) => {
732
+ collectNamespaces(f.ast, state);
733
+ });
734
+ files.forEach((f) => {
735
+ traverseAst(f.ast, null, (node) => {
736
+ switch (node.type) {
737
+ case "EnumStringBody":
738
+ if (
739
+ node.members.every((m) => {
740
+ const name = m.name || m.id.name;
741
+ return (
742
+ hasProperty(state.index, name) &&
743
+ !hasProperty(state.exposed, name)
744
+ );
745
+ })
746
+ ) {
747
+ node.enumType = [
748
+ ...new Set(
749
+ node.members.map((m) => {
750
+ if (!m.init) return "Number";
751
+ const [node, type] = getNodeValue(m.init);
752
+ if (!node) {
753
+ throw new Error("Failed to get type for eliminated enum");
754
+ }
755
+ return type;
756
+ })
757
+ ),
758
+ ].join(" or ");
759
+ node.members.splice(0);
760
+ }
761
+ break;
762
+ case "EnumDeclaration":
763
+ if (!node.body.members.length) {
764
+ if (!node.id) return false;
765
+ if (!node.body.enumType) {
766
+ throw new Error("Missing enumType on optimized enum");
767
+ }
768
+ replace(node, {
769
+ type: "TypedefDeclaration",
770
+ id: node.id,
771
+ ts: {
772
+ type: "UnaryExpression",
773
+ argument: { type: "TypeSpecList", ts: [node.body.enumType] },
774
+ prefix: true,
775
+ operator: " as",
776
+ },
777
+ });
778
+ }
779
+ break;
780
+ case "VariableDeclaration": {
781
+ node.declarations = node.declarations.filter(
782
+ (d) =>
783
+ !hasProperty(state.index, d.id.name) ||
784
+ hasProperty(state.exposed, d.id.name)
785
+ );
786
+ if (!node.declarations.length) {
787
+ return false;
788
+ }
789
+ break;
790
+ }
791
+ case "ClassElement":
792
+ if (!node.item) {
793
+ return false;
794
+ }
795
+ break;
796
+ case "FunctionDeclaration":
797
+ if (!maybeCalled(node)) {
798
+ return false;
799
+ }
800
+ break;
801
+ }
802
+ });
803
+ });
804
+
805
+ return files;
806
+ }
807
+
59
808
  ;// CONCATENATED MODULE: ./src/negative-fixups.js
60
809
  /*
61
810
  * This is strange. It pretty much has to be a bug in the sdk,
@@ -112,6 +861,8 @@ const negativeFixups = [
112
861
  "Toybox.WatchUi.LAYOUT_HALIGN_LEFT",
113
862
  ];
114
863
 
864
+ ;// CONCATENATED MODULE: external "./sdk-util.cjs"
865
+ const external_sdk_util_cjs_namespaceObject = require("./sdk-util.cjs");
115
866
  ;// CONCATENATED MODULE: ./src/api.js
116
867
 
117
868
 
@@ -119,7 +870,9 @@ const negativeFixups = [
119
870
 
120
871
 
121
872
 
122
- const LiteralIntegerRe = /^(0x[0-9a-f]+|\d+)(l)?$/;
873
+
874
+
875
+ const api_LiteralIntegerRe = /^(0x[0-9a-f]+|\d+)(l)?$/i;
123
876
  /*
124
877
  * This is an unfortunate hack. I want to be able to extract things
125
878
  * like the types of all of a Class's variables (in particular the type
@@ -131,11 +884,11 @@ const LiteralIntegerRe = /^(0x[0-9a-f]+|\d+)(l)?$/;
131
884
  */
132
885
 
133
886
  // Extract all enum values from api.mir
134
- async function getApiMapping(state) {
887
+ async function api_getApiMapping(state) {
135
888
  // get the path to the currently active sdk
136
889
  const parser = prettier_plugin_monkeyc_namespaceObject.parsers.monkeyc;
137
890
 
138
- const sdk = await (0,external_util_cjs_namespaceObject.getSdkPath)();
891
+ const sdk = await (0,external_sdk_util_cjs_namespaceObject.getSdkPath)();
139
892
 
140
893
  const api = (await promises_namespaceObject.readFile(`${sdk}bin/api.mir`))
141
894
  .toString()
@@ -145,7 +898,7 @@ async function getApiMapping(state) {
145
898
  .replace(/^(\s*type)\s/gm, "$1def ");
146
899
 
147
900
  try {
148
- const result = collectNamespaces(parser.parse(api, {}), state);
901
+ const result = api_collectNamespaces(parser.parse(api, {}), state);
149
902
  negativeFixups.forEach((fixup) => {
150
903
  const value = fixup.split(".").reduce((state, part) => {
151
904
  const decls = state.decls[part];
@@ -170,18 +923,18 @@ async function getApiMapping(state) {
170
923
  }
171
924
  }
172
925
 
173
- function hasProperty(obj, prop) {
926
+ function api_hasProperty(obj, prop) {
174
927
  return obj && Object.prototype.hasOwnProperty.call(obj, prop);
175
928
  }
176
929
 
177
- function collectNamespaces(ast, state) {
930
+ function api_collectNamespaces(ast, state) {
178
931
  state = state || {};
179
932
  if (!state.index) state.index = {};
180
933
  if (!state.stack) {
181
934
  state.stack = [{ type: "Program", name: "$", fullName: "$" }];
182
935
  }
183
936
  const checkOne = (ns, name) => {
184
- if (hasProperty(ns.decls, name)) {
937
+ if (api_hasProperty(ns.decls, name)) {
185
938
  return ns.decls[name];
186
939
  }
187
940
  return null;
@@ -219,7 +972,7 @@ function collectNamespaces(ast, state) {
219
972
  };
220
973
 
221
974
  state.traverse = (root) =>
222
- traverseAst(
975
+ api_traverseAst(
223
976
  root,
224
977
  (node) => {
225
978
  try {
@@ -263,7 +1016,7 @@ function collectNamespaces(ast, state) {
263
1016
  .join(".");
264
1017
  if (elm.name) {
265
1018
  if (!parent.decls) parent.decls = {};
266
- if (hasProperty(parent.decls, elm.name)) {
1019
+ if (api_hasProperty(parent.decls, elm.name)) {
267
1020
  const what =
268
1021
  node.type == "ModuleDeclaration" ? "type" : "node";
269
1022
  const e = parent.decls[elm.name].find(
@@ -296,7 +1049,7 @@ function collectNamespaces(ast, state) {
296
1049
  case "TypedefDeclaration": {
297
1050
  const [parent] = state.stack.slice(-1);
298
1051
  if (!parent.decls) parent.decls = {};
299
- if (!hasProperty(parent.decls, node.id.name)) {
1052
+ if (!api_hasProperty(parent.decls, node.id.name)) {
300
1053
  parent.decls[node.id.name] = [];
301
1054
  }
302
1055
  (0,external_util_cjs_namespaceObject.pushUnique)(
@@ -309,12 +1062,12 @@ function collectNamespaces(ast, state) {
309
1062
  const [parent] = state.stack.slice(-1);
310
1063
  if (!parent.decls) parent.decls = {};
311
1064
  node.declarations.forEach((decl) => {
312
- if (!hasProperty(parent.decls, decl.id.name)) {
1065
+ if (!api_hasProperty(parent.decls, decl.id.name)) {
313
1066
  parent.decls[decl.id.name] = [];
314
1067
  }
315
1068
  if (node.kind == "const") {
316
1069
  (0,external_util_cjs_namespaceObject.pushUnique)(parent.decls[decl.id.name], decl.init);
317
- if (!hasProperty(state.index, decl.id.name)) {
1070
+ if (!api_hasProperty(state.index, decl.id.name)) {
318
1071
  state.index[decl.id.name] = [];
319
1072
  }
320
1073
  (0,external_util_cjs_namespaceObject.pushUnique)(state.index[decl.id.name], parent);
@@ -335,10 +1088,16 @@ function collectNamespaces(ast, state) {
335
1088
  let name, init;
336
1089
  if (m.type == "EnumStringMember") {
337
1090
  name = m.id.name;
338
- init = m.init;
1091
+ init = getLiteralNode(m.init);
1092
+ if (!init) {
1093
+ throw new Error("Unexpected enum initializer");
1094
+ }
1095
+ if (init != m.init) {
1096
+ m.init = init;
1097
+ }
339
1098
  if (
340
1099
  init.type == "Literal" &&
341
- LiteralIntegerRe.test(init.raw)
1100
+ api_LiteralIntegerRe.test(init.raw)
342
1101
  ) {
343
1102
  prev = init.value;
344
1103
  }
@@ -359,11 +1118,11 @@ function collectNamespaces(ast, state) {
359
1118
  };
360
1119
  }
361
1120
  }
362
- if (!hasProperty(values, name)) {
1121
+ if (!api_hasProperty(values, name)) {
363
1122
  values[name] = [];
364
1123
  }
365
1124
  (0,external_util_cjs_namespaceObject.pushUnique)(values[name], init);
366
- if (!hasProperty(state.index, name)) {
1125
+ if (!api_hasProperty(state.index, name)) {
367
1126
  state.index[name] = [];
368
1127
  }
369
1128
  (0,external_util_cjs_namespaceObject.pushUnique)(state.index[name], parent);
@@ -413,7 +1172,7 @@ function collectNamespaces(ast, state) {
413
1172
  * - if post returns false, the node it was called on is
414
1173
  * removed.
415
1174
  */
416
- function traverseAst(node, pre, post) {
1175
+ function api_traverseAst(node, pre, post) {
417
1176
  const nodes = pre && pre(node);
418
1177
  if (nodes === false) return;
419
1178
  for (const key of nodes || Object.keys(node)) {
@@ -421,7 +1180,7 @@ function traverseAst(node, pre, post) {
421
1180
  if (!value) continue;
422
1181
  if (Array.isArray(value)) {
423
1182
  const deletions = value.reduce((state, obj, i) => {
424
- const repl = traverseAst(obj, pre, post);
1183
+ const repl = api_traverseAst(obj, pre, post);
425
1184
  if (repl === false) {
426
1185
  if (!state) state = {};
427
1186
  state[i] = true;
@@ -438,7 +1197,7 @@ function traverseAst(node, pre, post) {
438
1197
  );
439
1198
  }
440
1199
  } else if (typeof value == "object" && value.type) {
441
- const repl = traverseAst(value, pre, post);
1200
+ const repl = api_traverseAst(value, pre, post);
442
1201
  if (repl === false) {
443
1202
  delete node[key];
444
1203
  } else if (repl != null) {