diff --git a/src/LuaLib.ts b/src/LuaLib.ts index b50ab1e75..5ca4efacb 100644 --- a/src/LuaLib.ts +++ b/src/LuaLib.ts @@ -1,5 +1,5 @@ import * as path from "path"; -import { EmitHost } from "./Transpile"; +import { EmitHost } from "./transpilation"; export enum LuaLibFeature { ArrayConcat = "ArrayConcat", @@ -93,12 +93,12 @@ export function loadLuaLibFeatures(features: Iterable, 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}'`); } } @@ -108,3 +108,18 @@ export function loadLuaLibFeatures(features: Iterable, 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; +} diff --git a/src/LuaPrinter.ts b/src/LuaPrinter.ts index 38440698c..fda6e89ff 100644 --- a/src/LuaPrinter.ts +++ b/src/LuaPrinter.ts @@ -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 diff --git a/src/NoImplicitSelfTransformer.ts b/src/NoImplicitSelfTransformer.ts deleted file mode 100644 index 1b77c4070..000000000 --- a/src/NoImplicitSelfTransformer.ts +++ /dev/null @@ -1,10 +0,0 @@ -import * as ts from "typescript"; - -const transformSourceFile: ts.Transformer = node => { - const empty = ts.createNotEmittedStatement(undefined!); - ts.addSyntheticLeadingComment(empty, ts.SyntaxKind.MultiLineCommentTrivia, "* @noSelfInFile ", true); - return ts.updateSourceFileNode(node, [empty, ...node.statements], node.isDeclarationFile); -}; - -export const noImplicitSelfTransformer: ts.TransformerFactory = () => node => - ts.isBundle(node) ? ts.updateBundle(node, node.sourceFiles.map(transformSourceFile)) : transformSourceFile(node); diff --git a/src/index.ts b/src/index.ts index 7ed6e4ed0..8b0159a9c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -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, 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, 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"; diff --git a/src/bundle.ts b/src/transpilation/bundle.ts similarity index 92% rename from src/bundle.ts rename to src/transpilation/bundle.ts index e0ea3249b..7c587055c 100644 --- a/src/bundle.ts +++ b/src/transpilation/bundle.ts @@ -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)))); @@ -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 diff --git a/src/diagnostics.ts b/src/transpilation/diagnostics.ts similarity index 100% rename from src/diagnostics.ts rename to src/transpilation/diagnostics.ts diff --git a/src/Emit.ts b/src/transpilation/emit.ts similarity index 75% rename from src/Emit.ts rename to src/transpilation/emit.ts index 160ca49ff..0b6cef269 100644 --- a/src/Emit.ts +++ b/src/transpilation/emit.ts @@ -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[], @@ -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) }); } } diff --git a/src/transpilation/index.ts b/src/transpilation/index.ts new file mode 100644 index 000000000..530fbdd4c --- /dev/null +++ b/src/transpilation/index.ts @@ -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, 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, 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") }; +} diff --git a/src/plugins.ts b/src/transpilation/plugins.ts similarity index 86% rename from src/plugins.ts rename to src/transpilation/plugins.ts index ef2e07a1f..8731211d6 100644 --- a/src/plugins.ts +++ b/src/transpilation/plugins.ts @@ -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 { /** diff --git a/src/TSTransformers.ts b/src/transpilation/transformers.ts similarity index 91% rename from src/TSTransformers.ts rename to src/transpilation/transformers.ts index a2b606465..71371e208 100644 --- a/src/TSTransformers.ts +++ b/src/transpilation/transformers.ts @@ -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 = () => node => { + const transformSourceFile: ts.Transformer = 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, diff --git a/src/Transpile.ts b/src/transpilation/transpile.ts similarity index 94% rename from src/Transpile.ts rename to src/transpilation/transpile.ts index 57326e404..df8214718 100644 --- a/src/Transpile.ts +++ b/src/transpilation/transpile.ts @@ -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; diff --git a/test/unit/bundle.spec.ts b/test/unit/bundle.spec.ts index 7bca27ae5..582b79177 100644 --- a/test/unit/bundle.spec.ts +++ b/test/unit/bundle.spec.ts @@ -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", () => {