Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
23 changes: 19 additions & 4 deletions src/LuaLib.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as path from "path";
import { EmitHost } from "./Transpile";
import { EmitHost } from "./transpilation";

export enum LuaLibFeature {
ArrayConcat = "ArrayConcat",
Expand Down Expand Up @@ -93,12 +93,12 @@ export function loadLuaLibFeatures(features: Iterable<LuaLibFeature>, emitHost:
dependencies.forEach(load);
}

const featureFile = path.resolve(__dirname, `../dist/lualib/${feature}.lua`);
const luaLibFeature = emitHost.readFile(featureFile);
const featurePath = path.resolve(__dirname, `../dist/lualib/${feature}.lua`);
const luaLibFeature = emitHost.readFile(featurePath);
if (luaLibFeature !== undefined) {
result += luaLibFeature + "\n";
} else {
throw new Error(`Could not read lualib feature ../dist/lualib/${feature}.lua`);
throw new Error(`Could not load lualib feature from '${featurePath}'`);
}
}

Expand All @@ -108,3 +108,18 @@ export function loadLuaLibFeatures(features: Iterable<LuaLibFeature>, emitHost:

return result;
}

let luaLibBundleContent: string;
export function getLuaLibBundle(emitHost: EmitHost): string {
if (luaLibBundleContent === undefined) {
const lualibPath = path.resolve(__dirname, "../dist/lualib/lualib_bundle.lua");
const result = emitHost.readFile(lualibPath);
if (result !== undefined) {
luaLibBundleContent = result;
} else {
throw new Error(`Could not load lualib bundle from '${lualibPath}'`);
}
}

return luaLibBundleContent;
}
2 changes: 1 addition & 1 deletion src/LuaPrinter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { CompilerOptions, LuaLibImportKind } from "./CompilerOptions";
import * as lua from "./LuaAST";
import { loadLuaLibFeatures, LuaLibFeature } from "./LuaLib";
import { isValidLuaIdentifier, luaKeywords } from "./transformation/utils/safe-names";
import { EmitHost } from "./Transpile";
import { EmitHost } from "./transpilation";
import { trimExtension } from "./utils";

// https://www.lua.org/pil/2.4.html
Expand Down
10 changes: 0 additions & 10 deletions src/NoImplicitSelfTransformer.ts

This file was deleted.

91 changes: 1 addition & 90 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,99 +1,10 @@
import * as fs from "fs";
import * as path from "path";
import * as ts from "typescript";
import { parseConfigFileWithSystem } from "./cli/tsconfig";
import { CompilerOptions } from "./CompilerOptions";
import { emitTranspiledFiles, OutputFile } from "./Emit";
import { transpile, TranspiledFile, TranspileResult } from "./Transpile";

export { version } from "./cli/information";
export { parseCommandLine, ParsedCommandLine, updateParsedConfigFile } from "./cli/parse";
export * from "./cli/report";
export * from "./CompilerOptions";
export * from "./Emit";
export * from "./LuaAST";
export { LuaLibFeature } from "./LuaLib";
export * from "./LuaPrinter";
export * from "./transformation/context";
export { TranspileError } from "./transformation/utils/errors";
export * from "./Transpile";

export interface TranspileFilesResult {
diagnostics: ts.Diagnostic[];
emitResult: OutputFile[];
}

export function transpileFiles(rootNames: string[], options: CompilerOptions = {}): TranspileFilesResult {
const program = ts.createProgram(rootNames, options);
const { transpiledFiles, diagnostics: transpileDiagnostics } = transpile({ program });
const emitResult = emitTranspiledFiles(program, transpiledFiles);

const diagnostics = ts.sortAndDeduplicateDiagnostics([
...ts.getPreEmitDiagnostics(program),
...transpileDiagnostics,
]);

return { diagnostics: [...diagnostics], emitResult };
}

export function transpileProject(configFileName: string, optionsToExtend?: CompilerOptions): TranspileFilesResult {
const parseResult = parseConfigFileWithSystem(configFileName, optionsToExtend);
if (parseResult.errors.length > 0) {
return { diagnostics: parseResult.errors, emitResult: [] };
}

return transpileFiles(parseResult.fileNames, parseResult.options);
}

const libCache: { [key: string]: ts.SourceFile } = {};

/** @internal */
export function createVirtualProgram(input: Record<string, string>, options: CompilerOptions = {}): ts.Program {
const compilerHost: ts.CompilerHost = {
fileExists: () => true,
getCanonicalFileName: fileName => fileName,
getCurrentDirectory: () => "",
getDefaultLibFileName: ts.getDefaultLibFileName,
readFile: () => "",
getNewLine: () => "\n",
useCaseSensitiveFileNames: () => false,
writeFile: () => {},

getSourceFile: filename => {
if (filename in input) {
return ts.createSourceFile(filename, input[filename], ts.ScriptTarget.Latest, false);
}

if (filename.startsWith("lib.")) {
if (libCache[filename]) return libCache[filename];
const typeScriptDir = path.dirname(require.resolve("typescript"));
const filePath = path.join(typeScriptDir, filename);
const content = fs.readFileSync(filePath, "utf8");

libCache[filename] = ts.createSourceFile(filename, content, ts.ScriptTarget.Latest, false);

return libCache[filename];
}
},
};

return ts.createProgram(Object.keys(input), options, compilerHost);
}

export function transpileVirtualProject(files: Record<string, string>, options: CompilerOptions = {}): TranspileResult {
const program = createVirtualProgram(files, options);
const result = transpile({ program });
const diagnostics = ts.sortAndDeduplicateDiagnostics([...ts.getPreEmitDiagnostics(program), ...result.diagnostics]);

return { ...result, diagnostics: [...diagnostics] };
}

export interface TranspileStringResult {
diagnostics: ts.Diagnostic[];
file?: TranspiledFile;
}

export function transpileString(main: string, options: CompilerOptions = {}): TranspileStringResult {
const { diagnostics, transpiledFiles } = transpileVirtualProject({ "main.ts": main }, options);
return { diagnostics, file: transpiledFiles.find(({ fileName }) => fileName === "main.ts") };
}
export * from "./transpilation";
12 changes: 6 additions & 6 deletions src/bundle.ts → src/transpilation/bundle.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import * as path from "path";
import { SourceNode } from "source-map";
import * as ts from "typescript";
import { CompilerOptions } from "./CompilerOptions";
import { CompilerOptions } from "../CompilerOptions";
import { getLuaLibBundle } from "../LuaLib";
import { escapeString } from "../LuaPrinter";
import { formatPathToLuaPath, normalizeSlashes, trimExtension } from "../utils";
import { couldNotFindBundleEntryPoint } from "./diagnostics";
import { escapeString } from "./LuaPrinter";
import { EmitHost, TranspiledFile } from "./Transpile";
import { formatPathToLuaPath, normalizeSlashes, trimExtension } from "./utils";
import { EmitHost, TranspiledFile } from "./transpile";

const createModulePath = (baseDir: string, pathToResolve: string) =>
escapeString(formatPathToLuaPath(trimExtension(path.relative(baseDir, pathToResolve))));
Expand Down Expand Up @@ -43,8 +44,7 @@ export function bundleTranspiledFiles(
// If any of the modules contains a require for lualib_bundle, add it to the module table.
const lualibRequired = transpiledFiles.some(f => f.lua?.includes(`require("lualib_bundle")`));
if (lualibRequired) {
const lualibBundle = emitHost.readFile(path.resolve(__dirname, "../dist/lualib/lualib_bundle.lua"));
moduleTableEntries.push(`["lualib_bundle"] = function() ${lualibBundle} end,\n`);
moduleTableEntries.push(`["lualib_bundle"] = function() ${getLuaLibBundle(emitHost)} end,\n`);
}

// Create ____modules table containing all entries from moduleTableEntries
Expand Down
File renamed without changes.
19 changes: 5 additions & 14 deletions src/Emit.ts → src/transpilation/emit.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import * as path from "path";
import * as ts from "typescript";
import { LuaLibImportKind } from "./CompilerOptions";
import { EmitHost, TranspiledFile } from "./Transpile";
import { normalizeSlashes, trimExtension } from "./utils";
import { LuaLibImportKind } from "../CompilerOptions";
import { getLuaLibBundle } from "../LuaLib";
import { normalizeSlashes, trimExtension } from "../utils";
import { EmitHost, TranspiledFile } from "./transpile";

export interface OutputFile {
name: string;
text: string;
}

let lualibContent: string;
export function emitTranspiledFiles(
program: ts.Program,
transpiledFiles: TranspiledFile[],
Expand Down Expand Up @@ -56,21 +56,12 @@ export function emitTranspiledFiles(
) {
const lualibRequired = files.some(f => f.text?.includes(`require("lualib_bundle")`));
if (lualibRequired) {
if (lualibContent === undefined) {
const lualibBundle = emitHost.readFile(path.resolve(__dirname, "../dist/lualib/lualib_bundle.lua"));
if (lualibBundle !== undefined) {
lualibContent = lualibBundle;
} else {
throw new Error("Could not load lualib bundle from ./dist/lualib/lualib_bundle.lua");
}
}

let outPath = path.resolve(rootDir, "lualib_bundle.lua");
if (outDir !== rootDir) {
outPath = path.join(outDir, path.relative(rootDir, outPath));
}

files.push({ name: normalizeSlashes(outPath), text: lualibContent });
files.push({ name: normalizeSlashes(outPath), text: getLuaLibBundle(emitHost) });
}
}

Expand Down
90 changes: 90 additions & 0 deletions src/transpilation/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import * as fs from "fs";
import * as path from "path";
import * as ts from "typescript";
import { parseConfigFileWithSystem } from "../cli/tsconfig";
import { CompilerOptions } from "../CompilerOptions";
import { emitTranspiledFiles, OutputFile } from "./emit";
import { transpile, TranspiledFile, TranspileResult } from "./transpile";

export * from "./emit";
export * from "./transpile";

export interface TranspileFilesResult {
diagnostics: ts.Diagnostic[];
emitResult: OutputFile[];
}

export function transpileFiles(rootNames: string[], options: CompilerOptions = {}): TranspileFilesResult {
const program = ts.createProgram(rootNames, options);
const { transpiledFiles, diagnostics: transpileDiagnostics } = transpile({ program });
const emitResult = emitTranspiledFiles(program, transpiledFiles);

const diagnostics = ts.sortAndDeduplicateDiagnostics([
...ts.getPreEmitDiagnostics(program),
...transpileDiagnostics,
]);

return { diagnostics: [...diagnostics], emitResult };
}

export function transpileProject(configFileName: string, optionsToExtend?: CompilerOptions): TranspileFilesResult {
const parseResult = parseConfigFileWithSystem(configFileName, optionsToExtend);
if (parseResult.errors.length > 0) {
return { diagnostics: parseResult.errors, emitResult: [] };
}

return transpileFiles(parseResult.fileNames, parseResult.options);
}

const libCache: { [key: string]: ts.SourceFile } = {};

/** @internal */
export function createVirtualProgram(input: Record<string, string>, options: CompilerOptions = {}): ts.Program {
const compilerHost: ts.CompilerHost = {
fileExists: () => true,
getCanonicalFileName: fileName => fileName,
getCurrentDirectory: () => "",
getDefaultLibFileName: ts.getDefaultLibFileName,
readFile: () => "",
getNewLine: () => "\n",
useCaseSensitiveFileNames: () => false,
writeFile: () => {},

getSourceFile: filename => {
if (filename in input) {
return ts.createSourceFile(filename, input[filename], ts.ScriptTarget.Latest, false);
}

if (filename.startsWith("lib.")) {
if (libCache[filename]) return libCache[filename];
const typeScriptDir = path.dirname(require.resolve("typescript"));
const filePath = path.join(typeScriptDir, filename);
const content = fs.readFileSync(filePath, "utf8");

libCache[filename] = ts.createSourceFile(filename, content, ts.ScriptTarget.Latest, false);

return libCache[filename];
}
},
};

return ts.createProgram(Object.keys(input), options, compilerHost);
}

export function transpileVirtualProject(files: Record<string, string>, options: CompilerOptions = {}): TranspileResult {
const program = createVirtualProgram(files, options);
const result = transpile({ program });
const diagnostics = ts.sortAndDeduplicateDiagnostics([...ts.getPreEmitDiagnostics(program), ...result.diagnostics]);

return { ...result, diagnostics: [...diagnostics] };
}

export interface TranspileStringResult {
diagnostics: ts.Diagnostic[];
file?: TranspiledFile;
}

export function transpileString(main: string, options: CompilerOptions = {}): TranspileStringResult {
const { diagnostics, transpiledFiles } = transpileVirtualProject({ "main.ts": main }, options);
return { diagnostics, file: transpiledFiles.find(({ fileName }) => fileName === "main.ts") };
}
4 changes: 2 additions & 2 deletions src/plugins.ts → src/transpilation/plugins.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as ts from "typescript";
import { Printer } from "./LuaPrinter";
import { Visitors } from "./transformation/context";
import { Printer } from "../LuaPrinter";
import { Visitors } from "../transformation/context";

export interface Plugin {
/**
Expand Down
18 changes: 15 additions & 3 deletions src/TSTransformers.ts → src/transpilation/transformers.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,22 @@
import * as path from "path";
import * as resolve from "resolve";
import * as ts from "typescript";
import * as cliDiagnostics from "./cli/diagnostics";
import { CompilerOptions, TransformerImport } from "./CompilerOptions";
// TODO: Don't depend on CLI?
import * as cliDiagnostics from "../cli/diagnostics";
import { CompilerOptions, TransformerImport } from "../CompilerOptions";
import * as diagnosticFactories from "./diagnostics";
import { noImplicitSelfTransformer } from "./NoImplicitSelfTransformer";

export const noImplicitSelfTransformer: ts.TransformerFactory<ts.SourceFile | ts.Bundle> = () => node => {
const transformSourceFile: ts.Transformer<ts.SourceFile> = node => {
const empty = ts.createNotEmittedStatement(undefined!);
ts.addSyntheticLeadingComment(empty, ts.SyntaxKind.MultiLineCommentTrivia, "* @noSelfInFile ", true);
return ts.updateSourceFileNode(node, [empty, ...node.statements], node.isDeclarationFile);
};

return ts.isBundle(node)
? ts.updateBundle(node, node.sourceFiles.map(transformSourceFile))
: transformSourceFile(node);
};

export function getCustomTransformers(
program: ts.Program,
Expand Down
12 changes: 6 additions & 6 deletions src/Transpile.ts → src/transpilation/transpile.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { SourceNode } from "source-map";
import * as ts from "typescript";
import { CompilerOptions, validateOptions } from "../CompilerOptions";
import { Block } from "../LuaAST";
import { createPrinter } from "../LuaPrinter";
import { createVisitorMap, transformSourceFile } from "../transformation";
import { isNonNull } from "../utils";
import { bundleTranspiledFiles } from "./bundle";
import { CompilerOptions, validateOptions } from "./CompilerOptions";
import { Block } from "./LuaAST";
import { createPrinter } from "./LuaPrinter";
import { getPlugins, Plugin } from "./plugins";
import { createVisitorMap, transformSourceFile } from "./transformation";
import { getCustomTransformers } from "./TSTransformers";
import { isNonNull } from "./utils";
import { getCustomTransformers } from "./transformers";

export interface TranspiledFile {
fileName: string;
Expand Down
2 changes: 1 addition & 1 deletion test/unit/bundle.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as path from "path";
import * as ts from "typescript";
import { DiagnosticCategory } from "typescript";
import { LuaLibImportKind } from "../../src";
import { couldNotFindBundleEntryPoint } from "../../src/diagnostics";
import { couldNotFindBundleEntryPoint } from "../../src/transpilation/diagnostics";
import * as util from "../util";

test("no entry point", () => {
Expand Down