Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
4fd3e31
Fix import and export bugs
hazzard993 Jul 15, 2019
289f2c2
Add full import and export support
hazzard993 Jul 15, 2019
1996602
Fix spelling mistake
hazzard993 Jul 17, 2019
879db6b
Remove exportedIdentifier stringliteral change
hazzard993 Jul 17, 2019
7db0fc1
Fix export default from use case
hazzard993 Jul 17, 2019
5d8a015
Move default export modifier checking to tsHelper
hazzard993 Jul 17, 2019
5ab02cf
Remove duplicate test
hazzard993 Jul 17, 2019
3654f93
Add export equals test, fix require test formatting
hazzard993 Jul 17, 2019
995569a
Fix hasDefaultExportModifier return type
hazzard993 Jul 17, 2019
7d198f1
Enable export default to work on name-less classes
hazzard993 Jul 17, 2019
8ff268e
Fix public and protected method signatures for import and export methods
hazzard993 Jul 17, 2019
4d2be04
Make default class declaration expressions use default as name
hazzard993 Jul 17, 2019
f7b5f02
Make export equals transform to a local statement
hazzard993 Jul 17, 2019
b0bc74d
Add undefined condition if export equals is ellidable
hazzard993 Jul 17, 2019
16396e8
Inline isExportable with getExportable
hazzard993 Jul 17, 2019
d6e2689
Move visitedExportEquals to setupState
hazzard993 Jul 17, 2019
e889875
Merge class tests and ensure classes retain their names
hazzard993 Jul 17, 2019
e3383ba
Change transformExportSpecifier to protected
hazzard993 Jul 19, 2019
a06c825
Merge remote-tracking branch 'upstream/master' into default-exports
hazzard993 Jul 19, 2019
a4c6ac5
Merge remote-tracking branch 'upstream/master' into default-exports
hazzard993 Jul 20, 2019
f192edb
Make importSpecifier protected and use if statement instead of nested…
hazzard993 Jul 21, 2019
f81fa43
Merge remote-tracking branch 'upstream/master' into default-exports
hazzard993 Jul 23, 2019
6f3bee7
Merge remote-tracking branch 'upstream/master' into default-exports
hazzard993 Jul 23, 2019
8932f1c
Merge remote-tracking branch 'upstream/master' into default-exports
hazzard993 Jul 25, 2019
4c43da7
Merge remote-tracking branch 'upstream/master' into default-exports
hazzard993 Jul 27, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
538 changes: 304 additions & 234 deletions src/LuaTransformer.ts

Large diffs are not rendered by default.

96 changes: 96 additions & 0 deletions src/TSHelper.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import * as ts from "typescript";
import * as path from "path";
import { Decorator, DecoratorKind } from "./Decorator";
import * as tstl from "./LuaAST";
import * as TSTLErrors from "./TSTLErrors";
import { EmitResolver } from "./LuaTransformer";

export enum ContextType {
None,
Expand Down Expand Up @@ -57,6 +60,47 @@ export function isAssignmentPattern(node: ts.Node): node is ts.AssignmentPattern
return ts.isObjectLiteralExpression(node) || ts.isArrayLiteralExpression(node);
}

export function getExportable(exportSpecifiers: ts.NamedExports, resolver: EmitResolver): ts.ExportSpecifier[] {
return exportSpecifiers.elements.filter(exportSpecifier => resolver.isValueAliasDeclaration(exportSpecifier));
}

export function isDefaultExportSpecifier(node: ts.ExportSpecifier): boolean {
return (
(node.name !== undefined && node.name.originalKeywordKind === ts.SyntaxKind.DefaultKeyword) ||
(node.propertyName !== undefined && node.propertyName.originalKeywordKind === ts.SyntaxKind.DefaultKeyword)
);
}

export function hasDefaultExportModifier(modifiers?: ts.NodeArray<ts.Modifier>): boolean {
return modifiers ? modifiers.some(modifier => modifier.kind === ts.SyntaxKind.DefaultKeyword) : false;
}

export function shouldResolveModulePath(moduleSpecifier: ts.Expression, checker: ts.TypeChecker): boolean {
const moduleOwnerSymbol = checker.getSymbolAtLocation(moduleSpecifier);
if (moduleOwnerSymbol) {
const decorators = new Map<DecoratorKind, Decorator>();
collectCustomDecorators(moduleOwnerSymbol, checker, decorators);
if (decorators.has(DecoratorKind.NoResolution)) {
return false;
}
}
return true;
}

export function shouldBeImported(
importNode: ts.ImportClause | ts.ImportSpecifier,
checker: ts.TypeChecker,
resolver: EmitResolver
): boolean {
const decorators = getCustomDecorators(checker.getTypeAtLocation(importNode), checker);

return (
resolver.isReferencedAliasDeclaration(importNode) &&
!decorators.has(DecoratorKind.Extension) &&
!decorators.has(DecoratorKind.MetaExtension)
);
}

export function isFileModule(sourceFile: ts.SourceFile): boolean {
return sourceFile.statements.some(isStatementExported);
}
Expand Down Expand Up @@ -968,3 +1012,55 @@ export function isSimpleExpression(expression: tstl.Expression): boolean {
}
return true;
}

export function getAbsoluteImportPath(
relativePath: string,
directoryPath: string,
options: ts.CompilerOptions
): string {
if (relativePath.charAt(0) !== "." && options.baseUrl) {
return path.resolve(options.baseUrl, relativePath);
}

return path.resolve(directoryPath, relativePath);
}

export function getImportPath(
fileName: string,
relativePath: string,
node: ts.Node,
options: ts.CompilerOptions
): string {
const rootDir = options.rootDir ? path.resolve(options.rootDir) : path.resolve(".");

const absoluteImportPath = path.format(
path.parse(getAbsoluteImportPath(relativePath, path.dirname(fileName), options))
);
const absoluteRootDirPath = path.format(path.parse(rootDir));
if (absoluteImportPath.includes(absoluteRootDirPath)) {
return formatPathToLuaPath(absoluteImportPath.replace(absoluteRootDirPath, "").slice(1));
} else {
throw TSTLErrors.UnresolvableRequirePath(
node,
`Cannot create require path. Module does not exist within --rootDir`,
relativePath
);
}
}

export function getExportPath(fileName: string, options: ts.CompilerOptions): string {
const rootDir = options.rootDir ? path.resolve(options.rootDir) : path.resolve(".");

const absolutePath = path.resolve(fileName.replace(/.ts$/, ""));
const absoluteRootDirPath = path.format(path.parse(rootDir));
return formatPathToLuaPath(absolutePath.replace(absoluteRootDirPath, "").slice(1));
}

export function formatPathToLuaPath(filePath: string): string {
filePath = filePath.replace(/\.json$/, "");
if (process.platform === "win32") {
// Windows can use backslashes
filePath = filePath.replace(/\.\\/g, "").replace(/\\/g, ".");
}
return filePath.replace(/\.\//g, "").replace(/\//g, ".");
}
6 changes: 0 additions & 6 deletions src/TSTLErrors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,6 @@ const getLuaTargetName = (version: LuaTarget) => (version === LuaTarget.LuaJIT ?
export const CouldNotCast = (castName: string) =>
new Error(`Failed to cast all elements to expected type using ${castName}.`);

export const DefaultImportsNotSupported = (node: ts.Node) =>
new TranspileError(`Default Imports are not supported, please use named imports instead!`, node);

export const ForbiddenEllipsisDestruction = (node: ts.Node) =>
new TranspileError(`Ellipsis destruction is not allowed.`, node);

Expand Down Expand Up @@ -103,9 +100,6 @@ export const UndefinedTypeNode = (node: ts.Node) => new TranspileError("Failed t
export const UnknownSuperType = (node: ts.Node) =>
new TranspileError("Unable to resolve type of super expression.", node);

export const UnsupportedDefaultExport = (node: ts.Node) =>
new TranspileError(`Default exports are not supported.`, node);

export const UnsupportedImportType = (node: ts.Node) => new TranspileError(`Unsupported import type.`, node);

export const UnsupportedKind = (description: string, kind: ts.SyntaxKind, node: ts.Node) =>
Expand Down
10 changes: 10 additions & 0 deletions test/translation/__snapshots__/transformation.spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,11 @@ TestEnum.val3 = \\"baz\\"
TestEnum.baz = \\"val3\\""
`;

exports[`Transformation (exportEquals) 1`] = `
"local ____exports = true
return ____exports"
`;

exports[`Transformation (exportStatement) 1`] = `
"local ____exports = {}
local xyz = 4
Expand Down Expand Up @@ -660,6 +665,11 @@ exports[`Transformation (typeAssert) 1`] = `
local test2 = 10"
`;

exports[`Transformation (unusedDefaultWithNamespaceImport) 1`] = `
"local x = require(\\"module\\")
local ____ = x"
`;

exports[`Transformation (while) 1`] = `
"local d = 10
while d > 0 do
Expand Down
1 change: 1 addition & 0 deletions test/translation/transformation/exportEquals.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export = true;
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import def, * as x from "module";
x;
17 changes: 0 additions & 17 deletions test/unit/modules.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import * as ts from "typescript";
import * as tstl from "../../src";
import * as TSTLErrors from "../../src/TSTLErrors";
import * as util from "../util";

describe("module import/export elision", () => {
Expand Down Expand Up @@ -62,16 +61,6 @@ describe("module import/export elision", () => {
});
});

test.each([
"export { default } from '...'",
"export { x as default } from '...';",
"export { default as x } from '...';",
])("Export default keyword disallowed (%p)", exportStatement => {
expect(() => util.transpileString(exportStatement)).toThrowExactError(
TSTLErrors.UnsupportedDefaultExport(util.nodeStub)
);
});

test.each(["ke-bab", "dollar$", "singlequote'", "hash#", "s p a c e", "ɥɣɎɌͼƛಠ", "_̀ः٠‿"])(
"Import module names with invalid lua identifier characters (%p)",
name => {
Expand All @@ -89,12 +78,6 @@ test.each(["ke-bab", "dollar$", "singlequote'", "hash#", "s p a c e", "ɥɣɎɌ
}
);

test("defaultImport", () => {
expect(() => {
util.transpileString(`import TestClass from "test"`);
}).toThrowExactError(TSTLErrors.DefaultImportsNotSupported(util.nodeStub));
});

test("lualibRequire", () => {
const lua = util.transpileString(`let a = b instanceof c;`, {
luaLibImport: tstl.LuaLibImportKind.Require,
Expand Down
145 changes: 145 additions & 0 deletions test/unit/require.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,3 +187,148 @@ test("ImportEquals declaration require", () => {
expect(match[1]).toBe("foo.bar");
}
});

test.each(["export default value;", "export { value as default };"])("Export Default From (%p)", exportStatement => {
const [result] = util.transpileAndExecuteProjectReturningMainExport(
{
"main.ts": `
export { default } from "./module";
`,
"module.ts": `
export const value = true;
${exportStatement};
`,
},
"default"
);

expect(result).toBe(true);
});

test("Default Import and Export Expression", () => {
const [result] = util.transpileAndExecuteProjectReturningMainExport(
{
"main.ts": `
import defaultExport from "./module";
export const value = defaultExport;
`,
"module.ts": `
export default 1 + 2 + 3;
`,
},
"value"
);

expect(result).toBe(6);
});

test("Import and Export Assignment", () => {
const [result] = util.transpileAndExecuteProjectReturningMainExport(
{
"main.ts": `
import * as m from "./module";
export const value = m;
`,
"module.ts": `
export = true;
`,
},
"value"
);

expect(result).toBe(true);
});

test("Mixed Exports, Default and Named Imports", () => {
const [result] = util.transpileAndExecuteProjectReturningMainExport(
{
"main.ts": `
import defaultExport, { a, b, c } from "./module";
export const value = defaultExport + b + c;
`,
"module.ts": `
export const a = 1;
export default a;
export const b = 2;
export const c = 3;
`,
},
"value"
);

expect(result).toBe(6);
});

test("Mixed Exports, Default and Namespace Import", () => {
const [result] = util.transpileAndExecuteProjectReturningMainExport(
{
"main.ts": `
import defaultExport, * as ns from "./module";
export const value = defaultExport + ns.b + ns.c;
`,
"module.ts": `
export const a = 1;
export default a;
export const b = 2;
export const c = 3;
`,
},
"value"
);

expect(result).toBe(6);
});

test("Export Default Function", () => {
const [result] = util.transpileAndExecuteProjectReturningMainExport(
{
"main.ts": `
import defaultExport from "./module";
export const value = defaultExport();
`,
"module.ts": `
export default function() {
return true;
}
`,
},
"value"
);

expect(result).toBe(true);
});

test.each([
["Test", "export default class Test { static method() { return true; } }"],
["default", "export default class { static method() { return true; } }"],
])("Export Default Class Name (%p)", (expectedClassName, classDeclarationStatement) => {
const [result] = util.transpileAndExecuteProjectReturningMainExport(
{
"main.ts": `
import defaultExport from "./module";
export const value = defaultExport.name;
`,
"module.ts": classDeclarationStatement,
},
"value"
);

expect(result).toBe(expectedClassName);
});

test("Export Equals", () => {
const [result] = util.transpileAndExecuteProjectReturningMainExport(
{
"main.ts": `
import * as module from "./module";
export const value = module;
`,
"module.ts": `
export = true;
`,
},
"value"
);

expect(result).toBe(true);
});
Loading