@defold-typescript/transpiler 0.43.0 → 0.44.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,7 @@
1
- import type { Plugin } from "typescript-to-lua";
1
+ import { type Plugin } from "typescript-to-lua";
2
2
  export declare const HSM_MODULE_SPECIFIER = "@defold-typescript/types/hsm";
3
3
  export declare const HSM_REQUIRE_ROOT = "defold_typescript_hsm";
4
+ export declare const HSM_LUALIB_MODULES: ReadonlySet<string>;
4
5
  export interface HsmModule {
5
6
  /** The source file's basename without `.ts`. */
6
7
  readonly name: string;
package/dist/index.d.ts CHANGED
@@ -9,7 +9,7 @@ export { buildGuiFlipbookIndex } from "./gui-flipbook-index";
9
9
  export type { GuiNodeIndex } from "./gui-node-index";
10
10
  export { buildGuiNodeIndex, guiScriptResourceOf, guiScriptResourcesOf, } from "./gui-node-index";
11
11
  export type { HsmModule } from "./hsm-builtin";
12
- export { HSM_MODULE_SPECIFIER, HSM_REQUIRE_ROOT, hsmModules, hsmRequireName, hsmSourceFiles, resolveHsmSourceDir, } from "./hsm-builtin";
12
+ export { HSM_MODULE_SPECIFIER, HSM_REQUIRE_ROOT, hsmModules, hsmRequireName, hsmSourceFiles, requireHsmSourceDir, resolveHsmSourceDir, } from "./hsm-builtin";
13
13
  export { buildInputActionIndex } from "./input-action-index";
14
14
  export type { ExtensionDependency, LibraryIncludedEntries, LibrarySceneDocument, LibrarySceneDocuments, LibrarySharedEntry, } from "./library-dependencies";
15
15
  export { archiveWrapperOf, isContainedResourcePath, LIBRARY_DEPENDENCY_ROOT, libraryIncludedEntries, readGameProjectDependencies, readLibrarySceneDocuments, } from "./library-dependencies";
package/dist/index.js CHANGED
@@ -1388,6 +1388,7 @@ import { existsSync, readdirSync } from "node:fs";
1388
1388
  import * as path from "node:path";
1389
1389
  import { fileURLToPath } from "node:url";
1390
1390
  import * as ts4 from "typescript";
1391
+ import { LuaLibFeature } from "typescript-to-lua";
1391
1392
 
1392
1393
  // src/lua-require-scan.ts
1393
1394
  var PLACEHOLDER = "\x00";
@@ -1485,6 +1486,7 @@ function scanEmittedRequires(lua) {
1485
1486
  // src/hsm-builtin.ts
1486
1487
  var HSM_MODULE_SPECIFIER = "@defold-typescript/types/hsm";
1487
1488
  var HSM_REQUIRE_ROOT = "defold_typescript_hsm";
1489
+ var HSM_LUALIB_MODULES = new Set(["async"]);
1488
1490
  function hsmRequireName(name) {
1489
1491
  return `${HSM_REQUIRE_ROOT}.${name}`;
1490
1492
  }
@@ -1575,6 +1577,33 @@ function hsmClosure(chunks, compile) {
1575
1577
  function lowerSpecifier(module) {
1576
1578
  return ts4.factory.createStringLiteral(`@NoResolution:${module.requireName}`);
1577
1579
  }
1580
+ function loweredAnything(lowered) {
1581
+ return Array.isArray(lowered) ? lowered.length > 0 : lowered !== undefined;
1582
+ }
1583
+ function importSurvivesElision(node, context) {
1584
+ const clause = node.importClause;
1585
+ if (clause === undefined) {
1586
+ return true;
1587
+ }
1588
+ const { resolver } = context;
1589
+ if (clause.name !== undefined && resolver.isReferencedAliasDeclaration(clause)) {
1590
+ return true;
1591
+ }
1592
+ const bindings = clause.namedBindings;
1593
+ if (bindings === undefined) {
1594
+ return false;
1595
+ }
1596
+ if (ts4.isNamespaceImport(bindings)) {
1597
+ return resolver.isReferencedAliasDeclaration(bindings);
1598
+ }
1599
+ return bindings.elements.some((element) => resolver.isReferencedAliasDeclaration(element));
1600
+ }
1601
+ function registerLualib(module, emitted, context) {
1602
+ if (emitted && HSM_LUALIB_MODULES.has(module.name)) {
1603
+ context.usedLuaLibFeatures.add(LuaLibFeature.Promise);
1604
+ context.usedLuaLibFeatures.add(LuaLibFeature.New);
1605
+ }
1606
+ }
1578
1607
  var hsmLoweringPlugin = {
1579
1608
  visitors: {
1580
1609
  [ts4.SyntaxKind.ImportDeclaration]: (node, context) => {
@@ -1582,6 +1611,7 @@ var hsmLoweringPlugin = {
1582
1611
  if (module === undefined) {
1583
1612
  return context.superTransformStatements(node);
1584
1613
  }
1614
+ registerLualib(module, importSurvivesElision(node, context), context);
1585
1615
  return context.superTransformStatements(ts4.factory.updateImportDeclaration(node, node.modifiers, node.importClause, lowerSpecifier(module), node.attributes));
1586
1616
  },
1587
1617
  [ts4.SyntaxKind.ExportDeclaration]: (node, context) => {
@@ -1589,7 +1619,9 @@ var hsmLoweringPlugin = {
1589
1619
  if (module === undefined) {
1590
1620
  return context.superTransformStatements(node);
1591
1621
  }
1592
- return context.superTransformStatements(ts4.factory.updateExportDeclaration(node, node.modifiers, node.isTypeOnly, node.exportClause, lowerSpecifier(module), node.attributes));
1622
+ const lowered = context.superTransformStatements(ts4.factory.updateExportDeclaration(node, node.modifiers, node.isTypeOnly, node.exportClause, lowerSpecifier(module), node.attributes));
1623
+ registerLualib(module, loweredAnything(lowered), context);
1624
+ return lowered;
1593
1625
  }
1594
1626
  }
1595
1627
  };
@@ -2239,7 +2271,7 @@ var messageGuardLoweringPlugin = {
2239
2271
 
2240
2272
  // src/timers-lowering.ts
2241
2273
  import * as ts7 from "typescript";
2242
- import { LuaLibFeature } from "typescript-to-lua";
2274
+ import { LuaLibFeature as LuaLibFeature2 } from "typescript-to-lua";
2243
2275
  function isTimersSpecifier(node) {
2244
2276
  return ts7.isStringLiteral(node) && node.text === TIMERS_MODULE_SPECIFIER;
2245
2277
  }
@@ -2253,8 +2285,8 @@ var timersLoweringPlugin = {
2253
2285
  if (!isTimersSpecifier(node.moduleSpecifier)) {
2254
2286
  return context.superTransformStatements(node);
2255
2287
  }
2256
- context.usedLuaLibFeatures.add(LuaLibFeature.Promise);
2257
- context.usedLuaLibFeatures.add(LuaLibFeature.New);
2288
+ context.usedLuaLibFeatures.add(LuaLibFeature2.Promise);
2289
+ context.usedLuaLibFeatures.add(LuaLibFeature2.New);
2258
2290
  const rewritten = ts7.factory.updateImportDeclaration(node, node.modifiers, node.importClause, ts7.factory.createStringLiteral(TIMERS_REQUIRE_PATH), node.attributes);
2259
2291
  return context.superTransformStatements(rewritten);
2260
2292
  }
@@ -3650,6 +3682,9 @@ function transpileProject(input) {
3650
3682
  return collectOutputs(result.transpiledFiles, result.diagnostics, userKeys, companionEmit.companions);
3651
3683
  }
3652
3684
  var compiledHsm;
3685
+ function requiresLualib(lua) {
3686
+ return scanEmittedRequires(lua).some(({ path: requirePath }) => requirePath === "lualib_bundle");
3687
+ }
3653
3688
  function compileHsmModules() {
3654
3689
  if (compiledHsm !== undefined) {
3655
3690
  return compiledHsm;
@@ -3667,14 +3702,17 @@ function compileHsmModules() {
3667
3702
  const lua = {};
3668
3703
  for (const file of result.transpiledFiles) {
3669
3704
  if (isLualibBundle(file)) {
3670
- problems.push("the compiled Lua requires lualib_bundle");
3671
3705
  continue;
3672
3706
  }
3673
3707
  const source = file.sourceFiles.find((s) => nameByKey.has(s.fileName));
3674
3708
  const name = source === undefined ? undefined : nameByKey.get(source.fileName);
3675
- if (name !== undefined && typeof file.lua === "string") {
3676
- lua[name] = file.lua;
3709
+ if (name === undefined || typeof file.lua !== "string") {
3710
+ continue;
3711
+ }
3712
+ if (!HSM_LUALIB_MODULES.has(name) && requiresLualib(file.lua)) {
3713
+ problems.push(`the compiled ${name} module requires lualib_bundle`);
3677
3714
  }
3715
+ lua[name] = file.lua;
3678
3716
  }
3679
3717
  if (problems.length > 0) {
3680
3718
  throw new Error(`@defold-typescript/transpiler: the hsm library does not compile:
@@ -4282,6 +4320,7 @@ export {
4282
4320
  readLibrarySceneDocuments,
4283
4321
  readSceneDocuments,
4284
4322
  relativeAddressesFrom,
4323
+ requireHsmSourceDir,
4285
4324
  requirePathForRel,
4286
4325
  resolveClassifiedSlotAtPosition,
4287
4326
  resolveHsmSourceDir,
@@ -38,7 +38,8 @@ export declare function transpileProject(input: TranspileProjectInput): Transpil
38
38
  * project gets always comes from the compiler that builds it. Each file sits at
39
39
  * `defold_typescript_hsm/<module>.ts`, so a require between modules is already
40
40
  * spelled the way the user's lowered imports are. Throws when the compile
41
- * reports anything or needs the lualib bundle, which hsm must never do.
41
+ * reports anything, or when a module outside `HSM_LUALIB_MODULES` needs the
42
+ * lualib bundle.
42
43
  */
43
44
  export declare function compileHsmModules(): Readonly<Record<string, string>>;
44
45
  export declare function transpile(source: string): TranspileResult;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@defold-typescript/transpiler",
3
- "version": "0.43.0",
3
+ "version": "0.44.1",
4
4
  "description": "TypeScript-to-Lua build pipeline tuned for Defold's runtime.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -35,8 +35,9 @@
35
35
  "test": "bun test"
36
36
  },
37
37
  "dependencies": {
38
- "@defold-typescript/types": "0.43.0",
38
+ "@defold-typescript/types": "0.44.1",
39
39
  "@typescript-to-lua/language-extensions": "1.19.0",
40
+ "typescript": "6.0.2",
40
41
  "typescript-to-lua": "^1.36.0"
41
42
  }
42
43
  }
@@ -2,7 +2,7 @@ import { existsSync, readdirSync } from "node:fs";
2
2
  import * as path from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
4
  import * as ts from "typescript";
5
- import type { Plugin } from "typescript-to-lua";
5
+ import { LuaLibFeature, type Plugin, type TransformationContext } from "typescript-to-lua";
6
6
  import { scanEmittedRequires } from "./lua-require-scan";
7
7
 
8
8
  // `@defold-typescript/types/hsm` is the `index` module; every other module is a
@@ -12,6 +12,10 @@ import { scanEmittedRequires } from "./lua-require-scan";
12
12
  export const HSM_MODULE_SPECIFIER = "@defold-typescript/types/hsm";
13
13
  export const HSM_REQUIRE_ROOT = "defold_typescript_hsm";
14
14
 
15
+ // The hsm modules whose Lua requires the lualib bundle. Every other module stays
16
+ // plain Lua, so a program that imports none of these ships no bundle.
17
+ export const HSM_LUALIB_MODULES: ReadonlySet<string> = new Set(["async"]);
18
+
15
19
  export interface HsmModule {
16
20
  /** The source file's basename without `.ts`. */
17
21
  readonly name: string;
@@ -152,12 +156,56 @@ export function hsmClosure(
152
156
 
153
157
  // Rewrite an hsm specifier to its flat require, the way the timers lowering
154
158
  // does: `@NoResolution:` keeps TSTL from looking for a Lua source behind the
155
- // `.d.ts` the import type-checks against. No lualib feature is registered,
156
- // because hsm compiles to plain Lua.
159
+ // `.d.ts` the import type-checks against.
157
160
  function lowerSpecifier(module: HsmModule): ts.StringLiteral {
158
161
  return ts.factory.createStringLiteral(`@NoResolution:${module.requireName}`);
159
162
  }
160
163
 
164
+ type Lowered = ReturnType<TransformationContext["superTransformStatements"]>;
165
+
166
+ function loweredAnything(lowered: Lowered): boolean {
167
+ return Array.isArray(lowered) ? lowered.length > 0 : lowered !== undefined;
168
+ }
169
+
170
+ // The same resolver verdict TSTL's own import transform uses to decide which
171
+ // bindings it requires, so the bundle and the emitted require cannot disagree.
172
+ // Takes the original node: the resolver has no binding for a synthesized copy.
173
+ function importSurvivesElision(
174
+ node: ts.ImportDeclaration,
175
+ context: TransformationContext,
176
+ ): boolean {
177
+ const clause = node.importClause;
178
+ if (clause === undefined) {
179
+ return true;
180
+ }
181
+ const { resolver } = context;
182
+ if (clause.name !== undefined && resolver.isReferencedAliasDeclaration(clause)) {
183
+ return true;
184
+ }
185
+ const bindings = clause.namedBindings;
186
+ if (bindings === undefined) {
187
+ return false;
188
+ }
189
+ if (ts.isNamespaceImport(bindings)) {
190
+ return resolver.isReferencedAliasDeclaration(bindings);
191
+ }
192
+ return bindings.elements.some((element) => resolver.isReferencedAliasDeclaration(element));
193
+ }
194
+
195
+ // TSTL writes `lualib_bundle.lua` only for features a user chunk registers; it
196
+ // never sees the hsm module's own source, which the CLI writes beside the build.
197
+ // An elided import (type-only, unused) registers nothing. The import visitor
198
+ // passes the resolver's elision verdict, because TSTL's import transform always
199
+ // returns nothing and queues its require on the scope instead; the export
200
+ // visitor passes whether its lowered statements are non-empty, because TSTL's
201
+ // export transform returns them.
202
+ function registerLualib(module: HsmModule, emitted: boolean, context: TransformationContext): void {
203
+ if (emitted && HSM_LUALIB_MODULES.has(module.name)) {
204
+ context.usedLuaLibFeatures.add(LuaLibFeature.Promise);
205
+ context.usedLuaLibFeatures.add(LuaLibFeature.New);
206
+ }
207
+ }
208
+
161
209
  export const hsmLoweringPlugin: Plugin = {
162
210
  visitors: {
163
211
  [ts.SyntaxKind.ImportDeclaration]: (node, context) => {
@@ -165,6 +213,7 @@ export const hsmLoweringPlugin: Plugin = {
165
213
  if (module === undefined) {
166
214
  return context.superTransformStatements(node);
167
215
  }
216
+ registerLualib(module, importSurvivesElision(node, context), context);
168
217
  return context.superTransformStatements(
169
218
  ts.factory.updateImportDeclaration(
170
219
  node,
@@ -180,7 +229,7 @@ export const hsmLoweringPlugin: Plugin = {
180
229
  if (module === undefined) {
181
230
  return context.superTransformStatements(node);
182
231
  }
183
- return context.superTransformStatements(
232
+ const lowered = context.superTransformStatements(
184
233
  ts.factory.updateExportDeclaration(
185
234
  node,
186
235
  node.modifiers,
@@ -190,6 +239,8 @@ export const hsmLoweringPlugin: Plugin = {
190
239
  node.attributes,
191
240
  ),
192
241
  );
242
+ registerLualib(module, loweredAnything(lowered), context);
243
+ return lowered;
193
244
  },
194
245
  },
195
246
  };
package/src/index.ts CHANGED
@@ -27,6 +27,7 @@ export {
27
27
  hsmModules,
28
28
  hsmRequireName,
29
29
  hsmSourceFiles,
30
+ requireHsmSourceDir,
30
31
  resolveHsmSourceDir,
31
32
  } from "./hsm-builtin";
32
33
  export { buildInputActionIndex } from "./input-action-index";
package/src/transpile.ts CHANGED
@@ -10,6 +10,7 @@ import {
10
10
  GO_PROPERTY_DIRECT_CALL_MESSAGE,
11
11
  } from "./go-property-direct-call";
12
12
  import {
13
+ HSM_LUALIB_MODULES,
13
14
  HSM_REQUIRE_ROOT,
14
15
  hsmClosure,
15
16
  hsmLoweringPlugin,
@@ -17,6 +18,7 @@ import {
17
18
  requireHsmSourceDir,
18
19
  } from "./hsm-builtin";
19
20
  import { lifecycleErasurePlugin } from "./lifecycle-erasure";
21
+ import { scanEmittedRequires } from "./lua-require-scan";
20
22
  import { messageDispatchLoweringPlugin } from "./message-dispatch-lowering";
21
23
  import { messageGuardLoweringPlugin } from "./message-guard-lowering";
22
24
  import { importsTimersModule, timersLoweringPlugin } from "./timers-lowering";
@@ -371,13 +373,18 @@ export function transpileProject(input: TranspileProjectInput): TranspileProject
371
373
 
372
374
  let compiledHsm: Readonly<Record<string, string>> | undefined;
373
375
 
376
+ function requiresLualib(lua: string): boolean {
377
+ return scanEmittedRequires(lua).some(({ path: requirePath }) => requirePath === "lualib_bundle");
378
+ }
379
+
374
380
  /**
375
381
  * Every `hsm` module compiled to Lua, keyed by module name, once per process.
376
382
  * The source is the shipped TypeScript, never a prebuilt Lua copy, so the Lua a
377
383
  * project gets always comes from the compiler that builds it. Each file sits at
378
384
  * `defold_typescript_hsm/<module>.ts`, so a require between modules is already
379
385
  * spelled the way the user's lowered imports are. Throws when the compile
380
- * reports anything or needs the lualib bundle, which hsm must never do.
386
+ * reports anything, or when a module outside `HSM_LUALIB_MODULES` needs the
387
+ * lualib bundle.
381
388
  */
382
389
  export function compileHsmModules(): Readonly<Record<string, string>> {
383
390
  if (compiledHsm !== undefined) {
@@ -399,14 +406,17 @@ export function compileHsmModules(): Readonly<Record<string, string>> {
399
406
  const lua: Record<string, string> = {};
400
407
  for (const file of result.transpiledFiles) {
401
408
  if (isLualibBundle(file)) {
402
- problems.push("the compiled Lua requires lualib_bundle");
403
409
  continue;
404
410
  }
405
411
  const source = file.sourceFiles.find((s) => nameByKey.has(s.fileName));
406
412
  const name = source === undefined ? undefined : nameByKey.get(source.fileName);
407
- if (name !== undefined && typeof file.lua === "string") {
408
- lua[name] = file.lua;
413
+ if (name === undefined || typeof file.lua !== "string") {
414
+ continue;
415
+ }
416
+ if (!HSM_LUALIB_MODULES.has(name) && requiresLualib(file.lua)) {
417
+ problems.push(`the compiled ${name} module requires lualib_bundle`);
409
418
  }
419
+ lua[name] = file.lua;
410
420
  }
411
421
  if (problems.length > 0) {
412
422
  throw new Error(