Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
a1825a1
Implement bundle transformation
hazzard993 Oct 7, 2019
2d164c2
Merge remote-tracking branch 'upstream/master' into out-file
hazzard993 Oct 7, 2019
eb05fcb
Remove unneeded changes
hazzard993 Oct 7, 2019
74ef4e3
Merge remote-tracking branch 'upstream/master' into out-file
hazzard993 Oct 7, 2019
299fda8
Add option luaModuleSystem
hazzard993 Oct 7, 2019
e983764
Update luaModule system test name
hazzard993 Oct 7, 2019
c6447c4
Improve formatting of luaModuleSystem test
hazzard993 Oct 7, 2019
c50ad14
Remove luaModuleSystem
hazzard993 Oct 11, 2019
635ba3b
Add string array luaEntry option
hazzard993 Oct 11, 2019
e777b88
Allow luaEntry to be specified via command line
hazzard993 Oct 13, 2019
1606517
Ensure outFile tests are run
hazzard993 Oct 13, 2019
7b17745
Support cyclic imports in output bundles
hazzard993 Oct 15, 2019
7c6ab11
Split up outFile tests
hazzard993 Oct 15, 2019
d36356d
Resolve luaEntry paths relative to tsconfig
hazzard993 Oct 15, 2019
ba2cc1f
Reduce luaEntry option to a string naming a module to export
hazzard993 Oct 15, 2019
69ad1b0
Use bundle exports in outFile tests
hazzard993 Oct 15, 2019
4634d63
Remove string array parsing
hazzard993 Oct 15, 2019
b2ab7db
Remove describe from outFile tests
hazzard993 Oct 16, 2019
b683a48
Allow export equals in bundles
hazzard993 Oct 16, 2019
cc71c2a
Use seperate variables for bundle module system
hazzard993 Oct 16, 2019
8972539
Add primative type command line option
hazzard993 Oct 16, 2019
6500548
Refactor to new transformSourceFileWithState method
hazzard993 Oct 16, 2019
69693f0
Generate and use a full luaEntry path if project is specified
hazzard993 Oct 16, 2019
d45395f
Use suggested config based resolution for luaEntrys in tsconfig
hazzard993 Oct 17, 2019
a9a94d3
Check for configFilePath for luaEntry, not project
hazzard993 Oct 18, 2019
1276819
Make transformBundle private
hazzard993 Oct 18, 2019
16b1f04
Add default options to BundleTestBuilder
hazzard993 Oct 18, 2019
ecc2450
Simplify LuaLib outFile test
hazzard993 Oct 18, 2019
8ababd5
Update src/LuaTransformer.ts
hazzard993 Oct 22, 2019
1479285
Update src/LuaTransformer.ts
hazzard993 Oct 22, 2019
cf2b791
Rename LuaRequire globals
hazzard993 Oct 22, 2019
e43f841
Refactor undefined configFilePath condition in transformer
hazzard993 Oct 22, 2019
bda19b8
Change requireCallString to requireFunctionName
hazzard993 Oct 22, 2019
0c700d4
Disable lint rule in LuaRequire and fix transformer
hazzard993 Oct 22, 2019
e1e6964
Avoid mutable luaEntryPath in LuaTransformer
hazzard993 Oct 22, 2019
b95f068
Use camelCasing in LuaRequire
hazzard993 Oct 22, 2019
2d7e1ce
Inline variables in outFile tests
hazzard993 Oct 22, 2019
360fdd2
Improve cyclic import outFile test
hazzard993 Oct 22, 2019
d40a619
Allow optional TranspileError node parameter
hazzard993 Oct 22, 2019
5d63ba8
Move transformSourceFileWithState above transformSourceFile
hazzard993 Oct 25, 2019
cb560be
Merge remote-tracking branch 'upstream/master' into out-file
hazzard993 Oct 27, 2019
c50805f
Merge remote-tracking branch 'upstream/master' into out-file
hazzard993 Nov 2, 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
1 change: 1 addition & 0 deletions src/CompilerOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export interface TransformerImport {
export type CompilerOptions = OmitIndexSignature<ts.CompilerOptions> & {
noImplicitSelf?: boolean;
noHeader?: boolean;
luaEntry?: string;
luaTarget?: LuaTarget;
luaLibImport?: LuaLibImportKind;
noHoisting?: boolean;
Expand Down
1 change: 1 addition & 0 deletions src/LuaLib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export enum LuaLibFeature {
InstanceOf = "InstanceOf",
InstanceOfObject = "InstanceOfObject",
Iterator = "Iterator",
LuaRequire = "LuaRequire",
Map = "Map",
NewIndex = "NewIndex",
Number = "Number",
Expand Down
89 changes: 72 additions & 17 deletions src/LuaTransformer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,6 @@ export class LuaTransformer {
this.genVarCounter = 0;
this.luaLibFeatureSet = new Set<LuaLibFeature>();

this.visitedExportEquals = false;

this.scopeStack = [];
this.classStack = [];

Expand All @@ -104,24 +102,63 @@ export class LuaTransformer {

protected currentSourceFile!: ts.SourceFile;
protected isModule!: boolean;
protected isWithinBundle!: boolean;
protected resolver!: EmitResolver;

/** @internal */
public transform(sourceFile: ts.SourceFile): [tstl.Block, Set<LuaLibFeature>] {
public transform(node: ts.Bundle | ts.SourceFile): [tstl.Block, Set<LuaLibFeature>] {
this.setupState();
this.currentSourceFile = sourceFile;
this.isModule = tsHelper.isFileModule(sourceFile);
if (ts.isSourceFile(node)) {
return [this.transformSourceFileWithState(node), this.luaLibFeatureSet];
} else {
return [this.transformBundle(node), this.luaLibFeatureSet];
}
}

private transformBundle(bundle: ts.Bundle): tstl.Block {
this.isWithinBundle = true;
const combinedStatements = bundle.sourceFiles.reduce(
(statements: tstl.Statement[], sourceFile: ts.SourceFile) => {
const transformResult = this.transformSourceFileWithState(sourceFile);
return [...statements, ...transformResult.statements];
},
[]
);

if (this.options.luaEntry) {
const basedir = this.options.configFilePath
? path.dirname(this.options.configFilePath as string)
: undefined;
const luaEntryPath = basedir ? path.resolve(basedir, this.options.luaEntry) : this.options.luaEntry;
const sourceFile = this.program.getSourceFile(luaEntryPath);
if (sourceFile) {
const formattedEntryName = tsHelper.getExportPath(sourceFile.fileName, this.options);
const requireCall = this.createModuleRequire(ts.createStringLiteral(formattedEntryName), false);
const returnStatement = tstl.createReturnStatement([requireCall]);
combinedStatements.push(returnStatement);
} else {
throw TSTLErrors.LuaEntryNotFound(this.options.luaEntry);
}
}

return tstl.createBlock(combinedStatements, bundle);
}

private transformSourceFileWithState(node: ts.SourceFile): tstl.Block {
this.currentSourceFile = node;
this.isModule = tsHelper.isFileModule(node);

// Use `getParseTreeNode` to get original SourceFile node, before it was substituted by custom transformers.
// It's required because otherwise `getEmitResolver` won't use cached diagnostics, produced in `emitWorker`
// and would try to re-analyze the file, which would fail because of replaced nodes.
const originalSourceFile = ts.getParseTreeNode(sourceFile, ts.isSourceFile) || sourceFile;
const originalSourceFile = ts.getParseTreeNode(node, ts.isSourceFile) || node;
this.resolver = this.checker.getEmitResolver(originalSourceFile);

return [this.transformSourceFile(sourceFile), this.luaLibFeatureSet];
return this.transformSourceFile(node);
}

public transformSourceFile(sourceFile: ts.SourceFile): tstl.Block {
this.visitedExportEquals = false;
let statements: tstl.Statement[] = [];
if (sourceFile.flags & ts.NodeFlags.JsonFile) {
const statement = sourceFile.statements[0];
Expand All @@ -136,22 +173,35 @@ export class LuaTransformer {
this.popScope();

if (this.isModule) {
// If export equals was not used. Create the exports table.
// local exports = {}
if (!this.visitedExportEquals) {
statements.unshift(
tstl.createVariableDeclarationStatement(
this.createExportsIdentifier(),
tstl.createTableExpression()
)
);
if (!this.isWithinBundle) {
// If export equals was not used. Create the exports table.
// local exports = {}
if (!this.visitedExportEquals) {
statements.unshift(
tstl.createVariableDeclarationStatement(
this.createExportsIdentifier(),
tstl.createTableExpression()
)
);
}
}

// return exports
statements.push(tstl.createReturnStatement([this.createExportsIdentifier()]));
}
}

if (this.isWithinBundle) {
const moduleTableIdentifier = tstl.createIdentifier("____modules");
const exportPath = tsHelper.getExportPath(sourceFile.fileName, this.options);
const moduleParameters = this.visitedExportEquals ? undefined : [this.createExportsIdentifier()];
const moduleDeclaration = tstl.createAssignmentStatement(
tstl.createTableIndexExpression(moduleTableIdentifier, tstl.createStringLiteral(exportPath)),
tstl.createFunctionExpression(tstl.createBlock(statements, sourceFile), moduleParameters)
);
return tstl.createBlock([moduleDeclaration], sourceFile);
}

return tstl.createBlock(statements, sourceFile);
}

Expand Down Expand Up @@ -511,7 +561,12 @@ export class LuaTransformer {
)
: moduleSpecifier.text;
const modulePath = tstl.createStringLiteral(modulePathString);
return tstl.createCallExpression(tstl.createIdentifier("require"), [modulePath], moduleSpecifier);
const requireFunctionName = this.options.outFile ? "__TS__LuaRequire" : "require";
if (this.options.outFile) {
this.importLuaLibFeature(LuaLibFeature.LuaRequire);
}

return tstl.createCallExpression(tstl.createIdentifier(requireFunctionName), [modulePath], moduleSpecifier);
}

protected validateClassElement(element: ts.ClassElement): void {
Expand Down
3 changes: 3 additions & 0 deletions src/TSTLErrors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ export const InvalidElementCall = (node: ts.Node) =>
export const ForbiddenStaticClassPropertyName = (node: ts.Node, name: string) =>
new TranspileError(`Cannot use "${name}" as a static class property or method name.`, node);

export const LuaEntryNotFound = (entryName: string) =>
new TranspileError(`Could not find luaEntry file '${entryName}'.`);

export const MissingClassName = (node: ts.Node) => new TranspileError(`Class declarations must have a name.`, node);

export const MissingForOfVariables = (node: ts.Node) =>
Expand Down
16 changes: 11 additions & 5 deletions src/TSTransformers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,18 @@ export function getCustomTransformers(
program: ts.Program,
diagnostics: ts.Diagnostic[],
customTransformers: ts.CustomTransformers,
onSourceFile: (sourceFile: ts.SourceFile) => void
onRootNode: (node: ts.Bundle | ts.SourceFile) => void
): ts.CustomTransformers {
const luaTransformer: ts.TransformerFactory<ts.SourceFile> = () => sourceFile => {
onSourceFile(sourceFile);
return ts.createSourceFile(sourceFile.fileName, "", ts.ScriptTarget.ESNext);
};
const luaTransformer: ts.CustomTransformerFactory = () => ({
transformBundle: node => {
onRootNode(node);
return ts.createBundle([]);
},
transformSourceFile: node => {
onRootNode(node);
return ts.createSourceFile(node.fileName, "", ts.ScriptTarget.ESNext);
},
});

const transformersFromOptions = loadTransformersFromOptions(program, diagnostics);

Expand Down
22 changes: 14 additions & 8 deletions src/Transpile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,26 +78,32 @@ export function transpile({
}
}

const processSourceFile = (sourceFile: ts.SourceFile) => {
const processRootNode = (node: ts.Bundle | ts.SourceFile) => {
if (ts.isBundle(node) && !options.outFile) {
throw new Error("The option outFile must be specified when transforming a bundle.");
}

const fileName = ts.isBundle(node) ? options.outFile! : node.fileName;

try {
const [luaAst, lualibFeatureSet] = transformer.transform(sourceFile);
const [luaAst, lualibFeatureSet] = transformer.transform(node);
if (!options.noEmit && !options.emitDeclarationOnly) {
const [lua, sourceMap] = printer.print(luaAst, lualibFeatureSet, sourceFile.fileName);
updateTranspiledFile(sourceFile.fileName, { luaAst, lua, sourceMap });
const [lua, sourceMap] = printer.print(luaAst, lualibFeatureSet, fileName);
updateTranspiledFile(fileName, { luaAst, lua, sourceMap });
}
} catch (err) {
if (!(err instanceof TranspileError)) throw err;

diagnostics.push(diagnosticFactories.transpileError(err));

updateTranspiledFile(sourceFile.fileName, {
updateTranspiledFile(fileName, {
lua: `error(${JSON.stringify(err.message)})\n`,
sourceMap: "",
});
}
};

const transformers = getCustomTransformers(program, diagnostics, customTransformers, processSourceFile);
const transformers = getCustomTransformers(program, diagnostics, customTransformers, processRootNode);

const writeFile: ts.WriteFileCallback = (fileName, data, _bom, _onError, sourceFiles = []) => {
for (const sourceFile of sourceFiles) {
Expand All @@ -123,7 +129,7 @@ export function transpile({
if (targetSourceFiles) {
for (const file of targetSourceFiles) {
if (isEmittableJsonFile(file)) {
processSourceFile(file);
processRootNode(file);
} else {
diagnostics.push(...program.emit(file, writeFile, undefined, false, transformers).diagnostics);
}
Expand All @@ -135,7 +141,7 @@ export function transpile({
program
.getSourceFiles()
.filter(isEmittableJsonFile)
.forEach(processSourceFile);
.forEach(processRootNode);
}

options.noEmit = oldNoEmit;
Expand Down
2 changes: 1 addition & 1 deletion src/TranspileError.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as ts from "typescript";

export class TranspileError extends Error {
public name = "TranspileError";
constructor(message: string, public node: ts.Node) {
constructor(message: string, public node?: ts.Node) {
super(message);
}
}
16 changes: 11 additions & 5 deletions src/cli/parse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,18 @@ interface CommandLineOptionOfEnum extends CommandLineOptionBase {
choices: string[];
}

interface CommandLineOptionOfBoolean extends CommandLineOptionBase {
type: "boolean";
interface CommandLineOptionOfPrimitiveType extends CommandLineOptionBase {
type: "string" | "boolean";
}

type CommandLineOption = CommandLineOptionOfEnum | CommandLineOptionOfBoolean;
type CommandLineOption = CommandLineOptionOfEnum | CommandLineOptionOfPrimitiveType;

export const optionDeclarations: CommandLineOption[] = [
{
name: "luaEntry",
description: "Specifies an entry point that will be executed in the resulting output file.",
type: "string",
},
{
name: "luaLibImport",
description: "Specifies how js standard features missing in lua are imported.",
Expand Down Expand Up @@ -164,11 +169,12 @@ function readValue(option: CommandLineOption, value: unknown): ReadValueResult {
if (value === null) return { value };

switch (option.type) {
case "string":
case "boolean": {
if (typeof value !== "boolean") {
if (typeof value !== option.type) {
return {
value: undefined,
error: cliDiagnostics.compilerOptionRequiresAValueOfType(option.name, "boolean"),
error: cliDiagnostics.compilerOptionRequiresAValueOfType(option.name, option.type),
};
}

Expand Down
6 changes: 3 additions & 3 deletions src/diagnostics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import * as ts from "typescript";
import { TranspileError } from "./TranspileError";

export const transpileError = (error: TranspileError): ts.Diagnostic => ({
file: error.node.getSourceFile(),
start: error.node.getStart(),
length: error.node.getWidth(),
file: error.node ? error.node.getSourceFile() : undefined,
start: error.node ? error.node.getStart() : undefined,
length: error.node ? error.node.getWidth() : undefined,
category: ts.DiagnosticCategory.Error,
code: 0,
source: "typescript-to-lua",
Expand Down
18 changes: 18 additions & 0 deletions src/lualib/LuaRequire.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// tslint:disable: variable-name
const ____modules: Record<string, (this: void, exports: object) => any> = {};
const ____moduleCache: Record<string, any> = {};

function __TS__LuaRequire(this: void, moduleName: string): any {
if (____moduleCache[moduleName]) {
return ____moduleCache[moduleName];
}
const loadScript = ____modules[moduleName];
if (!loadScript) {
// tslint:disable-next-line: no-string-throw
throw `module '${moduleName}' not found`;
}
const moduleExports = {};
____moduleCache[moduleName] = moduleExports;
____moduleCache[moduleName] = loadScript(moduleExports);
return ____moduleCache[moduleName];
}
Loading