X Tutup
Skip to content

Commit 97fa41e

Browse files
ark120202Perryvw
authored andcommitted
Add module import/export elision (#565)
* Add module import/export elision * Reference imported values in tests to avoid elision * Remove TODO comment
1 parent 026317c commit 97fa41e

12 files changed

+211
-94
lines changed

src/LuaTransformer.ts

Lines changed: 67 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,16 @@ interface Scope {
3838
loopContinued?: boolean;
3939
}
4040

41+
export interface EmitResolver {
42+
isValueAliasDeclaration(node: ts.Node): boolean;
43+
isReferencedAliasDeclaration(node: ts.Node, checkChildren?: boolean): boolean;
44+
moduleExportsSomeValue(moduleReferenceExpression: ts.Expression): boolean;
45+
}
46+
47+
export interface DiagnosticsProducingTypeChecker extends ts.TypeChecker {
48+
getEmitResolver(sourceFile?: ts.SourceFile, cancellationToken?: ts.CancellationToken): EmitResolver;
49+
}
50+
4151
export class LuaTransformer {
4252
public luaKeywords: Set<string> = new Set([
4353
"_G", "and", "assert", "break", "coroutine", "debug", "do", "else", "elseif", "end", "error", "false", "for",
@@ -49,11 +59,13 @@ export class LuaTransformer {
4959
private isStrict: boolean;
5060
private luaTarget: LuaTarget;
5161

52-
private checker: ts.TypeChecker;
62+
private checker: DiagnosticsProducingTypeChecker;
5363
protected options: CompilerOptions;
5464

55-
private isModule = false;
65+
// Resolver is lazy-initialized in transformSourceFile to avoid type-checking all files
66+
private resolver!: EmitResolver;
5667

68+
private isModule = false;
5769
private currentSourceFile?: ts.SourceFile;
5870

5971
private currentNamespace: ts.ModuleDeclaration | undefined;
@@ -72,7 +84,7 @@ export class LuaTransformer {
7284
private readonly typeValidationCache: Map<ts.Type, Set<ts.Type>> = new Map<ts.Type, Set<ts.Type>>();
7385

7486
public constructor(protected program: ts.Program) {
75-
this.checker = program.getTypeChecker();
87+
this.checker = (program as any).getDiagnosticsProducingTypeChecker();
7688
this.options = program.getCompilerOptions();
7789
this.isStrict = this.options.alwaysStrict !== undefined
7890
|| (this.options.strict !== undefined && this.options.alwaysStrict !== false)
@@ -102,6 +114,7 @@ export class LuaTransformer {
102114
this.setupState();
103115

104116
this.currentSourceFile = node;
117+
this.resolver = this.checker.getEmitResolver(node);
105118

106119
let statements: tstl.Statement[] = [];
107120
if (node.flags & ts.NodeFlags.JsonFile) {
@@ -233,36 +246,6 @@ export class LuaTransformer {
233246
}
234247

235248
public transformExportDeclaration(statement: ts.ExportDeclaration): StatementVisitResult {
236-
if (statement.moduleSpecifier === undefined) {
237-
if (statement.exportClause === undefined) {
238-
throw TSTLErrors.InvalidExportDeclaration(statement);
239-
}
240-
241-
const result = [];
242-
for (const exportElement of statement.exportClause.elements) {
243-
let exportedIdentifier: tstl.Expression | undefined;
244-
if (exportElement.propertyName !== undefined) {
245-
exportedIdentifier = this.transformIdentifier(exportElement.propertyName);
246-
247-
} else {
248-
const exportedSymbol = this.checker.getExportSpecifierLocalTargetSymbol(exportElement);
249-
if (exportedSymbol !== undefined) {
250-
exportedIdentifier = this.createIdentifierFromSymbol(exportedSymbol, exportElement.name);
251-
} else {
252-
exportedIdentifier = this.transformIdentifier(exportElement.name);
253-
}
254-
}
255-
256-
result.push(
257-
tstl.createAssignmentStatement(
258-
this.createExportedIdentifier(this.transformIdentifier(exportElement.name)),
259-
exportedIdentifier
260-
)
261-
);
262-
}
263-
return result;
264-
}
265-
266249
if (statement.exportClause) {
267250
if (statement.exportClause.elements.some(e =>
268251
(e.name !== undefined && e.name.originalKeywordKind === ts.SyntaxKind.DefaultKeyword)
@@ -272,11 +255,40 @@ export class LuaTransformer {
272255
throw TSTLErrors.UnsupportedDefaultExport(statement);
273256
}
274257

258+
if (!this.resolver.isValueAliasDeclaration(statement)) {
259+
return undefined;
260+
}
261+
262+
const exportSpecifiers = statement.exportClause.elements.filter(e =>
263+
this.resolver.isValueAliasDeclaration(e)
264+
);
265+
266+
if (statement.moduleSpecifier === undefined) {
267+
return exportSpecifiers.map(specifier => {
268+
let exportedIdentifier: tstl.Expression | undefined;
269+
if (specifier.propertyName !== undefined) {
270+
exportedIdentifier = this.transformIdentifier(specifier.propertyName);
271+
} else {
272+
const exportedSymbol = this.checker.getExportSpecifierLocalTargetSymbol(specifier);
273+
if (exportedSymbol !== undefined) {
274+
exportedIdentifier = this.createIdentifierFromSymbol(exportedSymbol, specifier.name);
275+
} else {
276+
exportedIdentifier = this.transformIdentifier(specifier.name);
277+
}
278+
}
279+
280+
return tstl.createAssignmentStatement(
281+
this.createExportedIdentifier(this.transformIdentifier(specifier.name)),
282+
exportedIdentifier
283+
);
284+
});
285+
}
286+
275287
// First transpile as import clause
276288
const importClause = ts.createImportClause(
277289
undefined,
278-
ts.createNamedImports(statement.exportClause.elements
279-
.map(e => ts.createImportSpecifier(e.propertyName, e.name))
290+
ts.createNamedImports(
291+
exportSpecifiers.map(s => ts.createImportSpecifier(s.propertyName, s.name))
280292
)
281293
);
282294

@@ -292,18 +304,26 @@ export class LuaTransformer {
292304
const result = this.transformBlock(block).statements;
293305

294306
// Now the module is imported, add the imports to the export table
295-
for (const exportVariable of statement.exportClause.elements) {
307+
for (const specifier of exportSpecifiers) {
296308
result.push(
297309
tstl.createAssignmentStatement(
298-
this.createExportedIdentifier(this.transformIdentifier(exportVariable.name)),
299-
this.transformIdentifier(exportVariable.name)
310+
this.createExportedIdentifier(this.transformIdentifier(specifier.name)),
311+
this.transformIdentifier(specifier.name)
300312
)
301313
);
302314
}
303315

304316
// Wrap this in a DoStatement to prevent polluting the scope.
305317
return tstl.createDoStatement(this.filterUndefined(result), statement);
306318
} else {
319+
if (statement.moduleSpecifier === undefined) {
320+
throw TSTLErrors.InvalidExportDeclaration(statement);
321+
}
322+
323+
if (!this.resolver.moduleExportsSomeValue(statement.moduleSpecifier)) {
324+
return undefined;
325+
}
326+
307327
const moduleRequire = this.createModuleRequire(statement.moduleSpecifier as ts.StringLiteral);
308328
const tempModuleIdentifier = tstl.createIdentifier("__TSTL_export");
309329

@@ -375,7 +395,11 @@ export class LuaTransformer {
375395
if (ts.isNamedImports(imports)) {
376396
const filteredElements = imports.elements.filter(e => {
377397
const decorators = tsHelper.getCustomDecorators(this.checker.getTypeAtLocation(e), this.checker);
378-
return !decorators.has(DecoratorKind.Extension) && !decorators.has(DecoratorKind.MetaExtension);
398+
return (
399+
this.resolver.isReferencedAliasDeclaration(e)
400+
&& !decorators.has(DecoratorKind.Extension)
401+
&& !decorators.has(DecoratorKind.MetaExtension)
402+
);
379403
});
380404

381405
// Elide import if all imported types are extension classes
@@ -418,6 +442,10 @@ export class LuaTransformer {
418442
}
419443

420444
} else if (ts.isNamespaceImport(imports)) {
445+
if (!this.resolver.isReferencedAliasDeclaration(imports)) {
446+
return undefined;
447+
}
448+
421449
const requireStatement = tstl.createVariableDeclarationStatement(
422450
this.transformIdentifier(imports.name),
423451
requireCall,

test/translation/__snapshots__/transformation.spec.ts.snap

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -383,42 +383,57 @@ exports[`Transformation (modulesFunctionNoExport) 1`] = `
383383
end"
384384
`;
385385

386-
exports[`Transformation (modulesImportAll) 1`] = `"local Test = require(\\"test\\")"`;
386+
exports[`Transformation (modulesImportAll) 1`] = `
387+
"local Test = require(\\"test\\")
388+
local ____ = Test"
389+
`;
387390

388391
exports[`Transformation (modulesImportNamed) 1`] = `
389392
"local __TSTL_test = require(\\"test\\")
390-
local TestClass = __TSTL_test.TestClass"
393+
local TestClass = __TSTL_test.TestClass
394+
local ____ = TestClass"
391395
`;
392396

393397
exports[`Transformation (modulesImportNamedSpecialChars) 1`] = `
394398
"local __TSTL_kebab_2Dmodule = require(\\"kebab-module\\")
395-
local TestClass = __TSTL_kebab_2Dmodule.TestClass
399+
local TestClass1 = __TSTL_kebab_2Dmodule.TestClass1
396400
local __TSTL_dollar_24module = require(\\"dollar$module\\")
397-
local TestClass = __TSTL_dollar_24module.TestClass
401+
local TestClass2 = __TSTL_dollar_24module.TestClass2
398402
local __TSTL_singlequote_27module = require(\\"singlequote'module\\")
399-
local TestClass = __TSTL_singlequote_27module.TestClass
403+
local TestClass3 = __TSTL_singlequote_27module.TestClass3
400404
local __TSTL_hash_23module = require(\\"hash#module\\")
401-
local TestClass = __TSTL_hash_23module.TestClass
405+
local TestClass4 = __TSTL_hash_23module.TestClass4
402406
local __TSTL_space_20module = require(\\"space module\\")
403-
local TestClass = __TSTL_space_20module.TestClass"
407+
local TestClass5 = __TSTL_space_20module.TestClass5
408+
local ____ = TestClass1
409+
local ____ = TestClass2
410+
local ____ = TestClass3
411+
local ____ = TestClass4
412+
local ____ = TestClass5"
404413
`;
405414

406415
exports[`Transformation (modulesImportRenamed) 1`] = `
407416
"local __TSTL_test = require(\\"test\\")
408-
local RenamedClass = __TSTL_test.TestClass"
417+
local RenamedClass = __TSTL_test.TestClass
418+
local ____ = RenamedClass"
409419
`;
410420

411421
exports[`Transformation (modulesImportRenamedSpecialChars) 1`] = `
412422
"local __TSTL_kebab_2Dmodule = require(\\"kebab-module\\")
413-
local RenamedClass = __TSTL_kebab_2Dmodule.TestClass
423+
local RenamedClass1 = __TSTL_kebab_2Dmodule.TestClass
414424
local __TSTL_dollar_24module = require(\\"dollar$module\\")
415-
local RenamedClass = __TSTL_dollar_24module.TestClass
425+
local RenamedClass2 = __TSTL_dollar_24module.TestClass
416426
local __TSTL_singlequote_27module = require(\\"singlequote'module\\")
417-
local RenamedClass = __TSTL_singlequote_27module.TestClass
427+
local RenamedClass3 = __TSTL_singlequote_27module.TestClass
418428
local __TSTL_hash_23module = require(\\"hash#module\\")
419-
local RenamedClass = __TSTL_hash_23module.TestClass
429+
local RenamedClass4 = __TSTL_hash_23module.TestClass
420430
local __TSTL_space_20module = require(\\"space module\\")
421-
local RenamedClass = __TSTL_space_20module.TestClass"
431+
local RenamedClass5 = __TSTL_space_20module.TestClass
432+
local ____ = RenamedClass1
433+
local ____ = RenamedClass2
434+
local ____ = RenamedClass3
435+
local ____ = RenamedClass4
436+
local ____ = RenamedClass5"
422437
`;
423438

424439
exports[`Transformation (modulesImportWithoutFromClause) 1`] = `"require(\\"test\\")"`;
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
11
import * as Test from "test";
2+
3+
Test;
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
11
import { TestClass } from "test";
2+
3+
TestClass;
Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
1-
import { TestClass } from "kebab-module";
2-
import { TestClass } from "dollar$module";
3-
import { TestClass } from "singlequote'module";
4-
import { TestClass } from "hash#module";
5-
import { TestClass } from "space module";
1+
import { TestClass1 } from "kebab-module";
2+
import { TestClass2 } from "dollar$module";
3+
import { TestClass3 } from "singlequote'module";
4+
import { TestClass4 } from "hash#module";
5+
import { TestClass5 } from "space module";
6+
7+
TestClass1;
8+
TestClass2;
9+
TestClass3;
10+
TestClass4;
11+
TestClass5;
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
11
import { TestClass as RenamedClass } from "test";
2+
3+
RenamedClass;
Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
1-
import { TestClass as RenamedClass } from "kebab-module";
2-
import { TestClass as RenamedClass } from "dollar$module";
3-
import { TestClass as RenamedClass } from "singlequote'module";
4-
import { TestClass as RenamedClass } from "hash#module";
5-
import { TestClass as RenamedClass } from "space module";
1+
import { TestClass as RenamedClass1 } from "kebab-module";
2+
import { TestClass as RenamedClass2 } from "dollar$module";
3+
import { TestClass as RenamedClass3 } from "singlequote'module";
4+
import { TestClass as RenamedClass4 } from "hash#module";
5+
import { TestClass as RenamedClass5 } from "space module";
6+
7+
RenamedClass1;
8+
RenamedClass2;
9+
RenamedClass3;
10+
RenamedClass4;
11+
RenamedClass5;

test/unit/identifiers.spec.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -388,12 +388,14 @@ describe("lua keyword as identifier doesn't interfere with lua's value", () => {
388388
package.loaded.someModule = {type = "foobar"}`;
389389

390390
const code = `
391-
import {${importName}} from "someModule";
392-
return typeof 7 + "|" + type;`;
391+
import {${importName}} from "someModule";
392+
export const result = typeof 7 + "|" + type;
393+
`;
393394

394395
const lua = util.transpileString(code);
396+
const result = util.executeLua(`${luaHeader} return (function() ${lua} end)().result`);
395397

396-
expect(util.executeLua(`${luaHeader} ${lua}`)).toBe("number|foobar");
398+
expect(result).toBe("number|foobar");
397399
});
398400

399401
test.each([

test/unit/importexport.spec.ts

Lines changed: 0 additions & 27 deletions
This file was deleted.

0 commit comments

Comments
 (0)
X Tutup