@defold-typescript/transpiler 0.53.0 → 0.54.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1665,6 +1665,41 @@ function mask(lua) {
1665
1665
  return { text, literals };
1666
1666
  }
1667
1667
  var REQUIRE_RE = new RegExp(String.raw`(?<![A-Za-z0-9_.])require\s*\(\s*${PLACEHOLDER}(\d+)${PLACEHOLDER}\s*\)`, "g");
1668
+ function isLuaSpace(c) {
1669
+ return c === " " || c === "\t" || c === `
1670
+ ` || c === "\r";
1671
+ }
1672
+ var REQUIRE_WORD = "require";
1673
+ function callSpan(lua, literal) {
1674
+ let i = literal.start - 1;
1675
+ while (isLuaSpace(lua[i])) {
1676
+ i--;
1677
+ }
1678
+ if (lua[i] !== "(") {
1679
+ return;
1680
+ }
1681
+ i--;
1682
+ while (isLuaSpace(lua[i])) {
1683
+ i--;
1684
+ }
1685
+ const wordEnd = i + 1;
1686
+ const wordStart = wordEnd - REQUIRE_WORD.length;
1687
+ if (wordStart < 0 || lua.slice(wordStart, wordEnd) !== REQUIRE_WORD) {
1688
+ return;
1689
+ }
1690
+ const before = lua[wordStart - 1];
1691
+ if (before !== undefined && /[A-Za-z0-9_.]/.test(before)) {
1692
+ return;
1693
+ }
1694
+ let j = literal.end;
1695
+ while (isLuaSpace(lua[j])) {
1696
+ j++;
1697
+ }
1698
+ if (lua[j] !== ")") {
1699
+ return;
1700
+ }
1701
+ return { callStart: wordStart, callEnd: j + 1 };
1702
+ }
1668
1703
  function scanEmittedRequires(lua) {
1669
1704
  const { text, literals } = mask(lua);
1670
1705
  const sites = [];
@@ -1673,7 +1708,12 @@ function scanEmittedRequires(lua) {
1673
1708
  if (literal === undefined) {
1674
1709
  continue;
1675
1710
  }
1676
- sites.push({ path: literal.value, start: literal.start, end: literal.end });
1711
+ sites.push({
1712
+ path: literal.value,
1713
+ start: literal.start,
1714
+ end: literal.end,
1715
+ ...callSpan(lua, literal)
1716
+ });
1677
1717
  }
1678
1718
  return sites;
1679
1719
  }
@@ -3186,7 +3226,7 @@ function relativeAddressesFrom(index, context) {
3186
3226
  import { readFileSync as readFileSync2 } from "node:fs";
3187
3227
  import { createRequire as createRequire2 } from "node:module";
3188
3228
  import * as path4 from "node:path";
3189
- import * as ts16 from "typescript";
3229
+ import * as ts17 from "typescript";
3190
3230
  import * as tstl3 from "typescript-to-lua";
3191
3231
 
3192
3232
  // src/companion-emit.ts
@@ -3735,6 +3775,128 @@ var editorScriptErasurePlugin = {
3735
3775
  }
3736
3776
  };
3737
3777
 
3778
+ // src/native-global-modules.ts
3779
+ import * as ts15 from "typescript";
3780
+ var GLOBAL_TABLE_TAG = "globalTable";
3781
+ var GLOBAL_TABLE_REQUIRE_MARKER = "@defold-typescript:global-table:";
3782
+ var NO_RESOLUTION_PREFIX = "@NoResolution:";
3783
+ var LUA_GLOBAL_NAME_RE = /^[A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z_][A-Za-z0-9_]*)*$/;
3784
+ function resolveGlobalTableTag(specifier, checker) {
3785
+ if (!ts15.isStringLiteral(specifier)) {
3786
+ return { kind: "untagged" };
3787
+ }
3788
+ const symbol = checker.getSymbolAtLocation(specifier);
3789
+ for (const declaration of symbol?.declarations ?? []) {
3790
+ if (!ts15.isModuleDeclaration(declaration) || !ts15.isStringLiteral(declaration.name)) {
3791
+ continue;
3792
+ }
3793
+ const tag = ts15.getJSDocTags(declaration).find((t) => t.tagName.text === GLOBAL_TABLE_TAG);
3794
+ if (tag === undefined) {
3795
+ continue;
3796
+ }
3797
+ const comment = ts15.getTextOfJSDocComment(tag.comment)?.trim();
3798
+ const globalName = comment === undefined || comment === "" ? specifier.text : comment.split(/\s+/)[0] ?? specifier.text;
3799
+ if (!LUA_GLOBAL_NAME_RE.test(globalName)) {
3800
+ return { kind: "invalid", globalName, moduleId: specifier.text };
3801
+ }
3802
+ return { kind: "tagged", globalName };
3803
+ }
3804
+ return { kind: "untagged" };
3805
+ }
3806
+ function markerRequirePath(globalName) {
3807
+ return `${NO_RESOLUTION_PREFIX}${GLOBAL_TABLE_REQUIRE_MARKER}${globalName}`;
3808
+ }
3809
+ function invalidGlobalName(node, resolution) {
3810
+ const file = node.getSourceFile();
3811
+ const start = node.getStart(file);
3812
+ return {
3813
+ file,
3814
+ start,
3815
+ length: node.getEnd() - start,
3816
+ category: ts15.DiagnosticCategory.Error,
3817
+ code: 0,
3818
+ messageText: `@${GLOBAL_TABLE_TAG} on module "${resolution.moduleId}" names \`${resolution.globalName}\`, which is not a valid Lua global name; use dotted identifiers (\`pixels\`, \`b2d.body\`).`
3819
+ };
3820
+ }
3821
+ var nativeGlobalModulesPlugin = {
3822
+ visitors: {
3823
+ [ts15.SyntaxKind.ImportDeclaration]: (node, context) => {
3824
+ const resolution = resolveGlobalTableTag(node.moduleSpecifier, context.checker);
3825
+ if (resolution.kind === "invalid") {
3826
+ context.diagnostics.push(invalidGlobalName(node.moduleSpecifier, resolution));
3827
+ return context.superTransformStatements(node);
3828
+ }
3829
+ if (resolution.kind === "untagged") {
3830
+ return context.superTransformStatements(node);
3831
+ }
3832
+ if (node.importClause === undefined) {
3833
+ return [];
3834
+ }
3835
+ const rewritten = ts15.factory.updateImportDeclaration(node, node.modifiers, node.importClause, ts15.factory.createStringLiteral(markerRequirePath(resolution.globalName)), node.attributes);
3836
+ return context.superTransformStatements(rewritten);
3837
+ },
3838
+ [ts15.SyntaxKind.ExportDeclaration]: (node, context) => {
3839
+ const specifier = node.moduleSpecifier;
3840
+ if (specifier === undefined) {
3841
+ return context.superTransformStatements(node);
3842
+ }
3843
+ const resolution = resolveGlobalTableTag(specifier, context.checker);
3844
+ if (resolution.kind === "invalid") {
3845
+ context.diagnostics.push(invalidGlobalName(specifier, resolution));
3846
+ return context.superTransformStatements(node);
3847
+ }
3848
+ if (resolution.kind === "untagged") {
3849
+ return context.superTransformStatements(node);
3850
+ }
3851
+ const rewritten = ts15.factory.updateExportDeclaration(node, node.modifiers, node.isTypeOnly, node.exportClause, ts15.factory.createStringLiteral(markerRequirePath(resolution.globalName)), node.attributes);
3852
+ return context.superTransformStatements(rewritten);
3853
+ },
3854
+ [ts15.SyntaxKind.CallExpression]: (node, context) => {
3855
+ const specifier = node.arguments[0];
3856
+ if (node.expression.kind !== ts15.SyntaxKind.ImportKeyword || specifier === undefined) {
3857
+ return context.superTransformExpression(node);
3858
+ }
3859
+ const resolution = resolveGlobalTableTag(specifier, context.checker);
3860
+ if (resolution.kind === "invalid") {
3861
+ context.diagnostics.push(invalidGlobalName(specifier, resolution));
3862
+ return context.superTransformExpression(node);
3863
+ }
3864
+ if (resolution.kind === "untagged") {
3865
+ return context.superTransformExpression(node);
3866
+ }
3867
+ const rewritten = ts15.factory.updateCallExpression(node, node.expression, node.typeArguments, [
3868
+ ts15.factory.createStringLiteral(markerRequirePath(resolution.globalName)),
3869
+ ...node.arguments.slice(1)
3870
+ ]);
3871
+ return context.superTransformExpression(rewritten);
3872
+ }
3873
+ }
3874
+ };
3875
+ function rewriteGlobalAliasRequires(lua) {
3876
+ let out = "";
3877
+ let cursor = 0;
3878
+ for (const site of scanEmittedRequires(lua)) {
3879
+ if (!site.path.startsWith(GLOBAL_TABLE_REQUIRE_MARKER)) {
3880
+ continue;
3881
+ }
3882
+ if (site.callStart === undefined || site.callEnd === undefined) {
3883
+ continue;
3884
+ }
3885
+ out += lua.slice(cursor, site.callStart);
3886
+ out += `_G.${site.path.slice(GLOBAL_TABLE_REQUIRE_MARKER.length)}`;
3887
+ cursor = site.callEnd;
3888
+ }
3889
+ return out + lua.slice(cursor);
3890
+ }
3891
+ function findGlobalAliasMarkerLeak(lua) {
3892
+ for (const site of scanEmittedRequires(lua)) {
3893
+ if (site.path.startsWith(GLOBAL_TABLE_REQUIRE_MARKER)) {
3894
+ return site.path.slice(GLOBAL_TABLE_REQUIRE_MARKER.length);
3895
+ }
3896
+ }
3897
+ return;
3898
+ }
3899
+
3738
3900
  // src/transpile.ts
3739
3901
  import { readdirSync as readdirSync2, readFileSync } from "node:fs";
3740
3902
  import { createRequire } from "node:module";
@@ -3742,17 +3904,17 @@ import * as path3 from "node:path";
3742
3904
  import * as tstl2 from "typescript-to-lua";
3743
3905
 
3744
3906
  // src/go-property-direct-call.ts
3745
- import * as ts15 from "typescript";
3907
+ import * as ts16 from "typescript";
3746
3908
  var GO_PROPERTY_DIRECT_CALL_MESSAGE = "`go.property` called directly is deprecated; declare the property in `defineScript({ properties })` so it types onto `self` (the transpiler emits the registration for you).";
3747
3909
  function findDirectGoPropertyCalls(sourceFile) {
3748
3910
  const calls = [];
3749
3911
  const visit = (node) => {
3750
- if (ts15.isCallExpression(node) && ts15.isPropertyAccessExpression(node.expression) && node.expression.name.text === "property" && ts15.isIdentifier(node.expression.expression) && node.expression.expression.text === "go") {
3912
+ if (ts16.isCallExpression(node) && ts16.isPropertyAccessExpression(node.expression) && node.expression.name.text === "property" && ts16.isIdentifier(node.expression.expression) && node.expression.expression.text === "go") {
3751
3913
  calls.push(node);
3752
3914
  }
3753
- ts15.forEachChild(node, visit);
3915
+ ts16.forEachChild(node, visit);
3754
3916
  };
3755
- ts15.forEachChild(sourceFile, visit);
3917
+ ts16.forEachChild(sourceFile, visit);
3756
3918
  return calls;
3757
3919
  }
3758
3920
 
@@ -3860,6 +4022,7 @@ function collectOutputs(transpiledFiles, diagnostics, userKeys, emittedCompanion
3860
4022
  const lua = {};
3861
4023
  const sourceMaps = {};
3862
4024
  const companions = {};
4025
+ const markerLeaks = [];
3863
4026
  let lualib;
3864
4027
  for (const file of transpiledFiles) {
3865
4028
  if (isLualibBundle(file) && typeof file.lua === "string") {
@@ -3870,13 +4033,23 @@ function collectOutputs(transpiledFiles, diagnostics, userKeys, emittedCompanion
3870
4033
  if (!userSource || typeof file.lua !== "string") {
3871
4034
  continue;
3872
4035
  }
3873
- lua[userSource.fileName] = file.lua;
4036
+ const swapped = rewriteGlobalAliasRequires(file.lua);
4037
+ lua[userSource.fileName] = swapped;
4038
+ const leaked = findGlobalAliasMarkerLeak(swapped);
4039
+ if (leaked !== undefined) {
4040
+ markerLeaks.push({ file: userSource.fileName, globalName: leaked });
4041
+ }
3874
4042
  if (typeof file.luaSourceMap === "string") {
3875
4043
  sourceMaps[userSource.fileName] = file.luaSourceMap;
3876
4044
  }
3877
4045
  const companion = emittedCompanions.get(userSource.fileName);
3878
4046
  if (companion !== undefined) {
3879
- companions[userSource.fileName] = companion;
4047
+ const swappedCompanion = rewriteGlobalAliasRequires(companion);
4048
+ companions[userSource.fileName] = swappedCompanion;
4049
+ const companionLeak = findGlobalAliasMarkerLeak(swappedCompanion);
4050
+ if (companionLeak !== undefined) {
4051
+ markerLeaks.push({ file: userSource.fileName, globalName: companionLeak });
4052
+ }
3880
4053
  }
3881
4054
  }
3882
4055
  const collectedDiagnostics = diagnostics.map((d) => {
@@ -3891,6 +4064,12 @@ function collectOutputs(transpiledFiles, diagnostics, userKeys, emittedCompanion
3891
4064
  const { line, character } = d.file.getLineAndCharacterOfPosition(d.start);
3892
4065
  return { file: fileName, message, line: line + 1, column: character + 1 };
3893
4066
  });
4067
+ for (const leak of markerLeaks) {
4068
+ collectedDiagnostics.push({
4069
+ file: leak.file,
4070
+ message: `@globalTable lowering left an unrewritten require for \`${leak.globalName}\` in the emitted chunk; the output would fail to load in Defold.`
4071
+ });
4072
+ }
3894
4073
  const scanned = new Set;
3895
4074
  let timersImported = false;
3896
4075
  for (const file of transpiledFiles) {
@@ -3951,6 +4130,7 @@ function transpileVirtual(files, companionEmit, overrides = {}) {
3951
4130
  { plugin: physicsEventGuardLoweringPlugin },
3952
4131
  { plugin: messageDispatchLoweringPlugin },
3953
4132
  { plugin: timersLoweringPlugin },
4133
+ { plugin: nativeGlobalModulesPlugin },
3954
4134
  { plugin: hsmLoweringPlugin },
3955
4135
  { plugin: typeApplicationErasurePlugin },
3956
4136
  { plugin: companionEmit.plugin }
@@ -4042,6 +4222,7 @@ function compilerOptions(companionEmit) {
4042
4222
  { plugin: physicsEventGuardLoweringPlugin },
4043
4223
  { plugin: messageDispatchLoweringPlugin },
4044
4224
  { plugin: timersLoweringPlugin },
4225
+ { plugin: nativeGlobalModulesPlugin },
4045
4226
  { plugin: hsmLoweringPlugin },
4046
4227
  { plugin: typeApplicationErasurePlugin },
4047
4228
  { plugin: companionEmit.plugin }
@@ -4086,7 +4267,7 @@ function createTranspileSession() {
4086
4267
  if (cached) {
4087
4268
  return cached;
4088
4269
  }
4089
- const created = ts16.createSourceFile(fileName, content, ts16.ScriptTarget.Latest, false);
4270
+ const created = ts17.createSourceFile(fileName, content, ts17.ScriptTarget.Latest, false);
4090
4271
  sourceFileCache.set(normalized, created);
4091
4272
  return created;
4092
4273
  }
@@ -4105,7 +4286,7 @@ function createTranspileSession() {
4105
4286
  return cached;
4106
4287
  }
4107
4288
  const fileContent = readFileSync2(filePath, "utf8");
4108
- const created = ts16.createSourceFile(filePath, fileContent, ts16.ScriptTarget.Latest, false);
4289
+ const created = ts17.createSourceFile(filePath, fileContent, ts17.ScriptTarget.Latest, false);
4109
4290
  libCache.set(fileName, created);
4110
4291
  return created;
4111
4292
  }
@@ -4118,10 +4299,10 @@ function createTranspileSession() {
4118
4299
  return AMBIENT_FILES[normalized];
4119
4300
  }
4120
4301
  const host = {
4121
- fileExists: (fileName) => mergedContent(normalizeSlashes(fileName)) !== undefined || ts16.sys.fileExists(fileName),
4302
+ fileExists: (fileName) => mergedContent(normalizeSlashes(fileName)) !== undefined || ts17.sys.fileExists(fileName),
4122
4303
  getCanonicalFileName: (fileName) => fileName,
4123
4304
  getCurrentDirectory: () => "",
4124
- getDefaultLibFileName: ts16.getDefaultLibFileName,
4305
+ getDefaultLibFileName: ts17.getDefaultLibFileName,
4125
4306
  readFile: () => "",
4126
4307
  getNewLine: () => `
4127
4308
  `,
@@ -4143,15 +4324,15 @@ function createTranspileSession() {
4143
4324
  const userKeys = new Set(userFiles.keys());
4144
4325
  const rootNames = [...Object.keys(AMBIENT_FILES).map(normalizeSlashes), ...userKeys];
4145
4326
  const companionEmit = createCompanionEmitPlugin();
4146
- program = ts16.createProgram(rootNames, compilerOptions(companionEmit), host, program);
4147
- const preEmitDiagnostics = ts16.getPreEmitDiagnostics(program);
4327
+ program = ts17.createProgram(rootNames, compilerOptions(companionEmit), host, program);
4328
+ const preEmitDiagnostics = ts17.getPreEmitDiagnostics(program);
4148
4329
  const collector = createOutputCollector();
4149
4330
  const { diagnostics: transpileDiagnostics } = new tstl3.Transpiler().emit({
4150
4331
  program,
4151
4332
  writeFile: collector.writeFile
4152
4333
  });
4153
4334
  const diagnostics = [
4154
- ...ts16.sortAndDeduplicateDiagnostics([...preEmitDiagnostics, ...transpileDiagnostics])
4335
+ ...ts17.sortAndDeduplicateDiagnostics([...preEmitDiagnostics, ...transpileDiagnostics])
4155
4336
  ];
4156
4337
  return collectOutputs(collector.files, diagnostics, userKeys, companionEmit.companions);
4157
4338
  }
@@ -4161,7 +4342,7 @@ function createTranspileSession() {
4161
4342
  };
4162
4343
  }
4163
4344
  // src/text-script-property.ts
4164
- import * as ts17 from "typescript";
4345
+ import * as ts18 from "typescript";
4165
4346
  function findTextScriptProperties(program, fileNames) {
4166
4347
  const checker = program.getTypeChecker();
4167
4348
  const findings = [];
@@ -4172,7 +4353,7 @@ function findTextScriptProperties(program, fileNames) {
4172
4353
  }
4173
4354
  const located = [];
4174
4355
  const visit = (node) => {
4175
- if (ts17.isCallExpression(node)) {
4356
+ if (ts18.isCallExpression(node)) {
4176
4357
  const hooks = factoryHooks(node, checker);
4177
4358
  for (const property of hooks?.properties ?? []) {
4178
4359
  if (!isPropertiesHook(property)) {
@@ -4185,7 +4366,7 @@ function findTextScriptProperties(program, fileNames) {
4185
4366
  }
4186
4367
  }
4187
4368
  }
4188
- ts17.forEachChild(node, visit);
4369
+ ts18.forEachChild(node, visit);
4189
4370
  };
4190
4371
  visit(sourceFile);
4191
4372
  for (const call of findDirectGoPropertyCalls(sourceFile)) {
@@ -4194,7 +4375,7 @@ function findTextScriptProperties(program, fileNames) {
4194
4375
  continue;
4195
4376
  }
4196
4377
  if (mayHoldString(checker.getTypeAtLocation(value), checker)) {
4197
- const name = ts17.isStringLiteralLike(nameArgument) ? nameArgument.text : nameArgument.getText(sourceFile);
4378
+ const name = ts18.isStringLiteralLike(nameArgument) ? nameArgument.text : nameArgument.getText(sourceFile);
4198
4379
  located.push({ name, value });
4199
4380
  }
4200
4381
  }
@@ -4211,20 +4392,20 @@ function mayHoldString(type, checker, visited = new Set) {
4211
4392
  return false;
4212
4393
  }
4213
4394
  visited.add(type);
4214
- if (type.flags & ts17.TypeFlags.StringLike) {
4395
+ if (type.flags & ts18.TypeFlags.StringLike) {
4215
4396
  return true;
4216
4397
  }
4217
4398
  if (type.isUnionOrIntersection()) {
4218
4399
  return type.types.some((member) => mayHoldString(member, checker, visited));
4219
4400
  }
4220
- if (type.flags & ts17.TypeFlags.Instantiable) {
4401
+ if (type.flags & ts18.TypeFlags.Instantiable) {
4221
4402
  const constraint = checker.getBaseConstraintOfType(type);
4222
4403
  return constraint !== undefined && constraint !== type ? mayHoldString(constraint, checker, visited) : false;
4223
4404
  }
4224
4405
  return false;
4225
4406
  }
4226
4407
  // src/url-address-slots.ts
4227
- import * as ts18 from "typescript";
4408
+ import * as ts19 from "typescript";
4228
4409
  var ADDRESS_CLASSES = {
4229
4410
  "game-object": true,
4230
4411
  component: true,
@@ -4243,7 +4424,7 @@ function isFragmentCaret(slot, position) {
4243
4424
  return slot.fragmentStart !== -1 && position >= slot.fragmentStart;
4244
4425
  }
4245
4426
  function tableKey(checker, callee) {
4246
- const symbol = checker.getSymbolAtLocation(ts18.isIdentifier(callee) ? callee : callee.name);
4427
+ const symbol = checker.getSymbolAtLocation(ts19.isIdentifier(callee) ? callee : callee.name);
4247
4428
  if (!symbol)
4248
4429
  return;
4249
4430
  const fqn = checker.getFullyQualifiedName(symbol);
@@ -4251,43 +4432,43 @@ function tableKey(checker, callee) {
4251
4432
  }
4252
4433
  function parameterName(checker, call, index) {
4253
4434
  const declaration = checker.getResolvedSignature(call)?.declaration;
4254
- if (!declaration || !ts18.isFunctionLike(declaration))
4435
+ if (!declaration || !ts19.isFunctionLike(declaration))
4255
4436
  return;
4256
4437
  const parameter = declaration.parameters[index];
4257
- if (!parameter || parameter.dotDotDotToken || !ts18.isIdentifier(parameter.name))
4438
+ if (!parameter || parameter.dotDotDotToken || !ts19.isIdentifier(parameter.name))
4258
4439
  return;
4259
4440
  return parameter.name.text;
4260
4441
  }
4261
4442
  var EQUALITY_OPERATORS = new Set([
4262
- ts18.SyntaxKind.EqualsEqualsEqualsToken,
4263
- ts18.SyntaxKind.ExclamationEqualsEqualsToken,
4264
- ts18.SyntaxKind.EqualsEqualsToken,
4265
- ts18.SyntaxKind.ExclamationEqualsToken
4443
+ ts19.SyntaxKind.EqualsEqualsEqualsToken,
4444
+ ts19.SyntaxKind.ExclamationEqualsEqualsToken,
4445
+ ts19.SyntaxKind.EqualsEqualsToken,
4446
+ ts19.SyntaxKind.ExclamationEqualsToken
4266
4447
  ]);
4267
4448
  function comparisonSatisfied(checker, entry, call) {
4268
4449
  if (entry.comparedParameter === undefined)
4269
4450
  return true;
4270
4451
  let node = call;
4271
- while (ts18.isParenthesizedExpression(node.parent))
4452
+ while (ts19.isParenthesizedExpression(node.parent))
4272
4453
  node = node.parent;
4273
4454
  const comparison = node.parent;
4274
- if (!ts18.isBinaryExpression(comparison))
4455
+ if (!ts19.isBinaryExpression(comparison))
4275
4456
  return false;
4276
4457
  if (!EQUALITY_OPERATORS.has(comparison.operatorToken.kind))
4277
4458
  return false;
4278
4459
  const other = comparison.left === node ? comparison.right : comparison.left;
4279
4460
  const symbol = checker.getSymbolAtLocation(other);
4280
- return (symbol?.declarations ?? []).some((declaration) => ts18.isParameter(declaration) && ts18.isIdentifier(declaration.name) && declaration.name.text === entry.comparedParameter);
4461
+ return (symbol?.declarations ?? []).some((declaration) => ts19.isParameter(declaration) && ts19.isIdentifier(declaration.name) && declaration.name.text === entry.comparedParameter);
4281
4462
  }
4282
4463
  function classifiedEntryOfArgument(checker, table, argument) {
4283
4464
  const call = argument.parent;
4284
- if (!ts18.isCallExpression(call))
4465
+ if (!ts19.isCallExpression(call))
4285
4466
  return;
4286
4467
  const argumentIndex = call.arguments.indexOf(argument);
4287
4468
  if (argumentIndex === -1)
4288
4469
  return;
4289
4470
  const callee = call.expression;
4290
- if (!ts18.isPropertyAccessExpression(callee) && !ts18.isIdentifier(callee))
4471
+ if (!ts19.isPropertyAccessExpression(callee) && !ts19.isIdentifier(callee))
4291
4472
  return;
4292
4473
  const fqn = tableKey(checker, callee);
4293
4474
  if (fqn === undefined)
@@ -4304,10 +4485,10 @@ function classifiedEntryOfArgument(checker, table, argument) {
4304
4485
  }
4305
4486
  function slotThroughHashedCall(checker, table, literal) {
4306
4487
  const call = literal.parent;
4307
- if (!ts18.isCallExpression(call))
4488
+ if (!ts19.isCallExpression(call))
4308
4489
  return;
4309
4490
  const callee = call.expression;
4310
- if (!ts18.isPropertyAccessExpression(callee) && !ts18.isIdentifier(callee))
4491
+ if (!ts19.isPropertyAccessExpression(callee) && !ts19.isIdentifier(callee))
4311
4492
  return;
4312
4493
  if (tableKey(checker, callee) !== "hash")
4313
4494
  return;
@@ -4325,24 +4506,24 @@ function addressTextOf(checker, entry, call) {
4325
4506
  if (entry.addressParameter === undefined)
4326
4507
  return;
4327
4508
  const declaration = checker.getResolvedSignature(call)?.declaration;
4328
- if (!declaration || !ts18.isFunctionLike(declaration))
4509
+ if (!declaration || !ts19.isFunctionLike(declaration))
4329
4510
  return;
4330
- const index = declaration.parameters.findIndex((parameter) => !parameter.dotDotDotToken && ts18.isIdentifier(parameter.name) && parameter.name.text === entry.addressParameter);
4511
+ const index = declaration.parameters.findIndex((parameter) => !parameter.dotDotDotToken && ts19.isIdentifier(parameter.name) && parameter.name.text === entry.addressParameter);
4331
4512
  if (index === -1)
4332
4513
  return;
4333
4514
  const argument = call.arguments[index];
4334
- return argument && ts18.isStringLiteralLike(argument) ? argument.text : undefined;
4515
+ return argument && ts19.isStringLiteralLike(argument) ? argument.text : undefined;
4335
4516
  }
4336
4517
  function innermostLiteralAt(sourceFile, position) {
4337
4518
  let found;
4338
4519
  const visit = (node) => {
4339
4520
  if (position < node.getStart(sourceFile) || position > node.getEnd())
4340
4521
  return;
4341
- if (ts18.isStringLiteralLike(node))
4522
+ if (ts19.isStringLiteralLike(node))
4342
4523
  found = node;
4343
- ts18.forEachChild(node, visit);
4524
+ ts19.forEachChild(node, visit);
4344
4525
  };
4345
- ts18.forEachChild(sourceFile, visit);
4526
+ ts19.forEachChild(sourceFile, visit);
4346
4527
  return found;
4347
4528
  }
4348
4529
  function resolveClassifiedSlotAtPosition(input) {
@@ -4376,23 +4557,23 @@ function resolveClassifiedSlotAtPosition(input) {
4376
4557
  };
4377
4558
  }
4378
4559
  // src/url-cross-world-address.ts
4379
- import * as ts20 from "typescript";
4560
+ import * as ts21 from "typescript";
4380
4561
 
4381
4562
  // src/url-address-literals.ts
4382
- import * as ts19 from "typescript";
4563
+ import * as ts20 from "typescript";
4383
4564
  function isAmbient2(fileName) {
4384
4565
  return /[\\/]node_modules[\\/]/.test(fileName) || /(^|[\\/])lib\.[^\\/]*\.d\.ts$/.test(fileName);
4385
4566
  }
4386
4567
  function referenceTargetSymbol(type) {
4387
- if ((type.flags & ts19.TypeFlags.Object) === 0)
4568
+ if ((type.flags & ts20.TypeFlags.Object) === 0)
4388
4569
  return;
4389
- if ((type.objectFlags & ts19.ObjectFlags.Reference) === 0)
4570
+ if ((type.objectFlags & ts20.ObjectFlags.Reference) === 0)
4390
4571
  return;
4391
4572
  return type.target.symbol;
4392
4573
  }
4393
4574
  function ambientHashAliasTargets(checker, anchor) {
4394
4575
  const targets = new Set;
4395
- for (const symbol of checker.getSymbolsInScope(anchor, ts19.SymbolFlags.Type)) {
4576
+ for (const symbol of checker.getSymbolsInScope(anchor, ts20.SymbolFlags.Type)) {
4396
4577
  if (symbol.name !== "Hash")
4397
4578
  continue;
4398
4579
  const target = referenceTargetSymbol(checker.getDeclaredTypeOfSymbol(symbol));
@@ -4408,7 +4589,7 @@ function canonicalHashSymbols(checker, program) {
4408
4589
  if (!anchor)
4409
4590
  return canonical;
4410
4591
  const aliased = ambientHashAliasTargets(checker, anchor);
4411
- for (const symbol of checker.getSymbolsInScope(anchor, ts19.SymbolFlags.Function)) {
4592
+ for (const symbol of checker.getSymbolsInScope(anchor, ts20.SymbolFlags.Function)) {
4412
4593
  if (symbol.name !== "hash")
4413
4594
  continue;
4414
4595
  const declaration = symbol.declarations?.[0];
@@ -4430,7 +4611,7 @@ function hashedSourceOfType(checker, type, canonical) {
4430
4611
  return source?.isStringLiteral() ? source.value : undefined;
4431
4612
  }
4432
4613
  function staticAddressTextOf(checker, node, canonical) {
4433
- return ts19.isStringLiteralLike(node) ? node.text : hashedSourceOfType(checker, checker.getTypeAtLocation(node), canonical);
4614
+ return ts20.isStringLiteralLike(node) ? node.text : hashedSourceOfType(checker, checker.getTypeAtLocation(node), canonical);
4434
4615
  }
4435
4616
 
4436
4617
  // src/url-cross-world-address.ts
@@ -4454,7 +4635,7 @@ function checkCrossWorldAddresses(input) {
4454
4635
  if (worlds.length === 0)
4455
4636
  continue;
4456
4637
  const visit = (node) => {
4457
- if (ts20.isExpression(node)) {
4638
+ if (ts21.isExpression(node)) {
4458
4639
  const entry = addressEntryOfArgument(checker, table, node);
4459
4640
  if (entry !== undefined && slotRejectsForeignSocket(entry)) {
4460
4641
  const address = staticAddressTextOf(checker, node, canonical);
@@ -4471,14 +4652,14 @@ function checkCrossWorldAddresses(input) {
4471
4652
  }
4472
4653
  }
4473
4654
  }
4474
- ts20.forEachChild(node, visit);
4655
+ ts21.forEachChild(node, visit);
4475
4656
  };
4476
- ts20.forEachChild(sourceFile, visit);
4657
+ ts21.forEachChild(sourceFile, visit);
4477
4658
  }
4478
4659
  return findings;
4479
4660
  }
4480
4661
  // src/url-fragment-reachability.ts
4481
- import * as ts21 from "typescript";
4662
+ import * as ts22 from "typescript";
4482
4663
  function pathOfKey(key) {
4483
4664
  const socket = socketOfAddress(key);
4484
4665
  return socket === undefined ? key : key.slice(socket.length + 1);
@@ -4523,7 +4704,7 @@ function checkUrlFragmentReachability(input) {
4523
4704
  if (isAmbient2(sourceFile.fileName))
4524
4705
  continue;
4525
4706
  const visit = (node) => {
4526
- if (ts21.isExpression(node) && isAddressClass(addressClassOfArgument(checker, table, node))) {
4707
+ if (ts22.isExpression(node) && isAddressClass(addressClassOfArgument(checker, table, node))) {
4527
4708
  const text = staticAddressTextOf(checker, node, canonical);
4528
4709
  if (text !== undefined) {
4529
4710
  const hash = text.indexOf("#");
@@ -4542,9 +4723,9 @@ function checkUrlFragmentReachability(input) {
4542
4723
  }
4543
4724
  }
4544
4725
  }
4545
- ts21.forEachChild(node, visit);
4726
+ ts22.forEachChild(node, visit);
4546
4727
  };
4547
- ts21.forEachChild(sourceFile, visit);
4728
+ ts22.forEachChild(sourceFile, visit);
4548
4729
  }
4549
4730
  return { kind: "checked", findings };
4550
4731
  }
@@ -5,6 +5,10 @@ export interface EmittedRequireSite {
5
5
  readonly start: number;
6
6
  /** Offset one past its closing delimiter. */
7
7
  readonly end: number;
8
+ /** Offset of the `require` keyword, when the whole call is recoverable. */
9
+ readonly callStart?: number;
10
+ /** Offset one past the closing paren, when the whole call is recoverable. */
11
+ readonly callEnd?: number;
8
12
  }
9
13
  /**
10
14
  * Every code-position `require` of a string literal in an emitted Lua chunk, in
@@ -0,0 +1,20 @@
1
+ import type { Plugin } from "typescript-to-lua";
2
+ export declare const GLOBAL_TABLE_REQUIRE_MARKER = "@defold-typescript:global-table:";
3
+ export declare const nativeGlobalModulesPlugin: Plugin;
4
+ /**
5
+ * Replace every marker require the scanner can span with the global name it
6
+ * encodes: `local p = require("<marker>pixels")` becomes `local p = _G.pixels`,
7
+ * rooted in `_G` so no local in scope — including one named `pixels` — can
8
+ * shadow the global. A marker site without a recoverable call span is left in
9
+ * place for `findGlobalAliasMarkerLeak` to fail the build on.
10
+ */
11
+ export declare function rewriteGlobalAliasRequires(lua: string): string;
12
+ /**
13
+ * The global name of the first marker require surviving in a chunk after the
14
+ * swap, or undefined when the chunk is clean. Only require paths the scanner
15
+ * yields count — marker text in an ordinary string or comment is user data,
16
+ * not a leak. A survivor means an emit shape the swap cannot reach; the output
17
+ * would fail to load in Defold, so the caller turns it into a build error
18
+ * rather than shipping it.
19
+ */
20
+ export declare function findGlobalAliasMarkerLeak(lua: string): string | undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@defold-typescript/transpiler",
3
- "version": "0.53.0",
3
+ "version": "0.54.0",
4
4
  "description": "TypeScript-to-Lua build pipeline tuned for Defold's runtime.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -35,7 +35,7 @@
35
35
  "test": "bun test"
36
36
  },
37
37
  "dependencies": {
38
- "@defold-typescript/types": "0.53.0",
38
+ "@defold-typescript/types": "0.54.0",
39
39
  "@typescript-to-lua/language-extensions": "1.19.0",
40
40
  "typescript": "6.0.2",
41
41
  "typescript-to-lua": "^1.36.0"
Binary file
@@ -0,0 +1,183 @@
1
+ import * as ts from "typescript";
2
+ import type { Plugin } from "typescript-to-lua";
3
+ import { scanEmittedRequires } from "./lua-require-scan";
4
+
5
+ const GLOBAL_TABLE_TAG = "globalTable";
6
+
7
+ // Survives into the emitted require path, so it must collide with no real
8
+ // module path a build can own: `@` and `:` are impossible in one.
9
+ export const GLOBAL_TABLE_REQUIRE_MARKER = "@defold-typescript:global-table:";
10
+
11
+ // `@NoResolution:` tells TSTL's resolver to leave the path alone (the module
12
+ // has no source file in the program); the resolver strips the prefix, leaving
13
+ // the marker path for the post-pass swap.
14
+ const NO_RESOLUTION_PREFIX = "@NoResolution:";
15
+
16
+ const LUA_GLOBAL_NAME_RE = /^[A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z_][A-Za-z0-9_]*)*$/;
17
+
18
+ type TagResolution =
19
+ | { readonly kind: "untagged" }
20
+ | { readonly kind: "tagged"; readonly globalName: string }
21
+ | { readonly kind: "invalid"; readonly globalName: string; readonly moduleId: string };
22
+
23
+ // The tag is consulted only when the specifier resolves to an ambient
24
+ // `declare module "<id>"`; a real source file sharing the id resolves to the
25
+ // file and stays on the ordinary require lane.
26
+ function resolveGlobalTableTag(specifier: ts.Expression, checker: ts.TypeChecker): TagResolution {
27
+ if (!ts.isStringLiteral(specifier)) {
28
+ return { kind: "untagged" };
29
+ }
30
+ const symbol = checker.getSymbolAtLocation(specifier);
31
+ for (const declaration of symbol?.declarations ?? []) {
32
+ if (!ts.isModuleDeclaration(declaration) || !ts.isStringLiteral(declaration.name)) {
33
+ continue;
34
+ }
35
+ const tag = ts.getJSDocTags(declaration).find((t) => t.tagName.text === GLOBAL_TABLE_TAG);
36
+ if (tag === undefined) {
37
+ continue;
38
+ }
39
+ const comment = ts.getTextOfJSDocComment(tag.comment)?.trim();
40
+ const globalName =
41
+ comment === undefined || comment === ""
42
+ ? specifier.text
43
+ : (comment.split(/\s+/)[0] ?? specifier.text);
44
+ if (!LUA_GLOBAL_NAME_RE.test(globalName)) {
45
+ return { kind: "invalid", globalName, moduleId: specifier.text };
46
+ }
47
+ return { kind: "tagged", globalName };
48
+ }
49
+ return { kind: "untagged" };
50
+ }
51
+
52
+ function markerRequirePath(globalName: string): string {
53
+ return `${NO_RESOLUTION_PREFIX}${GLOBAL_TABLE_REQUIRE_MARKER}${globalName}`;
54
+ }
55
+
56
+ function invalidGlobalName(
57
+ node: ts.Node,
58
+ resolution: {
59
+ readonly globalName: string;
60
+ readonly moduleId: string;
61
+ },
62
+ ): ts.Diagnostic {
63
+ const file = node.getSourceFile();
64
+ const start = node.getStart(file);
65
+ return {
66
+ file,
67
+ start,
68
+ length: node.getEnd() - start,
69
+ category: ts.DiagnosticCategory.Error,
70
+ code: 0,
71
+ messageText: `@${GLOBAL_TABLE_TAG} on module "${resolution.moduleId}" names \`${resolution.globalName}\`, which is not a valid Lua global name; use dotted identifiers (\`pixels\`, \`b2d.body\`).`,
72
+ };
73
+ }
74
+
75
+ export const nativeGlobalModulesPlugin: Plugin = {
76
+ visitors: {
77
+ [ts.SyntaxKind.ImportDeclaration]: (node, context) => {
78
+ const resolution = resolveGlobalTableTag(node.moduleSpecifier, context.checker);
79
+ if (resolution.kind === "invalid") {
80
+ context.diagnostics.push(invalidGlobalName(node.moduleSpecifier, resolution));
81
+ return context.superTransformStatements(node);
82
+ }
83
+ if (resolution.kind === "untagged") {
84
+ return context.superTransformStatements(node);
85
+ }
86
+ if (node.importClause === undefined) {
87
+ // A side-effect import of a runtime global loads nothing.
88
+ return [];
89
+ }
90
+ const rewritten = ts.factory.updateImportDeclaration(
91
+ node,
92
+ node.modifiers,
93
+ node.importClause,
94
+ ts.factory.createStringLiteral(markerRequirePath(resolution.globalName)),
95
+ node.attributes,
96
+ );
97
+ return context.superTransformStatements(rewritten);
98
+ },
99
+ [ts.SyntaxKind.ExportDeclaration]: (node, context) => {
100
+ const specifier = node.moduleSpecifier;
101
+ if (specifier === undefined) {
102
+ return context.superTransformStatements(node);
103
+ }
104
+ const resolution = resolveGlobalTableTag(specifier, context.checker);
105
+ if (resolution.kind === "invalid") {
106
+ context.diagnostics.push(invalidGlobalName(specifier, resolution));
107
+ return context.superTransformStatements(node);
108
+ }
109
+ if (resolution.kind === "untagged") {
110
+ return context.superTransformStatements(node);
111
+ }
112
+ const rewritten = ts.factory.updateExportDeclaration(
113
+ node,
114
+ node.modifiers,
115
+ node.isTypeOnly,
116
+ node.exportClause,
117
+ ts.factory.createStringLiteral(markerRequirePath(resolution.globalName)),
118
+ node.attributes,
119
+ );
120
+ return context.superTransformStatements(rewritten);
121
+ },
122
+ [ts.SyntaxKind.CallExpression]: (node, context) => {
123
+ const specifier = node.arguments[0];
124
+ if (node.expression.kind !== ts.SyntaxKind.ImportKeyword || specifier === undefined) {
125
+ return context.superTransformExpression(node);
126
+ }
127
+ const resolution = resolveGlobalTableTag(specifier, context.checker);
128
+ if (resolution.kind === "invalid") {
129
+ context.diagnostics.push(invalidGlobalName(specifier, resolution));
130
+ return context.superTransformExpression(node);
131
+ }
132
+ if (resolution.kind === "untagged") {
133
+ return context.superTransformExpression(node);
134
+ }
135
+ const rewritten = ts.factory.updateCallExpression(node, node.expression, node.typeArguments, [
136
+ ts.factory.createStringLiteral(markerRequirePath(resolution.globalName)),
137
+ ...node.arguments.slice(1),
138
+ ]);
139
+ return context.superTransformExpression(rewritten);
140
+ },
141
+ },
142
+ };
143
+
144
+ /**
145
+ * Replace every marker require the scanner can span with the global name it
146
+ * encodes: `local p = require("<marker>pixels")` becomes `local p = _G.pixels`,
147
+ * rooted in `_G` so no local in scope — including one named `pixels` — can
148
+ * shadow the global. A marker site without a recoverable call span is left in
149
+ * place for `findGlobalAliasMarkerLeak` to fail the build on.
150
+ */
151
+ export function rewriteGlobalAliasRequires(lua: string): string {
152
+ let out = "";
153
+ let cursor = 0;
154
+ for (const site of scanEmittedRequires(lua)) {
155
+ if (!site.path.startsWith(GLOBAL_TABLE_REQUIRE_MARKER)) {
156
+ continue;
157
+ }
158
+ if (site.callStart === undefined || site.callEnd === undefined) {
159
+ continue;
160
+ }
161
+ out += lua.slice(cursor, site.callStart);
162
+ out += `_G.${site.path.slice(GLOBAL_TABLE_REQUIRE_MARKER.length)}`;
163
+ cursor = site.callEnd;
164
+ }
165
+ return out + lua.slice(cursor);
166
+ }
167
+
168
+ /**
169
+ * The global name of the first marker require surviving in a chunk after the
170
+ * swap, or undefined when the chunk is clean. Only require paths the scanner
171
+ * yields count — marker text in an ordinary string or comment is user data,
172
+ * not a leak. A survivor means an emit shape the swap cannot reach; the output
173
+ * would fail to load in Defold, so the caller turns it into a build error
174
+ * rather than shipping it.
175
+ */
176
+ export function findGlobalAliasMarkerLeak(lua: string): string | undefined {
177
+ for (const site of scanEmittedRequires(lua)) {
178
+ if (site.path.startsWith(GLOBAL_TABLE_REQUIRE_MARKER)) {
179
+ return site.path.slice(GLOBAL_TABLE_REQUIRE_MARKER.length);
180
+ }
181
+ }
182
+ return undefined;
183
+ }
package/src/session.ts CHANGED
@@ -9,6 +9,7 @@ import { hsmLoweringPlugin } from "./hsm-builtin";
9
9
  import { lifecycleErasurePlugin } from "./lifecycle-erasure";
10
10
  import { messageDispatchLoweringPlugin } from "./message-dispatch-lowering";
11
11
  import { messageGuardLoweringPlugin } from "./message-guard-lowering";
12
+ import { nativeGlobalModulesPlugin } from "./native-global-modules";
12
13
  import { physicsEventGuardLoweringPlugin } from "./physics-event-guard-lowering";
13
14
  import { timersLoweringPlugin } from "./timers-lowering";
14
15
  import { AMBIENT_FILES, collectOutputs, type TranspileProjectResult } from "./transpile";
@@ -54,6 +55,7 @@ function compilerOptions(companionEmit: CompanionEmitter): tstl.CompilerOptions
54
55
  { plugin: physicsEventGuardLoweringPlugin },
55
56
  { plugin: messageDispatchLoweringPlugin },
56
57
  { plugin: timersLoweringPlugin },
58
+ { plugin: nativeGlobalModulesPlugin },
57
59
  { plugin: hsmLoweringPlugin },
58
60
  { plugin: typeApplicationErasurePlugin },
59
61
  { plugin: companionEmit.plugin },
package/src/transpile.ts CHANGED
@@ -21,6 +21,11 @@ import { lifecycleErasurePlugin } from "./lifecycle-erasure";
21
21
  import { scanEmittedRequires } from "./lua-require-scan";
22
22
  import { messageDispatchLoweringPlugin } from "./message-dispatch-lowering";
23
23
  import { messageGuardLoweringPlugin } from "./message-guard-lowering";
24
+ import {
25
+ findGlobalAliasMarkerLeak,
26
+ nativeGlobalModulesPlugin,
27
+ rewriteGlobalAliasRequires,
28
+ } from "./native-global-modules";
24
29
  import { physicsEventGuardLoweringPlugin } from "./physics-event-guard-lowering";
25
30
  import { importsTimersModule, timersLoweringPlugin } from "./timers-lowering";
26
31
  import { TIMERS_RUNTIME } from "./timers-runtime";
@@ -258,6 +263,7 @@ export function collectOutputs(
258
263
  const lua: Record<string, string> = {};
259
264
  const sourceMaps: Record<string, string> = {};
260
265
  const companions: Record<string, string> = {};
266
+ const markerLeaks: { file: string; globalName: string }[] = [];
261
267
  let lualib: string | undefined;
262
268
  for (const file of transpiledFiles) {
263
269
  if (isLualibBundle(file) && typeof file.lua === "string") {
@@ -268,13 +274,25 @@ export function collectOutputs(
268
274
  if (!userSource || typeof file.lua !== "string") {
269
275
  continue;
270
276
  }
271
- lua[userSource.fileName] = file.lua;
277
+ // Column offsets after a swap shift, as they already do under the CLI's
278
+ // require respelling — accepted.
279
+ const swapped = rewriteGlobalAliasRequires(file.lua);
280
+ lua[userSource.fileName] = swapped;
281
+ const leaked = findGlobalAliasMarkerLeak(swapped);
282
+ if (leaked !== undefined) {
283
+ markerLeaks.push({ file: userSource.fileName, globalName: leaked });
284
+ }
272
285
  if (typeof file.luaSourceMap === "string") {
273
286
  sourceMaps[userSource.fileName] = file.luaSourceMap;
274
287
  }
275
288
  const companion = emittedCompanions.get(userSource.fileName);
276
289
  if (companion !== undefined) {
277
- companions[userSource.fileName] = companion;
290
+ const swappedCompanion = rewriteGlobalAliasRequires(companion);
291
+ companions[userSource.fileName] = swappedCompanion;
292
+ const companionLeak = findGlobalAliasMarkerLeak(swappedCompanion);
293
+ if (companionLeak !== undefined) {
294
+ markerLeaks.push({ file: userSource.fileName, globalName: companionLeak });
295
+ }
278
296
  }
279
297
  }
280
298
 
@@ -291,6 +309,15 @@ export function collectOutputs(
291
309
  return { file: fileName, message, line: line + 1, column: character + 1 };
292
310
  });
293
311
 
312
+ // A surviving marker is a build error, not a fallback: the alternative —
313
+ // leaving the original require — fails silently at load time in Defold.
314
+ for (const leak of markerLeaks) {
315
+ collectedDiagnostics.push({
316
+ file: leak.file,
317
+ message: `@globalTable lowering left an unrewritten require for \`${leak.globalName}\` in the emitted chunk; the output would fail to load in Defold.`,
318
+ });
319
+ }
320
+
294
321
  // Advisory scan of the user TypeScript AST for the deprecated direct
295
322
  // `go.property` call, plus detection of the timers polyfill import. Run here
296
323
  // (shared by transpileProject and the watch session) so both paths report it
@@ -378,6 +405,7 @@ function transpileVirtual(
378
405
  { plugin: physicsEventGuardLoweringPlugin },
379
406
  { plugin: messageDispatchLoweringPlugin },
380
407
  { plugin: timersLoweringPlugin },
408
+ { plugin: nativeGlobalModulesPlugin },
381
409
  { plugin: hsmLoweringPlugin },
382
410
  { plugin: typeApplicationErasurePlugin },
383
411
  { plugin: companionEmit.plugin },