From 6b34308e83b34c51e684be3cd4e7e9ff8f5c0dc4 Mon Sep 17 00:00:00 2001 From: ark120202 Date: Mon, 27 Jul 2020 14:53:13 +0500 Subject: [PATCH 01/58] Node-like module resolution --- src/CompilerOptions.ts | 4 +- src/LuaPrinter.ts | 15 +- src/transformation/utils/diagnostics.ts | 4 - src/transformation/visitors/modules/import.ts | 39 +---- src/transpilation/bundle.ts | 38 ++--- src/transpilation/diagnostics.ts | 4 + src/transpilation/index.ts | 1 - src/transpilation/macro.ts | 28 +++ .../{transpile.ts => transpile/index.ts} | 12 +- src/transpilation/{ => transpile}/plugins.ts | 8 +- .../{ => transpile}/transformers.ts | 8 +- src/transpilation/transpiler.ts | 160 +++++++++++++++--- src/transpilation/utils.ts | 6 +- src/utils.ts | 9 - 14 files changed, 219 insertions(+), 117 deletions(-) create mode 100644 src/transpilation/macro.ts rename src/transpilation/{transpile.ts => transpile/index.ts} (93%) rename src/transpilation/{ => transpile}/plugins.ts (85%) rename src/transpilation/{ => transpile}/transformers.ts (96%) diff --git a/src/CompilerOptions.ts b/src/CompilerOptions.ts index 2e78b2b87..92c2cbf70 100644 --- a/src/CompilerOptions.ts +++ b/src/CompilerOptions.ts @@ -51,7 +51,9 @@ export enum LuaTarget { LuaJIT = "JIT", } -export const isBundleEnabled = (options: CompilerOptions) => +export const isBundleEnabled = ( + options: CompilerOptions +): options is CompilerOptions & Required> => options.luaBundle !== undefined && options.luaBundleEntry !== undefined; export function validateOptions(options: CompilerOptions): ts.Diagnostic[] { diff --git a/src/LuaPrinter.ts b/src/LuaPrinter.ts index 63654c595..0fe726604 100644 --- a/src/LuaPrinter.ts +++ b/src/LuaPrinter.ts @@ -6,7 +6,7 @@ import * as lua from "./LuaAST"; import { loadLuaLibFeatures, LuaLibFeature } from "./LuaLib"; import { isValidLuaIdentifier } from "./transformation/utils/safe-names"; import { EmitHost } from "./transpilation"; -import { intersperse, trimExtension, normalizeSlashes } from "./utils"; +import { assert, intersperse, normalizeSlashes, trimExtension } from "./utils"; // https://www.lua.org/pil/2.4.html // https://www.ecma-international.org/ecma-262/10.0/index.html#table-34 @@ -25,6 +25,12 @@ const escapeStringMap: Record = { export const escapeString = (value: string) => `"${value.replace(escapeStringRegExp, char => escapeStringMap[char])}"`; +export const unescapeLuaString = (value: string) => { + assert(value.startsWith('"') && value.endsWith(""), 'Only strings generated by "escapeString" can be unescaped'); + // TODO: Unescape + return value.slice(1, -1); +}; + /** * Checks that a name is valid for use in lua function declaration syntax: * @@ -39,6 +45,13 @@ const isValidLuaFunctionDeclarationName = (str: string) => /^[a-zA-Z0-9_.]+$/.te function isSimpleExpression(expression: lua.Expression): boolean { switch (expression.kind) { case lua.SyntaxKind.CallExpression: + const calledExpression = (expression as lua.CallExpression).expression; + // __TS__Resolve macro is guaranteed to be pure + if (lua.isIdentifier(calledExpression) && calledExpression.text === "__TS__Resolve") { + return true; + } + + return false; case lua.SyntaxKind.MethodCallExpression: case lua.SyntaxKind.FunctionExpression: return false; diff --git a/src/transformation/utils/diagnostics.ts b/src/transformation/utils/diagnostics.ts index a5bdb35db..5e3f78361 100644 --- a/src/transformation/utils/diagnostics.ts +++ b/src/transformation/utils/diagnostics.ts @@ -119,10 +119,6 @@ export const invalidAmbientIdentifierName = createDiagnosticFactory( (text: string) => `Invalid ambient identifier name '${text}'. Ambient identifiers must be valid lua identifiers.` ); -export const unresolvableRequirePath = createDiagnosticFactory( - (path: string) => `Cannot create require path. Module '${path}' does not exist within --rootDir.` -); - export const unsupportedVarDeclaration = createDiagnosticFactory( "`var` declarations are not supported. Use `let` or `const` instead." ); diff --git a/src/transformation/visitors/modules/import.ts b/src/transformation/visitors/modules/import.ts index 34fa216d6..07ceda820 100644 --- a/src/transformation/visitors/modules/import.ts +++ b/src/transformation/visitors/modules/import.ts @@ -1,7 +1,6 @@ import * as path from "path"; import * as ts from "typescript"; import * as lua from "../../../LuaAST"; -import { formatPathToLuaPath } from "../../../utils"; import { FunctionVisitor, TransformationContext } from "../../context"; import { AnnotationKind, getSymbolAnnotations, getTypeAnnotations } from "../../utils/annotations"; import { createDefaultExportStringLiteral } from "../../utils/export"; @@ -10,29 +9,6 @@ import { createSafeName } from "../../utils/safe-names"; import { peekScope } from "../../utils/scope"; import { transformIdentifier } from "../identifier"; import { transformPropertyName } from "../literal"; -import { unresolvableRequirePath } from "../../utils/diagnostics"; - -const getAbsoluteImportPath = (relativePath: string, directoryPath: string, options: ts.CompilerOptions): string => - !relativePath.startsWith(".") && options.baseUrl - ? path.resolve(options.baseUrl, relativePath) - : path.resolve(directoryPath, relativePath); - -function getImportPath(context: TransformationContext, relativePath: string, node: ts.Node): string { - const { options, sourceFile } = context; - const { fileName } = sourceFile; - 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 { - context.diagnostics.push(unresolvableRequirePath(node, relativePath)); - return relativePath; - } -} function shouldResolveModulePath(context: TransformationContext, moduleSpecifier: ts.Expression): boolean { const moduleOwnerSymbol = context.checker.getSymbolAtLocation(moduleSpecifier); @@ -49,11 +25,12 @@ export function createModuleRequire( ): lua.CallExpression { const params: lua.Expression[] = []; if (ts.isStringLiteral(moduleSpecifier)) { - const modulePath = shouldResolveModulePath(context, moduleSpecifier) - ? getImportPath(context, moduleSpecifier.text.replace(/"/g, ""), moduleSpecifier) - : moduleSpecifier.text; - - params.push(lua.createStringLiteral(modulePath)); + const module = lua.createStringLiteral(moduleSpecifier.text); + params.push( + shouldResolveModulePath(context, moduleSpecifier) + ? lua.createCallExpression(lua.createIdentifier("__TS__Resolve"), [module]) + : module + ); } return lua.createCallExpression(lua.createIdentifier("require"), params, tsOriginal); @@ -110,9 +87,7 @@ export const transformImportDeclaration: FunctionVisitor = } } - const importPath = ts.isStringLiteral(statement.moduleSpecifier) - ? statement.moduleSpecifier.text.replace(/"/g, "") - : "module"; + const importPath = ts.isStringLiteral(statement.moduleSpecifier) ? statement.moduleSpecifier.text : "module"; // Create the require statement to extract values. // local ____module = require("module") diff --git a/src/transpilation/bundle.ts b/src/transpilation/bundle.ts index c696c6900..9c28acedf 100644 --- a/src/transpilation/bundle.ts +++ b/src/transpilation/bundle.ts @@ -1,15 +1,12 @@ import * as path from "path"; import { SourceNode } from "source-map"; import * as ts from "typescript"; -import { CompilerOptions } from "../CompilerOptions"; +import { CompilerOptions, isBundleEnabled } from "../CompilerOptions"; import { escapeString } from "../LuaPrinter"; -import { cast, formatPathToLuaPath, isNonNull, normalizeSlashes, trimExtension } from "../utils"; +import { assert, normalizeSlashes } from "../utils"; import { couldNotFindBundleEntryPoint } from "./diagnostics"; import { EmitFile, EmitHost, ProcessedFile } from "./utils"; -const createModulePath = (baseDir: string, pathToResolve: string) => - escapeString(formatPathToLuaPath(trimExtension(path.relative(baseDir, pathToResolve)))); - // Override `require` to read from ____modules table. const requireOverride = ` local ____modules = {} @@ -35,37 +32,36 @@ end export function getBundleResult( program: ts.Program, emitHost: EmitHost, - files: ProcessedFile[] + files: ProcessedFile[], + getRequirePath: (file: ProcessedFile) => string ): [ts.Diagnostic[], EmitFile] { const diagnostics: ts.Diagnostic[] = []; const options = program.getCompilerOptions() as CompilerOptions; - const bundleFile = cast(options.luaBundle, isNonNull); - const entryModule = cast(options.luaBundleEntry, isNonNull); - - const rootDir = program.getCommonSourceDirectory(); - const outDir = options.outDir ?? rootDir; + assert(isBundleEnabled(options)); + const bundleFile = options.luaBundle; + const entryModule = options.luaBundleEntry; const projectRootDir = options.configFilePath ? path.dirname(options.configFilePath) : emitHost.getCurrentDirectory(); + const outputPath = normalizeSlashes(path.resolve(projectRootDir, bundleFile)); // Resolve project settings relative to project file. const resolvedEntryModule = path.resolve(projectRootDir, entryModule); - const outputPath = normalizeSlashes(path.resolve(projectRootDir, bundleFile)); - - if (!files.some(f => f.fileName === resolvedEntryModule)) { + const entryFile = files.find(f => f.fileName === resolvedEntryModule); + if (entryFile === undefined) { diagnostics.push(couldNotFindBundleEntryPoint(entryModule)); return [diagnostics, { outputPath, code: "" }]; } // For each file: [""] = function() end, - const moduleTableEntries = files.map(f => moduleSourceNode(f, createModulePath(outDir, f.fileName))); + const moduleTableEntries = files.map(f => moduleSourceNode(f, escapeString(getRequirePath(f)))); // Create ____modules table containing all entries from moduleTableEntries const moduleTable = createModuleTableNode(moduleTableEntries); // return require("") - const entryPoint = `return require(${createModulePath(outDir, resolvedEntryModule)})\n`; + const entryPoint = `return require(${escapeString(getRequirePath(entryFile))})\n`; const bundleNode = joinSourceChunks([requireOverride, moduleTable, entryPoint]); const { code, map } = bundleNode.toStringWithSourceMap(); @@ -82,17 +78,11 @@ export function getBundleResult( } function moduleSourceNode({ code, sourceMapNode }: ProcessedFile, modulePath: string): SourceNode { - const tableEntryHead = `[${modulePath}] = function() `; - const tableEntryTail = "end,\n"; - - return joinSourceChunks([tableEntryHead, sourceMapNode ?? code, tableEntryTail]); + return joinSourceChunks([`[${modulePath}] = function()\n`, sourceMapNode ?? code, "\nend,\n"]); } function createModuleTableNode(fileChunks: SourceChunk[]): SourceNode { - const tableHead = "____modules = {\n"; - const tableEnd = "}\n"; - - return joinSourceChunks([tableHead, ...fileChunks, tableEnd]); + return joinSourceChunks(["____modules = {\n", ...fileChunks, "}\n"]); } type SourceChunk = string | SourceNode; diff --git a/src/transpilation/diagnostics.ts b/src/transpilation/diagnostics.ts index d0e609677..86aee7e95 100644 --- a/src/transpilation/diagnostics.ts +++ b/src/transpilation/diagnostics.ts @@ -37,3 +37,7 @@ export const usingLuaBundleWithInlineMightGenerateDuplicateCode = createSerialDi "Using 'luaBundle' with 'luaLibImport: \"inline\"' might generate duplicate code. " + "It is recommended to use 'luaLibImport: \"require\"'.", })); + +export const unresolvableRequirePath = createDiagnosticFactory( + (path: string) => `Cannot create require path. Module '${path}' does not exist within --rootDir.` +); diff --git a/src/transpilation/index.ts b/src/transpilation/index.ts index 628246ec9..8229e8ddd 100644 --- a/src/transpilation/index.ts +++ b/src/transpilation/index.ts @@ -6,7 +6,6 @@ import { CompilerOptions } from "../CompilerOptions"; import { createEmitOutputCollector, TranspiledFile } from "./output-collector"; import { EmitResult, Transpiler } from "./transpiler"; -export { Plugin } from "./plugins"; export * from "./transpile"; export * from "./transpiler"; export { EmitHost } from "./utils"; diff --git a/src/transpilation/macro.ts b/src/transpilation/macro.ts new file mode 100644 index 000000000..01f7c960d --- /dev/null +++ b/src/transpilation/macro.ts @@ -0,0 +1,28 @@ +import { SourceNode } from "source-map"; +import { escapeString, unescapeLuaString } from "../LuaPrinter"; + +export type ResolveMacroReplacer = (request: string) => string | { error: string }; + +export function replaceResolveMacroSourceNodes(rootNode: SourceNode, replacer: ResolveMacroReplacer) { + function walkSourceNode(node: SourceNode, parent: SourceNode) { + for (const child of node.children) { + if ((child as any) === "__TS__Resolve") { + parent.children = [replaceResolveMacroInSource(parent.toString(), replacer) as any]; + } else if (typeof child === "object") { + walkSourceNode(child, node); + } + } + } + + walkSourceNode(rootNode, rootNode); +} + +export function replaceResolveMacroInSource(source: string, replacer: ResolveMacroReplacer) { + return source.replace(/__TS__Resolve\((".*?")\)/, (_, match) => { + const request = unescapeLuaString(match); + const replacement = replacer(request); + return typeof replacement === "string" + ? escapeString(replacement) + : `--[[ ${request} ]] error(${escapeString(replacement.error)})`; + }); +} diff --git a/src/transpilation/transpile.ts b/src/transpilation/transpile/index.ts similarity index 93% rename from src/transpilation/transpile.ts rename to src/transpilation/transpile/index.ts index 01d9278c5..a8b9762ed 100644 --- a/src/transpilation/transpile.ts +++ b/src/transpilation/transpile/index.ts @@ -1,12 +1,14 @@ import * as path from "path"; import * as ts from "typescript"; -import { CompilerOptions, validateOptions } from "../CompilerOptions"; -import { createPrinter } from "../LuaPrinter"; -import { createVisitorMap, transformSourceFile } from "../transformation"; -import { isNonNull } from "../utils"; +import { CompilerOptions, validateOptions } from "../../CompilerOptions"; +import { createPrinter } from "../../LuaPrinter"; +import { createVisitorMap, transformSourceFile } from "../../transformation"; +import { isNonNull } from "../../utils"; import { getPlugins, Plugin } from "./plugins"; import { getTransformers } from "./transformers"; -import { EmitHost, ProcessedFile } from "./utils"; +import { EmitHost, ProcessedFile } from "../utils"; + +export { Plugin }; export interface TranspileOptions { program: ts.Program; diff --git a/src/transpilation/plugins.ts b/src/transpilation/transpile/plugins.ts similarity index 85% rename from src/transpilation/plugins.ts rename to src/transpilation/transpile/plugins.ts index 0311efbbd..6c137d03c 100644 --- a/src/transpilation/plugins.ts +++ b/src/transpilation/transpile/plugins.ts @@ -1,8 +1,8 @@ import * as ts from "typescript"; -import { CompilerOptions } from "../CompilerOptions"; -import { Printer } from "../LuaPrinter"; -import { Visitors } from "../transformation/context"; -import { getConfigDirectory, resolvePlugin } from "./utils"; +import { CompilerOptions } from "../../CompilerOptions"; +import { Printer } from "../../LuaPrinter"; +import { Visitors } from "../../transformation/context"; +import { getConfigDirectory, resolvePlugin } from "../utils"; export interface Plugin { /** diff --git a/src/transpilation/transformers.ts b/src/transpilation/transpile/transformers.ts similarity index 96% rename from src/transpilation/transformers.ts rename to src/transpilation/transpile/transformers.ts index 470e7ad13..10b2bb343 100644 --- a/src/transpilation/transformers.ts +++ b/src/transpilation/transpile/transformers.ts @@ -1,9 +1,9 @@ import * as ts from "typescript"; // TODO: Don't depend on CLI? -import * as cliDiagnostics from "../cli/diagnostics"; -import { CompilerOptions, TransformerImport } from "../CompilerOptions"; -import * as diagnosticFactories from "./diagnostics"; -import { getConfigDirectory, resolvePlugin } from "./utils"; +import * as cliDiagnostics from "../../cli/diagnostics"; +import { CompilerOptions, TransformerImport } from "../../CompilerOptions"; +import * as diagnosticFactories from "../diagnostics"; +import { getConfigDirectory, resolvePlugin } from "../utils"; export function getTransformers( program: ts.Program, diff --git a/src/transpilation/transpiler.ts b/src/transpilation/transpiler.ts index 205b6b2db..e1ed09d36 100644 --- a/src/transpilation/transpiler.ts +++ b/src/transpilation/transpiler.ts @@ -1,9 +1,11 @@ import * as path from "path"; +import * as resolve from "resolve"; import * as ts from "typescript"; -import { isBundleEnabled } from "../CompilerOptions"; +import { CompilerOptions, isBundleEnabled, LuaTarget } from "../CompilerOptions"; import { getLuaLibBundle } from "../LuaLib"; -import { normalizeSlashes, trimExtension } from "../utils"; +import { cast, isNonNull, normalizeSlashes, trimExtension } from "../utils"; import { getBundleResult } from "./bundle"; +import { replaceResolveMacroInSource, replaceResolveMacroSourceNodes, ResolveMacroReplacer } from "./macro"; import { getProgramTranspileResult, TranspileOptions } from "./transpile"; import { EmitFile, EmitHost, ProcessedFile } from "./utils"; @@ -28,12 +30,8 @@ export class Transpiler { public emit(emitOptions: EmitOptions): EmitResult { const { program, writeFile = this.emitHost.writeFile } = emitOptions; - const { diagnostics, transpiledFiles: freshFiles } = getProgramTranspileResult( - this.emitHost, - writeFile, - emitOptions - ); - const { emitPlan } = this.getEmitPlan(program, diagnostics, freshFiles); + const { diagnostics, transpiledFiles } = getProgramTranspileResult(this.emitHost, writeFile, emitOptions); + const emitPlan = this.getEmitPlan(program, diagnostics, transpiledFiles); const options = program.getCompilerOptions(); const emitBOM = options.emitBOM ?? false; @@ -47,34 +45,142 @@ export class Transpiler { return { diagnostics, emitSkipped: emitPlan.length === 0 }; } - protected getEmitPlan( - program: ts.Program, - diagnostics: ts.Diagnostic[], - files: ProcessedFile[] - ): { emitPlan: EmitFile[] } { - const options = program.getCompilerOptions(); - const rootDir = program.getCommonSourceDirectory(); - const outDir = options.outDir ?? rootDir; + private getEmitPlan(program: ts.Program, diagnostics: ts.Diagnostic[], transpiledFiles: ProcessedFile[]) { + const transpilation = new Transpilation(this.emitHost, program); + const emitPlan = transpilation.emit(transpiledFiles); + diagnostics.push(...transpilation.diagnostics); + return emitPlan; + } +} + +// TODO: JIT -> jit? +type PackageLuaField = string | Record; + +class Transpilation { + public readonly diagnostics: ts.Diagnostic[] = []; + private seenFiles = new Set(); + private files: ProcessedFile[] = []; - const lualibRequired = files.some(f => f.code.includes('require("lualib_bundle")')); + constructor(private emitHost: EmitHost, private program: ts.Program) {} + + private options = this.program.getCompilerOptions() as CompilerOptions; + private rootDir = this.program.getCommonSourceDirectory(); + private outDir = this.options.outDir ?? this.rootDir; + + public emit(transpiledFiles: ProcessedFile[]): EmitFile[] { + transpiledFiles.forEach(file => this.seenFiles.add(file.fileName)); + transpiledFiles.forEach(file => this.handleProcessedFile(file)); + + const lualibRequired = this.files.some(f => f.code.includes('require("lualib_bundle")')); if (lualibRequired) { - const fileName = normalizeSlashes(path.resolve(rootDir, "lualib_bundle.lua")); - files.unshift({ fileName, code: getLuaLibBundle(this.emitHost) }); + const fileName = normalizeSlashes(path.resolve(this.rootDir, "lualib_bundle.lua")); + this.files.unshift({ fileName, code: getLuaLibBundle(this.emitHost) }); } - let emitPlan: EmitFile[]; - if (isBundleEnabled(options)) { - const [bundleDiagnostics, bundleFile] = getBundleResult(program, this.emitHost, files); - diagnostics.push(...bundleDiagnostics); - emitPlan = [bundleFile]; + if (isBundleEnabled(this.options)) { + const [bundleDiagnostics, bundleFile] = getBundleResult(this.program, this.emitHost, this.files, file => + this.toRequireParameter(this.toOutputStructure(file.fileName)) + ); + this.diagnostics.push(...bundleDiagnostics); + return [bundleFile]; } else { - emitPlan = files.map(file => { - const pathInOutDir = path.resolve(outDir, path.relative(rootDir, file.fileName)); + return this.files.map(file => { + const pathInOutDir = this.toAbsoluteOutputPath(this.toOutputStructure(file.fileName)); const outputPath = normalizeSlashes(trimExtension(pathInOutDir) + ".lua"); return { ...file, outputPath }; }); } + } + + private handleProcessedFile(file: ProcessedFile) { + const replacer: ResolveMacroReplacer = (request: string) => { + let resolvedPath: string; + try { + resolvedPath = this.resolveRequest(request, file.fileName); + } catch (error) { + this.diagnostics.push({ + category: ts.DiagnosticCategory.Error, + code: -1, + file: ts.createSourceFile(file.fileName, "", ts.ScriptTarget.ES3), + start: undefined, + length: undefined, + messageText: error.message, + }); + return { error: error.message }; + } + + this.handleProcessedFile({ + fileName: resolvedPath, + code: cast(this.emitHost.readFile(resolvedPath), isNonNull), + // TODO: Load source map files + }); + + return this.toRequireParameter(this.toOutputStructure(resolvedPath)); + }; + + if (file.sourceMapNode) { + replaceResolveMacroSourceNodes(file.sourceMapNode, replacer); + const { code, map } = file.sourceMapNode.toStringWithSourceMap(); + file.code = code; + file.sourceMap = JSON.stringify(map.toJSON()); + } else { + file.code = replaceResolveMacroInSource(file.code, replacer); + } + + this.files.push(file); + } + + protected resolveRequest(request: string, issuer: string) { + const resolved = resolve.sync(request, { + basedir: path.dirname(issuer), + extensions: [".lua", ".ts", ".tsx", ".js", ".jsx"], + packageFilter: (pkg, pkgFile) => { + delete pkg.main; + + const lua: PackageLuaField = pkg.lua; + if (lua !== undefined) { + if (typeof lua === "string") { + pkg.main = lua; + } else if (typeof lua === "object") { + const luaTarget = this.options.luaTarget ?? LuaTarget.Universal; + pkg.main = lua[luaTarget]; + if (pkg.main === undefined) { + throw new Error(`${pkgFile} not supports Lua ${luaTarget} target.`); + } + } else { + throw new Error(`${pkgFile} has invalid "lua" field value.`); + } + } + + return pkg; + }, + }); + + if (resolve.isCore(resolved)) { + throw new Error(`"${resolved}" is builtin Node.js module.`); + } + + this.toOutputStructure(resolved); + + return resolved; + } + + protected toOutputStructure(fileName: string) { + const result = path.relative(this.rootDir, trimExtension(fileName)); + + if (result.startsWith("..") || path.isAbsolute(result)) { + // TODO: Walk up to find package.json and put it to node_modules/name_version_hash/ + throw new Error(`Cannot resolve "${fileName}" within rootDir`); + } + + return result.replace(/\./g, "__"); + } + + protected toRequireParameter(fileName: string) { + return fileName.replace(/[/\\]/g, "."); + } - return { emitPlan }; + protected toAbsoluteOutputPath(fileName: string) { + return path.resolve(this.outDir, `${fileName}.lua`); } } diff --git a/src/transpilation/utils.ts b/src/transpilation/utils.ts index 98c3f5cc6..a29329b7f 100644 --- a/src/transpilation/utils.ts +++ b/src/transpilation/utils.ts @@ -7,11 +7,7 @@ import * as cliDiagnostics from "../cli/diagnostics"; import * as lua from "../LuaAST"; import * as diagnosticFactories from "./diagnostics"; -export interface EmitHost { - getCurrentDirectory(): string; - readFile(path: string): string | undefined; - writeFile: ts.WriteFileCallback; -} +export type EmitHost = Pick; interface BaseFile { code: string; diff --git a/src/utils.ts b/src/utils.ts index 1244068a4..2f90bc7ba 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -38,15 +38,6 @@ export const createSerialDiagnosticFactory = (creat export const normalizeSlashes = (filePath: string) => filePath.replace(/\\/g, "/"); export const trimExtension = (filePath: string) => filePath.slice(0, -path.extname(filePath).length); -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, "."); -} - type NoInfer = [T][T extends any ? 0 : never]; export function getOrUpdate( From 781c286e8e12e0155b129167831ad2cef969153c Mon Sep 17 00:00:00 2001 From: ark120202 Date: Sat, 12 Sep 2020 21:10:22 +0000 Subject: [PATCH 02/58] Basic node_modules resolution support --- src/transpilation/transpiler.ts | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/src/transpilation/transpiler.ts b/src/transpilation/transpiler.ts index e1ed09d36..c796440af 100644 --- a/src/transpilation/transpiler.ts +++ b/src/transpilation/transpiler.ts @@ -3,7 +3,7 @@ import * as resolve from "resolve"; import * as ts from "typescript"; import { CompilerOptions, isBundleEnabled, LuaTarget } from "../CompilerOptions"; import { getLuaLibBundle } from "../LuaLib"; -import { cast, isNonNull, normalizeSlashes, trimExtension } from "../utils"; +import { assert, cast, isNonNull, normalizeSlashes, trimExtension } from "../utils"; import { getBundleResult } from "./bundle"; import { replaceResolveMacroInSource, replaceResolveMacroSourceNodes, ResolveMacroReplacer } from "./macro"; import { getProgramTranspileResult, TranspileOptions } from "./transpile"; @@ -79,13 +79,13 @@ class Transpilation { if (isBundleEnabled(this.options)) { const [bundleDiagnostics, bundleFile] = getBundleResult(this.program, this.emitHost, this.files, file => - this.toRequireParameter(this.toOutputStructure(file.fileName)) + this.toRequireParameter(this.toGeneratedFileName(file.fileName)) ); this.diagnostics.push(...bundleDiagnostics); return [bundleFile]; } else { return this.files.map(file => { - const pathInOutDir = this.toAbsoluteOutputPath(this.toOutputStructure(file.fileName)); + const pathInOutDir = this.toAbsoluteOutputPath(this.toGeneratedFileName(file.fileName)); const outputPath = normalizeSlashes(trimExtension(pathInOutDir) + ".lua"); return { ...file, outputPath }; }); @@ -115,7 +115,7 @@ class Transpilation { // TODO: Load source map files }); - return this.toRequireParameter(this.toOutputStructure(resolvedPath)); + return this.toRequireParameter(this.toGeneratedFileName(resolvedPath)); }; if (file.sourceMapNode) { @@ -160,20 +160,14 @@ class Transpilation { throw new Error(`"${resolved}" is builtin Node.js module.`); } - this.toOutputStructure(resolved); - return resolved; } - protected toOutputStructure(fileName: string) { + protected toGeneratedFileName(fileName: string) { const result = path.relative(this.rootDir, trimExtension(fileName)); - - if (result.startsWith("..") || path.isAbsolute(result)) { - // TODO: Walk up to find package.json and put it to node_modules/name_version_hash/ - throw new Error(`Cannot resolve "${fileName}" within rootDir`); - } - - return result.replace(/\./g, "__"); + // TODO: handle files on other drives + assert(!path.isAbsolute(result), `Invalid path: ${result}`); + return result.replace(/\.\.\//g, "_/").replace(/\./g, "__"); } protected toRequireParameter(fileName: string) { From 42537d681d9e81b6e105934f476085b18cf67b8f Mon Sep 17 00:00:00 2001 From: ark120202 Date: Sat, 12 Sep 2020 21:14:15 +0000 Subject: [PATCH 03/58] Fix binary files git type --- .gitattributes | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitattributes b/.gitattributes index fcadb2cf9..6313b56c5 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1 @@ -* text eol=lf +* text=auto eol=lf From f168977d5c6dbdf44f2b2d5b8dfdc576f1e8e291 Mon Sep 17 00:00:00 2001 From: ark120202 Date: Fri, 2 Oct 2020 19:18:23 +0000 Subject: [PATCH 04/58] Move resolutions tests to `transpile` directory --- .../modules => transpile}/__snapshots__/resolution.spec.ts.snap | 0 test/{unit/modules => transpile}/resolution.spec.ts | 0 test/unit/{modules => }/modules.spec.ts | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename test/{unit/modules => transpile}/__snapshots__/resolution.spec.ts.snap (100%) rename test/{unit/modules => transpile}/resolution.spec.ts (100%) rename test/unit/{modules => }/modules.spec.ts (100%) diff --git a/test/unit/modules/__snapshots__/resolution.spec.ts.snap b/test/transpile/__snapshots__/resolution.spec.ts.snap similarity index 100% rename from test/unit/modules/__snapshots__/resolution.spec.ts.snap rename to test/transpile/__snapshots__/resolution.spec.ts.snap diff --git a/test/unit/modules/resolution.spec.ts b/test/transpile/resolution.spec.ts similarity index 100% rename from test/unit/modules/resolution.spec.ts rename to test/transpile/resolution.spec.ts diff --git a/test/unit/modules/modules.spec.ts b/test/unit/modules.spec.ts similarity index 100% rename from test/unit/modules/modules.spec.ts rename to test/unit/modules.spec.ts From 3dd2c7e13b74ef10de4822a60713d2c7812216bf Mon Sep 17 00:00:00 2001 From: ark120202 Date: Fri, 2 Oct 2020 19:18:58 +0000 Subject: [PATCH 05/58] Implement `unescapeLuaString` --- src/LuaPrinter.ts | 9 +++++---- src/utils.ts | 3 +++ 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/LuaPrinter.ts b/src/LuaPrinter.ts index 0fe726604..ec0ead80a 100644 --- a/src/LuaPrinter.ts +++ b/src/LuaPrinter.ts @@ -6,7 +6,7 @@ import * as lua from "./LuaAST"; import { loadLuaLibFeatures, LuaLibFeature } from "./LuaLib"; import { isValidLuaIdentifier } from "./transformation/utils/safe-names"; import { EmitHost } from "./transpilation"; -import { assert, intersperse, normalizeSlashes, trimExtension } from "./utils"; +import { assert, intersperse, invertObject, normalizeSlashes, trimExtension } from "./utils"; // https://www.lua.org/pil/2.4.html // https://www.ecma-international.org/ecma-262/10.0/index.html#table-34 @@ -25,10 +25,11 @@ const escapeStringMap: Record = { export const escapeString = (value: string) => `"${value.replace(escapeStringRegExp, char => escapeStringMap[char])}"`; +const unescapeStringRegExp = new RegExp(`\\\\${escapeStringRegExp.source}`, "g"); +const unescapeStingMap = invertObject(escapeStringMap); export const unescapeLuaString = (value: string) => { - assert(value.startsWith('"') && value.endsWith(""), 'Only strings generated by "escapeString" can be unescaped'); - // TODO: Unescape - return value.slice(1, -1); + assert(value.startsWith('"') && value.endsWith('"'), 'Only strings generated by "escapeString" can be unescaped'); + return value.slice(1, -1).replace(unescapeStringRegExp, char => unescapeStingMap[char]); }; /** diff --git a/src/utils.ts b/src/utils.ts index 2f90bc7ba..8a8330f8e 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -16,6 +16,9 @@ export const union = (...values: Array>): T[] => [...new Set(...v export const intersection = (first: readonly T[], ...rest: Array): T[] => union(first).filter(x => rest.every(r => r.includes(x))); +export const invertObject = (record: Record): Record => + Object.fromEntries(Object.entries(record).map(([key, value]) => [value, key])); + type DiagnosticFactory = (...args: any) => Partial & Pick; export const createDiagnosticFactoryWithCode = (code: number, create: T) => Object.assign( From a5597b67540619baa9adda866d0ef059d288e12e Mon Sep 17 00:00:00 2001 From: ark120202 Date: Fri, 2 Oct 2020 20:08:13 +0000 Subject: [PATCH 06/58] Use `enhanced-resolve` for module resolution --- package-lock.json | 21 +++++++++++++++ package.json | 1 + src/transpilation/transpiler.ts | 46 +++++++++------------------------ src/transpilation/utils.ts | 5 +++- 4 files changed, 38 insertions(+), 35 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2a7114663..b6c723c98 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2887,6 +2887,22 @@ "once": "^1.4.0" } }, + "enhanced-resolve": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.2.0.tgz", + "integrity": "sha512-NZlGLl8DxmZoq0uqPPtJfsCAir68uR047+Udsh1FH4+5ydGQdMurn/A430A1BtxASVmMEuS7/XiJ5OxJ9apAzQ==", + "requires": { + "graceful-fs": "^4.2.4", + "tapable": "^2.0.0" + }, + "dependencies": { + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==" + } + } + }, "error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -8727,6 +8743,11 @@ } } }, + "tapable": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.0.0.tgz", + "integrity": "sha512-bjzn0C0RWoffnNdTzNi7rNDhs1Zlwk2tRXgk8EiHKAOX1Mag3d6T0Y5zNa7l9CJ+EoUne/0UHdwS8tMbkh9zDg==" + }, "terminal-link": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", diff --git a/package.json b/package.json index 744d4571b..2e3fb92a9 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "node": ">=12.13.0" }, "dependencies": { + "enhanced-resolve": "^5.2.0", "resolve": "^1.15.1", "source-map": "^0.7.3", "typescript": "^3.9.2" diff --git a/src/transpilation/transpiler.ts b/src/transpilation/transpiler.ts index c796440af..e06fceabf 100644 --- a/src/transpilation/transpiler.ts +++ b/src/transpilation/transpiler.ts @@ -1,5 +1,6 @@ +import { ResolverFactory } from "enhanced-resolve"; +import * as fs from "fs"; import * as path from "path"; -import * as resolve from "resolve"; import * as ts from "typescript"; import { CompilerOptions, isBundleEnabled, LuaTarget } from "../CompilerOptions"; import { getLuaLibBundle } from "../LuaLib"; @@ -53,9 +54,6 @@ export class Transpiler { } } -// TODO: JIT -> jit? -type PackageLuaField = string | Record; - class Transpilation { public readonly diagnostics: ts.Diagnostic[] = []; private seenFiles = new Set(); @@ -130,37 +128,17 @@ class Transpilation { this.files.push(file); } - protected resolveRequest(request: string, issuer: string) { - const resolved = resolve.sync(request, { - basedir: path.dirname(issuer), - extensions: [".lua", ".ts", ".tsx", ".js", ".jsx"], - packageFilter: (pkg, pkgFile) => { - delete pkg.main; - - const lua: PackageLuaField = pkg.lua; - if (lua !== undefined) { - if (typeof lua === "string") { - pkg.main = lua; - } else if (typeof lua === "object") { - const luaTarget = this.options.luaTarget ?? LuaTarget.Universal; - pkg.main = lua[luaTarget]; - if (pkg.main === undefined) { - throw new Error(`${pkgFile} not supports Lua ${luaTarget} target.`); - } - } else { - throw new Error(`${pkgFile} has invalid "lua" field value.`); - } - } - - return pkg; - }, - }); - - if (resolve.isCore(resolved)) { - throw new Error(`"${resolved}" is builtin Node.js module.`); - } + protected resolver = ResolverFactory.createResolver({ + extensions: [".lua", ".ts", ".tsx", ".js", ".jsx"], + conditionNames: ["lua", `lua:${this.options.luaTarget ?? LuaTarget.Universal}`], + fileSystem: this.emitHost.fileSystem ?? fs, + useSyncFileSystemCalls: true, + }); - return resolved; + protected resolveRequest(request: string, issuer: string) { + const result = this.resolver.resolveSync({}, path.dirname(issuer), request); + assert(typeof result === "string"); + return result; } protected toGeneratedFileName(fileName: string) { diff --git a/src/transpilation/utils.ts b/src/transpilation/utils.ts index a29329b7f..b41ffcb6a 100644 --- a/src/transpilation/utils.ts +++ b/src/transpilation/utils.ts @@ -1,3 +1,4 @@ +import * as fs from "fs"; import * as path from "path"; import * as resolve from "resolve"; import { SourceNode } from "source-map"; @@ -7,7 +8,9 @@ import * as cliDiagnostics from "../cli/diagnostics"; import * as lua from "../LuaAST"; import * as diagnosticFactories from "./diagnostics"; -export type EmitHost = Pick; +export interface EmitHost extends Pick { + fileSystem?: typeof fs; +} interface BaseFile { code: string; From 113cd29e8df74880c43ca01588f85927766e257f Mon Sep 17 00:00:00 2001 From: ark120202 Date: Tue, 6 Oct 2020 22:38:31 +0000 Subject: [PATCH 07/58] Add in-memory resolution testing util setup --- package-lock.json | 15 +++++++++++ package.json | 1 + src/transpilation/diagnostics.ts | 14 +++++++++++ src/transpilation/index.ts | 12 ++++++--- src/transpilation/transpile/index.ts | 2 +- src/transpilation/transpiler.ts | 24 ++++++------------ src/transpilation/utils.ts | 4 +-- test/util.ts | 37 +++++++++++++++++++++++++--- 8 files changed, 83 insertions(+), 26 deletions(-) diff --git a/package-lock.json b/package-lock.json index b6c723c98..ca89ad5f8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3767,6 +3767,12 @@ } } }, + "fs-monkey": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.1.tgz", + "integrity": "sha512-fcSa+wyTqZa46iWweI7/ZiUfegOZl0SG8+dltIwFXo7+zYU9J9kpS3NB6pZcSlJdhvIwp81Adx2XhZorncxiaA==", + "dev": true + }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -7203,6 +7209,15 @@ "object-visit": "^1.0.0" } }, + "memfs": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.2.0.tgz", + "integrity": "sha512-f/xxz2TpdKv6uDn6GtHee8ivFyxwxmPuXatBb1FBwxYNuVpbM3k/Y1Z+vC0mH/dIXXrukYfe3qe5J32Dfjg93A==", + "dev": true, + "requires": { + "fs-monkey": "1.0.1" + } + }, "merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", diff --git a/package.json b/package.json index 2e3fb92a9..d3c804fdb 100644 --- a/package.json +++ b/package.json @@ -58,6 +58,7 @@ "jest": "^26.0.1", "jest-circus": "^25.1.0", "lua-types": "^2.8.0", + "memfs": "^3.2.0", "prettier": "^2.0.5", "ts-jest": "^26.0.0", "ts-node": "^8.6.2" diff --git a/src/transpilation/diagnostics.ts b/src/transpilation/diagnostics.ts index 86aee7e95..c543165bb 100644 --- a/src/transpilation/diagnostics.ts +++ b/src/transpilation/diagnostics.ts @@ -1,4 +1,5 @@ import * as ts from "typescript"; +import { escapeString } from "../LuaPrinter"; import { createSerialDiagnosticFactory } from "../utils"; const createDiagnosticFactory = (getMessage: (...args: TArgs) => string) => @@ -41,3 +42,16 @@ export const usingLuaBundleWithInlineMightGenerateDuplicateCode = createSerialDi export const unresolvableRequirePath = createDiagnosticFactory( (path: string) => `Cannot create require path. Module '${path}' does not exist within --rootDir.` ); + +const sourceFileStub = ts.createSourceFile("", "", ts.ScriptTarget.ES3); +export const createResolutionErrorDiagnostic = createSerialDiagnosticFactory( + (messageText: string, request: string, fileName: string) => { + const text = `__TS__Resolve(${escapeString(request)})`; + return { + messageText, + file: { ...sourceFileStub, fileName, text }, + start: 0, + length: text.length, + }; + } +); diff --git a/src/transpilation/index.ts b/src/transpilation/index.ts index 8229e8ddd..85f6e2c99 100644 --- a/src/transpilation/index.ts +++ b/src/transpilation/index.ts @@ -43,15 +43,19 @@ const libCache: { [key: string]: ts.SourceFile } = {}; /** @internal */ export function createVirtualProgram(input: Record, options: CompilerOptions = {}): ts.Program { + function notImplemented(): never { + throw new Error("Not implemented"); + } + const compilerHost: ts.CompilerHost = { - fileExists: () => true, + useCaseSensitiveFileNames: () => false, getCanonicalFileName: fileName => fileName, getCurrentDirectory: () => "", + fileExists: fileName => fileName.startsWith("lib.") || fileName in input, + readFile: notImplemented, + writeFile: notImplemented, getDefaultLibFileName: ts.getDefaultLibFileName, - readFile: () => "", getNewLine: () => "\n", - useCaseSensitiveFileNames: () => false, - writeFile() {}, getSourceFile(fileName) { if (fileName in input) { diff --git a/src/transpilation/transpile/index.ts b/src/transpilation/transpile/index.ts index a8b9762ed..0f39cb4b7 100644 --- a/src/transpilation/transpile/index.ts +++ b/src/transpilation/transpile/index.ts @@ -72,7 +72,7 @@ export function getProgramTranspileResult( if (!options.noEmit && !options.emitDeclarationOnly) { const printResult = printer(program, emitHost, sourceFile.fileName, luaAst, luaLibFeatures); const sourceRootDir = program.getCommonSourceDirectory(); - const fileName = path.resolve(sourceRootDir, sourceFile.fileName); + const fileName = path.join(sourceRootDir, sourceFile.fileName); transpiledFiles.push({ sourceFiles: [sourceFile], fileName, luaAst, ...printResult }); } }; diff --git a/src/transpilation/transpiler.ts b/src/transpilation/transpiler.ts index e06fceabf..ee5128d09 100644 --- a/src/transpilation/transpiler.ts +++ b/src/transpilation/transpiler.ts @@ -6,6 +6,7 @@ import { CompilerOptions, isBundleEnabled, LuaTarget } from "../CompilerOptions" import { getLuaLibBundle } from "../LuaLib"; import { assert, cast, isNonNull, normalizeSlashes, trimExtension } from "../utils"; import { getBundleResult } from "./bundle"; +import { createResolutionErrorDiagnostic } from "./diagnostics"; import { replaceResolveMacroInSource, replaceResolveMacroSourceNodes, ResolveMacroReplacer } from "./macro"; import { getProgramTranspileResult, TranspileOptions } from "./transpile"; import { EmitFile, EmitHost, ProcessedFile } from "./utils"; @@ -92,18 +93,15 @@ class Transpilation { private handleProcessedFile(file: ProcessedFile) { const replacer: ResolveMacroReplacer = (request: string) => { + let basedir = path.dirname(file.fileName); + if (basedir === ".") basedir = this.emitHost.getCurrentDirectory(); let resolvedPath: string; try { - resolvedPath = this.resolveRequest(request, file.fileName); + const result = this.resolver.resolveSync({}, basedir, request); + assert(typeof result === "string"); + resolvedPath = result; } catch (error) { - this.diagnostics.push({ - category: ts.DiagnosticCategory.Error, - code: -1, - file: ts.createSourceFile(file.fileName, "", ts.ScriptTarget.ES3), - start: undefined, - length: undefined, - messageText: error.message, - }); + this.diagnostics.push(createResolutionErrorDiagnostic(error.message, request, file.fileName)); return { error: error.message }; } @@ -131,16 +129,10 @@ class Transpilation { protected resolver = ResolverFactory.createResolver({ extensions: [".lua", ".ts", ".tsx", ".js", ".jsx"], conditionNames: ["lua", `lua:${this.options.luaTarget ?? LuaTarget.Universal}`], - fileSystem: this.emitHost.fileSystem ?? fs, + fileSystem: this.emitHost.resolutionFileSystem ?? fs, useSyncFileSystemCalls: true, }); - protected resolveRequest(request: string, issuer: string) { - const result = this.resolver.resolveSync({}, path.dirname(issuer), request); - assert(typeof result === "string"); - return result; - } - protected toGeneratedFileName(fileName: string) { const result = path.relative(this.rootDir, trimExtension(fileName)); // TODO: handle files on other drives diff --git a/src/transpilation/utils.ts b/src/transpilation/utils.ts index b41ffcb6a..10c822c92 100644 --- a/src/transpilation/utils.ts +++ b/src/transpilation/utils.ts @@ -1,4 +1,4 @@ -import * as fs from "fs"; +import { FileSystem } from "enhanced-resolve"; import * as path from "path"; import * as resolve from "resolve"; import { SourceNode } from "source-map"; @@ -9,7 +9,7 @@ import * as lua from "../LuaAST"; import * as diagnosticFactories from "./diagnostics"; export interface EmitHost extends Pick { - fileSystem?: typeof fs; + resolutionFileSystem?: FileSystem; } interface BaseFile { diff --git a/test/util.ts b/test/util.ts index 560b0102f..262a0ae8a 100644 --- a/test/util.ts +++ b/test/util.ts @@ -3,6 +3,7 @@ import * as nativeAssert from "assert"; import { lauxlib, lua, lualib, to_jsstring, to_luastring } from "fengari"; import * as fs from "fs"; import { stringify } from "javascript-stringify"; +import { Volume } from "memfs"; import * as path from "path"; import * as prettyFormat from "pretty-format"; import * as ts from "typescript"; @@ -176,6 +177,13 @@ export abstract class TestBuilder { return this; } + private nativeFileSystem = false; + public useNativeFileSystem() { + expect(this.hasProgram).toBe(false); + this.nativeFileSystem = true; + return this; + } + private options: tstl.CompilerOptions = { luaTarget: tstl.LuaTarget.Lua53, noHeader: true, @@ -207,6 +215,17 @@ export abstract class TestBuilder { return this; } + protected getSourceFiles() { + return { ...this.extraFiles, [this.mainFileName]: this.getTsCode() }; + } + + private extraRawFiles: Record = {}; + public addRawFile(fileName: string, content: string): this { + expect(this.hasProgram).toBe(false); + this.extraRawFiles[fileName] = content; + return this; + } + private customTransformers?: ts.CustomTransformers; public setCustomTransformers(customTransformers?: ts.CustomTransformers): this { expect(this.hasProgram).toBe(false); @@ -224,14 +243,26 @@ export abstract class TestBuilder { @memoize public getProgram(): ts.Program { this.hasProgram = true; - return tstl.createVirtualProgram({ ...this.extraFiles, [this.mainFileName]: this.getTsCode() }, this.options); + return tstl.createVirtualProgram(this.getSourceFiles(), this.options); } @memoize public getLuaResult(): tstl.TranspileVirtualProjectResult { - const program = this.getProgram(); const collector = createEmitOutputCollector(); - const { diagnostics: transpileDiagnostics } = new tstl.Transpiler().emit({ + const program = this.getProgram(); + + const emitHost: tstl.EmitHost = { ...ts.sys }; + if (!this.nativeFileSystem) { + const virtualFS = Volume.fromJSON({ ...this.extraRawFiles, ...this.getSourceFiles() }, "/"); + emitHost.resolutionFileSystem = virtualFS; + emitHost.getCurrentDirectory = () => "/"; + emitHost.readFile = (fileName, encoding = "utf8") => + fileName.endsWith("lualib_bundle.lua") + ? ts.sys.readFile(fileName, encoding) + : (virtualFS.readFileSync(fileName, encoding) as string); + } + + const { diagnostics: transpileDiagnostics } = new tstl.Transpiler({ emitHost }).emit({ program, customTransformers: this.customTransformers, writeFile: collector.writeFile, From fa25e245aa9e98c546640a4d44225779311b01a0 Mon Sep 17 00:00:00 2001 From: ark120202 Date: Tue, 6 Oct 2020 23:50:58 +0000 Subject: [PATCH 08/58] Remove `transpileAndExecuteProjectReturningMainExport` legacy util --- test/legacy-utils.ts | 49 ------------- test/unit/modules.spec.ts | 151 ++++++++++++++------------------------ 2 files changed, 54 insertions(+), 146 deletions(-) diff --git a/test/legacy-utils.ts b/test/legacy-utils.ts index de781d954..83894fda8 100644 --- a/test/legacy-utils.ts +++ b/test/legacy-utils.ts @@ -3,7 +3,6 @@ import * as fs from "fs"; import * as path from "path"; import * as ts from "typescript"; import * as tstl from "../src"; -import { formatPathToLuaPath } from "../src/utils"; export function transpileString( str: string | { [filename: string]: string }, @@ -99,54 +98,6 @@ export function transpileAndExecute( return executeLua(lua); } -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 transpileAndExecuteProjectReturningMainExport( - typeScriptFiles: Record, - exportName: string, - options: tstl.CompilerOptions = {} -): [any, string] { - const mainFile = Object.keys(typeScriptFiles).find(typeScriptFileName => typeScriptFileName === "main.ts"); - if (!mainFile) { - throw new Error("An entry point file needs to be specified. This should be called main.ts"); - } - - const joinedTranspiledFiles = Object.keys(typeScriptFiles) - .filter(typeScriptFileName => typeScriptFileName !== "main.ts") - .map(typeScriptFileName => { - const modulePath = getExportPath(typeScriptFileName, options); - const tsCode = typeScriptFiles[typeScriptFileName]; - const luaCode = transpileString(tsCode, options); - return `package.preload["${modulePath}"] = function() - ${luaCode} - end`; - }) - .join("\n"); - - const luaCode = `return (function() - ${joinedTranspiledFiles} - ${transpileString(typeScriptFiles[mainFile])} - end)().${exportName}`; - - try { - return [executeLua(luaCode), luaCode]; - } catch (err) { - throw new Error(` - Encountered an error when executing the following Lua code: - - ${luaCode} - - ${err} - `); - } -} - export function transpileExecuteAndReturnExport( tsStr: string, returnExport: string, diff --git a/test/unit/modules.spec.ts b/test/unit/modules.spec.ts index 885f24541..7726037d7 100644 --- a/test/unit/modules.spec.ts +++ b/test/unit/modules.spec.ts @@ -1,5 +1,5 @@ import * as ts from "typescript"; -import * as util from "../../util"; +import * as util from "../util"; describe("module import/export elision", () => { const moduleDeclaration = ` @@ -66,130 +66,87 @@ test.each(["ke-bab", "dollar$", "singlequote'", "hash#", "s p a c e", "ɥɣɎɌ ); 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": ` + util.testModule` + export { default } from "./module"; + ` + .addExtraFile( + "module.ts", + ` export const value = true; ${exportStatement}; - `, - }, - "default" - ); - - expect(result).toBe(true); + ` + ) + .expectToEqual({ default: 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); + util.testModule` + import defaultExport from "./module"; + export const value = defaultExport; + ` + .addExtraFile("module.ts", "export default 1 + 2 + 3;") + .expectToEqual({ value: 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); + util.testModule` + import * as m from "./module"; + export const value = m; + ` + .addExtraFile("module.ts", "export = true;") + .expectToEqual({ value: 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": ` + util.testModule` + import defaultExport, { a, b, c } from "./module"; + export const value = defaultExport + b + c; + ` + .addExtraFile( + "module.ts", + ` export const a = 1; export const b = 2; export const c = 3; export default a; - `, - }, - "value" - ); - - expect(result).toBe(6); + ` + ) + .expectToEqual({ value: 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": ` + util.testModule` + import defaultExport, * as ns from "./module"; + export const value = defaultExport + ns.b + ns.c; + ` + .addExtraFile( + "module.ts", + ` export const a = 1; export const b = 2; export const c = 3; export default a; - `, - }, - "value" - ); - - expect(result).toBe(6); + ` + ) + .expectToEqual({ value: 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); + util.testModule` + import defaultExport from "./module"; + export const value = defaultExport(); + ` + .addExtraFile("module.ts", "export default function() { return true; }") + .expectToEqual({ value: true }); }); 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); + util.testModule` + import * as module from "./module"; + export const value = module; + ` + .addExtraFile("module.ts", "export = true;") + .expectToEqual({ value: true }); }); const reassignmentTestCases = [ From 06954755377f20b8dcd87beab79daf4ead214a8e Mon Sep 17 00:00:00 2001 From: ark120202 Date: Sat, 10 Oct 2020 00:14:14 +0000 Subject: [PATCH 09/58] Make absolute/relative paths handling more consistent --- src/transpilation/index.ts | 12 ++++++++---- src/transpilation/transpile/index.ts | 15 ++++++++++++--- src/transpilation/transpiler.ts | 21 ++++++++++++++------- src/transpilation/utils.ts | 1 + src/typescript-internal.d.ts | 1 + 5 files changed, 36 insertions(+), 14 deletions(-) diff --git a/src/transpilation/index.ts b/src/transpilation/index.ts index 85f6e2c99..096883491 100644 --- a/src/transpilation/index.ts +++ b/src/transpilation/index.ts @@ -47,19 +47,23 @@ export function createVirtualProgram(input: Record, options: Com throw new Error("Not implemented"); } + const getFileFromInput = (fileName: string) => + input[fileName] ?? (fileName.startsWith("/") ? input[fileName.slice(1)] : undefined); + const compilerHost: ts.CompilerHost = { useCaseSensitiveFileNames: () => false, getCanonicalFileName: fileName => fileName, - getCurrentDirectory: () => "", - fileExists: fileName => fileName.startsWith("lib.") || fileName in input, + getCurrentDirectory: () => "/", + fileExists: fileName => fileName.startsWith("lib.") || getFileFromInput(fileName) !== undefined, readFile: notImplemented, writeFile: notImplemented, getDefaultLibFileName: ts.getDefaultLibFileName, getNewLine: () => "\n", getSourceFile(fileName) { - if (fileName in input) { - return ts.createSourceFile(fileName, input[fileName], ts.ScriptTarget.Latest, false); + const fileFromInput = getFileFromInput(fileName); + if (fileFromInput !== undefined) { + return ts.createSourceFile(fileName, fileFromInput, ts.ScriptTarget.Latest, false); } if (fileName.startsWith("lib.")) { diff --git a/src/transpilation/transpile/index.ts b/src/transpilation/transpile/index.ts index 0f39cb4b7..a2cfcd7f2 100644 --- a/src/transpilation/transpile/index.ts +++ b/src/transpilation/transpile/index.ts @@ -3,7 +3,7 @@ import * as ts from "typescript"; import { CompilerOptions, validateOptions } from "../../CompilerOptions"; import { createPrinter } from "../../LuaPrinter"; import { createVisitorMap, transformSourceFile } from "../../transformation"; -import { isNonNull } from "../../utils"; +import { assert, isNonNull } from "../../utils"; import { getPlugins, Plugin } from "./plugins"; import { getTransformers } from "./transformers"; import { EmitHost, ProcessedFile } from "../utils"; @@ -71,8 +71,17 @@ export function getProgramTranspileResult( diagnostics.push(...transformDiagnostics); if (!options.noEmit && !options.emitDeclarationOnly) { const printResult = printer(program, emitHost, sourceFile.fileName, luaAst, luaLibFeatures); - const sourceRootDir = program.getCommonSourceDirectory(); - const fileName = path.join(sourceRootDir, sourceFile.fileName); + + let fileName: string; + if (path.isAbsolute(sourceFile.fileName)) { + fileName = sourceFile.fileName; + } else { + const currentDirectory = emitHost.getCurrentDirectory(); + // Having no absolute path in path.resolve would make it fallback to real cwd + assert(path.isAbsolute(currentDirectory), `Invalid path: ${currentDirectory}`); + fileName = path.resolve(currentDirectory, sourceFile.fileName); + } + transpiledFiles.push({ sourceFiles: [sourceFile], fileName, luaAst, ...printResult }); } }; diff --git a/src/transpilation/transpiler.ts b/src/transpilation/transpiler.ts index ee5128d09..2b16cf7a2 100644 --- a/src/transpilation/transpiler.ts +++ b/src/transpilation/transpiler.ts @@ -60,11 +60,20 @@ class Transpilation { private seenFiles = new Set(); private files: ProcessedFile[] = []; - constructor(private emitHost: EmitHost, private program: ts.Program) {} - private options = this.program.getCompilerOptions() as CompilerOptions; - private rootDir = this.program.getCommonSourceDirectory(); - private outDir = this.options.outDir ?? this.rootDir; + private rootDir: string; + private outDir: string; + + constructor(private emitHost: EmitHost, private program: ts.Program) { + const { rootDir } = program.getCompilerOptions(); + this.rootDir = + // getCommonSourceDirectory ignores provided rootDir when TS6059 is emitted + rootDir == null + ? program.getCommonSourceDirectory() + : ts.getNormalizedAbsolutePath(rootDir, emitHost.getCurrentDirectory()); + + this.outDir = this.options.outDir ?? this.rootDir; + } public emit(transpiledFiles: ProcessedFile[]): EmitFile[] { transpiledFiles.forEach(file => this.seenFiles.add(file.fileName)); @@ -93,11 +102,9 @@ class Transpilation { private handleProcessedFile(file: ProcessedFile) { const replacer: ResolveMacroReplacer = (request: string) => { - let basedir = path.dirname(file.fileName); - if (basedir === ".") basedir = this.emitHost.getCurrentDirectory(); let resolvedPath: string; try { - const result = this.resolver.resolveSync({}, basedir, request); + const result = this.resolver.resolveSync({}, path.dirname(file.fileName), request); assert(typeof result === "string"); resolvedPath = result; } catch (error) { diff --git a/src/transpilation/utils.ts b/src/transpilation/utils.ts index 10c822c92..e60175c23 100644 --- a/src/transpilation/utils.ts +++ b/src/transpilation/utils.ts @@ -19,6 +19,7 @@ interface BaseFile { } export interface ProcessedFile extends BaseFile { + /** Absolute source file path. */ fileName: string; luaAst?: lua.Block; /** @internal */ diff --git a/src/typescript-internal.d.ts b/src/typescript-internal.d.ts index 7444d89d7..cc6df2d0b 100644 --- a/src/typescript-internal.d.ts +++ b/src/typescript-internal.d.ts @@ -3,6 +3,7 @@ export {}; declare module "typescript" { function createDiagnosticReporter(system: System, pretty?: boolean): DiagnosticReporter; function createWatchStatusReporter(system: System, pretty?: boolean): WatchStatusReporter; + function getNormalizedAbsolutePath(fileName: string, currentDirectory: string): string; interface System { setBlocking?(): void; From d494d6fb8787d71092adb73706c844e7de084bc4 Mon Sep 17 00:00:00 2001 From: ark120202 Date: Sat, 10 Oct 2020 01:39:52 +0000 Subject: [PATCH 10/58] Error on resolution to script files that aren't included in the project --- src/transpilation/diagnostics.ts | 4 ---- src/transpilation/transpiler.ts | 25 +++++++++++++++++++------ 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/src/transpilation/diagnostics.ts b/src/transpilation/diagnostics.ts index c543165bb..06732f57e 100644 --- a/src/transpilation/diagnostics.ts +++ b/src/transpilation/diagnostics.ts @@ -39,10 +39,6 @@ export const usingLuaBundleWithInlineMightGenerateDuplicateCode = createSerialDi "It is recommended to use 'luaLibImport: \"require\"'.", })); -export const unresolvableRequirePath = createDiagnosticFactory( - (path: string) => `Cannot create require path. Module '${path}' does not exist within --rootDir.` -); - const sourceFileStub = ts.createSourceFile("", "", ts.ScriptTarget.ES3); export const createResolutionErrorDiagnostic = createSerialDiagnosticFactory( (messageText: string, request: string, fileName: string) => { diff --git a/src/transpilation/transpiler.ts b/src/transpilation/transpiler.ts index 2b16cf7a2..a75c81199 100644 --- a/src/transpilation/transpiler.ts +++ b/src/transpilation/transpiler.ts @@ -112,11 +112,23 @@ class Transpilation { return { error: error.message }; } - this.handleProcessedFile({ - fileName: resolvedPath, - code: cast(this.emitHost.readFile(resolvedPath), isNonNull), - // TODO: Load source map files - }); + if (!this.seenFiles.has(resolvedPath)) { + if ( + this.scriptExtensions.some(extension => resolvedPath.endsWith(extension)) || + resolvedPath.endsWith(".json") + ) { + const message = `Resolved source file '${resolvedPath}' is not a part of the project.`; + this.diagnostics.push(createResolutionErrorDiagnostic(message, request, file.fileName)); + return { error: message }; + } else { + this.seenFiles.add(resolvedPath); + this.handleProcessedFile({ + fileName: resolvedPath, + code: cast(this.emitHost.readFile(resolvedPath), isNonNull), + // TODO: Load source map files + }); + } + } return this.toRequireParameter(this.toGeneratedFileName(resolvedPath)); }; @@ -133,8 +145,9 @@ class Transpilation { this.files.push(file); } + private readonly scriptExtensions = [".ts", ".tsx", ".js", ".jsx"]; protected resolver = ResolverFactory.createResolver({ - extensions: [".lua", ".ts", ".tsx", ".js", ".jsx"], + extensions: [".lua", ...this.scriptExtensions], conditionNames: ["lua", `lua:${this.options.luaTarget ?? LuaTarget.Universal}`], fileSystem: this.emitHost.resolutionFileSystem ?? fs, useSyncFileSystemCalls: true, From 6afdc45a4f6f596edc79f23bda6432613163cf7d Mon Sep 17 00:00:00 2001 From: ark120202 Date: Sat, 10 Oct 2020 01:53:25 +0000 Subject: [PATCH 11/58] Simplify concepts used in module id generation --- src/transpilation/bundle.ts | 6 +++--- src/transpilation/transpiler.ts | 28 +++++++++++++--------------- 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/src/transpilation/bundle.ts b/src/transpilation/bundle.ts index 9c28acedf..5eb7213c4 100644 --- a/src/transpilation/bundle.ts +++ b/src/transpilation/bundle.ts @@ -33,7 +33,7 @@ export function getBundleResult( program: ts.Program, emitHost: EmitHost, files: ProcessedFile[], - getRequirePath: (file: ProcessedFile) => string + createModuleId: (file: ProcessedFile) => string ): [ts.Diagnostic[], EmitFile] { const diagnostics: ts.Diagnostic[] = []; @@ -55,13 +55,13 @@ export function getBundleResult( } // For each file: [""] = function() end, - const moduleTableEntries = files.map(f => moduleSourceNode(f, escapeString(getRequirePath(f)))); + const moduleTableEntries = files.map(f => moduleSourceNode(f, escapeString(createModuleId(f)))); // Create ____modules table containing all entries from moduleTableEntries const moduleTable = createModuleTableNode(moduleTableEntries); // return require("") - const entryPoint = `return require(${escapeString(getRequirePath(entryFile))})\n`; + const entryPoint = `return require(${escapeString(createModuleId(entryFile))})\n`; const bundleNode = joinSourceChunks([requireOverride, moduleTable, entryPoint]); const { code, map } = bundleNode.toStringWithSourceMap(); diff --git a/src/transpilation/transpiler.ts b/src/transpilation/transpiler.ts index a75c81199..b395770a1 100644 --- a/src/transpilation/transpiler.ts +++ b/src/transpilation/transpiler.ts @@ -87,16 +87,15 @@ class Transpilation { if (isBundleEnabled(this.options)) { const [bundleDiagnostics, bundleFile] = getBundleResult(this.program, this.emitHost, this.files, file => - this.toRequireParameter(this.toGeneratedFileName(file.fileName)) + this.createModuleId(file.fileName) ); this.diagnostics.push(...bundleDiagnostics); return [bundleFile]; } else { - return this.files.map(file => { - const pathInOutDir = this.toAbsoluteOutputPath(this.toGeneratedFileName(file.fileName)); - const outputPath = normalizeSlashes(trimExtension(pathInOutDir) + ".lua"); - return { ...file, outputPath }; - }); + return this.files.map(file => ({ + ...file, + outputPath: this.moduleIdToOutputPath(this.createModuleId(file.fileName)), + })); } } @@ -130,7 +129,7 @@ class Transpilation { } } - return this.toRequireParameter(this.toGeneratedFileName(resolvedPath)); + return this.createModuleId(resolvedPath); }; if (file.sourceMapNode) { @@ -153,18 +152,17 @@ class Transpilation { useSyncFileSystemCalls: true, }); - protected toGeneratedFileName(fileName: string) { + protected createModuleId(fileName: string) { const result = path.relative(this.rootDir, trimExtension(fileName)); // TODO: handle files on other drives assert(!path.isAbsolute(result), `Invalid path: ${result}`); - return result.replace(/\.\.\//g, "_/").replace(/\./g, "__"); + return result + .replace(/\.\.[/\\]/g, "_/") + .replace(/\./g, "__") + .replace(/[/\\]/g, "."); } - protected toRequireParameter(fileName: string) { - return fileName.replace(/[/\\]/g, "."); - } - - protected toAbsoluteOutputPath(fileName: string) { - return path.resolve(this.outDir, `${fileName}.lua`); + protected moduleIdToOutputPath(moduleId: string) { + return normalizeSlashes(path.resolve(this.outDir, `${moduleId.replace(/\./g, "/")}.lua`)); } } From e1a3dfd88606c6f388d013333226fd0c261ec090 Mon Sep 17 00:00:00 2001 From: ark120202 Date: Sat, 10 Oct 2020 03:25:34 +0000 Subject: [PATCH 12/58] Improve terminology --- src/transpilation/bundle.ts | 36 ++++++++--------- src/transpilation/macro.ts | 6 +-- src/transpilation/transpile/index.ts | 16 ++++---- src/transpilation/transpiler.ts | 58 ++++++++++++++-------------- src/transpilation/utils.ts | 9 +++-- 5 files changed, 61 insertions(+), 64 deletions(-) diff --git a/src/transpilation/bundle.ts b/src/transpilation/bundle.ts index 5eb7213c4..da83c3889 100644 --- a/src/transpilation/bundle.ts +++ b/src/transpilation/bundle.ts @@ -5,7 +5,7 @@ import { CompilerOptions, isBundleEnabled } from "../CompilerOptions"; import { escapeString } from "../LuaPrinter"; import { assert, normalizeSlashes } from "../utils"; import { couldNotFindBundleEntryPoint } from "./diagnostics"; -import { EmitFile, EmitHost, ProcessedFile } from "./utils"; +import { Chunk, EmitHost, getConfigDirectory, Module } from "./utils"; // Override `require` to read from ____modules table. const requireOverride = ` @@ -29,41 +29,37 @@ local function require(file) end `; -export function getBundleResult( +export function getBundleChunk( program: ts.Program, emitHost: EmitHost, - files: ProcessedFile[], - createModuleId: (file: ProcessedFile) => string -): [ts.Diagnostic[], EmitFile] { + modules: Module[], + createModuleId: (file: Module) => string +): [ts.Diagnostic[], Chunk] { const diagnostics: ts.Diagnostic[] = []; const options = program.getCompilerOptions() as CompilerOptions; assert(isBundleEnabled(options)); - const bundleFile = options.luaBundle; - const entryModule = options.luaBundleEntry; - const projectRootDir = options.configFilePath - ? path.dirname(options.configFilePath) - : emitHost.getCurrentDirectory(); - const outputPath = normalizeSlashes(path.resolve(projectRootDir, bundleFile)); + const projectDirectory = getConfigDirectory(options, emitHost); + const outputPath = normalizeSlashes(path.resolve(projectDirectory, options.luaBundle)); // Resolve project settings relative to project file. - const resolvedEntryModule = path.resolve(projectRootDir, entryModule); - const entryFile = files.find(f => f.fileName === resolvedEntryModule); - if (entryFile === undefined) { - diagnostics.push(couldNotFindBundleEntryPoint(entryModule)); + const entryFileName = normalizeSlashes(path.resolve(projectDirectory, options.luaBundleEntry)); + const entryModule = modules.find(m => m.fileName === entryFileName); + if (entryModule === undefined) { + diagnostics.push(couldNotFindBundleEntryPoint(options.luaBundleEntry)); return [diagnostics, { outputPath, code: "" }]; } // For each file: [""] = function() end, - const moduleTableEntries = files.map(f => moduleSourceNode(f, escapeString(createModuleId(f)))); + const moduleTableEntries = modules.map(f => moduleSourceNode(f, escapeString(createModuleId(f)))); // Create ____modules table containing all entries from moduleTableEntries const moduleTable = createModuleTableNode(moduleTableEntries); // return require("") - const entryPoint = `return require(${escapeString(createModuleId(entryFile))})\n`; + const bootstrap = `return require(${escapeString(createModuleId(entryModule))})\n`; - const bundleNode = joinSourceChunks([requireOverride, moduleTable, entryPoint]); + const bundleNode = joinSourceChunks([requireOverride, moduleTable, bootstrap]); const { code, map } = bundleNode.toStringWithSourceMap(); return [ @@ -72,12 +68,12 @@ export function getBundleResult( outputPath, code, sourceMap: map.toString(), - sourceFiles: files.flatMap(x => x.sourceFiles ?? []), + sourceFiles: modules.flatMap(x => x.sourceFiles ?? []), }, ]; } -function moduleSourceNode({ code, sourceMapNode }: ProcessedFile, modulePath: string): SourceNode { +function moduleSourceNode({ code, sourceMapNode }: Module, modulePath: string): SourceNode { return joinSourceChunks([`[${modulePath}] = function()\n`, sourceMapNode ?? code, "\nend,\n"]); } diff --git a/src/transpilation/macro.ts b/src/transpilation/macro.ts index 01f7c960d..391dc220a 100644 --- a/src/transpilation/macro.ts +++ b/src/transpilation/macro.ts @@ -1,9 +1,9 @@ import { SourceNode } from "source-map"; import { escapeString, unescapeLuaString } from "../LuaPrinter"; -export type ResolveMacroReplacer = (request: string) => string | { error: string }; +export type MacroDependencyResolver = (request: string) => string | { error: string }; -export function replaceResolveMacroSourceNodes(rootNode: SourceNode, replacer: ResolveMacroReplacer) { +export function replaceResolveMacroSourceNodes(rootNode: SourceNode, replacer: MacroDependencyResolver) { function walkSourceNode(node: SourceNode, parent: SourceNode) { for (const child of node.children) { if ((child as any) === "__TS__Resolve") { @@ -17,7 +17,7 @@ export function replaceResolveMacroSourceNodes(rootNode: SourceNode, replacer: R walkSourceNode(rootNode, rootNode); } -export function replaceResolveMacroInSource(source: string, replacer: ResolveMacroReplacer) { +export function replaceResolveMacroInSource(source: string, replacer: MacroDependencyResolver) { return source.replace(/__TS__Resolve\((".*?")\)/, (_, match) => { const request = unescapeLuaString(match); const replacement = replacer(request); diff --git a/src/transpilation/transpile/index.ts b/src/transpilation/transpile/index.ts index a2cfcd7f2..691ef168d 100644 --- a/src/transpilation/transpile/index.ts +++ b/src/transpilation/transpile/index.ts @@ -4,9 +4,9 @@ import { CompilerOptions, validateOptions } from "../../CompilerOptions"; import { createPrinter } from "../../LuaPrinter"; import { createVisitorMap, transformSourceFile } from "../../transformation"; import { assert, isNonNull } from "../../utils"; +import { EmitHost, Module } from "../utils"; import { getPlugins, Plugin } from "./plugins"; import { getTransformers } from "./transformers"; -import { EmitHost, ProcessedFile } from "../utils"; export { Plugin }; @@ -19,10 +19,10 @@ export interface TranspileOptions { export interface TranspileResult { diagnostics: ts.Diagnostic[]; - transpiledFiles: ProcessedFile[]; + modules: Module[]; } -export function getProgramTranspileResult( +export function emitProgramModules( emitHost: EmitHost, writeFileResult: ts.WriteFileCallback, { program, sourceFiles: targetSourceFiles, customTransformers = {}, plugins: customPlugins = [] }: TranspileOptions @@ -30,7 +30,7 @@ export function getProgramTranspileResult( const options = program.getCompilerOptions() as CompilerOptions; const diagnostics = validateOptions(options); - let transpiledFiles: ProcessedFile[] = []; + let modules: Module[] = []; if (options.noEmitOnError) { const preEmitDiagnostics = [ @@ -54,7 +54,7 @@ export function getProgramTranspileResult( } if (preEmitDiagnostics.length > 0) { - return { diagnostics: preEmitDiagnostics, transpiledFiles }; + return { diagnostics: preEmitDiagnostics, modules }; } } @@ -82,7 +82,7 @@ export function getProgramTranspileResult( fileName = path.resolve(currentDirectory, sourceFile.fileName); } - transpiledFiles.push({ sourceFiles: [sourceFile], fileName, luaAst, ...printResult }); + modules.push({ sourceFiles: [sourceFile], fileName, luaAst, ...printResult }); } }; @@ -121,8 +121,8 @@ export function getProgramTranspileResult( options.noEmit = oldNoEmit; if (options.noEmit || (options.noEmitOnError && diagnostics.length > 0)) { - transpiledFiles = []; + modules = []; } - return { diagnostics, transpiledFiles }; + return { diagnostics, modules }; } diff --git a/src/transpilation/transpiler.ts b/src/transpilation/transpiler.ts index b395770a1..bae2af184 100644 --- a/src/transpilation/transpiler.ts +++ b/src/transpilation/transpiler.ts @@ -5,11 +5,11 @@ import * as ts from "typescript"; import { CompilerOptions, isBundleEnabled, LuaTarget } from "../CompilerOptions"; import { getLuaLibBundle } from "../LuaLib"; import { assert, cast, isNonNull, normalizeSlashes, trimExtension } from "../utils"; -import { getBundleResult } from "./bundle"; +import { getBundleChunk } from "./bundle"; import { createResolutionErrorDiagnostic } from "./diagnostics"; -import { replaceResolveMacroInSource, replaceResolveMacroSourceNodes, ResolveMacroReplacer } from "./macro"; -import { getProgramTranspileResult, TranspileOptions } from "./transpile"; -import { EmitFile, EmitHost, ProcessedFile } from "./utils"; +import { replaceResolveMacroInSource, replaceResolveMacroSourceNodes, MacroDependencyResolver } from "./macro"; +import { emitProgramModules, TranspileOptions } from "./transpile"; +import { Chunk, EmitHost, Module } from "./utils"; export interface TranspilerOptions { emitHost?: EmitHost; @@ -32,8 +32,8 @@ export class Transpiler { public emit(emitOptions: EmitOptions): EmitResult { const { program, writeFile = this.emitHost.writeFile } = emitOptions; - const { diagnostics, transpiledFiles } = getProgramTranspileResult(this.emitHost, writeFile, emitOptions); - const emitPlan = this.getEmitPlan(program, diagnostics, transpiledFiles); + const { diagnostics, modules } = emitProgramModules(this.emitHost, writeFile, emitOptions); + const emitPlan = this.getEmitPlan(program, diagnostics, modules); const options = program.getCompilerOptions(); const emitBOM = options.emitBOM ?? false; @@ -47,7 +47,7 @@ export class Transpiler { return { diagnostics, emitSkipped: emitPlan.length === 0 }; } - private getEmitPlan(program: ts.Program, diagnostics: ts.Diagnostic[], transpiledFiles: ProcessedFile[]) { + private getEmitPlan(program: ts.Program, diagnostics: ts.Diagnostic[], transpiledFiles: Module[]) { const transpilation = new Transpilation(this.emitHost, program); const emitPlan = transpilation.emit(transpiledFiles); diagnostics.push(...transpilation.diagnostics); @@ -58,7 +58,7 @@ export class Transpiler { class Transpilation { public readonly diagnostics: ts.Diagnostic[] = []; private seenFiles = new Set(); - private files: ProcessedFile[] = []; + private modules: Module[] = []; private options = this.program.getCompilerOptions() as CompilerOptions; private rootDir: string; @@ -75,39 +75,39 @@ class Transpilation { this.outDir = this.options.outDir ?? this.rootDir; } - public emit(transpiledFiles: ProcessedFile[]): EmitFile[] { - transpiledFiles.forEach(file => this.seenFiles.add(file.fileName)); - transpiledFiles.forEach(file => this.handleProcessedFile(file)); + public emit(programModules: Module[]): Chunk[] { + programModules.forEach(file => this.seenFiles.add(file.fileName)); + programModules.forEach(file => this.addModule(file)); - const lualibRequired = this.files.some(f => f.code.includes('require("lualib_bundle")')); + const lualibRequired = this.modules.some(f => f.code.includes('require("lualib_bundle")')); if (lualibRequired) { const fileName = normalizeSlashes(path.resolve(this.rootDir, "lualib_bundle.lua")); - this.files.unshift({ fileName, code: getLuaLibBundle(this.emitHost) }); + this.modules.unshift({ fileName, code: getLuaLibBundle(this.emitHost) }); } if (isBundleEnabled(this.options)) { - const [bundleDiagnostics, bundleFile] = getBundleResult(this.program, this.emitHost, this.files, file => + const [bundleDiagnostics, bundleChunk] = getBundleChunk(this.program, this.emitHost, this.modules, file => this.createModuleId(file.fileName) ); this.diagnostics.push(...bundleDiagnostics); - return [bundleFile]; + return [bundleChunk]; } else { - return this.files.map(file => ({ + return this.modules.map(file => ({ ...file, outputPath: this.moduleIdToOutputPath(this.createModuleId(file.fileName)), })); } } - private handleProcessedFile(file: ProcessedFile) { - const replacer: ResolveMacroReplacer = (request: string) => { + private addModule(module: Module) { + const dependencyResolver: MacroDependencyResolver = (request: string) => { let resolvedPath: string; try { - const result = this.resolver.resolveSync({}, path.dirname(file.fileName), request); + const result = this.resolver.resolveSync({}, path.dirname(module.fileName), request); assert(typeof result === "string"); resolvedPath = result; } catch (error) { - this.diagnostics.push(createResolutionErrorDiagnostic(error.message, request, file.fileName)); + this.diagnostics.push(createResolutionErrorDiagnostic(error.message, request, module.fileName)); return { error: error.message }; } @@ -117,11 +117,11 @@ class Transpilation { resolvedPath.endsWith(".json") ) { const message = `Resolved source file '${resolvedPath}' is not a part of the project.`; - this.diagnostics.push(createResolutionErrorDiagnostic(message, request, file.fileName)); + this.diagnostics.push(createResolutionErrorDiagnostic(message, request, module.fileName)); return { error: message }; } else { this.seenFiles.add(resolvedPath); - this.handleProcessedFile({ + this.addModule({ fileName: resolvedPath, code: cast(this.emitHost.readFile(resolvedPath), isNonNull), // TODO: Load source map files @@ -132,16 +132,16 @@ class Transpilation { return this.createModuleId(resolvedPath); }; - if (file.sourceMapNode) { - replaceResolveMacroSourceNodes(file.sourceMapNode, replacer); - const { code, map } = file.sourceMapNode.toStringWithSourceMap(); - file.code = code; - file.sourceMap = JSON.stringify(map.toJSON()); + if (module.sourceMapNode) { + replaceResolveMacroSourceNodes(module.sourceMapNode, dependencyResolver); + const { code, map } = module.sourceMapNode.toStringWithSourceMap(); + module.code = code; + module.sourceMap = JSON.stringify(map.toJSON()); } else { - file.code = replaceResolveMacroInSource(file.code, replacer); + module.code = replaceResolveMacroInSource(module.code, dependencyResolver); } - this.files.push(file); + this.modules.push(module); } private readonly scriptExtensions = [".ts", ".tsx", ".js", ".jsx"]; diff --git a/src/transpilation/utils.ts b/src/transpilation/utils.ts index e60175c23..d1d52b90c 100644 --- a/src/transpilation/utils.ts +++ b/src/transpilation/utils.ts @@ -18,7 +18,7 @@ interface BaseFile { sourceFiles?: ts.SourceFile[]; } -export interface ProcessedFile extends BaseFile { +export interface Module extends BaseFile { /** Absolute source file path. */ fileName: string; luaAst?: lua.Block; @@ -26,12 +26,13 @@ export interface ProcessedFile extends BaseFile { sourceMapNode?: SourceNode; } -export interface EmitFile extends BaseFile { +export interface Chunk extends BaseFile { outputPath: string; } -export const getConfigDirectory = (options: ts.CompilerOptions) => - options.configFilePath ? path.dirname(options.configFilePath) : process.cwd(); +// TODO: Require emit host +export const getConfigDirectory = (options: ts.CompilerOptions, emitHost?: EmitHost) => + options.configFilePath ? path.dirname(options.configFilePath) : emitHost?.getCurrentDirectory() ?? process.cwd(); export function resolvePlugin( kind: string, From 1ff2963e00a27a98ef60fec4ba8d3c7abfdc7d18 Mon Sep 17 00:00:00 2001 From: ark120202 Date: Sat, 10 Oct 2020 03:33:52 +0000 Subject: [PATCH 13/58] Move high-level API to managed.ts --- src/transpilation/index.ts | 114 +---------------------------------- src/transpilation/managed.ts | 69 +++++++++++++++++++++ src/transpilation/utils.ts | 44 ++++++++++++++ test/util.ts | 3 +- 4 files changed, 116 insertions(+), 114 deletions(-) create mode 100644 src/transpilation/managed.ts diff --git a/src/transpilation/index.ts b/src/transpilation/index.ts index 096883491..80e53ac02 100644 --- a/src/transpilation/index.ts +++ b/src/transpilation/index.ts @@ -1,116 +1,4 @@ -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 { createEmitOutputCollector, TranspiledFile } from "./output-collector"; -import { EmitResult, Transpiler } from "./transpiler"; - +export * from "./managed"; export * from "./transpile"; export * from "./transpiler"; export { EmitHost } from "./utils"; -export { TranspiledFile }; - -export function transpileFiles( - rootNames: string[], - options: CompilerOptions = {}, - writeFile?: ts.WriteFileCallback -): EmitResult { - const program = ts.createProgram(rootNames, options); - const { diagnostics: transpileDiagnostics, emitSkipped } = new Transpiler().emit({ program, writeFile }); - const diagnostics = ts.sortAndDeduplicateDiagnostics([ - ...ts.getPreEmitDiagnostics(program), - ...transpileDiagnostics, - ]); - - return { diagnostics: [...diagnostics], emitSkipped }; -} - -export function transpileProject( - configFileName: string, - optionsToExtend?: CompilerOptions, - writeFile?: ts.WriteFileCallback -): EmitResult { - const parseResult = parseConfigFileWithSystem(configFileName, optionsToExtend); - if (parseResult.errors.length > 0) { - return { diagnostics: parseResult.errors, emitSkipped: true }; - } - - return transpileFiles(parseResult.fileNames, parseResult.options, writeFile); -} - -const libCache: { [key: string]: ts.SourceFile } = {}; - -/** @internal */ -export function createVirtualProgram(input: Record, options: CompilerOptions = {}): ts.Program { - function notImplemented(): never { - throw new Error("Not implemented"); - } - - const getFileFromInput = (fileName: string) => - input[fileName] ?? (fileName.startsWith("/") ? input[fileName.slice(1)] : undefined); - - const compilerHost: ts.CompilerHost = { - useCaseSensitiveFileNames: () => false, - getCanonicalFileName: fileName => fileName, - getCurrentDirectory: () => "/", - fileExists: fileName => fileName.startsWith("lib.") || getFileFromInput(fileName) !== undefined, - readFile: notImplemented, - writeFile: notImplemented, - getDefaultLibFileName: ts.getDefaultLibFileName, - getNewLine: () => "\n", - - getSourceFile(fileName) { - const fileFromInput = getFileFromInput(fileName); - if (fileFromInput !== undefined) { - return ts.createSourceFile(fileName, fileFromInput, 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 interface TranspileVirtualProjectResult { - diagnostics: ts.Diagnostic[]; - transpiledFiles: TranspiledFile[]; -} - -export function transpileVirtualProject( - files: Record, - options: CompilerOptions = {} -): TranspileVirtualProjectResult { - const program = createVirtualProgram(files, options); - const collector = createEmitOutputCollector(); - const { diagnostics: transpileDiagnostics } = new Transpiler().emit({ program, writeFile: collector.writeFile }); - const diagnostics = ts.sortAndDeduplicateDiagnostics([ - ...ts.getPreEmitDiagnostics(program), - ...transpileDiagnostics, - ]); - - return { diagnostics: [...diagnostics], transpiledFiles: collector.files }; -} - -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(({ sourceFiles }) => sourceFiles.some(f => f.fileName === "main.ts")), - }; -} diff --git a/src/transpilation/managed.ts b/src/transpilation/managed.ts new file mode 100644 index 000000000..53ee46417 --- /dev/null +++ b/src/transpilation/managed.ts @@ -0,0 +1,69 @@ +import * as ts from "typescript"; +import { parseConfigFileWithSystem } from "../cli/tsconfig"; +import { CompilerOptions } from "../CompilerOptions"; +import { createEmitOutputCollector, TranspiledFile } from "./output-collector"; +import { EmitResult, Transpiler } from "./transpiler"; +import { createVirtualProgram } from "./utils"; + +export { TranspiledFile }; + +export function transpileFiles( + rootNames: string[], + options: CompilerOptions = {}, + writeFile?: ts.WriteFileCallback +): EmitResult { + const program = ts.createProgram(rootNames, options); + const { diagnostics: transpileDiagnostics, emitSkipped } = new Transpiler().emit({ program, writeFile }); + const diagnostics = ts.sortAndDeduplicateDiagnostics([ + ...ts.getPreEmitDiagnostics(program), + ...transpileDiagnostics, + ]); + + return { diagnostics: [...diagnostics], emitSkipped }; +} + +export function transpileProject( + configFileName: string, + optionsToExtend?: CompilerOptions, + writeFile?: ts.WriteFileCallback +): EmitResult { + const parseResult = parseConfigFileWithSystem(configFileName, optionsToExtend); + if (parseResult.errors.length > 0) { + return { diagnostics: parseResult.errors, emitSkipped: true }; + } + + return transpileFiles(parseResult.fileNames, parseResult.options, writeFile); +} + +export interface TranspileVirtualProjectResult { + diagnostics: ts.Diagnostic[]; + transpiledFiles: TranspiledFile[]; +} + +export function transpileVirtualProject( + files: Record, + options: CompilerOptions = {} +): TranspileVirtualProjectResult { + const program = createVirtualProgram(files, options); + const collector = createEmitOutputCollector(); + const { diagnostics: transpileDiagnostics } = new Transpiler().emit({ program, writeFile: collector.writeFile }); + const diagnostics = ts.sortAndDeduplicateDiagnostics([ + ...ts.getPreEmitDiagnostics(program), + ...transpileDiagnostics, + ]); + + return { diagnostics: [...diagnostics], transpiledFiles: collector.files }; +} + +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(({ sourceFiles }) => sourceFiles.some(f => f.fileName === "main.ts")), + }; +} diff --git a/src/transpilation/utils.ts b/src/transpilation/utils.ts index d1d52b90c..11b2b7fda 100644 --- a/src/transpilation/utils.ts +++ b/src/transpilation/utils.ts @@ -1,10 +1,12 @@ import { FileSystem } from "enhanced-resolve"; +import * as fs from "fs"; import * as path from "path"; import * as resolve from "resolve"; import { SourceNode } from "source-map"; import * as ts from "typescript"; // TODO: Don't depend on CLI? import * as cliDiagnostics from "../cli/diagnostics"; +import { CompilerOptions } from "../CompilerOptions"; import * as lua from "../LuaAST"; import * as diagnosticFactories from "./diagnostics"; @@ -74,3 +76,45 @@ export function resolvePlugin( return { result }; } + +const libCache = new Map(); +export function createVirtualProgram(input: Record, options: CompilerOptions = {}): ts.Program { + function notImplemented(): never { + throw new Error("Not implemented"); + } + + const getFileFromInput = (fileName: string) => + input[fileName] ?? (fileName.startsWith("/") ? input[fileName.slice(1)] : undefined); + + const compilerHost: ts.CompilerHost = { + useCaseSensitiveFileNames: () => false, + getCanonicalFileName: fileName => fileName, + getCurrentDirectory: () => "/", + fileExists: fileName => fileName.startsWith("lib.") || getFileFromInput(fileName) !== undefined, + readFile: notImplemented, + writeFile: notImplemented, + getDefaultLibFileName: ts.getDefaultLibFileName, + getNewLine: () => "\n", + + getSourceFile(fileName) { + const fileFromInput = getFileFromInput(fileName); + if (fileFromInput !== undefined) { + return ts.createSourceFile(fileName, fileFromInput, ts.ScriptTarget.Latest, false); + } + + if (libCache.has(fileName)) return libCache.get(fileName)!; + + if (fileName.startsWith("lib.")) { + const typeScriptDir = path.dirname(require.resolve("typescript")); + const filePath = path.join(typeScriptDir, fileName); + const content = fs.readFileSync(filePath, "utf8"); + + const sourceFile = ts.createSourceFile(fileName, content, ts.ScriptTarget.Latest, false); + libCache.set(fileName, sourceFile); + return sourceFile; + } + }, + }; + + return ts.createProgram(Object.keys(input), options, compilerHost); +} diff --git a/test/util.ts b/test/util.ts index 262a0ae8a..126a2222d 100644 --- a/test/util.ts +++ b/test/util.ts @@ -10,6 +10,7 @@ import * as ts from "typescript"; import * as vm from "vm"; import * as tstl from "../src"; import { createEmitOutputCollector } from "../src/transpilation/output-collector"; +import { createVirtualProgram } from "../src/transpilation/utils"; export * from "./legacy-utils"; @@ -243,7 +244,7 @@ export abstract class TestBuilder { @memoize public getProgram(): ts.Program { this.hasProgram = true; - return tstl.createVirtualProgram(this.getSourceFiles(), this.options); + return createVirtualProgram(this.getSourceFiles(), this.options); } @memoize From f405f67e9fea06409a94acee45c0f09d3dacd113 Mon Sep 17 00:00:00 2001 From: ark120202 Date: Wed, 14 Oct 2020 10:14:41 +0000 Subject: [PATCH 14/58] Refactor --- src/transpilation/{bundle.ts => chunk.ts} | 47 ++++--- src/transpilation/macro.ts | 28 ----- src/transpilation/module.ts | 52 ++++++++ src/transpilation/transpile/index.ts | 17 +-- src/transpilation/transpiler.ts | 146 ++++++++++------------ src/transpilation/utils.ts | 20 --- 6 files changed, 154 insertions(+), 156 deletions(-) rename src/transpilation/{bundle.ts => chunk.ts} (58%) delete mode 100644 src/transpilation/macro.ts create mode 100644 src/transpilation/module.ts diff --git a/src/transpilation/bundle.ts b/src/transpilation/chunk.ts similarity index 58% rename from src/transpilation/bundle.ts rename to src/transpilation/chunk.ts index da83c3889..07bfa9526 100644 --- a/src/transpilation/bundle.ts +++ b/src/transpilation/chunk.ts @@ -5,7 +5,24 @@ import { CompilerOptions, isBundleEnabled } from "../CompilerOptions"; import { escapeString } from "../LuaPrinter"; import { assert, normalizeSlashes } from "../utils"; import { couldNotFindBundleEntryPoint } from "./diagnostics"; -import { Chunk, EmitHost, getConfigDirectory, Module } from "./utils"; +import { Module } from "./module"; +import { Transpilation } from "./transpiler"; +import { getConfigDirectory } from "./utils"; + +export interface Chunk { + outputPath: string; + code: string; + sourceMap?: string; + sourceFiles?: ts.SourceFile[]; +} + +export function modulesToChunks(transpilation: Transpilation, modules: Module[]): Chunk[] { + return modules.map(module => { + const moduleId = transpilation.getModuleId(module); + const outputPath = normalizeSlashes(path.resolve(transpilation.outDir, `${moduleId.replace(/\./g, "/")}.lua`)); + return { outputPath, code: module.code, sourceMap: module.sourceMap, sourceFiles: module.sourceFiles }; + }); +} // Override `require` to read from ____modules table. const requireOverride = ` @@ -29,41 +46,33 @@ local function require(file) end `; -export function getBundleChunk( - program: ts.Program, - emitHost: EmitHost, - modules: Module[], - createModuleId: (file: Module) => string -): [ts.Diagnostic[], Chunk] { - const diagnostics: ts.Diagnostic[] = []; - - const options = program.getCompilerOptions() as CompilerOptions; +export function modulesToBundleChunks(transpilation: Transpilation, modules: Module[]): Chunk[] { + const options = transpilation.program.getCompilerOptions() as CompilerOptions; assert(isBundleEnabled(options)); - const projectDirectory = getConfigDirectory(options, emitHost); + const projectDirectory = getConfigDirectory(options, transpilation.emitHost); const outputPath = normalizeSlashes(path.resolve(projectDirectory, options.luaBundle)); // Resolve project settings relative to project file. const entryFileName = normalizeSlashes(path.resolve(projectDirectory, options.luaBundleEntry)); - const entryModule = modules.find(m => m.fileName === entryFileName); + const entryModule = modules.find(m => m.request === entryFileName); if (entryModule === undefined) { - diagnostics.push(couldNotFindBundleEntryPoint(options.luaBundleEntry)); - return [diagnostics, { outputPath, code: "" }]; + transpilation.diagnostics.push(couldNotFindBundleEntryPoint(options.luaBundleEntry)); + return [{ outputPath, code: "" }]; } // For each file: [""] = function() end, - const moduleTableEntries = modules.map(f => moduleSourceNode(f, escapeString(createModuleId(f)))); + const moduleTableEntries = modules.map(m => moduleSourceNode(m, escapeString(transpilation.getModuleId(m)))); // Create ____modules table containing all entries from moduleTableEntries const moduleTable = createModuleTableNode(moduleTableEntries); // return require("") - const bootstrap = `return require(${escapeString(createModuleId(entryModule))})\n`; + const bootstrap = `return require(${escapeString(transpilation.getModuleId(entryModule))})\n`; const bundleNode = joinSourceChunks([requireOverride, moduleTable, bootstrap]); const { code, map } = bundleNode.toStringWithSourceMap(); return [ - diagnostics, { outputPath, code, @@ -73,8 +82,8 @@ export function getBundleChunk( ]; } -function moduleSourceNode({ code, sourceMapNode }: Module, modulePath: string): SourceNode { - return joinSourceChunks([`[${modulePath}] = function()\n`, sourceMapNode ?? code, "\nend,\n"]); +function moduleSourceNode(module: Module, modulePath: string): SourceNode { + return joinSourceChunks([`[${modulePath}] = function()\n`, module.sourceMapNode ?? module.code, "\nend,\n"]); } function createModuleTableNode(fileChunks: SourceChunk[]): SourceNode { diff --git a/src/transpilation/macro.ts b/src/transpilation/macro.ts deleted file mode 100644 index 391dc220a..000000000 --- a/src/transpilation/macro.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { SourceNode } from "source-map"; -import { escapeString, unescapeLuaString } from "../LuaPrinter"; - -export type MacroDependencyResolver = (request: string) => string | { error: string }; - -export function replaceResolveMacroSourceNodes(rootNode: SourceNode, replacer: MacroDependencyResolver) { - function walkSourceNode(node: SourceNode, parent: SourceNode) { - for (const child of node.children) { - if ((child as any) === "__TS__Resolve") { - parent.children = [replaceResolveMacroInSource(parent.toString(), replacer) as any]; - } else if (typeof child === "object") { - walkSourceNode(child, node); - } - } - } - - walkSourceNode(rootNode, rootNode); -} - -export function replaceResolveMacroInSource(source: string, replacer: MacroDependencyResolver) { - return source.replace(/__TS__Resolve\((".*?")\)/, (_, match) => { - const request = unescapeLuaString(match); - const replacement = replacer(request); - return typeof replacement === "string" - ? escapeString(replacement) - : `--[[ ${request} ]] error(${escapeString(replacement.error)})`; - }); -} diff --git a/src/transpilation/module.ts b/src/transpilation/module.ts new file mode 100644 index 000000000..81a451e84 --- /dev/null +++ b/src/transpilation/module.ts @@ -0,0 +1,52 @@ +import { SourceNode } from "source-map"; +import * as ts from "typescript"; +import { escapeString, unescapeLuaString } from "../LuaPrinter"; + +export interface Module { + request: string; + isBuilt: boolean; + code: string; + sourceMap?: string; + sourceMapNode?: SourceNode; + sourceFiles?: ts.SourceFile[]; +} + +export type ModuleDependencyResolver = (request: string) => string | { error: string }; + +export function buildModule(module: Module, dependencyResolver: ModuleDependencyResolver) { + if (module.isBuilt) return; + module.isBuilt = true; + + if (module.sourceMapNode) { + replaceResolveMacroSourceNodes(module.sourceMapNode, dependencyResolver); + const { code, map } = module.sourceMapNode.toStringWithSourceMap(); + module.code = code; + module.sourceMap = JSON.stringify(map.toJSON()); + } else { + module.code = replaceResolveMacroInSource(module.code, dependencyResolver); + } +} + +function replaceResolveMacroSourceNodes(rootNode: SourceNode, replacer: ModuleDependencyResolver) { + function walkSourceNode(node: SourceNode, parent: SourceNode) { + for (const child of node.children) { + if ((child as any) === "__TS__Resolve") { + parent.children = [replaceResolveMacroInSource(parent.toString(), replacer) as any]; + } else if (typeof child === "object") { + walkSourceNode(child, node); + } + } + } + + walkSourceNode(rootNode, rootNode); +} + +function replaceResolveMacroInSource(source: string, replacer: ModuleDependencyResolver) { + return source.replace(/__TS__Resolve\((".*?")\)/, (_, match) => { + const request = unescapeLuaString(match); + const replacement = replacer(request); + return typeof replacement === "string" + ? escapeString(replacement) + : `--[[ ${request} ]] error(${escapeString(replacement.error)})`; + }); +} diff --git a/src/transpilation/transpile/index.ts b/src/transpilation/transpile/index.ts index 691ef168d..fee839ad3 100644 --- a/src/transpilation/transpile/index.ts +++ b/src/transpilation/transpile/index.ts @@ -4,7 +4,8 @@ import { CompilerOptions, validateOptions } from "../../CompilerOptions"; import { createPrinter } from "../../LuaPrinter"; import { createVisitorMap, transformSourceFile } from "../../transformation"; import { assert, isNonNull } from "../../utils"; -import { EmitHost, Module } from "../utils"; +import { Module } from "../module"; +import { EmitHost } from "../utils"; import { getPlugins, Plugin } from "./plugins"; import { getTransformers } from "./transformers"; @@ -17,16 +18,11 @@ export interface TranspileOptions { plugins?: Plugin[]; } -export interface TranspileResult { - diagnostics: ts.Diagnostic[]; - modules: Module[]; -} - export function emitProgramModules( emitHost: EmitHost, writeFileResult: ts.WriteFileCallback, { program, sourceFiles: targetSourceFiles, customTransformers = {}, plugins: customPlugins = [] }: TranspileOptions -): TranspileResult { +) { const options = program.getCompilerOptions() as CompilerOptions; const diagnostics = validateOptions(options); @@ -82,7 +78,12 @@ export function emitProgramModules( fileName = path.resolve(currentDirectory, sourceFile.fileName); } - modules.push({ sourceFiles: [sourceFile], fileName, luaAst, ...printResult }); + modules.push({ + sourceFiles: [sourceFile], + request: fileName, + isBuilt: false, + ...printResult, + }); } }; diff --git a/src/transpilation/transpiler.ts b/src/transpilation/transpiler.ts index bae2af184..d6b9ad1cd 100644 --- a/src/transpilation/transpiler.ts +++ b/src/transpilation/transpiler.ts @@ -1,15 +1,15 @@ -import { ResolverFactory } from "enhanced-resolve"; +import { Resolver, ResolverFactory } from "enhanced-resolve"; import * as fs from "fs"; import * as path from "path"; import * as ts from "typescript"; import { CompilerOptions, isBundleEnabled, LuaTarget } from "../CompilerOptions"; import { getLuaLibBundle } from "../LuaLib"; import { assert, cast, isNonNull, normalizeSlashes, trimExtension } from "../utils"; -import { getBundleChunk } from "./bundle"; +import { Chunk, modulesToBundleChunks, modulesToChunks } from "./chunk"; import { createResolutionErrorDiagnostic } from "./diagnostics"; -import { replaceResolveMacroInSource, replaceResolveMacroSourceNodes, MacroDependencyResolver } from "./macro"; +import { buildModule, Module } from "./module"; import { emitProgramModules, TranspileOptions } from "./transpile"; -import { Chunk, EmitHost, Module } from "./utils"; +import { EmitHost } from "./utils"; export interface TranspilerOptions { emitHost?: EmitHost; @@ -25,15 +25,17 @@ export interface EmitResult { } export class Transpiler { - protected emitHost: EmitHost; + public emitHost: EmitHost; constructor({ emitHost = ts.sys }: TranspilerOptions = {}) { this.emitHost = emitHost; } public emit(emitOptions: EmitOptions): EmitResult { const { program, writeFile = this.emitHost.writeFile } = emitOptions; + const transpilation = new Transpilation(this, program); const { diagnostics, modules } = emitProgramModules(this.emitHost, writeFile, emitOptions); - const emitPlan = this.getEmitPlan(program, diagnostics, modules); + const emitPlan = transpilation.emit(modules); + diagnostics.push(...transpilation.diagnostics); const options = program.getCompilerOptions(); const emitBOM = options.emitBOM ?? false; @@ -46,114 +48,96 @@ export class Transpiler { return { diagnostics, emitSkipped: emitPlan.length === 0 }; } - - private getEmitPlan(program: ts.Program, diagnostics: ts.Diagnostic[], transpiledFiles: Module[]) { - const transpilation = new Transpilation(this.emitHost, program); - const emitPlan = transpilation.emit(transpiledFiles); - diagnostics.push(...transpilation.diagnostics); - return emitPlan; - } } -class Transpilation { +export class Transpilation { public readonly diagnostics: ts.Diagnostic[] = []; - private seenFiles = new Set(); private modules: Module[] = []; - private options = this.program.getCompilerOptions() as CompilerOptions; - private rootDir: string; - private outDir: string; + public options = this.program.getCompilerOptions() as CompilerOptions; + public rootDir: string; + public outDir: string; + public emitHost: EmitHost; + + private readonly implicitScriptExtensions = [".ts", ".tsx", ".js", ".jsx"] as const; + protected resolver: Resolver; + + constructor(public transpiler: Transpiler, public program: ts.Program) { + this.emitHost = transpiler.emitHost; - constructor(private emitHost: EmitHost, private program: ts.Program) { const { rootDir } = program.getCompilerOptions(); this.rootDir = // getCommonSourceDirectory ignores provided rootDir when TS6059 is emitted rootDir == null ? program.getCommonSourceDirectory() - : ts.getNormalizedAbsolutePath(rootDir, emitHost.getCurrentDirectory()); + : ts.getNormalizedAbsolutePath(rootDir, this.emitHost.getCurrentDirectory()); this.outDir = this.options.outDir ?? this.rootDir; + + this.resolver = ResolverFactory.createResolver({ + extensions: [".lua", ...this.implicitScriptExtensions], + conditionNames: ["lua", `lua:${this.options.luaTarget ?? LuaTarget.Universal}`], + fileSystem: this.emitHost.resolutionFileSystem ?? fs, + useSyncFileSystemCalls: true, + }); } public emit(programModules: Module[]): Chunk[] { - programModules.forEach(file => this.seenFiles.add(file.fileName)); - programModules.forEach(file => this.addModule(file)); + programModules.forEach(module => this.modules.push(module)); + programModules.forEach(module => this.buildModule(module)); - const lualibRequired = this.modules.some(f => f.code.includes('require("lualib_bundle")')); + const lualibRequired = this.modules.some(m => m.code.toString().includes('require("lualib_bundle")')); if (lualibRequired) { const fileName = normalizeSlashes(path.resolve(this.rootDir, "lualib_bundle.lua")); - this.modules.unshift({ fileName, code: getLuaLibBundle(this.emitHost) }); + this.modules.unshift({ request: fileName, code: getLuaLibBundle(this.emitHost), isBuilt: true }); } - if (isBundleEnabled(this.options)) { - const [bundleDiagnostics, bundleChunk] = getBundleChunk(this.program, this.emitHost, this.modules, file => - this.createModuleId(file.fileName) - ); - this.diagnostics.push(...bundleDiagnostics); - return [bundleChunk]; - } else { - return this.modules.map(file => ({ - ...file, - outputPath: this.moduleIdToOutputPath(this.createModuleId(file.fileName)), - })); - } + return this.mapModulesToChunks(this.modules); } - private addModule(module: Module) { - const dependencyResolver: MacroDependencyResolver = (request: string) => { - let resolvedPath: string; + private buildModule(module: Module) { + buildModule(module, request => { + let resolvedModule: Module; try { - const result = this.resolver.resolveSync({}, path.dirname(module.fileName), request); - assert(typeof result === "string"); - resolvedPath = result; + resolvedModule = this.resolveRequestToModule(module.request, request); } catch (error) { - this.diagnostics.push(createResolutionErrorDiagnostic(error.message, request, module.fileName)); + this.diagnostics.push(createResolutionErrorDiagnostic(error.message, request, module.request)); return { error: error.message }; } - if (!this.seenFiles.has(resolvedPath)) { - if ( - this.scriptExtensions.some(extension => resolvedPath.endsWith(extension)) || - resolvedPath.endsWith(".json") - ) { - const message = `Resolved source file '${resolvedPath}' is not a part of the project.`; - this.diagnostics.push(createResolutionErrorDiagnostic(message, request, module.fileName)); - return { error: message }; - } else { - this.seenFiles.add(resolvedPath); - this.addModule({ - fileName: resolvedPath, - code: cast(this.emitHost.readFile(resolvedPath), isNonNull), - // TODO: Load source map files - }); - } + return this.getModuleId(resolvedModule); + }); + } + + private resolveRequestToModule(issuer: string, request: string) { + const resolvedPath = this.resolver.resolveSync({}, path.dirname(issuer), request); + assert(typeof resolvedPath === "string", `Invalid resolution result: ${resolvedPath}`); + + let module = this.modules.find(m => m.request === resolvedPath); + if (!module) { + if ( + this.implicitScriptExtensions.some(extension => resolvedPath.endsWith(extension)) || + resolvedPath.endsWith(".json") + ) { + throw new Error(`Resolved source file '${resolvedPath}' is not a part of the project.`); } - return this.createModuleId(resolvedPath); - }; + // TODO: Load source map files + module = { + request: resolvedPath, + code: cast(this.emitHost.readFile(resolvedPath), isNonNull), + isBuilt: false, + }; - if (module.sourceMapNode) { - replaceResolveMacroSourceNodes(module.sourceMapNode, dependencyResolver); - const { code, map } = module.sourceMapNode.toStringWithSourceMap(); - module.code = code; - module.sourceMap = JSON.stringify(map.toJSON()); - } else { - module.code = replaceResolveMacroInSource(module.code, dependencyResolver); + this.modules.push(module); + this.buildModule(module); } - this.modules.push(module); + return module; } - private readonly scriptExtensions = [".ts", ".tsx", ".js", ".jsx"]; - protected resolver = ResolverFactory.createResolver({ - extensions: [".lua", ...this.scriptExtensions], - conditionNames: ["lua", `lua:${this.options.luaTarget ?? LuaTarget.Universal}`], - fileSystem: this.emitHost.resolutionFileSystem ?? fs, - useSyncFileSystemCalls: true, - }); - - protected createModuleId(fileName: string) { - const result = path.relative(this.rootDir, trimExtension(fileName)); + public getModuleId(module: Module) { + const result = path.relative(this.rootDir, trimExtension(module.request)); // TODO: handle files on other drives assert(!path.isAbsolute(result), `Invalid path: ${result}`); return result @@ -162,7 +146,7 @@ class Transpilation { .replace(/[/\\]/g, "."); } - protected moduleIdToOutputPath(moduleId: string) { - return normalizeSlashes(path.resolve(this.outDir, `${moduleId.replace(/\./g, "/")}.lua`)); + private mapModulesToChunks(modules: Module[]): Chunk[] { + return isBundleEnabled(this.options) ? modulesToBundleChunks(this, modules) : modulesToChunks(this, modules); } } diff --git a/src/transpilation/utils.ts b/src/transpilation/utils.ts index 11b2b7fda..ca68cf178 100644 --- a/src/transpilation/utils.ts +++ b/src/transpilation/utils.ts @@ -2,36 +2,16 @@ import { FileSystem } from "enhanced-resolve"; import * as fs from "fs"; import * as path from "path"; import * as resolve from "resolve"; -import { SourceNode } from "source-map"; import * as ts from "typescript"; // TODO: Don't depend on CLI? import * as cliDiagnostics from "../cli/diagnostics"; import { CompilerOptions } from "../CompilerOptions"; -import * as lua from "../LuaAST"; import * as diagnosticFactories from "./diagnostics"; export interface EmitHost extends Pick { resolutionFileSystem?: FileSystem; } -interface BaseFile { - code: string; - sourceMap?: string; - sourceFiles?: ts.SourceFile[]; -} - -export interface Module extends BaseFile { - /** Absolute source file path. */ - fileName: string; - luaAst?: lua.Block; - /** @internal */ - sourceMapNode?: SourceNode; -} - -export interface Chunk extends BaseFile { - outputPath: string; -} - // TODO: Require emit host export const getConfigDirectory = (options: ts.CompilerOptions, emitHost?: EmitHost) => options.configFilePath ? path.dirname(options.configFilePath) : emitHost?.getCurrentDirectory() ?? process.cwd(); From ae52da3ad8ff85f79248bcf4d4f5717829056a5b Mon Sep 17 00:00:00 2001 From: ark120202 Date: Wed, 14 Oct 2020 10:16:46 +0000 Subject: [PATCH 15/58] Extract Transpilation class into its own file --- src/transpilation/chunk.ts | 2 +- src/transpilation/transpilation.ts | 113 +++++++++++++++++++++++++++++ src/transpilation/transpiler.ts | 111 +--------------------------- 3 files changed, 115 insertions(+), 111 deletions(-) create mode 100644 src/transpilation/transpilation.ts diff --git a/src/transpilation/chunk.ts b/src/transpilation/chunk.ts index 07bfa9526..99eafda8c 100644 --- a/src/transpilation/chunk.ts +++ b/src/transpilation/chunk.ts @@ -6,7 +6,7 @@ import { escapeString } from "../LuaPrinter"; import { assert, normalizeSlashes } from "../utils"; import { couldNotFindBundleEntryPoint } from "./diagnostics"; import { Module } from "./module"; -import { Transpilation } from "./transpiler"; +import { Transpilation } from "./transpilation"; import { getConfigDirectory } from "./utils"; export interface Chunk { diff --git a/src/transpilation/transpilation.ts b/src/transpilation/transpilation.ts new file mode 100644 index 000000000..bcd29a5d5 --- /dev/null +++ b/src/transpilation/transpilation.ts @@ -0,0 +1,113 @@ +import { Resolver, ResolverFactory } from "enhanced-resolve"; +import * as fs from "fs"; +import * as path from "path"; +import * as ts from "typescript"; +import { CompilerOptions, isBundleEnabled, LuaTarget } from "../CompilerOptions"; +import { getLuaLibBundle } from "../LuaLib"; +import { assert, cast, isNonNull, normalizeSlashes, trimExtension } from "../utils"; +import { Chunk, modulesToBundleChunks, modulesToChunks } from "./chunk"; +import { createResolutionErrorDiagnostic } from "./diagnostics"; +import { buildModule, Module } from "./module"; +import { Transpiler } from "./transpiler"; +import { EmitHost } from "./utils"; + +export class Transpilation { + public readonly diagnostics: ts.Diagnostic[] = []; + private modules: Module[] = []; + + public options = this.program.getCompilerOptions() as CompilerOptions; + public rootDir: string; + public outDir: string; + public emitHost: EmitHost; + + private readonly implicitScriptExtensions = [".ts", ".tsx", ".js", ".jsx"] as const; + protected resolver: Resolver; + + constructor(public transpiler: Transpiler, public program: ts.Program) { + this.emitHost = transpiler.emitHost; + + const { rootDir } = program.getCompilerOptions(); + this.rootDir = + // getCommonSourceDirectory ignores provided rootDir when TS6059 is emitted + rootDir == null + ? program.getCommonSourceDirectory() + : ts.getNormalizedAbsolutePath(rootDir, this.emitHost.getCurrentDirectory()); + + this.outDir = this.options.outDir ?? this.rootDir; + + this.resolver = ResolverFactory.createResolver({ + extensions: [".lua", ...this.implicitScriptExtensions], + conditionNames: ["lua", `lua:${this.options.luaTarget ?? LuaTarget.Universal}`], + fileSystem: this.emitHost.resolutionFileSystem ?? fs, + useSyncFileSystemCalls: true, + }); + } + + public emit(programModules: Module[]): Chunk[] { + programModules.forEach(module => this.modules.push(module)); + programModules.forEach(module => this.buildModule(module)); + + const lualibRequired = this.modules.some(m => m.code.toString().includes('require("lualib_bundle")')); + if (lualibRequired) { + const fileName = normalizeSlashes(path.resolve(this.rootDir, "lualib_bundle.lua")); + this.modules.unshift({ request: fileName, code: getLuaLibBundle(this.emitHost), isBuilt: true }); + } + + return this.mapModulesToChunks(this.modules); + } + + private buildModule(module: Module) { + buildModule(module, request => { + let resolvedModule: Module; + try { + resolvedModule = this.resolveRequestToModule(module.request, request); + } catch (error) { + this.diagnostics.push(createResolutionErrorDiagnostic(error.message, request, module.request)); + return { error: error.message }; + } + + return this.getModuleId(resolvedModule); + }); + } + + private resolveRequestToModule(issuer: string, request: string) { + const resolvedPath = this.resolver.resolveSync({}, path.dirname(issuer), request); + assert(typeof resolvedPath === "string", `Invalid resolution result: ${resolvedPath}`); + + let module = this.modules.find(m => m.request === resolvedPath); + if (!module) { + if ( + this.implicitScriptExtensions.some(extension => resolvedPath.endsWith(extension)) || + resolvedPath.endsWith(".json") + ) { + throw new Error(`Resolved source file '${resolvedPath}' is not a part of the project.`); + } + + // TODO: Load source map files + module = { + request: resolvedPath, + code: cast(this.emitHost.readFile(resolvedPath), isNonNull), + isBuilt: false, + }; + + this.modules.push(module); + this.buildModule(module); + } + + return module; + } + + public getModuleId(module: Module) { + const result = path.relative(this.rootDir, trimExtension(module.request)); + // TODO: handle files on other drives + assert(!path.isAbsolute(result), `Invalid path: ${result}`); + return result + .replace(/\.\.[/\\]/g, "_/") + .replace(/\./g, "__") + .replace(/[/\\]/g, "."); + } + + private mapModulesToChunks(modules: Module[]): Chunk[] { + return isBundleEnabled(this.options) ? modulesToBundleChunks(this, modules) : modulesToChunks(this, modules); + } +} diff --git a/src/transpilation/transpiler.ts b/src/transpilation/transpiler.ts index d6b9ad1cd..a64f3f301 100644 --- a/src/transpilation/transpiler.ts +++ b/src/transpilation/transpiler.ts @@ -1,13 +1,5 @@ -import { Resolver, ResolverFactory } from "enhanced-resolve"; -import * as fs from "fs"; -import * as path from "path"; import * as ts from "typescript"; -import { CompilerOptions, isBundleEnabled, LuaTarget } from "../CompilerOptions"; -import { getLuaLibBundle } from "../LuaLib"; -import { assert, cast, isNonNull, normalizeSlashes, trimExtension } from "../utils"; -import { Chunk, modulesToBundleChunks, modulesToChunks } from "./chunk"; -import { createResolutionErrorDiagnostic } from "./diagnostics"; -import { buildModule, Module } from "./module"; +import { Transpilation } from "./transpilation"; import { emitProgramModules, TranspileOptions } from "./transpile"; import { EmitHost } from "./utils"; @@ -49,104 +41,3 @@ export class Transpiler { return { diagnostics, emitSkipped: emitPlan.length === 0 }; } } - -export class Transpilation { - public readonly diagnostics: ts.Diagnostic[] = []; - private modules: Module[] = []; - - public options = this.program.getCompilerOptions() as CompilerOptions; - public rootDir: string; - public outDir: string; - public emitHost: EmitHost; - - private readonly implicitScriptExtensions = [".ts", ".tsx", ".js", ".jsx"] as const; - protected resolver: Resolver; - - constructor(public transpiler: Transpiler, public program: ts.Program) { - this.emitHost = transpiler.emitHost; - - const { rootDir } = program.getCompilerOptions(); - this.rootDir = - // getCommonSourceDirectory ignores provided rootDir when TS6059 is emitted - rootDir == null - ? program.getCommonSourceDirectory() - : ts.getNormalizedAbsolutePath(rootDir, this.emitHost.getCurrentDirectory()); - - this.outDir = this.options.outDir ?? this.rootDir; - - this.resolver = ResolverFactory.createResolver({ - extensions: [".lua", ...this.implicitScriptExtensions], - conditionNames: ["lua", `lua:${this.options.luaTarget ?? LuaTarget.Universal}`], - fileSystem: this.emitHost.resolutionFileSystem ?? fs, - useSyncFileSystemCalls: true, - }); - } - - public emit(programModules: Module[]): Chunk[] { - programModules.forEach(module => this.modules.push(module)); - programModules.forEach(module => this.buildModule(module)); - - const lualibRequired = this.modules.some(m => m.code.toString().includes('require("lualib_bundle")')); - if (lualibRequired) { - const fileName = normalizeSlashes(path.resolve(this.rootDir, "lualib_bundle.lua")); - this.modules.unshift({ request: fileName, code: getLuaLibBundle(this.emitHost), isBuilt: true }); - } - - return this.mapModulesToChunks(this.modules); - } - - private buildModule(module: Module) { - buildModule(module, request => { - let resolvedModule: Module; - try { - resolvedModule = this.resolveRequestToModule(module.request, request); - } catch (error) { - this.diagnostics.push(createResolutionErrorDiagnostic(error.message, request, module.request)); - return { error: error.message }; - } - - return this.getModuleId(resolvedModule); - }); - } - - private resolveRequestToModule(issuer: string, request: string) { - const resolvedPath = this.resolver.resolveSync({}, path.dirname(issuer), request); - assert(typeof resolvedPath === "string", `Invalid resolution result: ${resolvedPath}`); - - let module = this.modules.find(m => m.request === resolvedPath); - if (!module) { - if ( - this.implicitScriptExtensions.some(extension => resolvedPath.endsWith(extension)) || - resolvedPath.endsWith(".json") - ) { - throw new Error(`Resolved source file '${resolvedPath}' is not a part of the project.`); - } - - // TODO: Load source map files - module = { - request: resolvedPath, - code: cast(this.emitHost.readFile(resolvedPath), isNonNull), - isBuilt: false, - }; - - this.modules.push(module); - this.buildModule(module); - } - - return module; - } - - public getModuleId(module: Module) { - const result = path.relative(this.rootDir, trimExtension(module.request)); - // TODO: handle files on other drives - assert(!path.isAbsolute(result), `Invalid path: ${result}`); - return result - .replace(/\.\.[/\\]/g, "_/") - .replace(/\./g, "__") - .replace(/[/\\]/g, "."); - } - - private mapModulesToChunks(modules: Module[]): Chunk[] { - return isBundleEnabled(this.options) ? modulesToBundleChunks(this, modules) : modulesToChunks(this, modules); - } -} From f90befb1e9787cd354eb826396484554e7772e10 Mon Sep 17 00:00:00 2001 From: ark120202 Date: Wed, 14 Oct 2020 10:21:49 +0000 Subject: [PATCH 16/58] EmitHost -> TranspilerHost --- src/LuaLib.ts | 10 +++++----- src/LuaPrinter.ts | 10 +++++----- src/transpilation/chunk.ts | 2 +- src/transpilation/index.ts | 1 - src/transpilation/transpilation.ts | 15 +++++++-------- src/transpilation/transpile/index.ts | 8 ++++---- src/transpilation/transpiler.ts | 18 +++++++++++------- src/transpilation/utils.ts | 10 +++------- test/transpile/plugins/printer.ts | 4 ++-- test/util.ts | 10 +++++----- 10 files changed, 43 insertions(+), 45 deletions(-) diff --git a/src/LuaLib.ts b/src/LuaLib.ts index c476882c7..b5a3d86a0 100644 --- a/src/LuaLib.ts +++ b/src/LuaLib.ts @@ -1,5 +1,5 @@ import * as path from "path"; -import { EmitHost } from "./transpilation"; +import { TranspilerHost } from "./transpilation"; export enum LuaLibFeature { ArrayConcat = "ArrayConcat", @@ -93,7 +93,7 @@ const luaLibDependencies: Partial> = { SymbolRegistry: [LuaLibFeature.Symbol], }; -export function loadLuaLibFeatures(features: Iterable, emitHost: EmitHost): string { +export function loadLuaLibFeatures(features: Iterable, host: TranspilerHost): string { let result = ""; const loadedFeatures = new Set(); @@ -108,7 +108,7 @@ export function loadLuaLibFeatures(features: Iterable, emitHost: } const featurePath = path.resolve(__dirname, `../dist/lualib/${feature}.lua`); - const luaLibFeature = emitHost.readFile(featurePath); + const luaLibFeature = host.readFile(featurePath); if (luaLibFeature !== undefined) { result += luaLibFeature + "\n"; } else { @@ -124,10 +124,10 @@ export function loadLuaLibFeatures(features: Iterable, emitHost: } let luaLibBundleContent: string; -export function getLuaLibBundle(emitHost: EmitHost): string { +export function getLuaLibBundle(host: TranspilerHost): string { if (luaLibBundleContent === undefined) { const lualibPath = path.resolve(__dirname, "../dist/lualib/lualib_bundle.lua"); - const result = emitHost.readFile(lualibPath); + const result = host.readFile(lualibPath); if (result !== undefined) { luaLibBundleContent = result; } else { diff --git a/src/LuaPrinter.ts b/src/LuaPrinter.ts index ec0ead80a..f52da4163 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 } from "./transformation/utils/safe-names"; -import { EmitHost } from "./transpilation"; +import { TranspilerHost } from "./transpilation"; import { assert, intersperse, invertObject, normalizeSlashes, trimExtension } from "./utils"; // https://www.lua.org/pil/2.4.html @@ -87,7 +87,7 @@ type SourceChunk = string | SourceNode; export type Printer = ( program: ts.Program, - emitHost: EmitHost, + host: TranspilerHost, fileName: string, block: lua.Block, luaLibFeatures: Set @@ -101,7 +101,7 @@ export interface PrintResult { export function createPrinter(printers: Printer[]): Printer { if (printers.length === 0) { - return (program, emitHost, fileName, ...args) => new LuaPrinter(emitHost, program, fileName).print(...args); + return (program, host, fileName, ...args) => new LuaPrinter(host, program, fileName).print(...args); } else if (printers.length === 1) { return printers[0]; } else { @@ -142,7 +142,7 @@ export class LuaPrinter { private sourceFile: string; private options: CompilerOptions; - constructor(private emitHost: EmitHost, program: ts.Program, fileName: string) { + constructor(private host: TranspilerHost, program: ts.Program, fileName: string) { this.options = program.getCompilerOptions(); if (this.options.outDir) { @@ -234,7 +234,7 @@ export class LuaPrinter { } else if (luaLibImport === LuaLibImportKind.Inline && luaLibFeatures.size > 0) { // Inline lualib features header += "-- Lua Library inline imports\n"; - header += loadLuaLibFeatures(luaLibFeatures, this.emitHost); + header += loadLuaLibFeatures(luaLibFeatures, this.host); } if (this.options.sourceMapTraceback) { diff --git a/src/transpilation/chunk.ts b/src/transpilation/chunk.ts index 99eafda8c..36d51f6f7 100644 --- a/src/transpilation/chunk.ts +++ b/src/transpilation/chunk.ts @@ -49,7 +49,7 @@ end export function modulesToBundleChunks(transpilation: Transpilation, modules: Module[]): Chunk[] { const options = transpilation.program.getCompilerOptions() as CompilerOptions; assert(isBundleEnabled(options)); - const projectDirectory = getConfigDirectory(options, transpilation.emitHost); + const projectDirectory = getConfigDirectory(options, transpilation.host); const outputPath = normalizeSlashes(path.resolve(projectDirectory, options.luaBundle)); // Resolve project settings relative to project file. diff --git a/src/transpilation/index.ts b/src/transpilation/index.ts index 80e53ac02..fb77d544a 100644 --- a/src/transpilation/index.ts +++ b/src/transpilation/index.ts @@ -1,4 +1,3 @@ export * from "./managed"; export * from "./transpile"; export * from "./transpiler"; -export { EmitHost } from "./utils"; diff --git a/src/transpilation/transpilation.ts b/src/transpilation/transpilation.ts index bcd29a5d5..6b38c2e61 100644 --- a/src/transpilation/transpilation.ts +++ b/src/transpilation/transpilation.ts @@ -8,8 +8,7 @@ import { assert, cast, isNonNull, normalizeSlashes, trimExtension } from "../uti import { Chunk, modulesToBundleChunks, modulesToChunks } from "./chunk"; import { createResolutionErrorDiagnostic } from "./diagnostics"; import { buildModule, Module } from "./module"; -import { Transpiler } from "./transpiler"; -import { EmitHost } from "./utils"; +import { Transpiler, TranspilerHost } from "./transpiler"; export class Transpilation { public readonly diagnostics: ts.Diagnostic[] = []; @@ -18,27 +17,27 @@ export class Transpilation { public options = this.program.getCompilerOptions() as CompilerOptions; public rootDir: string; public outDir: string; - public emitHost: EmitHost; + public host: TranspilerHost; private readonly implicitScriptExtensions = [".ts", ".tsx", ".js", ".jsx"] as const; protected resolver: Resolver; constructor(public transpiler: Transpiler, public program: ts.Program) { - this.emitHost = transpiler.emitHost; + this.host = transpiler.host; const { rootDir } = program.getCompilerOptions(); this.rootDir = // getCommonSourceDirectory ignores provided rootDir when TS6059 is emitted rootDir == null ? program.getCommonSourceDirectory() - : ts.getNormalizedAbsolutePath(rootDir, this.emitHost.getCurrentDirectory()); + : ts.getNormalizedAbsolutePath(rootDir, this.host.getCurrentDirectory()); this.outDir = this.options.outDir ?? this.rootDir; this.resolver = ResolverFactory.createResolver({ extensions: [".lua", ...this.implicitScriptExtensions], conditionNames: ["lua", `lua:${this.options.luaTarget ?? LuaTarget.Universal}`], - fileSystem: this.emitHost.resolutionFileSystem ?? fs, + fileSystem: this.host.resolutionFileSystem ?? fs, useSyncFileSystemCalls: true, }); } @@ -50,7 +49,7 @@ export class Transpilation { const lualibRequired = this.modules.some(m => m.code.toString().includes('require("lualib_bundle")')); if (lualibRequired) { const fileName = normalizeSlashes(path.resolve(this.rootDir, "lualib_bundle.lua")); - this.modules.unshift({ request: fileName, code: getLuaLibBundle(this.emitHost), isBuilt: true }); + this.modules.unshift({ request: fileName, code: getLuaLibBundle(this.host), isBuilt: true }); } return this.mapModulesToChunks(this.modules); @@ -86,7 +85,7 @@ export class Transpilation { // TODO: Load source map files module = { request: resolvedPath, - code: cast(this.emitHost.readFile(resolvedPath), isNonNull), + code: cast(this.host.readFile(resolvedPath), isNonNull), isBuilt: false, }; diff --git a/src/transpilation/transpile/index.ts b/src/transpilation/transpile/index.ts index fee839ad3..e7ec370cb 100644 --- a/src/transpilation/transpile/index.ts +++ b/src/transpilation/transpile/index.ts @@ -5,7 +5,7 @@ import { createPrinter } from "../../LuaPrinter"; import { createVisitorMap, transformSourceFile } from "../../transformation"; import { assert, isNonNull } from "../../utils"; import { Module } from "../module"; -import { EmitHost } from "../utils"; +import { TranspilerHost } from "../transpiler"; import { getPlugins, Plugin } from "./plugins"; import { getTransformers } from "./transformers"; @@ -19,7 +19,7 @@ export interface TranspileOptions { } export function emitProgramModules( - emitHost: EmitHost, + host: TranspilerHost, writeFileResult: ts.WriteFileCallback, { program, sourceFiles: targetSourceFiles, customTransformers = {}, plugins: customPlugins = [] }: TranspileOptions ) { @@ -66,13 +66,13 @@ export function emitProgramModules( diagnostics.push(...transformDiagnostics); if (!options.noEmit && !options.emitDeclarationOnly) { - const printResult = printer(program, emitHost, sourceFile.fileName, luaAst, luaLibFeatures); + const printResult = printer(program, host, sourceFile.fileName, luaAst, luaLibFeatures); let fileName: string; if (path.isAbsolute(sourceFile.fileName)) { fileName = sourceFile.fileName; } else { - const currentDirectory = emitHost.getCurrentDirectory(); + const currentDirectory = host.getCurrentDirectory(); // Having no absolute path in path.resolve would make it fallback to real cwd assert(path.isAbsolute(currentDirectory), `Invalid path: ${currentDirectory}`); fileName = path.resolve(currentDirectory, sourceFile.fileName); diff --git a/src/transpilation/transpiler.ts b/src/transpilation/transpiler.ts index a64f3f301..222be6b26 100644 --- a/src/transpilation/transpiler.ts +++ b/src/transpilation/transpiler.ts @@ -1,10 +1,14 @@ +import { FileSystem } from "enhanced-resolve"; import * as ts from "typescript"; import { Transpilation } from "./transpilation"; import { emitProgramModules, TranspileOptions } from "./transpile"; -import { EmitHost } from "./utils"; + +export interface TranspilerHost extends Pick { + resolutionFileSystem?: FileSystem; +} export interface TranspilerOptions { - emitHost?: EmitHost; + host?: TranspilerHost; } export interface EmitOptions extends TranspileOptions { @@ -17,15 +21,15 @@ export interface EmitResult { } export class Transpiler { - public emitHost: EmitHost; - constructor({ emitHost = ts.sys }: TranspilerOptions = {}) { - this.emitHost = emitHost; + public host: TranspilerHost; + constructor({ host = ts.sys }: TranspilerOptions = {}) { + this.host = host; } public emit(emitOptions: EmitOptions): EmitResult { - const { program, writeFile = this.emitHost.writeFile } = emitOptions; + const { program, writeFile = this.host.writeFile } = emitOptions; const transpilation = new Transpilation(this, program); - const { diagnostics, modules } = emitProgramModules(this.emitHost, writeFile, emitOptions); + const { diagnostics, modules } = emitProgramModules(this.host, writeFile, emitOptions); const emitPlan = transpilation.emit(modules); diagnostics.push(...transpilation.diagnostics); diff --git a/src/transpilation/utils.ts b/src/transpilation/utils.ts index ca68cf178..008885838 100644 --- a/src/transpilation/utils.ts +++ b/src/transpilation/utils.ts @@ -1,4 +1,3 @@ -import { FileSystem } from "enhanced-resolve"; import * as fs from "fs"; import * as path from "path"; import * as resolve from "resolve"; @@ -7,14 +6,11 @@ import * as ts from "typescript"; import * as cliDiagnostics from "../cli/diagnostics"; import { CompilerOptions } from "../CompilerOptions"; import * as diagnosticFactories from "./diagnostics"; - -export interface EmitHost extends Pick { - resolutionFileSystem?: FileSystem; -} +import { TranspilerHost } from "./transpiler"; // TODO: Require emit host -export const getConfigDirectory = (options: ts.CompilerOptions, emitHost?: EmitHost) => - options.configFilePath ? path.dirname(options.configFilePath) : emitHost?.getCurrentDirectory() ?? process.cwd(); +export const getConfigDirectory = (options: ts.CompilerOptions, host?: TranspilerHost) => + options.configFilePath ? path.dirname(options.configFilePath) : host?.getCurrentDirectory() ?? process.cwd(); export function resolvePlugin( kind: string, diff --git a/test/transpile/plugins/printer.ts b/test/transpile/plugins/printer.ts index 7ae55ca01..904226363 100644 --- a/test/transpile/plugins/printer.ts +++ b/test/transpile/plugins/printer.ts @@ -1,8 +1,8 @@ import * as tstl from "../../../src"; const plugin: tstl.Plugin = { - printer(program, emitHost, fileName, ...args) { - const result = new tstl.LuaPrinter(emitHost, program, fileName).print(...args); + printer(program, host, fileName, ...args) { + const result = new tstl.LuaPrinter(host, program, fileName).print(...args); result.code = `-- Plugin\n${result.code}`; return result; }, diff --git a/test/util.ts b/test/util.ts index 126a2222d..c998c47fb 100644 --- a/test/util.ts +++ b/test/util.ts @@ -252,18 +252,18 @@ export abstract class TestBuilder { const collector = createEmitOutputCollector(); const program = this.getProgram(); - const emitHost: tstl.EmitHost = { ...ts.sys }; + const host: tstl.TranspilerHost = { ...ts.sys }; if (!this.nativeFileSystem) { const virtualFS = Volume.fromJSON({ ...this.extraRawFiles, ...this.getSourceFiles() }, "/"); - emitHost.resolutionFileSystem = virtualFS; - emitHost.getCurrentDirectory = () => "/"; - emitHost.readFile = (fileName, encoding = "utf8") => + host.resolutionFileSystem = virtualFS; + host.getCurrentDirectory = () => "/"; + host.readFile = (fileName, encoding = "utf8") => fileName.endsWith("lualib_bundle.lua") ? ts.sys.readFile(fileName, encoding) : (virtualFS.readFileSync(fileName, encoding) as string); } - const { diagnostics: transpileDiagnostics } = new tstl.Transpiler({ emitHost }).emit({ + const { diagnostics: transpileDiagnostics } = new tstl.Transpiler({ host }).emit({ program, customTransformers: this.customTransformers, writeFile: collector.writeFile, From 53ba64eb2a805985f937fc8d9cf28bfaabcb8c51 Mon Sep 17 00:00:00 2001 From: ark120202 Date: Wed, 14 Oct 2020 10:53:13 +0000 Subject: [PATCH 17/58] Pass Transpilation instance as a context --- src/transpilation/transpilation.ts | 7 ++-- src/transpilation/transpile/index.ts | 41 ++++++++------------- src/transpilation/transpile/plugins.ts | 12 +++--- src/transpilation/transpile/transformers.ts | 29 ++++++++------- src/transpilation/transpiler.ts | 16 +++++--- 5 files changed, 50 insertions(+), 55 deletions(-) diff --git a/src/transpilation/transpilation.ts b/src/transpilation/transpilation.ts index 6b38c2e61..a8b51546a 100644 --- a/src/transpilation/transpilation.ts +++ b/src/transpilation/transpilation.ts @@ -12,7 +12,7 @@ import { Transpiler, TranspilerHost } from "./transpiler"; export class Transpilation { public readonly diagnostics: ts.Diagnostic[] = []; - private modules: Module[] = []; + public modules: Module[] = []; public options = this.program.getCompilerOptions() as CompilerOptions; public rootDir: string; @@ -42,9 +42,8 @@ export class Transpilation { }); } - public emit(programModules: Module[]): Chunk[] { - programModules.forEach(module => this.modules.push(module)); - programModules.forEach(module => this.buildModule(module)); + public emit(): Chunk[] { + this.modules.forEach(module => this.buildModule(module)); const lualibRequired = this.modules.some(m => m.code.toString().includes('require("lualib_bundle")')); if (lualibRequired) { diff --git a/src/transpilation/transpile/index.ts b/src/transpilation/transpile/index.ts index e7ec370cb..0e8b3f725 100644 --- a/src/transpilation/transpile/index.ts +++ b/src/transpilation/transpile/index.ts @@ -4,8 +4,7 @@ import { CompilerOptions, validateOptions } from "../../CompilerOptions"; import { createPrinter } from "../../LuaPrinter"; import { createVisitorMap, transformSourceFile } from "../../transformation"; import { assert, isNonNull } from "../../utils"; -import { Module } from "../module"; -import { TranspilerHost } from "../transpiler"; +import { Transpilation } from "../transpilation"; import { getPlugins, Plugin } from "./plugins"; import { getTransformers } from "./transformers"; @@ -19,21 +18,16 @@ export interface TranspileOptions { } export function emitProgramModules( - host: TranspilerHost, + transpilation: Transpilation, writeFileResult: ts.WriteFileCallback, { program, sourceFiles: targetSourceFiles, customTransformers = {}, plugins: customPlugins = [] }: TranspileOptions ) { const options = program.getCompilerOptions() as CompilerOptions; - const diagnostics = validateOptions(options); - let modules: Module[] = []; + transpilation.diagnostics.push(...validateOptions(options)); if (options.noEmitOnError) { - const preEmitDiagnostics = [ - ...diagnostics, - ...program.getOptionsDiagnostics(), - ...program.getGlobalDiagnostics(), - ]; + const preEmitDiagnostics = [...program.getOptionsDiagnostics(), ...program.getGlobalDiagnostics()]; if (targetSourceFiles) { for (const sourceFile of targetSourceFiles) { @@ -50,11 +44,12 @@ export function emitProgramModules( } if (preEmitDiagnostics.length > 0) { - return { diagnostics: preEmitDiagnostics, modules }; + transpilation.diagnostics.push(...preEmitDiagnostics); + return; } } - const plugins = getPlugins(program, diagnostics, customPlugins); + const plugins = getPlugins(transpilation, customPlugins); const visitorMap = createVisitorMap(plugins.map(p => p.visitors).filter(isNonNull)); const printer = createPrinter(plugins.map(p => p.printer).filter(isNonNull)); const processSourceFile = (sourceFile: ts.SourceFile) => { @@ -64,21 +59,21 @@ export function emitProgramModules( visitorMap ); - diagnostics.push(...transformDiagnostics); + transpilation.diagnostics.push(...transformDiagnostics); if (!options.noEmit && !options.emitDeclarationOnly) { - const printResult = printer(program, host, sourceFile.fileName, luaAst, luaLibFeatures); + const printResult = printer(program, transpilation.host, sourceFile.fileName, luaAst, luaLibFeatures); let fileName: string; if (path.isAbsolute(sourceFile.fileName)) { fileName = sourceFile.fileName; } else { - const currentDirectory = host.getCurrentDirectory(); + const currentDirectory = transpilation.host.getCurrentDirectory(); // Having no absolute path in path.resolve would make it fallback to real cwd assert(path.isAbsolute(currentDirectory), `Invalid path: ${currentDirectory}`); fileName = path.resolve(currentDirectory, sourceFile.fileName); } - modules.push({ + transpilation.modules.push({ sourceFiles: [sourceFile], request: fileName, isBuilt: false, @@ -87,7 +82,7 @@ export function emitProgramModules( } }; - const transformers = getTransformers(program, diagnostics, customTransformers, processSourceFile); + const transformers = getTransformers(transpilation, customTransformers, processSourceFile); const isEmittableJsonFile = (sourceFile: ts.SourceFile) => sourceFile.flags & ts.NodeFlags.JsonFile && @@ -109,21 +104,17 @@ export function emitProgramModules( if (isEmittableJsonFile(file)) { processSourceFile(file); } else { - diagnostics.push(...program.emit(file, writeFile, undefined, false, transformers).diagnostics); + const { diagnostics } = program.emit(file, writeFile, undefined, false, transformers); + transpilation.diagnostics.push(...diagnostics); } } } else { - diagnostics.push(...program.emit(undefined, writeFile, undefined, false, transformers).diagnostics); + const { diagnostics } = program.emit(undefined, writeFile, undefined, false, transformers); + transpilation.diagnostics.push(...diagnostics); // JSON files don't get through transformers and aren't written when outDir is the same as rootDir program.getSourceFiles().filter(isEmittableJsonFile).forEach(processSourceFile); } options.noEmit = oldNoEmit; - - if (options.noEmit || (options.noEmitOnError && diagnostics.length > 0)) { - modules = []; - } - - return { diagnostics, modules }; } diff --git a/src/transpilation/transpile/plugins.ts b/src/transpilation/transpile/plugins.ts index 6c137d03c..108218a06 100644 --- a/src/transpilation/transpile/plugins.ts +++ b/src/transpilation/transpile/plugins.ts @@ -1,7 +1,6 @@ -import * as ts from "typescript"; -import { CompilerOptions } from "../../CompilerOptions"; import { Printer } from "../../LuaPrinter"; import { Visitors } from "../../transformation/context"; +import { Transpilation } from "../transpilation"; import { getConfigDirectory, resolvePlugin } from "../utils"; export interface Plugin { @@ -20,22 +19,21 @@ export interface Plugin { printer?: Printer; } -export function getPlugins(program: ts.Program, diagnostics: ts.Diagnostic[], customPlugins: Plugin[]): Plugin[] { +export function getPlugins(transpilation: Transpilation, customPlugins: Plugin[]): Plugin[] { const pluginsFromOptions: Plugin[] = []; - const options = program.getCompilerOptions() as CompilerOptions; - for (const [index, pluginOption] of (options.luaPlugins ?? []).entries()) { + for (const [index, pluginOption] of (transpilation.options.luaPlugins ?? []).entries()) { const optionName = `tstl.luaPlugins[${index}]`; const { error: resolveError, result: factory } = resolvePlugin( "plugin", `${optionName}.name`, - getConfigDirectory(options), + getConfigDirectory(transpilation.options), pluginOption.name, pluginOption.import ); - if (resolveError) diagnostics.push(resolveError); + if (resolveError) transpilation.diagnostics.push(resolveError); if (factory === undefined) continue; const plugin = typeof factory === "function" ? factory(pluginOption) : factory; diff --git a/src/transpilation/transpile/transformers.ts b/src/transpilation/transpile/transformers.ts index 10b2bb343..fada33370 100644 --- a/src/transpilation/transpile/transformers.ts +++ b/src/transpilation/transpile/transformers.ts @@ -3,11 +3,11 @@ import * as ts from "typescript"; import * as cliDiagnostics from "../../cli/diagnostics"; import { CompilerOptions, TransformerImport } from "../../CompilerOptions"; import * as diagnosticFactories from "../diagnostics"; +import { Transpilation } from "../transpilation"; import { getConfigDirectory, resolvePlugin } from "../utils"; export function getTransformers( - program: ts.Program, - diagnostics: ts.Diagnostic[], + transpilation: Transpilation, customTransformers: ts.CustomTransformers, onSourceFile: (sourceFile: ts.SourceFile) => void ): ts.CustomTransformers { @@ -16,15 +16,14 @@ export function getTransformers( return ts.createSourceFile(sourceFile.fileName, "", ts.ScriptTarget.ESNext); }; - const transformersFromOptions = loadTransformersFromOptions(program, diagnostics); + const transformersFromOptions = loadTransformersFromOptions(transpilation); const afterDeclarations = [ ...(transformersFromOptions.afterDeclarations ?? []), ...(customTransformers.afterDeclarations ?? []), ]; - const options = program.getCompilerOptions() as CompilerOptions; - if (options.noImplicitSelf) { + if (transpilation.options.noImplicitSelf) { afterDeclarations.unshift(noImplicitSelfTransformer); } @@ -53,33 +52,37 @@ export const noImplicitSelfTransformer: ts.TransformerFactory = { before: [], after: [], afterDeclarations: [], }; - const options = program.getCompilerOptions() as CompilerOptions; - if (!options.plugins) return customTransformers; + if (!transpilation.options.plugins) return customTransformers; - for (const [index, transformerImport] of options.plugins.entries()) { + for (const [index, transformerImport] of transpilation.options.plugins.entries()) { if (!("transform" in transformerImport)) continue; const optionName = `compilerOptions.plugins[${index}]`; const { error: resolveError, result: factory } = resolvePlugin( "transformer", `${optionName}.transform`, - getConfigDirectory(options), + getConfigDirectory(transpilation.options), transformerImport.transform, transformerImport.import ); - if (resolveError) diagnostics.push(resolveError); + if (resolveError) transpilation.diagnostics.push(resolveError); if (factory === undefined) continue; - const { error: loadError, transformer } = loadTransformer(optionName, program, factory, transformerImport); - if (loadError) diagnostics.push(loadError); + const { error: loadError, transformer } = loadTransformer( + optionName, + transpilation.program, + factory, + transformerImport + ); + if (loadError) transpilation.diagnostics.push(loadError); if (transformer === undefined) continue; if (transformer.before) { diff --git a/src/transpilation/transpiler.ts b/src/transpilation/transpiler.ts index 222be6b26..759ab1ede 100644 --- a/src/transpilation/transpiler.ts +++ b/src/transpilation/transpiler.ts @@ -28,20 +28,24 @@ export class Transpiler { public emit(emitOptions: EmitOptions): EmitResult { const { program, writeFile = this.host.writeFile } = emitOptions; + const options = program.getCompilerOptions(); + const transpilation = new Transpilation(this, program); - const { diagnostics, modules } = emitProgramModules(this.host, writeFile, emitOptions); - const emitPlan = transpilation.emit(modules); - diagnostics.push(...transpilation.diagnostics); + emitProgramModules(transpilation, writeFile, emitOptions); + if (options.noEmit || (options.noEmitOnError && transpilation.diagnostics.length > 0)) { + return { diagnostics: transpilation.diagnostics, emitSkipped: true }; + } + + const chunks = transpilation.emit(); - const options = program.getCompilerOptions(); const emitBOM = options.emitBOM ?? false; - for (const { outputPath, code, sourceMap, sourceFiles } of emitPlan) { + for (const { outputPath, code, sourceMap, sourceFiles } of chunks) { writeFile(outputPath, code, emitBOM, undefined, sourceFiles); if (options.sourceMap && sourceMap !== undefined) { writeFile(outputPath + ".map", sourceMap, emitBOM, undefined, sourceFiles); } } - return { diagnostics, emitSkipped: emitPlan.length === 0 }; + return { diagnostics: transpilation.diagnostics, emitSkipped: chunks.length === 0 }; } } From deefc88e305b6b53343ffada60947ef41511b398 Mon Sep 17 00:00:00 2001 From: ark120202 Date: Wed, 14 Oct 2020 11:09:53 +0000 Subject: [PATCH 18/58] Move plugin handling to Transpilation class --- src/transpilation/index.ts | 1 + src/transpilation/{transpile => }/plugins.ts | 8 ++++---- src/transpilation/transpilation.ts | 11 +++++++---- src/transpilation/transpile/index.ts | 11 +++-------- src/transpilation/transpiler.ts | 4 +++- 5 files changed, 18 insertions(+), 17 deletions(-) rename src/transpilation/{transpile => }/plugins.ts (85%) diff --git a/src/transpilation/index.ts b/src/transpilation/index.ts index fb77d544a..a89a52fff 100644 --- a/src/transpilation/index.ts +++ b/src/transpilation/index.ts @@ -1,3 +1,4 @@ export * from "./managed"; +export { Plugin } from "./plugins"; export * from "./transpile"; export * from "./transpiler"; diff --git a/src/transpilation/transpile/plugins.ts b/src/transpilation/plugins.ts similarity index 85% rename from src/transpilation/transpile/plugins.ts rename to src/transpilation/plugins.ts index 108218a06..9de3d0979 100644 --- a/src/transpilation/transpile/plugins.ts +++ b/src/transpilation/plugins.ts @@ -1,7 +1,7 @@ -import { Printer } from "../../LuaPrinter"; -import { Visitors } from "../../transformation/context"; -import { Transpilation } from "../transpilation"; -import { getConfigDirectory, resolvePlugin } from "../utils"; +import { Printer } from "../LuaPrinter"; +import { Visitors } from "../transformation/context"; +import { Transpilation } from "./transpilation"; +import { getConfigDirectory, resolvePlugin } from "./utils"; export interface Plugin { /** diff --git a/src/transpilation/transpilation.ts b/src/transpilation/transpilation.ts index a8b51546a..cd5ec5916 100644 --- a/src/transpilation/transpilation.ts +++ b/src/transpilation/transpilation.ts @@ -8,6 +8,7 @@ import { assert, cast, isNonNull, normalizeSlashes, trimExtension } from "../uti import { Chunk, modulesToBundleChunks, modulesToChunks } from "./chunk"; import { createResolutionErrorDiagnostic } from "./diagnostics"; import { buildModule, Module } from "./module"; +import { getPlugins, Plugin } from "./plugins"; import { Transpiler, TranspilerHost } from "./transpiler"; export class Transpilation { @@ -21,16 +22,16 @@ export class Transpilation { private readonly implicitScriptExtensions = [".ts", ".tsx", ".js", ".jsx"] as const; protected resolver: Resolver; + public plugins: Plugin[]; - constructor(public transpiler: Transpiler, public program: ts.Program) { + constructor(public transpiler: Transpiler, public program: ts.Program, extraPlugins: Plugin[]) { this.host = transpiler.host; - const { rootDir } = program.getCompilerOptions(); this.rootDir = // getCommonSourceDirectory ignores provided rootDir when TS6059 is emitted - rootDir == null + this.options.rootDir == null ? program.getCommonSourceDirectory() - : ts.getNormalizedAbsolutePath(rootDir, this.host.getCurrentDirectory()); + : ts.getNormalizedAbsolutePath(this.options.rootDir, this.host.getCurrentDirectory()); this.outDir = this.options.outDir ?? this.rootDir; @@ -40,6 +41,8 @@ export class Transpilation { fileSystem: this.host.resolutionFileSystem ?? fs, useSyncFileSystemCalls: true, }); + + this.plugins = getPlugins(this, extraPlugins); } public emit(): Chunk[] { diff --git a/src/transpilation/transpile/index.ts b/src/transpilation/transpile/index.ts index 0e8b3f725..c7e9be783 100644 --- a/src/transpilation/transpile/index.ts +++ b/src/transpilation/transpile/index.ts @@ -5,22 +5,18 @@ import { createPrinter } from "../../LuaPrinter"; import { createVisitorMap, transformSourceFile } from "../../transformation"; import { assert, isNonNull } from "../../utils"; import { Transpilation } from "../transpilation"; -import { getPlugins, Plugin } from "./plugins"; import { getTransformers } from "./transformers"; -export { Plugin }; - export interface TranspileOptions { program: ts.Program; sourceFiles?: ts.SourceFile[]; customTransformers?: ts.CustomTransformers; - plugins?: Plugin[]; } export function emitProgramModules( transpilation: Transpilation, writeFileResult: ts.WriteFileCallback, - { program, sourceFiles: targetSourceFiles, customTransformers = {}, plugins: customPlugins = [] }: TranspileOptions + { program, sourceFiles: targetSourceFiles, customTransformers = {} }: TranspileOptions ) { const options = program.getCompilerOptions() as CompilerOptions; @@ -49,9 +45,8 @@ export function emitProgramModules( } } - const plugins = getPlugins(transpilation, customPlugins); - const visitorMap = createVisitorMap(plugins.map(p => p.visitors).filter(isNonNull)); - const printer = createPrinter(plugins.map(p => p.printer).filter(isNonNull)); + const visitorMap = createVisitorMap(transpilation.plugins.map(p => p.visitors).filter(isNonNull)); + const printer = createPrinter(transpilation.plugins.map(p => p.printer).filter(isNonNull)); const processSourceFile = (sourceFile: ts.SourceFile) => { const { luaAst, luaLibFeatures, diagnostics: transformDiagnostics } = transformSourceFile( program, diff --git a/src/transpilation/transpiler.ts b/src/transpilation/transpiler.ts index 759ab1ede..89007d0c9 100644 --- a/src/transpilation/transpiler.ts +++ b/src/transpilation/transpiler.ts @@ -1,5 +1,6 @@ import { FileSystem } from "enhanced-resolve"; import * as ts from "typescript"; +import { Plugin } from "./plugins"; import { Transpilation } from "./transpilation"; import { emitProgramModules, TranspileOptions } from "./transpile"; @@ -13,6 +14,7 @@ export interface TranspilerOptions { export interface EmitOptions extends TranspileOptions { writeFile?: ts.WriteFileCallback; + plugins?: Plugin[]; } export interface EmitResult { @@ -30,7 +32,7 @@ export class Transpiler { const { program, writeFile = this.host.writeFile } = emitOptions; const options = program.getCompilerOptions(); - const transpilation = new Transpilation(this, program); + const transpilation = new Transpilation(this, program, emitOptions.plugins ?? []); emitProgramModules(transpilation, writeFile, emitOptions); if (options.noEmit || (options.noEmitOnError && transpilation.diagnostics.length > 0)) { return { diagnostics: transpilation.diagnostics, emitSkipped: true }; From 356798750b48333b0217729c44a1922bf2cfcc7f Mon Sep 17 00:00:00 2001 From: ark120202 Date: Thu, 15 Oct 2020 10:00:34 +0000 Subject: [PATCH 19/58] Add transpilation-related plugin hooks --- src/LuaPrinter.ts | 10 ----- src/transpilation/plugins.ts | 38 +++++++++++++++++++ src/transpilation/transpilation.ts | 15 ++++++-- src/transpilation/transpile/index.ts | 8 +++- .../__snapshots__/plugins.spec.ts.snap | 7 ++++ test/transpile/plugins/getModuleId.ts | 14 +++++++ test/transpile/plugins/getResolvePlugins.ts | 12 ++++++ test/transpile/plugins/plugins.spec.ts | 23 +++++++++++ 8 files changed, 111 insertions(+), 16 deletions(-) create mode 100644 test/transpile/plugins/__snapshots__/plugins.spec.ts.snap create mode 100644 test/transpile/plugins/getModuleId.ts create mode 100644 test/transpile/plugins/getResolvePlugins.ts diff --git a/src/LuaPrinter.ts b/src/LuaPrinter.ts index f52da4163..931dc35ca 100644 --- a/src/LuaPrinter.ts +++ b/src/LuaPrinter.ts @@ -99,16 +99,6 @@ export interface PrintResult { sourceMapNode: SourceNode; } -export function createPrinter(printers: Printer[]): Printer { - if (printers.length === 0) { - return (program, host, fileName, ...args) => new LuaPrinter(host, program, fileName).print(...args); - } else if (printers.length === 1) { - return printers[0]; - } else { - throw new Error("Only one plugin can specify 'printer'"); - } -} - export class LuaPrinter { private static operatorMap: Record = { [lua.SyntaxKind.AdditionOperator]: "+", diff --git a/src/transpilation/plugins.ts b/src/transpilation/plugins.ts index 9de3d0979..14b37d807 100644 --- a/src/transpilation/plugins.ts +++ b/src/transpilation/plugins.ts @@ -1,5 +1,8 @@ +import { Plugin as ResolvePlugin } from "enhanced-resolve"; import { Printer } from "../LuaPrinter"; import { Visitors } from "../transformation/context"; +import { Chunk } from "./chunk"; +import { Module } from "./module"; import { Transpilation } from "./transpilation"; import { getConfigDirectory, resolvePlugin } from "./utils"; @@ -17,6 +20,23 @@ export interface Plugin { * At most one custom printer can be provided across all plugins. */ printer?: Printer; + + /** + * Provide extra [enhanced-resolve](https://github.com/webpack/enhanced-resolve) plugins, + * used for `.lua` module resolution. + */ + getResolvePlugins?(transpilation: Transpilation): ResolvePlugin[]; + + /** + * Transform modules into chunks. + */ + mapModulesToChunks?(modules: Module[], transpilation: Transpilation): Chunk[]; + + /** + * Produce a unique identifier for a module, which would be used as `require` call parameter, + * and may be used for chunk naming. + */ + getModuleId?(module: Module, transpilation: Transpilation): string | undefined; } export function getPlugins(transpilation: Transpilation, customPlugins: Plugin[]): Plugin[] { @@ -42,3 +62,21 @@ export function getPlugins(transpilation: Transpilation, customPlugins: Plugin[] return [...customPlugins, ...pluginsFromOptions]; } + +export function applyBailPlugin(plugins: Plugin[], callback: (plugin: Plugin) => T | undefined): T | undefined { + for (const plugin of plugins) { + const result = callback(plugin); + if (result !== undefined) { + return result; + } + } +} + +export function applySinglePlugin

(plugins: Plugin[], property: P): Plugin[P] | undefined { + const results = plugins.filter(p => p[property] !== undefined); + if (results.length === 1) { + return results[0][property]; + } else if (results.length > 1) { + throw new Error(`Only one plugin can specify '${property}'`); + } +} diff --git a/src/transpilation/transpilation.ts b/src/transpilation/transpilation.ts index cd5ec5916..25c63ae0e 100644 --- a/src/transpilation/transpilation.ts +++ b/src/transpilation/transpilation.ts @@ -8,7 +8,7 @@ import { assert, cast, isNonNull, normalizeSlashes, trimExtension } from "../uti import { Chunk, modulesToBundleChunks, modulesToChunks } from "./chunk"; import { createResolutionErrorDiagnostic } from "./diagnostics"; import { buildModule, Module } from "./module"; -import { getPlugins, Plugin } from "./plugins"; +import { applyBailPlugin, applySinglePlugin, getPlugins, Plugin } from "./plugins"; import { Transpiler, TranspilerHost } from "./transpiler"; export class Transpilation { @@ -35,14 +35,15 @@ export class Transpilation { this.outDir = this.options.outDir ?? this.rootDir; + this.plugins = getPlugins(this, extraPlugins); + this.resolver = ResolverFactory.createResolver({ extensions: [".lua", ...this.implicitScriptExtensions], conditionNames: ["lua", `lua:${this.options.luaTarget ?? LuaTarget.Universal}`], fileSystem: this.host.resolutionFileSystem ?? fs, useSyncFileSystemCalls: true, + plugins: this.plugins.flatMap(p => p.getResolvePlugins?.(this) ?? []), }); - - this.plugins = getPlugins(this, extraPlugins); } public emit(): Chunk[] { @@ -99,6 +100,9 @@ export class Transpilation { } public getModuleId(module: Module) { + const pluginResult = applyBailPlugin(this.plugins, p => p.getModuleId?.(module, this)); + if (pluginResult !== undefined) return pluginResult; + const result = path.relative(this.rootDir, trimExtension(module.request)); // TODO: handle files on other drives assert(!path.isAbsolute(result), `Invalid path: ${result}`); @@ -109,6 +113,9 @@ export class Transpilation { } private mapModulesToChunks(modules: Module[]): Chunk[] { - return isBundleEnabled(this.options) ? modulesToBundleChunks(this, modules) : modulesToChunks(this, modules); + return ( + applySinglePlugin(this.plugins, "mapModulesToChunks")?.(modules, this) ?? + (isBundleEnabled(this.options) ? modulesToBundleChunks(this, modules) : modulesToChunks(this, modules)) + ); } } diff --git a/src/transpilation/transpile/index.ts b/src/transpilation/transpile/index.ts index c7e9be783..fb19d8ad8 100644 --- a/src/transpilation/transpile/index.ts +++ b/src/transpilation/transpile/index.ts @@ -1,9 +1,10 @@ import * as path from "path"; import * as ts from "typescript"; import { CompilerOptions, validateOptions } from "../../CompilerOptions"; -import { createPrinter } from "../../LuaPrinter"; +import { LuaPrinter } from "../../LuaPrinter"; import { createVisitorMap, transformSourceFile } from "../../transformation"; import { assert, isNonNull } from "../../utils"; +import { applySinglePlugin } from "../plugins"; import { Transpilation } from "../transpilation"; import { getTransformers } from "./transformers"; @@ -46,7 +47,10 @@ export function emitProgramModules( } const visitorMap = createVisitorMap(transpilation.plugins.map(p => p.visitors).filter(isNonNull)); - const printer = createPrinter(transpilation.plugins.map(p => p.printer).filter(isNonNull)); + const printer = + applySinglePlugin(transpilation.plugins, "printer") ?? + ((program, host, fileName, ...args) => new LuaPrinter(host, program, fileName).print(...args)); + const processSourceFile = (sourceFile: ts.SourceFile) => { const { luaAst, luaLibFeatures, diagnostics: transformDiagnostics } = transformSourceFile( program, diff --git a/test/transpile/plugins/__snapshots__/plugins.spec.ts.snap b/test/transpile/plugins/__snapshots__/plugins.spec.ts.snap new file mode 100644 index 000000000..8215236e5 --- /dev/null +++ b/test/transpile/plugins/__snapshots__/plugins.spec.ts.snap @@ -0,0 +1,7 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`getModuleId 1`] = ` +"local ____exports = {} +require(\\"5d05566c99dac259f6ff5742a268d405157a0d7c\\") +return ____exports" +`; diff --git a/test/transpile/plugins/getModuleId.ts b/test/transpile/plugins/getModuleId.ts new file mode 100644 index 000000000..f1e134f6a --- /dev/null +++ b/test/transpile/plugins/getModuleId.ts @@ -0,0 +1,14 @@ +import { createHash } from "crypto"; +import * as path from "path"; +import * as tstl from "../../../src"; + +const plugin: tstl.Plugin = { + getModuleId: (module, transpilation) => + createHash("sha1") + .update(module.code) + .update(path.relative(transpilation.rootDir, module.request)) + .digest("hex"), +}; + +// eslint-disable-next-line import/no-default-export +export default plugin; diff --git a/test/transpile/plugins/getResolvePlugins.ts b/test/transpile/plugins/getResolvePlugins.ts new file mode 100644 index 000000000..5d1ded4d7 --- /dev/null +++ b/test/transpile/plugins/getResolvePlugins.ts @@ -0,0 +1,12 @@ +// @ts-expect-error Could not find a declaration file for module 'enhanced-resolve/lib/AliasPlugin'. +import * as AliasPlugin from "enhanced-resolve/lib/AliasPlugin"; +import * as tstl from "../../../src"; + +const plugin: tstl.Plugin = { + getResolvePlugins: () => [ + new AliasPlugin("described-resolve", { name: "foo", alias: "/bar.ts" }, "internal-resolve"), + ], +}; + +// eslint-disable-next-line import/no-default-export +export default plugin; diff --git a/test/transpile/plugins/plugins.spec.ts b/test/transpile/plugins/plugins.spec.ts index ce2ebc7a6..05a3dc5a9 100644 --- a/test/transpile/plugins/plugins.spec.ts +++ b/test/transpile/plugins/plugins.spec.ts @@ -22,3 +22,26 @@ test("visitor using super", () => { .setOptions({ luaPlugins: [{ name: path.join(__dirname, "visitor-super.ts") }] }) .expectToEqual("bar"); }); + +test("getModuleId", () => { + util.testModule` + export { value } from "./foo"; + ` + .addExtraFile("foo.ts", "export const value = true;") + .setOptions({ luaPlugins: [{ name: path.join(__dirname, "getModuleId.ts") }] }) + .expectToEqual({ value: true }) + .expectLuaToMatchSnapshot(); +}); + +test("getResolvePlugins", () => { + util.testModule` + export { value } from "foo"; + ` + .addExtraFile("bar.ts", "export const value = true;") + .setOptions({ + luaPlugins: [{ name: path.join(__dirname, "getResolvePlugins.ts") }], + baseUrl: ".", + paths: { foo: ["bar"] }, + }) + .expectToEqual({ value: true }); +}); From 68e7ffaf989205826d70c4606afc5436965937b3 Mon Sep 17 00:00:00 2001 From: ark120202 Date: Fri, 16 Oct 2020 06:11:29 +0000 Subject: [PATCH 20/58] Treat all resolved extensions expect `.lua` as project members --- src/transpilation/transpilation.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/transpilation/transpilation.ts b/src/transpilation/transpilation.ts index 25c63ae0e..ff142bbdf 100644 --- a/src/transpilation/transpilation.ts +++ b/src/transpilation/transpilation.ts @@ -20,7 +20,6 @@ export class Transpilation { public outDir: string; public host: TranspilerHost; - private readonly implicitScriptExtensions = [".ts", ".tsx", ".js", ".jsx"] as const; protected resolver: Resolver; public plugins: Plugin[]; @@ -38,7 +37,7 @@ export class Transpilation { this.plugins = getPlugins(this, extraPlugins); this.resolver = ResolverFactory.createResolver({ - extensions: [".lua", ...this.implicitScriptExtensions], + extensions: [".lua", ".ts", ".tsx", ".js", ".jsx"], conditionNames: ["lua", `lua:${this.options.luaTarget ?? LuaTarget.Universal}`], fileSystem: this.host.resolutionFileSystem ?? fs, useSyncFileSystemCalls: true, @@ -78,10 +77,7 @@ export class Transpilation { let module = this.modules.find(m => m.request === resolvedPath); if (!module) { - if ( - this.implicitScriptExtensions.some(extension => resolvedPath.endsWith(extension)) || - resolvedPath.endsWith(".json") - ) { + if (!resolvedPath.endsWith(".lua")) { throw new Error(`Resolved source file '${resolvedPath}' is not a part of the project.`); } From ceff8f23d436f46e02b54ad1cb57df55fc983934 Mon Sep 17 00:00:00 2001 From: ark120202 Date: Fri, 16 Oct 2020 15:46:14 +0000 Subject: [PATCH 21/58] Move sourcemap handling to chunk level --- src/LuaPrinter.ts | 155 ++++----------------------- src/transpilation/chunk.ts | 25 ++--- src/transpilation/module.ts | 42 +++----- src/transpilation/print-chunk.ts | 110 +++++++++++++++++++ src/transpilation/transpilation.ts | 12 ++- src/transpilation/transpile/index.ts | 15 +-- src/transpilation/transpiler.ts | 10 +- 7 files changed, 172 insertions(+), 197 deletions(-) create mode 100644 src/transpilation/print-chunk.ts diff --git a/src/LuaPrinter.ts b/src/LuaPrinter.ts index 931dc35ca..e4a74c0d2 100644 --- a/src/LuaPrinter.ts +++ b/src/LuaPrinter.ts @@ -1,12 +1,12 @@ import * as path from "path"; -import { Mapping, SourceMapGenerator, SourceNode } from "source-map"; +import { SourceNode } from "source-map"; import * as ts from "typescript"; import { CompilerOptions, LuaLibImportKind } from "./CompilerOptions"; import * as lua from "./LuaAST"; import { loadLuaLibFeatures, LuaLibFeature } from "./LuaLib"; import { isValidLuaIdentifier } from "./transformation/utils/safe-names"; import { TranspilerHost } from "./transpilation"; -import { assert, intersperse, invertObject, normalizeSlashes, trimExtension } from "./utils"; +import { assert, intersperse, invertObject, normalizeSlashes } from "./utils"; // https://www.lua.org/pil/2.4.html // https://www.ecma-international.org/ecma-262/10.0/index.html#table-34 @@ -91,13 +91,7 @@ export type Printer = ( fileName: string, block: lua.Block, luaLibFeatures: Set -) => PrintResult; - -export interface PrintResult { - code: string; - sourceMap: string; - sourceMapNode: SourceNode; -} +) => SourceNode; export class LuaPrinter { private static operatorMap: Record = { @@ -129,7 +123,7 @@ export class LuaPrinter { }; private currentIndent = ""; - private sourceFile: string; + private fileName: string; private options: CompilerOptions; constructor(private host: TranspilerHost, program: ts.Program, fileName: string) { @@ -139,81 +133,31 @@ export class LuaPrinter { const relativeFileName = path.relative(program.getCommonSourceDirectory(), fileName); if (this.options.sourceRoot) { // When sourceRoot is specified, just use relative path inside rootDir - this.sourceFile = relativeFileName; + this.fileName = relativeFileName; } else { // Calculate relative path from rootDir to outDir const outputPath = path.resolve(this.options.outDir, relativeFileName); - this.sourceFile = path.relative(path.dirname(outputPath), fileName); + this.fileName = path.relative(path.dirname(outputPath), fileName); } // We want forward slashes, even in windows - this.sourceFile = normalizeSlashes(this.sourceFile); + this.fileName = normalizeSlashes(this.fileName); } else { - this.sourceFile = path.basename(fileName); // File will be in same dir as source + this.fileName = path.basename(fileName); // File will be in same dir as source } } - public print(block: lua.Block, luaLibFeatures: Set): PrintResult { - // Add traceback lualib if sourcemap traceback option is enabled - if (this.options.sourceMapTraceback) { - luaLibFeatures.add(LuaLibFeature.SourceMapTraceBack); - } - - const sourceRoot = this.options.sourceRoot - ? // According to spec, sourceRoot is simply prepended to the source name, so the slash should be included - this.options.sourceRoot.replace(/[\\/]+$/, "") + "/" - : ""; - const rootSourceNode = this.printImplementation(block, luaLibFeatures); - const sourceMap = this.buildSourceMap(sourceRoot, rootSourceNode); - - let code = rootSourceNode.toString(); - - if (this.options.inlineSourceMap) { - code += "\n" + this.printInlineSourceMap(sourceMap); - } - - if (this.options.sourceMapTraceback) { - const stackTraceOverride = this.printStackTraceOverride(rootSourceNode); - code = code.replace("{#SourceMapTraceback}", stackTraceOverride); - } - - return { code, sourceMap: sourceMap.toString(), sourceMapNode: rootSourceNode }; - } - - private printInlineSourceMap(sourceMap: SourceMapGenerator): string { - const map = sourceMap.toString(); - const base64Map = Buffer.from(map).toString("base64"); - - return `--# sourceMappingURL=data:application/json;base64,${base64Map}\n`; - } - - private printStackTraceOverride(rootNode: SourceNode): string { - let currentLine = 1; - const map: Record = {}; - rootNode.walk((chunk, mappedPosition) => { - if (mappedPosition.line !== undefined && mappedPosition.line > 0) { - if (map[currentLine] === undefined) { - map[currentLine] = mappedPosition.line; - } else { - map[currentLine] = Math.min(map[currentLine], mappedPosition.line); - } - } - - currentLine += chunk.split("\n").length - 1; - }); - - const mapItems = Object.entries(map).map(([line, original]) => `["${line}"] = ${original}`); - const mapString = "{" + mapItems.join(",") + "}"; - - return `__TS__SourceMapTraceBack(debug.getinfo(1).short_src, ${mapString});`; - } - - private printImplementation(block: lua.Block, luaLibFeatures: Set): SourceNode { + public print(block: lua.Block, luaLibFeatures: Set): SourceNode { let header = ""; if (!this.options.noHeader) { header += "--[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]]\n"; } + // Add traceback lualib if sourcemap traceback option is enabled + if (this.options.sourceMapTraceback) { + luaLibFeatures.add(LuaLibFeature.SourceMapTraceBack); + } + const luaLibImport = this.options.luaLibImport ?? LuaLibImportKind.Require; if ( luaLibImport === LuaLibImportKind.Always || @@ -252,12 +196,12 @@ export class LuaPrinter { const { line, column } = lua.getOriginalPos(node); return line !== undefined && column !== undefined - ? new SourceNode(line + 1, column, this.sourceFile, chunks, name) - : new SourceNode(null, null, this.sourceFile, chunks, name); + ? new SourceNode(line + 1, column, this.fileName, chunks, name) + : new SourceNode(null, null, this.fileName, chunks, name); } protected concatNodes(...chunks: SourceChunk[]): SourceNode { - return new SourceNode(null, null, this.sourceFile, chunks); + return new SourceNode(null, null, this.fileName, chunks); } protected printBlock(block: lua.Block): SourceNode { @@ -743,7 +687,7 @@ export class LuaPrinter { } public printOperator(kind: lua.Operator): SourceNode { - return new SourceNode(null, null, this.sourceFile, LuaPrinter.operatorMap[kind]); + return new SourceNode(null, null, this.fileName, LuaPrinter.operatorMap[kind]); } protected joinChunksWithComma(chunks: SourceChunk[]): SourceChunk[] { @@ -768,67 +712,4 @@ export class LuaPrinter { return chunks; } - - // The key difference between this and SourceNode.toStringWithSourceMap() is that SourceNodes with null line/column - // will not generate 'empty' mappings in the source map that point to nothing in the original TS. - private buildSourceMap(sourceRoot: string, rootSourceNode: SourceNode): SourceMapGenerator { - const map = new SourceMapGenerator({ - file: trimExtension(this.sourceFile) + ".lua", - sourceRoot, - }); - - let generatedLine = 1; - let generatedColumn = 0; - let currentMapping: Mapping | undefined; - - const isNewMapping = (sourceNode: SourceNode) => { - if (sourceNode.line === null) { - return false; - } - if (currentMapping === undefined) { - return true; - } - if ( - currentMapping.generated.line === generatedLine && - currentMapping.generated.column === generatedColumn && - currentMapping.name === sourceNode.name - ) { - return false; - } - return ( - currentMapping.original.line !== sourceNode.line || - currentMapping.original.column !== sourceNode.column || - currentMapping.name !== sourceNode.name - ); - }; - - const build = (sourceNode: SourceNode) => { - if (isNewMapping(sourceNode)) { - currentMapping = { - source: sourceNode.source, - original: { line: sourceNode.line, column: sourceNode.column }, - generated: { line: generatedLine, column: generatedColumn }, - name: sourceNode.name, - }; - map.addMapping(currentMapping); - } - - for (const chunk of sourceNode.children) { - if (typeof chunk === "string") { - const lines = (chunk as string).split("\n"); - if (lines.length > 1) { - generatedLine += lines.length - 1; - generatedColumn = 0; - currentMapping = undefined; // Mappings end at newlines - } - generatedColumn += lines[lines.length - 1].length; - } else { - build(chunk); - } - } - }; - build(rootSourceNode); - - return map; - } } diff --git a/src/transpilation/chunk.ts b/src/transpilation/chunk.ts index 36d51f6f7..47ebe46f5 100644 --- a/src/transpilation/chunk.ts +++ b/src/transpilation/chunk.ts @@ -11,8 +11,7 @@ import { getConfigDirectory } from "./utils"; export interface Chunk { outputPath: string; - code: string; - sourceMap?: string; + source: SourceNode; sourceFiles?: ts.SourceFile[]; } @@ -20,7 +19,7 @@ export function modulesToChunks(transpilation: Transpilation, modules: Module[]) return modules.map(module => { const moduleId = transpilation.getModuleId(module); const outputPath = normalizeSlashes(path.resolve(transpilation.outDir, `${moduleId.replace(/\./g, "/")}.lua`)); - return { outputPath, code: module.code, sourceMap: module.sourceMap, sourceFiles: module.sourceFiles }; + return { outputPath, source: module.source, sourceFiles: module.sourceFiles }; }); } @@ -57,11 +56,11 @@ export function modulesToBundleChunks(transpilation: Transpilation, modules: Mod const entryModule = modules.find(m => m.request === entryFileName); if (entryModule === undefined) { transpilation.diagnostics.push(couldNotFindBundleEntryPoint(options.luaBundleEntry)); - return [{ outputPath, code: "" }]; + return [{ outputPath, source: new SourceNode() }]; } // For each file: [""] = function() end, - const moduleTableEntries = modules.map(m => moduleSourceNode(m, escapeString(transpilation.getModuleId(m)))); + const moduleTableEntries = modules.map(m => moduleSourceNode(m, transpilation.getModuleId(m))); // Create ____modules table containing all entries from moduleTableEntries const moduleTable = createModuleTableNode(moduleTableEntries); @@ -70,20 +69,12 @@ export function modulesToBundleChunks(transpilation: Transpilation, modules: Mod const bootstrap = `return require(${escapeString(transpilation.getModuleId(entryModule))})\n`; const bundleNode = joinSourceChunks([requireOverride, moduleTable, bootstrap]); - const { code, map } = bundleNode.toStringWithSourceMap(); - - return [ - { - outputPath, - code, - sourceMap: map.toString(), - sourceFiles: modules.flatMap(x => x.sourceFiles ?? []), - }, - ]; + const sourceFiles = modules.flatMap(x => x.sourceFiles ?? []); + return [{ outputPath, source: bundleNode, sourceFiles }]; } -function moduleSourceNode(module: Module, modulePath: string): SourceNode { - return joinSourceChunks([`[${modulePath}] = function()\n`, module.sourceMapNode ?? module.code, "\nend,\n"]); +function moduleSourceNode(module: Module, moduleId: string): SourceNode { + return joinSourceChunks([`[${escapeString(moduleId)}] = function()\n`, module.source, "\nend,\n"]); } function createModuleTableNode(fileChunks: SourceChunk[]): SourceNode { diff --git a/src/transpilation/module.ts b/src/transpilation/module.ts index 81a451e84..0e0149995 100644 --- a/src/transpilation/module.ts +++ b/src/transpilation/module.ts @@ -5,9 +5,7 @@ import { escapeString, unescapeLuaString } from "../LuaPrinter"; export interface Module { request: string; isBuilt: boolean; - code: string; - sourceMap?: string; - sourceMapNode?: SourceNode; + source: SourceNode; sourceFiles?: ts.SourceFile[]; } @@ -16,37 +14,29 @@ export type ModuleDependencyResolver = (request: string) => string | { error: st export function buildModule(module: Module, dependencyResolver: ModuleDependencyResolver) { if (module.isBuilt) return; module.isBuilt = true; + replaceResolveRequests(module.source, dependencyResolver); +} - if (module.sourceMapNode) { - replaceResolveMacroSourceNodes(module.sourceMapNode, dependencyResolver); - const { code, map } = module.sourceMapNode.toStringWithSourceMap(); - module.code = code; - module.sourceMap = JSON.stringify(map.toJSON()); - } else { - module.code = replaceResolveMacroInSource(module.code, dependencyResolver); +function replaceResolveRequests(rootNode: SourceNode, dependencyResolver: ModuleDependencyResolver) { + function replaceInString(source: string) { + return source.replace(/__TS__Resolve\((".*?")\)/g, (_, match) => { + const request = unescapeLuaString(match); + const replacement = dependencyResolver(request); + return typeof replacement === "string" + ? escapeString(replacement) + : `--[[ ${request} ]] error(${escapeString(replacement.error)})`; + }); } -} -function replaceResolveMacroSourceNodes(rootNode: SourceNode, replacer: ModuleDependencyResolver) { function walkSourceNode(node: SourceNode, parent: SourceNode) { - for (const child of node.children) { - if ((child as any) === "__TS__Resolve") { - parent.children = [replaceResolveMacroInSource(parent.toString(), replacer) as any]; - } else if (typeof child === "object") { + for (const child of node.children as Array) { + if (typeof child === "object") { walkSourceNode(child, node); + } else if (child.includes("__TS__Resolve")) { + parent.children = [replaceInString(parent.toString()) as any]; } } } walkSourceNode(rootNode, rootNode); } - -function replaceResolveMacroInSource(source: string, replacer: ModuleDependencyResolver) { - return source.replace(/__TS__Resolve\((".*?")\)/, (_, match) => { - const request = unescapeLuaString(match); - const replacement = replacer(request); - return typeof replacement === "string" - ? escapeString(replacement) - : `--[[ ${request} ]] error(${escapeString(replacement.error)})`; - }); -} diff --git a/src/transpilation/print-chunk.ts b/src/transpilation/print-chunk.ts new file mode 100644 index 000000000..3e0a8f667 --- /dev/null +++ b/src/transpilation/print-chunk.ts @@ -0,0 +1,110 @@ +import * as path from "path"; +import { Mapping, SourceMapGenerator, SourceNode, StartOfSourceMap } from "source-map"; +import { CompilerOptions } from "../CompilerOptions"; +import { Chunk } from "./chunk"; + +export function printChunk(chunk: Chunk, options: CompilerOptions) { + const sourceRoot = options.sourceRoot + ? // According to spec, sourceRoot is simply prepended to the source name, so the slash should be included + options.sourceRoot.replace(/[\\/]+$/, "") + "/" + : ""; + + const map = buildSourceMap(chunk.source, { file: path.basename(chunk.outputPath), sourceRoot }); + const sourceMap = options.sourceMap ? map.toString() : undefined; + + let code = chunk.source.toString(); + + if (options.inlineSourceMap) { + code += printInlineSourceMap(map); + } + + if (options.sourceMapTraceback) { + code = code.replace("{#SourceMapTraceback}", printStackTraceOverride(chunk.source)); + } + + return { code, sourceMap }; +} + +function printInlineSourceMap(sourceMap: SourceMapGenerator): string { + const base64Map = Buffer.from(sourceMap.toString()).toString("base64"); + return `--# sourceMappingURL=data:application/json;base64,${base64Map}\n`; +} + +function printStackTraceOverride(rootNode: SourceNode): string { + let currentLine = 1; + const lineMap: Record = {}; + rootNode.walk((chunk, mappedPosition) => { + if (mappedPosition.line !== undefined && mappedPosition.line > 0) { + if (lineMap[currentLine] === undefined) { + lineMap[currentLine] = mappedPosition.line; + } else { + lineMap[currentLine] = Math.min(lineMap[currentLine], mappedPosition.line); + } + } + + currentLine += chunk.split("\n").length - 1; + }); + + const mappings = Object.entries(lineMap).map(([line, original]) => `["${line}"] = ${original}`); + return `__TS__SourceMapTraceBack(debug.getinfo(1).short_src, {${mappings.join(",")}});`; +} + +// The key difference between this and SourceNode.toStringWithSourceMap() is that SourceNodes with null line/column +// will not generate 'empty' mappings in the source map that point to nothing in the original TS. +function buildSourceMap(sourceNode: SourceNode, startOfSourceMap: StartOfSourceMap): SourceMapGenerator { + const map = new SourceMapGenerator(startOfSourceMap); + + let generatedLine = 1; + let generatedColumn = 0; + let currentMapping: Mapping | undefined; + + const isNewMapping = (sourceNode: SourceNode) => { + if (sourceNode.line === null) { + return false; + } + if (currentMapping === undefined) { + return true; + } + if ( + currentMapping.generated.line === generatedLine && + currentMapping.generated.column === generatedColumn && + currentMapping.name === sourceNode.name + ) { + return false; + } + return ( + currentMapping.original.line !== sourceNode.line || + currentMapping.original.column !== sourceNode.column || + currentMapping.name !== sourceNode.name + ); + }; + + const build = (sourceNode: SourceNode) => { + if (isNewMapping(sourceNode)) { + currentMapping = { + source: sourceNode.source, + original: { line: sourceNode.line, column: sourceNode.column }, + generated: { line: generatedLine, column: generatedColumn }, + name: sourceNode.name, + }; + map.addMapping(currentMapping); + } + + for (const chunk of sourceNode.children) { + if (typeof chunk === "string") { + const lines = (chunk as string).split("\n"); + if (lines.length > 1) { + generatedLine += lines.length - 1; + generatedColumn = 0; + currentMapping = undefined; // Mappings end at newlines + } + generatedColumn += lines[lines.length - 1].length; + } else { + build(chunk); + } + } + }; + build(sourceNode); + + return map; +} diff --git a/src/transpilation/transpilation.ts b/src/transpilation/transpilation.ts index ff142bbdf..499144f19 100644 --- a/src/transpilation/transpilation.ts +++ b/src/transpilation/transpilation.ts @@ -1,6 +1,7 @@ import { Resolver, ResolverFactory } from "enhanced-resolve"; import * as fs from "fs"; import * as path from "path"; +import { SourceNode } from "source-map"; import * as ts from "typescript"; import { CompilerOptions, isBundleEnabled, LuaTarget } from "../CompilerOptions"; import { getLuaLibBundle } from "../LuaLib"; @@ -48,10 +49,14 @@ export class Transpilation { public emit(): Chunk[] { this.modules.forEach(module => this.buildModule(module)); - const lualibRequired = this.modules.some(m => m.code.toString().includes('require("lualib_bundle")')); + const lualibRequired = this.modules.some(m => m.source.toString().includes('require("lualib_bundle")')); if (lualibRequired) { const fileName = normalizeSlashes(path.resolve(this.rootDir, "lualib_bundle.lua")); - this.modules.unshift({ request: fileName, code: getLuaLibBundle(this.host), isBuilt: true }); + this.modules.unshift({ + request: fileName, + isBuilt: true, + source: new SourceNode(null, null, null, getLuaLibBundle(this.host)), + }); } return this.mapModulesToChunks(this.modules); @@ -82,10 +87,11 @@ export class Transpilation { } // TODO: Load source map files + const code = cast(this.host.readFile(resolvedPath), isNonNull); module = { request: resolvedPath, - code: cast(this.host.readFile(resolvedPath), isNonNull), isBuilt: false, + source: new SourceNode(null, null, null, code), }; this.modules.push(module); diff --git a/src/transpilation/transpile/index.ts b/src/transpilation/transpile/index.ts index fb19d8ad8..a7383f16d 100644 --- a/src/transpilation/transpile/index.ts +++ b/src/transpilation/transpile/index.ts @@ -60,24 +60,19 @@ export function emitProgramModules( transpilation.diagnostics.push(...transformDiagnostics); if (!options.noEmit && !options.emitDeclarationOnly) { - const printResult = printer(program, transpilation.host, sourceFile.fileName, luaAst, luaLibFeatures); + const source = printer(program, transpilation.host, sourceFile.fileName, luaAst, luaLibFeatures); - let fileName: string; + let request: string; if (path.isAbsolute(sourceFile.fileName)) { - fileName = sourceFile.fileName; + request = sourceFile.fileName; } else { const currentDirectory = transpilation.host.getCurrentDirectory(); // Having no absolute path in path.resolve would make it fallback to real cwd assert(path.isAbsolute(currentDirectory), `Invalid path: ${currentDirectory}`); - fileName = path.resolve(currentDirectory, sourceFile.fileName); + request = path.resolve(currentDirectory, sourceFile.fileName); } - transpilation.modules.push({ - sourceFiles: [sourceFile], - request: fileName, - isBuilt: false, - ...printResult, - }); + transpilation.modules.push({ request, isBuilt: false, source, sourceFiles: [sourceFile] }); } }; diff --git a/src/transpilation/transpiler.ts b/src/transpilation/transpiler.ts index 89007d0c9..3cda66a8c 100644 --- a/src/transpilation/transpiler.ts +++ b/src/transpilation/transpiler.ts @@ -1,6 +1,7 @@ import { FileSystem } from "enhanced-resolve"; import * as ts from "typescript"; import { Plugin } from "./plugins"; +import { printChunk } from "./print-chunk"; import { Transpilation } from "./transpilation"; import { emitProgramModules, TranspileOptions } from "./transpile"; @@ -41,10 +42,11 @@ export class Transpiler { const chunks = transpilation.emit(); const emitBOM = options.emitBOM ?? false; - for (const { outputPath, code, sourceMap, sourceFiles } of chunks) { - writeFile(outputPath, code, emitBOM, undefined, sourceFiles); - if (options.sourceMap && sourceMap !== undefined) { - writeFile(outputPath + ".map", sourceMap, emitBOM, undefined, sourceFiles); + for (const chunk of chunks) { + const { code, sourceMap } = printChunk(chunk, options); + writeFile(chunk.outputPath, code, emitBOM, undefined, chunk.sourceFiles); + if (sourceMap !== undefined) { + writeFile(chunk.outputPath + ".map", sourceMap, emitBOM, undefined, chunk.sourceFiles); } } From 2fa14b814fdcfce150743a02f7a8798400e66d2b Mon Sep 17 00:00:00 2001 From: ark120202 Date: Fri, 16 Oct 2020 16:04:02 +0000 Subject: [PATCH 22/58] Refactor --- src/transformation/index.ts | 13 +-- .../{chunk.ts => chunk/bundle.ts} | 36 +++----- src/transpilation/chunk/index.ts | 23 +++++ .../{print-chunk.ts => chunk/print.ts} | 4 +- src/transpilation/index.ts | 4 +- .../{managed.ts => managed-api/index.ts} | 9 +- src/transpilation/managed-api/utils.ts | 88 +++++++++++++++++++ src/transpilation/output-collector.ts | 43 --------- src/transpilation/plugins.ts | 6 +- src/transpilation/transpilation.ts | 10 ++- src/transpilation/transpile/transformers.ts | 6 +- src/transpilation/transpiler.ts | 2 +- src/transpilation/utils.ts | 52 +---------- test/util.ts | 3 +- 14 files changed, 148 insertions(+), 151 deletions(-) rename src/transpilation/{chunk.ts => chunk/bundle.ts} (65%) create mode 100644 src/transpilation/chunk/index.ts rename src/transpilation/{print-chunk.ts => chunk/print.ts} (97%) rename src/transpilation/{managed.ts => managed-api/index.ts} (88%) create mode 100644 src/transpilation/managed-api/utils.ts delete mode 100644 src/transpilation/output-collector.ts diff --git a/src/transformation/index.ts b/src/transformation/index.ts index 69b098811..e99a5a141 100644 --- a/src/transformation/index.ts +++ b/src/transformation/index.ts @@ -1,6 +1,5 @@ import * as ts from "typescript"; import * as lua from "../LuaAST"; -import { LuaLibFeature } from "../LuaLib"; import { getOrUpdate } from "../utils"; import { ObjectVisitor, TransformationContext, VisitorMap, Visitors } from "./context"; import { getUsedLuaLibFeatures } from "./utils/lualib"; @@ -29,17 +28,7 @@ export function createVisitorMap(customVisitors: Visitors[]): VisitorMap { return visitorMap; } -export interface TransformSourceFileResult { - luaAst: lua.Block; - luaLibFeatures: Set; - diagnostics: ts.Diagnostic[]; -} - -export function transformSourceFile( - program: ts.Program, - sourceFile: ts.SourceFile, - visitorMap: VisitorMap -): TransformSourceFileResult { +export function transformSourceFile(program: ts.Program, sourceFile: ts.SourceFile, visitorMap: VisitorMap) { const context = new TransformationContext(program, sourceFile, visitorMap); const [luaAst] = context.transformNode(sourceFile) as [lua.Block]; diff --git a/src/transpilation/chunk.ts b/src/transpilation/chunk/bundle.ts similarity index 65% rename from src/transpilation/chunk.ts rename to src/transpilation/chunk/bundle.ts index 47ebe46f5..67f25f200 100644 --- a/src/transpilation/chunk.ts +++ b/src/transpilation/chunk/bundle.ts @@ -1,27 +1,12 @@ import * as path from "path"; import { SourceNode } from "source-map"; -import * as ts from "typescript"; -import { CompilerOptions, isBundleEnabled } from "../CompilerOptions"; -import { escapeString } from "../LuaPrinter"; -import { assert, normalizeSlashes } from "../utils"; -import { couldNotFindBundleEntryPoint } from "./diagnostics"; -import { Module } from "./module"; -import { Transpilation } from "./transpilation"; -import { getConfigDirectory } from "./utils"; - -export interface Chunk { - outputPath: string; - source: SourceNode; - sourceFiles?: ts.SourceFile[]; -} - -export function modulesToChunks(transpilation: Transpilation, modules: Module[]): Chunk[] { - return modules.map(module => { - const moduleId = transpilation.getModuleId(module); - const outputPath = normalizeSlashes(path.resolve(transpilation.outDir, `${moduleId.replace(/\./g, "/")}.lua`)); - return { outputPath, source: module.source, sourceFiles: module.sourceFiles }; - }); -} +import { Chunk } from "."; +import { CompilerOptions, isBundleEnabled } from "../../CompilerOptions"; +import { escapeString } from "../../LuaPrinter"; +import { assert, normalizeSlashes } from "../../utils"; +import { couldNotFindBundleEntryPoint } from "../diagnostics"; +import { Module } from "../module"; +import { Transpilation } from "../transpilation"; // Override `require` to read from ____modules table. const requireOverride = ` @@ -48,11 +33,10 @@ end export function modulesToBundleChunks(transpilation: Transpilation, modules: Module[]): Chunk[] { const options = transpilation.program.getCompilerOptions() as CompilerOptions; assert(isBundleEnabled(options)); - const projectDirectory = getConfigDirectory(options, transpilation.host); - const outputPath = normalizeSlashes(path.resolve(projectDirectory, options.luaBundle)); - // Resolve project settings relative to project file. - const entryFileName = normalizeSlashes(path.resolve(projectDirectory, options.luaBundleEntry)); + const outputPath = normalizeSlashes(path.resolve(transpilation.projectDir, options.luaBundle)); + const entryFileName = normalizeSlashes(path.resolve(transpilation.projectDir, options.luaBundleEntry)); + const entryModule = modules.find(m => m.request === entryFileName); if (entryModule === undefined) { transpilation.diagnostics.push(couldNotFindBundleEntryPoint(options.luaBundleEntry)); diff --git a/src/transpilation/chunk/index.ts b/src/transpilation/chunk/index.ts new file mode 100644 index 000000000..3faaa3009 --- /dev/null +++ b/src/transpilation/chunk/index.ts @@ -0,0 +1,23 @@ +import * as path from "path"; +import { SourceNode } from "source-map"; +import * as ts from "typescript"; +import { normalizeSlashes } from "../../utils"; +import { Module } from "../module"; +import { Transpilation } from "../transpilation"; + +export * from "./bundle"; +export * from "./print"; + +export interface Chunk { + outputPath: string; + source: SourceNode; + sourceFiles?: ts.SourceFile[]; +} + +export function modulesToChunks(transpilation: Transpilation, modules: Module[]): Chunk[] { + return modules.map(module => { + const moduleId = transpilation.getModuleId(module); + const outputPath = normalizeSlashes(path.resolve(transpilation.outDir, `${moduleId.replace(/\./g, "/")}.lua`)); + return { outputPath, source: module.source, sourceFiles: module.sourceFiles }; + }); +} diff --git a/src/transpilation/print-chunk.ts b/src/transpilation/chunk/print.ts similarity index 97% rename from src/transpilation/print-chunk.ts rename to src/transpilation/chunk/print.ts index 3e0a8f667..a6d25035d 100644 --- a/src/transpilation/print-chunk.ts +++ b/src/transpilation/chunk/print.ts @@ -1,7 +1,7 @@ import * as path from "path"; import { Mapping, SourceMapGenerator, SourceNode, StartOfSourceMap } from "source-map"; -import { CompilerOptions } from "../CompilerOptions"; -import { Chunk } from "./chunk"; +import { Chunk } from "."; +import { CompilerOptions } from "../../CompilerOptions"; export function printChunk(chunk: Chunk, options: CompilerOptions) { const sourceRoot = options.sourceRoot diff --git a/src/transpilation/index.ts b/src/transpilation/index.ts index a89a52fff..908c7189c 100644 --- a/src/transpilation/index.ts +++ b/src/transpilation/index.ts @@ -1,4 +1,6 @@ -export * from "./managed"; +export { Chunk } from "./chunk"; +export * from "./managed-api"; +export { Module } from "./module"; export { Plugin } from "./plugins"; export * from "./transpile"; export * from "./transpiler"; diff --git a/src/transpilation/managed.ts b/src/transpilation/managed-api/index.ts similarity index 88% rename from src/transpilation/managed.ts rename to src/transpilation/managed-api/index.ts index 53ee46417..380f9f8af 100644 --- a/src/transpilation/managed.ts +++ b/src/transpilation/managed-api/index.ts @@ -1,9 +1,8 @@ import * as ts from "typescript"; -import { parseConfigFileWithSystem } from "../cli/tsconfig"; -import { CompilerOptions } from "../CompilerOptions"; -import { createEmitOutputCollector, TranspiledFile } from "./output-collector"; -import { EmitResult, Transpiler } from "./transpiler"; -import { createVirtualProgram } from "./utils"; +import { parseConfigFileWithSystem } from "../../cli/tsconfig"; +import { CompilerOptions } from "../../CompilerOptions"; +import { EmitResult, Transpiler } from "../transpiler"; +import { createEmitOutputCollector, createVirtualProgram, TranspiledFile } from "./utils"; export { TranspiledFile }; diff --git a/src/transpilation/managed-api/utils.ts b/src/transpilation/managed-api/utils.ts new file mode 100644 index 000000000..7f60eee93 --- /dev/null +++ b/src/transpilation/managed-api/utils.ts @@ -0,0 +1,88 @@ +import * as fs from "fs"; +import * as path from "path"; +import * as ts from "typescript"; +import { CompilerOptions } from "../../CompilerOptions"; +import { intersection, union } from "../../utils"; + +const libCache = new Map(); +export function createVirtualProgram(input: Record, options: CompilerOptions = {}): ts.Program { + function notImplemented(): never { + throw new Error("Not implemented"); + } + + const getFileFromInput = (fileName: string) => + input[fileName] ?? (fileName.startsWith("/") ? input[fileName.slice(1)] : undefined); + + const compilerHost: ts.CompilerHost = { + useCaseSensitiveFileNames: () => false, + getCanonicalFileName: fileName => fileName, + getCurrentDirectory: () => "/", + fileExists: fileName => fileName.startsWith("lib.") || getFileFromInput(fileName) !== undefined, + readFile: notImplemented, + writeFile: notImplemented, + getDefaultLibFileName: ts.getDefaultLibFileName, + getNewLine: () => "\n", + + getSourceFile(fileName) { + const fileFromInput = getFileFromInput(fileName); + if (fileFromInput !== undefined) { + return ts.createSourceFile(fileName, fileFromInput, ts.ScriptTarget.Latest, false); + } + + if (libCache.has(fileName)) return libCache.get(fileName)!; + + if (fileName.startsWith("lib.")) { + const typeScriptDir = path.dirname(require.resolve("typescript")); + const filePath = path.join(typeScriptDir, fileName); + const content = fs.readFileSync(filePath, "utf8"); + + const sourceFile = ts.createSourceFile(fileName, content, ts.ScriptTarget.Latest, false); + libCache.set(fileName, sourceFile); + return sourceFile; + } + }, + }; + + return ts.createProgram(Object.keys(input), options, compilerHost); +} + +export interface TranspiledFile { + sourceFiles: ts.SourceFile[]; + lua?: string; + luaSourceMap?: string; + declaration?: string; + declarationMap?: string; + /** @internal */ + js?: string; + /** @internal */ + jsSourceMap?: string; +} + +export function createEmitOutputCollector() { + const files: TranspiledFile[] = []; + const writeFile: ts.WriteFileCallback = (fileName, data, _bom, _onError, sourceFiles = []) => { + let file = files.find(f => intersection(f.sourceFiles, sourceFiles).length > 0); + if (!file) { + file = { sourceFiles: [...sourceFiles] }; + files.push(file); + } else { + file.sourceFiles = union(file.sourceFiles, sourceFiles); + } + + if (fileName.endsWith(".lua")) { + file.lua = data; + } else if (fileName.endsWith(".lua.map")) { + file.luaSourceMap = data; + } else if (fileName.endsWith(".js")) { + file.js = data; + } else if (fileName.endsWith(".js.map")) { + file.jsSourceMap = data; + } else if (fileName.endsWith(".d.ts")) { + file.declaration = data; + } else if (fileName.endsWith(".d.ts.map")) { + file.declarationMap = data; + } + }; + + return { writeFile, files }; +} diff --git a/src/transpilation/output-collector.ts b/src/transpilation/output-collector.ts deleted file mode 100644 index d18137c5a..000000000 --- a/src/transpilation/output-collector.ts +++ /dev/null @@ -1,43 +0,0 @@ -import * as ts from "typescript"; -import { intersection, union } from "../utils"; - -export interface TranspiledFile { - sourceFiles: ts.SourceFile[]; - lua?: string; - luaSourceMap?: string; - declaration?: string; - declarationMap?: string; - /** @internal */ - js?: string; - /** @internal */ - jsSourceMap?: string; -} - -export function createEmitOutputCollector() { - const files: TranspiledFile[] = []; - const writeFile: ts.WriteFileCallback = (fileName, data, _bom, _onError, sourceFiles = []) => { - let file = files.find(f => intersection(f.sourceFiles, sourceFiles).length > 0); - if (!file) { - file = { sourceFiles: [...sourceFiles] }; - files.push(file); - } else { - file.sourceFiles = union(file.sourceFiles, sourceFiles); - } - - if (fileName.endsWith(".lua")) { - file.lua = data; - } else if (fileName.endsWith(".lua.map")) { - file.luaSourceMap = data; - } else if (fileName.endsWith(".js")) { - file.js = data; - } else if (fileName.endsWith(".js.map")) { - file.jsSourceMap = data; - } else if (fileName.endsWith(".d.ts")) { - file.declaration = data; - } else if (fileName.endsWith(".d.ts.map")) { - file.declarationMap = data; - } - }; - - return { writeFile, files }; -} diff --git a/src/transpilation/plugins.ts b/src/transpilation/plugins.ts index 14b37d807..835f5ec03 100644 --- a/src/transpilation/plugins.ts +++ b/src/transpilation/plugins.ts @@ -4,7 +4,7 @@ import { Visitors } from "../transformation/context"; import { Chunk } from "./chunk"; import { Module } from "./module"; import { Transpilation } from "./transpilation"; -import { getConfigDirectory, resolvePlugin } from "./utils"; +import { resolveConfigImport } from "./utils"; export interface Plugin { /** @@ -45,10 +45,10 @@ export function getPlugins(transpilation: Transpilation, customPlugins: Plugin[] for (const [index, pluginOption] of (transpilation.options.luaPlugins ?? []).entries()) { const optionName = `tstl.luaPlugins[${index}]`; - const { error: resolveError, result: factory } = resolvePlugin( + const { error: resolveError, result: factory } = resolveConfigImport( "plugin", `${optionName}.name`, - getConfigDirectory(transpilation.options), + transpilation.projectDir, pluginOption.name, pluginOption.import ); diff --git a/src/transpilation/transpilation.ts b/src/transpilation/transpilation.ts index 499144f19..369dfcf2e 100644 --- a/src/transpilation/transpilation.ts +++ b/src/transpilation/transpilation.ts @@ -16,13 +16,14 @@ export class Transpilation { public readonly diagnostics: ts.Diagnostic[] = []; public modules: Module[] = []; + public host: TranspilerHost; public options = this.program.getCompilerOptions() as CompilerOptions; public rootDir: string; public outDir: string; - public host: TranspilerHost; + public projectDir: string; - protected resolver: Resolver; public plugins: Plugin[]; + protected resolver: Resolver; constructor(public transpiler: Transpiler, public program: ts.Program, extraPlugins: Plugin[]) { this.host = transpiler.host; @@ -35,6 +36,11 @@ export class Transpilation { this.outDir = this.options.outDir ?? this.rootDir; + this.projectDir = + this.options.configFilePath !== undefined + ? path.dirname(this.options.configFilePath) + : this.host.getCurrentDirectory(); + this.plugins = getPlugins(this, extraPlugins); this.resolver = ResolverFactory.createResolver({ diff --git a/src/transpilation/transpile/transformers.ts b/src/transpilation/transpile/transformers.ts index fada33370..68f034b68 100644 --- a/src/transpilation/transpile/transformers.ts +++ b/src/transpilation/transpile/transformers.ts @@ -4,7 +4,7 @@ import * as cliDiagnostics from "../../cli/diagnostics"; import { CompilerOptions, TransformerImport } from "../../CompilerOptions"; import * as diagnosticFactories from "../diagnostics"; import { Transpilation } from "../transpilation"; -import { getConfigDirectory, resolvePlugin } from "../utils"; +import { resolveConfigImport } from "../utils"; export function getTransformers( transpilation: Transpilation, @@ -65,10 +65,10 @@ function loadTransformersFromOptions(transpilation: Transpilation): ts.CustomTra if (!("transform" in transformerImport)) continue; const optionName = `compilerOptions.plugins[${index}]`; - const { error: resolveError, result: factory } = resolvePlugin( + const { error: resolveError, result: factory } = resolveConfigImport( "transformer", `${optionName}.transform`, - getConfigDirectory(transpilation.options), + transpilation.projectDir, transformerImport.transform, transformerImport.import ); diff --git a/src/transpilation/transpiler.ts b/src/transpilation/transpiler.ts index 3cda66a8c..7c076eb53 100644 --- a/src/transpilation/transpiler.ts +++ b/src/transpilation/transpiler.ts @@ -1,7 +1,7 @@ import { FileSystem } from "enhanced-resolve"; import * as ts from "typescript"; +import { printChunk } from "./chunk"; import { Plugin } from "./plugins"; -import { printChunk } from "./print-chunk"; import { Transpilation } from "./transpilation"; import { emitProgramModules, TranspileOptions } from "./transpile"; diff --git a/src/transpilation/utils.ts b/src/transpilation/utils.ts index 008885838..99e29a504 100644 --- a/src/transpilation/utils.ts +++ b/src/transpilation/utils.ts @@ -1,18 +1,10 @@ -import * as fs from "fs"; -import * as path from "path"; import * as resolve from "resolve"; import * as ts from "typescript"; // TODO: Don't depend on CLI? import * as cliDiagnostics from "../cli/diagnostics"; -import { CompilerOptions } from "../CompilerOptions"; import * as diagnosticFactories from "./diagnostics"; -import { TranspilerHost } from "./transpiler"; -// TODO: Require emit host -export const getConfigDirectory = (options: ts.CompilerOptions, host?: TranspilerHost) => - options.configFilePath ? path.dirname(options.configFilePath) : host?.getCurrentDirectory() ?? process.cwd(); - -export function resolvePlugin( +export function resolveConfigImport( kind: string, optionName: string, basedir: string, @@ -52,45 +44,3 @@ export function resolvePlugin( return { result }; } - -const libCache = new Map(); -export function createVirtualProgram(input: Record, options: CompilerOptions = {}): ts.Program { - function notImplemented(): never { - throw new Error("Not implemented"); - } - - const getFileFromInput = (fileName: string) => - input[fileName] ?? (fileName.startsWith("/") ? input[fileName.slice(1)] : undefined); - - const compilerHost: ts.CompilerHost = { - useCaseSensitiveFileNames: () => false, - getCanonicalFileName: fileName => fileName, - getCurrentDirectory: () => "/", - fileExists: fileName => fileName.startsWith("lib.") || getFileFromInput(fileName) !== undefined, - readFile: notImplemented, - writeFile: notImplemented, - getDefaultLibFileName: ts.getDefaultLibFileName, - getNewLine: () => "\n", - - getSourceFile(fileName) { - const fileFromInput = getFileFromInput(fileName); - if (fileFromInput !== undefined) { - return ts.createSourceFile(fileName, fileFromInput, ts.ScriptTarget.Latest, false); - } - - if (libCache.has(fileName)) return libCache.get(fileName)!; - - if (fileName.startsWith("lib.")) { - const typeScriptDir = path.dirname(require.resolve("typescript")); - const filePath = path.join(typeScriptDir, fileName); - const content = fs.readFileSync(filePath, "utf8"); - - const sourceFile = ts.createSourceFile(fileName, content, ts.ScriptTarget.Latest, false); - libCache.set(fileName, sourceFile); - return sourceFile; - } - }, - }; - - return ts.createProgram(Object.keys(input), options, compilerHost); -} diff --git a/test/util.ts b/test/util.ts index c998c47fb..970e2b0f6 100644 --- a/test/util.ts +++ b/test/util.ts @@ -9,8 +9,7 @@ import * as prettyFormat from "pretty-format"; import * as ts from "typescript"; import * as vm from "vm"; import * as tstl from "../src"; -import { createEmitOutputCollector } from "../src/transpilation/output-collector"; -import { createVirtualProgram } from "../src/transpilation/utils"; +import { createEmitOutputCollector, createVirtualProgram } from "../src/transpilation/managed-api/utils"; export * from "./legacy-utils"; From a23eeb2bd548599edc0adc2088cf8b1f5ab49f98 Mon Sep 17 00:00:00 2001 From: ark120202 Date: Fri, 16 Oct 2020 18:05:56 +0000 Subject: [PATCH 23/58] Require `ts-node` on behalf of itself --- package.json | 8 ++++++++ src/transpilation/utils.ts | 4 +--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index d3c804fdb..eff4ff3e4 100644 --- a/package.json +++ b/package.json @@ -62,5 +62,13 @@ "prettier": "^2.0.5", "ts-jest": "^26.0.0", "ts-node": "^8.6.2" + }, + "peerDependencies": { + "ts-node": "*" + }, + "peerDependenciesMeta": { + "ts-node": { + "optional": true + } } } diff --git a/src/transpilation/utils.ts b/src/transpilation/utils.ts index 99e29a504..60cafe914 100644 --- a/src/transpilation/utils.ts +++ b/src/transpilation/utils.ts @@ -26,9 +26,7 @@ export function resolveConfigImport( const hasNoRequireHook = require.extensions[".ts"] === undefined; if (hasNoRequireHook && (resolved.endsWith(".ts") || resolved.endsWith(".tsx"))) { try { - const tsNodePath = resolve.sync("ts-node", { basedir }); - const tsNode: typeof import("ts-node") = require(tsNodePath); - tsNode.register({ transpileOnly: true }); + require("ts-node/register/transpile-only"); } catch (err) { if (err.code !== "MODULE_NOT_FOUND") throw err; return { error: diagnosticFactories.toLoadItShouldBeTranspiled(kind, query) }; From 08f8b068a3b92b6621fc6216032cd7515c7944db Mon Sep 17 00:00:00 2001 From: ark120202 Date: Fri, 16 Oct 2020 18:08:40 +0000 Subject: [PATCH 24/58] Fix plugin tests --- .../__snapshots__/plugins.spec.ts.snap | 41 +++++++++++++++++-- test/transpile/plugins/getModuleId.ts | 2 +- test/transpile/plugins/plugins.spec.ts | 4 +- test/transpile/plugins/printer.ts | 4 +- 4 files changed, 43 insertions(+), 8 deletions(-) diff --git a/test/transpile/plugins/__snapshots__/plugins.spec.ts.snap b/test/transpile/plugins/__snapshots__/plugins.spec.ts.snap index 8215236e5..d7363f2f6 100644 --- a/test/transpile/plugins/__snapshots__/plugins.spec.ts.snap +++ b/test/transpile/plugins/__snapshots__/plugins.spec.ts.snap @@ -1,7 +1,42 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`getModuleId 1`] = ` -"local ____exports = {} -require(\\"5d05566c99dac259f6ff5742a268d405157a0d7c\\") -return ____exports" +" +local ____modules = {} +local ____moduleCache = {} +local ____originalRequire = require +local function require(file) + if ____moduleCache[file] then + return ____moduleCache[file] + end + if ____modules[file] then + ____moduleCache[file] = ____modules[file]() + return ____moduleCache[file] + else + if ____originalRequire then + return ____originalRequire(file) + else + error(\\"module '\\" .. file .. \\"' not found\\") + end + end +end +____modules = { +[\\"5d05566c99dac259f6ff5742a268d405157a0d7c\\"] = function() +local ____exports = {} +____exports.value = true +return ____exports + +end, +[\\"9124a2db32045398a4f097c567d1fe6fcb7ab900\\"] = function() +local ____exports = {} +do + local ____foo = require(\\"5d05566c99dac259f6ff5742a268d405157a0d7c\\") + local value = ____foo.value + ____exports.value = value +end +return ____exports + +end, +} +return require(\\"9124a2db32045398a4f097c567d1fe6fcb7ab900\\")" `; diff --git a/test/transpile/plugins/getModuleId.ts b/test/transpile/plugins/getModuleId.ts index f1e134f6a..6ce5f2e20 100644 --- a/test/transpile/plugins/getModuleId.ts +++ b/test/transpile/plugins/getModuleId.ts @@ -5,7 +5,7 @@ import * as tstl from "../../../src"; const plugin: tstl.Plugin = { getModuleId: (module, transpilation) => createHash("sha1") - .update(module.code) + .update(module.source.toString()) .update(path.relative(transpilation.rootDir, module.request)) .digest("hex"), }; diff --git a/test/transpile/plugins/plugins.spec.ts b/test/transpile/plugins/plugins.spec.ts index 05a3dc5a9..d6de15caa 100644 --- a/test/transpile/plugins/plugins.spec.ts +++ b/test/transpile/plugins/plugins.spec.ts @@ -24,7 +24,7 @@ test("visitor using super", () => { }); test("getModuleId", () => { - util.testModule` + util.testBundle` export { value } from "./foo"; ` .addExtraFile("foo.ts", "export const value = true;") @@ -34,7 +34,7 @@ test("getModuleId", () => { }); test("getResolvePlugins", () => { - util.testModule` + util.testBundle` export { value } from "foo"; ` .addExtraFile("bar.ts", "export const value = true;") diff --git a/test/transpile/plugins/printer.ts b/test/transpile/plugins/printer.ts index 904226363..e14f3e838 100644 --- a/test/transpile/plugins/printer.ts +++ b/test/transpile/plugins/printer.ts @@ -1,10 +1,10 @@ +import { SourceNode } from "source-map"; import * as tstl from "../../../src"; const plugin: tstl.Plugin = { printer(program, host, fileName, ...args) { const result = new tstl.LuaPrinter(host, program, fileName).print(...args); - result.code = `-- Plugin\n${result.code}`; - return result; + return new SourceNode(null, null, null, ["-- Plugin\n", result.source]); }, }; From 7e11569a57d3c788525ac9f03b065170aa9f1bc9 Mon Sep 17 00:00:00 2001 From: ark120202 Date: Sat, 17 Oct 2020 05:04:31 +0000 Subject: [PATCH 25/58] Remove `resolve` dependency --- package-lock.json | 13 +++---------- package.json | 2 -- src/transpilation/transpilation.ts | 27 +++++++++++++++++---------- src/transpilation/utils.ts | 17 +++++++++++++---- 4 files changed, 33 insertions(+), 26 deletions(-) diff --git a/package-lock.json b/package-lock.json index ca89ad5f8..4c23446db 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1842,15 +1842,6 @@ "integrity": "sha512-5qOlnZscTn4xxM5MeGXAMOsIOIKIbh9e85zJWfBRVPlRMEVawzoPhINYbRGkBZCI8LxvBe7tJCdWiarA99OZfQ==", "dev": true }, - "@types/resolve": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.14.0.tgz", - "integrity": "sha512-bmjNBW6tok+67iOsASeYSJxSgY++BIR35nGyGLORTDirhra9reJ0shgGL3U7KPDUbOBCx8JrlCjd4d/y5uiMRQ==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, "@types/stack-utils": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz", @@ -7638,7 +7629,8 @@ "path-parse": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true }, "path-type": { "version": "2.0.0", @@ -7977,6 +7969,7 @@ "version": "1.15.1", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.15.1.tgz", "integrity": "sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w==", + "dev": true, "requires": { "path-parse": "^1.0.6" } diff --git a/package.json b/package.json index eff4ff3e4..7c2938773 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,6 @@ }, "dependencies": { "enhanced-resolve": "^5.2.0", - "resolve": "^1.15.1", "source-map": "^0.7.3", "typescript": "^3.9.2" }, @@ -46,7 +45,6 @@ "@types/glob": "^7.1.1", "@types/jest": "^25.1.3", "@types/node": "^13.7.7", - "@types/resolve": "1.14.0", "@typescript-eslint/eslint-plugin": "^2.31.0", "@typescript-eslint/parser": "^2.31.0", "eslint": "^6.8.0", diff --git a/src/transpilation/transpilation.ts b/src/transpilation/transpilation.ts index 369dfcf2e..0b6bb4d28 100644 --- a/src/transpilation/transpilation.ts +++ b/src/transpilation/transpilation.ts @@ -11,6 +11,7 @@ import { createResolutionErrorDiagnostic } from "./diagnostics"; import { buildModule, Module } from "./module"; import { applyBailPlugin, applySinglePlugin, getPlugins, Plugin } from "./plugins"; import { Transpiler, TranspilerHost } from "./transpiler"; +import { isResolveError } from "./utils"; export class Transpilation { public readonly diagnostics: ts.Diagnostic[] = []; @@ -70,26 +71,32 @@ export class Transpilation { private buildModule(module: Module) { buildModule(module, request => { - let resolvedModule: Module; - try { - resolvedModule = this.resolveRequestToModule(module.request, request); - } catch (error) { - this.diagnostics.push(createResolutionErrorDiagnostic(error.message, request, module.request)); - return { error: error.message }; + const result = this.resolveRequestToModule(module.request, request); + if ("error" in result) { + this.diagnostics.push(result.error); + return { error: ts.flattenDiagnosticMessageText(result.error.messageText, "\n") }; } - return this.getModuleId(resolvedModule); + return this.getModuleId(result); }); } private resolveRequestToModule(issuer: string, request: string) { - const resolvedPath = this.resolver.resolveSync({}, path.dirname(issuer), request); - assert(typeof resolvedPath === "string", `Invalid resolution result: ${resolvedPath}`); + let resolvedPath: string; + try { + const result = this.resolver.resolveSync({}, path.dirname(issuer), request); + assert(typeof result === "string", `Invalid resolution result: ${result}`); + resolvedPath = result; + } catch (error) { + if (!isResolveError(error)) throw error; + return { error: createResolutionErrorDiagnostic(error.message, request, issuer) }; + } let module = this.modules.find(m => m.request === resolvedPath); if (!module) { if (!resolvedPath.endsWith(".lua")) { - throw new Error(`Resolved source file '${resolvedPath}' is not a part of the project.`); + const messageText = `Resolved source file '${resolvedPath}' is not a part of the project.`; + return { error: createResolutionErrorDiagnostic(messageText, request, issuer) }; } // TODO: Load source map files diff --git a/src/transpilation/utils.ts b/src/transpilation/utils.ts index 60cafe914..eae262abd 100644 --- a/src/transpilation/utils.ts +++ b/src/transpilation/utils.ts @@ -1,9 +1,16 @@ -import * as resolve from "resolve"; +import { create as createResolve } from "enhanced-resolve"; import * as ts from "typescript"; // TODO: Don't depend on CLI? import * as cliDiagnostics from "../cli/diagnostics"; +import { assert } from "../utils"; import * as diagnosticFactories from "./diagnostics"; +// https://github.com/webpack/enhanced-resolve/blob/0001f80dacf033ac4a0e690b2766e0965c458266/lib/Resolver.js#L280-L288 +export const isResolveError = (error: unknown): error is Error & { details: string } => + error instanceof Error && "details" in error; + +const resolveImport = createResolve.sync({ extensions: [".js", ".ts", ".tsx"] }); + export function resolveConfigImport( kind: string, optionName: string, @@ -17,9 +24,11 @@ export function resolveConfigImport( let resolved: string; try { - resolved = resolve.sync(query, { basedir, extensions: [".js", ".ts", ".tsx"] }); - } catch (err) { - if (err.code !== "MODULE_NOT_FOUND") throw err; + const result = resolveImport({}, basedir, query); + assert(typeof result === "string"); + resolved = result; + } catch (error) { + if (!isResolveError(error)) throw error; return { error: diagnosticFactories.couldNotResolveFrom(kind, query, basedir) }; } From 6d594a211530510476c9e306fd0052d70dbaa943 Mon Sep 17 00:00:00 2001 From: ark120202 Date: Sat, 17 Oct 2020 06:25:28 +0000 Subject: [PATCH 26/58] Include source snippet in resolution errors --- src/transpilation/diagnostics.ts | 17 ++++++--------- src/transpilation/module.ts | 34 +++++++++++++++++++++--------- src/transpilation/transpilation.ts | 11 +++++----- tsconfig.json | 2 +- 4 files changed, 38 insertions(+), 26 deletions(-) diff --git a/src/transpilation/diagnostics.ts b/src/transpilation/diagnostics.ts index 06732f57e..5a3b3f294 100644 --- a/src/transpilation/diagnostics.ts +++ b/src/transpilation/diagnostics.ts @@ -1,6 +1,6 @@ import * as ts from "typescript"; -import { escapeString } from "../LuaPrinter"; import { createSerialDiagnosticFactory } from "../utils"; +import { Module } from "./module"; const createDiagnosticFactory = (getMessage: (...args: TArgs) => string) => createSerialDiagnosticFactory((...args: TArgs) => ({ messageText: getMessage(...args) })); @@ -41,13 +41,10 @@ export const usingLuaBundleWithInlineMightGenerateDuplicateCode = createSerialDi const sourceFileStub = ts.createSourceFile("", "", ts.ScriptTarget.ES3); export const createResolutionErrorDiagnostic = createSerialDiagnosticFactory( - (messageText: string, request: string, fileName: string) => { - const text = `__TS__Resolve(${escapeString(request)})`; - return { - messageText, - file: { ...sourceFileStub, fileName, text }, - start: 0, - length: text.length, - }; - } + (messageText: string, module: Module, position: ts.ReadonlyTextRange) => ({ + messageText, + file: { ...sourceFileStub, fileName: module.request, text: module.source.toString() }, + start: position.pos, + length: position.end - position.pos, + }) ); diff --git a/src/transpilation/module.ts b/src/transpilation/module.ts index 0e0149995..27e4984c9 100644 --- a/src/transpilation/module.ts +++ b/src/transpilation/module.ts @@ -9,7 +9,7 @@ export interface Module { sourceFiles?: ts.SourceFile[]; } -export type ModuleDependencyResolver = (request: string) => string | { error: string }; +export type ModuleDependencyResolver = (request: string, position: ts.ReadonlyTextRange) => string | { error: string }; export function buildModule(module: Module, dependencyResolver: ModuleDependencyResolver) { if (module.isBuilt) return; @@ -18,22 +18,36 @@ export function buildModule(module: Module, dependencyResolver: ModuleDependency } function replaceResolveRequests(rootNode: SourceNode, dependencyResolver: ModuleDependencyResolver) { + let currentPosition = 0; + function replaceInString(source: string) { - return source.replace(/__TS__Resolve\((".*?")\)/g, (_, match) => { - const request = unescapeLuaString(match); - const replacement = dependencyResolver(request); - return typeof replacement === "string" - ? escapeString(replacement) - : `--[[ ${request} ]] error(${escapeString(replacement.error)})`; - }); + const matches = source.matchAll(/__TS__Resolve\((".*?")\)/g); + for (const match of [...matches].reverse()) { + const request = unescapeLuaString(match[1]); + const pos = currentPosition + match.index!; + const end = pos + match[0].length; + const result = dependencyResolver(request, { pos, end }); + const replacement = + typeof result === "string" + ? escapeString(result) + : `--[[ ${request} ]] error(${escapeString(result.error)})`; + + source = source.slice(0, match.index) + replacement + source.slice(end); + } + + return source; } function walkSourceNode(node: SourceNode, parent: SourceNode) { for (const child of node.children as Array) { if (typeof child === "object") { walkSourceNode(child, node); - } else if (child.includes("__TS__Resolve")) { - parent.children = [replaceInString(parent.toString()) as any]; + } else { + if (child.includes("__TS__Resolve")) { + parent.children = [replaceInString(parent.toString()) as any]; + } + + currentPosition += child.length; } } } diff --git a/src/transpilation/transpilation.ts b/src/transpilation/transpilation.ts index 0b6bb4d28..c31dc5df6 100644 --- a/src/transpilation/transpilation.ts +++ b/src/transpilation/transpilation.ts @@ -70,11 +70,12 @@ export class Transpilation { } private buildModule(module: Module) { - buildModule(module, request => { + buildModule(module, (request, position) => { const result = this.resolveRequestToModule(module.request, request); if ("error" in result) { - this.diagnostics.push(result.error); - return { error: ts.flattenDiagnosticMessageText(result.error.messageText, "\n") }; + const diagnostic = createResolutionErrorDiagnostic(result.error, module, position); + this.diagnostics.push(diagnostic); + return result; } return this.getModuleId(result); @@ -89,14 +90,14 @@ export class Transpilation { resolvedPath = result; } catch (error) { if (!isResolveError(error)) throw error; - return { error: createResolutionErrorDiagnostic(error.message, request, issuer) }; + return { error: error.message }; } let module = this.modules.find(m => m.request === resolvedPath); if (!module) { if (!resolvedPath.endsWith(".lua")) { const messageText = `Resolved source file '${resolvedPath}' is not a part of the project.`; - return { error: createResolutionErrorDiagnostic(messageText, request, issuer) }; + return { error: messageText }; } // TODO: Load source map files diff --git a/tsconfig.json b/tsconfig.json index bf121bd20..3c41ff823 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { "target": "es2019", - "lib": ["es2019"], + "lib": ["es2019", "es2020.promise", "es2020.bigint", "es2020.string"], "types": ["node"], "module": "commonjs", "experimentalDecorators": true, From db06b6d470babb6fc4c88afd2da5e0357e637b0e Mon Sep 17 00:00:00 2001 From: ark120202 Date: Sat, 17 Oct 2020 06:51:00 +0000 Subject: [PATCH 27/58] modules.spec.ts --- src/transpilation/transpilation.ts | 3 +- .../__snapshots__/transformation.spec.ts.snap | 20 ----------- .../modulesImportNamedSpecialChars.ts | 11 ------ test/unit/__snapshots__/modules.spec.ts.snap | 15 ++++++++ test/unit/modules.spec.ts | 36 ++++++++----------- 5 files changed, 32 insertions(+), 53 deletions(-) delete mode 100644 test/translation/transformation/modulesImportNamedSpecialChars.ts create mode 100644 test/unit/__snapshots__/modules.spec.ts.snap diff --git a/src/transpilation/transpilation.ts b/src/transpilation/transpilation.ts index c31dc5df6..7883ec5a5 100644 --- a/src/transpilation/transpilation.ts +++ b/src/transpilation/transpilation.ts @@ -87,7 +87,8 @@ export class Transpilation { try { const result = this.resolver.resolveSync({}, path.dirname(issuer), request); assert(typeof result === "string", `Invalid resolution result: ${result}`); - resolvedPath = result; + // https://github.com/webpack/enhanced-resolve#escaping + resolvedPath = result.replace(/\0#/g, "#"); } catch (error) { if (!isResolveError(error)) throw error; return { error: error.message }; diff --git a/test/translation/__snapshots__/transformation.spec.ts.snap b/test/translation/__snapshots__/transformation.spec.ts.snap index 942f17aa2..f533103f6 100644 --- a/test/translation/__snapshots__/transformation.spec.ts.snap +++ b/test/translation/__snapshots__/transformation.spec.ts.snap @@ -154,26 +154,6 @@ local ____ = TestClass return ____exports" `; -exports[`Transformation (modulesImportNamedSpecialChars) 1`] = ` -"local ____exports = {} -local ____kebab_2Dmodule = require(\\"kebab-module\\") -local TestClass1 = ____kebab_2Dmodule.TestClass1 -local ____dollar_24module = require(\\"dollar$module\\") -local TestClass2 = ____dollar_24module.TestClass2 -local ____singlequote_27module = require(\\"singlequote'module\\") -local TestClass3 = ____singlequote_27module.TestClass3 -local ____hash_23module = require(\\"hash#module\\") -local TestClass4 = ____hash_23module.TestClass4 -local ____space_20module = require(\\"space module\\") -local TestClass5 = ____space_20module.TestClass5 -local ____ = TestClass1 -local ____ = TestClass2 -local ____ = TestClass3 -local ____ = TestClass4 -local ____ = TestClass5 -return ____exports" -`; - exports[`Transformation (modulesImportRenamed) 1`] = ` "local ____exports = {} local ____test = require(\\"test\\") diff --git a/test/translation/transformation/modulesImportNamedSpecialChars.ts b/test/translation/transformation/modulesImportNamedSpecialChars.ts deleted file mode 100644 index dbb54990c..000000000 --- a/test/translation/transformation/modulesImportNamedSpecialChars.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { TestClass1 } from "kebab-module"; -import { TestClass2 } from "dollar$module"; -import { TestClass3 } from "singlequote'module"; -import { TestClass4 } from "hash#module"; -import { TestClass5 } from "space module"; - -TestClass1; -TestClass2; -TestClass3; -TestClass4; -TestClass5; diff --git a/test/unit/__snapshots__/modules.spec.ts.snap b/test/unit/__snapshots__/modules.spec.ts.snap new file mode 100644 index 000000000..3bf3ddcc7 --- /dev/null +++ b/test/unit/__snapshots__/modules.spec.ts.snap @@ -0,0 +1,15 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Import module names with invalid lua identifier characters ("_̀ः٠‿"): local identifier 1`] = `"______300_903_660_203F"`; + +exports[`Import module names with invalid lua identifier characters ("dollar$"): local identifier 1`] = `"____dollar_24"`; + +exports[`Import module names with invalid lua identifier characters ("hash#"): local identifier 1`] = `"____hash_23"`; + +exports[`Import module names with invalid lua identifier characters ("ke-bab"): local identifier 1`] = `"____ke_2Dbab"`; + +exports[`Import module names with invalid lua identifier characters ("s p a c e"): local identifier 1`] = `"____s_20p_20a_20c_20e"`; + +exports[`Import module names with invalid lua identifier characters ("singlequote'"): local identifier 1`] = `"____singlequote_27"`; + +exports[`Import module names with invalid lua identifier characters ("ɥɣɎɌͼƛಠ"): local identifier 1`] = `"_____265_263_24E_24C_37C_19B_CA0"`; diff --git a/test/unit/modules.spec.ts b/test/unit/modules.spec.ts index 7726037d7..84e54bc7d 100644 --- a/test/unit/modules.spec.ts +++ b/test/unit/modules.spec.ts @@ -54,19 +54,21 @@ describe("module import/export elision", () => { test.each(["ke-bab", "dollar$", "singlequote'", "hash#", "s p a c e", "ɥɣɎɌͼƛಠ", "_̀ः٠‿"])( "Import module names with invalid lua identifier characters (%p)", name => { - util.testModule` + util.testBundle` import { foo } from "./${name}"; export { foo }; ` - .disableSemanticCheck() - .setLuaHeader('setmetatable(package.loaded, { __index = function() return { foo = "bar" } end })') - .setReturnExport("foo") - .expectToEqual("bar"); + .addExtraFile(`${name}.ts`, "export const foo = true;") + .expectToEqual({ foo: true }) + .tap(builder => { + const identifier = builder.getMainLuaCodeChunk().match(/local (.+) = require\(/)?.[1]; + expect(identifier).toMatchSnapshot("local identifier"); + }); } ); test.each(["export default value;", "export { value as default };"])("Export Default From (%p)", exportStatement => { - util.testModule` + util.testBundle` export { default } from "./module"; ` .addExtraFile( @@ -80,7 +82,7 @@ test.each(["export default value;", "export { value as default };"])("Export Def }); test("Default Import and Export Expression", () => { - util.testModule` + util.testBundle` import defaultExport from "./module"; export const value = defaultExport; ` @@ -89,16 +91,17 @@ test("Default Import and Export Expression", () => { }); test("Import and Export Assignment", () => { - util.testModule` - import * as m from "./module"; + util.testBundle` + import m = require("./module"); export const value = m; ` + .setOptions({ module: ts.ModuleKind.CommonJS }) .addExtraFile("module.ts", "export = true;") .expectToEqual({ value: true }); }); test("Mixed Exports, Default and Named Imports", () => { - util.testModule` + util.testBundle` import defaultExport, { a, b, c } from "./module"; export const value = defaultExport + b + c; ` @@ -115,7 +118,7 @@ test("Mixed Exports, Default and Named Imports", () => { }); test("Mixed Exports, Default and Namespace Import", () => { - util.testModule` + util.testBundle` import defaultExport, * as ns from "./module"; export const value = defaultExport + ns.b + ns.c; ` @@ -132,7 +135,7 @@ test("Mixed Exports, Default and Namespace Import", () => { }); test("Export Default Function", () => { - util.testModule` + util.testBundle` import defaultExport from "./module"; export const value = defaultExport(); ` @@ -140,15 +143,6 @@ test("Export Default Function", () => { .expectToEqual({ value: true }); }); -test("Export Equals", () => { - util.testModule` - import * as module from "./module"; - export const value = module; - ` - .addExtraFile("module.ts", "export = true;") - .expectToEqual({ value: true }); -}); - const reassignmentTestCases = [ "x = 1", "x++", From d2e4172d2aec3c44116ec3541a132c7c06bc4b82 Mon Sep 17 00:00:00 2001 From: ark120202 Date: Sat, 17 Oct 2020 06:54:22 +0000 Subject: [PATCH 28/58] `load-config-import.spec.ts` --- src/transpilation/plugins.ts | 4 +-- src/transpilation/transpile/transformers.ts | 4 +-- src/transpilation/utils.ts | 6 ++--- .../cjs.js | 0 .../import.ts | 0 .../load-config-import.spec.ts | 27 +++++++++++++++++++ .../transpiled-esm.js | 0 .../ts.ts | 0 .../resolve-plugin/resolve-plugin.spec.ts | 27 ------------------- 9 files changed, 34 insertions(+), 34 deletions(-) rename test/transpile/{resolve-plugin => load-config-import}/cjs.js (100%) rename test/transpile/{resolve-plugin => load-config-import}/import.ts (100%) create mode 100644 test/transpile/load-config-import/load-config-import.spec.ts rename test/transpile/{resolve-plugin => load-config-import}/transpiled-esm.js (100%) rename test/transpile/{resolve-plugin => load-config-import}/ts.ts (100%) delete mode 100644 test/transpile/resolve-plugin/resolve-plugin.spec.ts diff --git a/src/transpilation/plugins.ts b/src/transpilation/plugins.ts index 835f5ec03..6bd11b11d 100644 --- a/src/transpilation/plugins.ts +++ b/src/transpilation/plugins.ts @@ -4,7 +4,7 @@ import { Visitors } from "../transformation/context"; import { Chunk } from "./chunk"; import { Module } from "./module"; import { Transpilation } from "./transpilation"; -import { resolveConfigImport } from "./utils"; +import { loadConfigImport } from "./utils"; export interface Plugin { /** @@ -45,7 +45,7 @@ export function getPlugins(transpilation: Transpilation, customPlugins: Plugin[] for (const [index, pluginOption] of (transpilation.options.luaPlugins ?? []).entries()) { const optionName = `tstl.luaPlugins[${index}]`; - const { error: resolveError, result: factory } = resolveConfigImport( + const { error: resolveError, result: factory } = loadConfigImport( "plugin", `${optionName}.name`, transpilation.projectDir, diff --git a/src/transpilation/transpile/transformers.ts b/src/transpilation/transpile/transformers.ts index 68f034b68..a13b46056 100644 --- a/src/transpilation/transpile/transformers.ts +++ b/src/transpilation/transpile/transformers.ts @@ -4,7 +4,7 @@ import * as cliDiagnostics from "../../cli/diagnostics"; import { CompilerOptions, TransformerImport } from "../../CompilerOptions"; import * as diagnosticFactories from "../diagnostics"; import { Transpilation } from "../transpilation"; -import { resolveConfigImport } from "../utils"; +import { loadConfigImport } from "../utils"; export function getTransformers( transpilation: Transpilation, @@ -65,7 +65,7 @@ function loadTransformersFromOptions(transpilation: Transpilation): ts.CustomTra if (!("transform" in transformerImport)) continue; const optionName = `compilerOptions.plugins[${index}]`; - const { error: resolveError, result: factory } = resolveConfigImport( + const { error: resolveError, result: factory } = loadConfigImport( "transformer", `${optionName}.transform`, transpilation.projectDir, diff --git a/src/transpilation/utils.ts b/src/transpilation/utils.ts index eae262abd..db9229666 100644 --- a/src/transpilation/utils.ts +++ b/src/transpilation/utils.ts @@ -9,9 +9,9 @@ import * as diagnosticFactories from "./diagnostics"; export const isResolveError = (error: unknown): error is Error & { details: string } => error instanceof Error && "details" in error; -const resolveImport = createResolve.sync({ extensions: [".js", ".ts", ".tsx"] }); +const resolveConfigImport = createResolve.sync({ extensions: [".js", ".ts", ".tsx"] }); -export function resolveConfigImport( +export function loadConfigImport( kind: string, optionName: string, basedir: string, @@ -24,7 +24,7 @@ export function resolveConfigImport( let resolved: string; try { - const result = resolveImport({}, basedir, query); + const result = resolveConfigImport({}, basedir, query); assert(typeof result === "string"); resolved = result; } catch (error) { diff --git a/test/transpile/resolve-plugin/cjs.js b/test/transpile/load-config-import/cjs.js similarity index 100% rename from test/transpile/resolve-plugin/cjs.js rename to test/transpile/load-config-import/cjs.js diff --git a/test/transpile/resolve-plugin/import.ts b/test/transpile/load-config-import/import.ts similarity index 100% rename from test/transpile/resolve-plugin/import.ts rename to test/transpile/load-config-import/import.ts diff --git a/test/transpile/load-config-import/load-config-import.spec.ts b/test/transpile/load-config-import/load-config-import.spec.ts new file mode 100644 index 000000000..4e1b63a53 --- /dev/null +++ b/test/transpile/load-config-import/load-config-import.spec.ts @@ -0,0 +1,27 @@ +import * as path from "path"; +import { loadConfigImport } from "../../../src/transpilation/utils"; + +test("resolve relative module paths", () => { + const result = loadConfigImport("test", "test", __dirname, "./ts.ts"); + expect(result).toMatchObject({ result: true }); +}); + +test("load .ts modules", () => { + const result = loadConfigImport("test", "test", __dirname, path.join(__dirname, "ts.ts")); + expect(result).toMatchObject({ result: true }); +}); + +test("load CJS .js modules", () => { + const result = loadConfigImport("test", "test", __dirname, path.join(__dirname, "cjs.js")); + expect(result).toMatchObject({ result: true }); +}); + +test("load transpiled ESM .js modules", () => { + const result = loadConfigImport("test", "test", __dirname, path.join(__dirname, "transpiled-esm.js")); + expect(result).toMatchObject({ result: true }); +}); + +test('"import" option', () => { + const result = loadConfigImport("test", "test", __dirname, path.join(__dirname, "import.ts"), "named"); + expect(result).toMatchObject({ result: true }); +}); diff --git a/test/transpile/resolve-plugin/transpiled-esm.js b/test/transpile/load-config-import/transpiled-esm.js similarity index 100% rename from test/transpile/resolve-plugin/transpiled-esm.js rename to test/transpile/load-config-import/transpiled-esm.js diff --git a/test/transpile/resolve-plugin/ts.ts b/test/transpile/load-config-import/ts.ts similarity index 100% rename from test/transpile/resolve-plugin/ts.ts rename to test/transpile/load-config-import/ts.ts diff --git a/test/transpile/resolve-plugin/resolve-plugin.spec.ts b/test/transpile/resolve-plugin/resolve-plugin.spec.ts deleted file mode 100644 index 7b48bad8f..000000000 --- a/test/transpile/resolve-plugin/resolve-plugin.spec.ts +++ /dev/null @@ -1,27 +0,0 @@ -import * as path from "path"; -import { resolvePlugin } from "../../../src/transpilation/utils"; - -test("resolve relative module paths", () => { - const result = resolvePlugin("test", "test", __dirname, "./ts.ts"); - expect(result).toMatchObject({ result: true }); -}); - -test("load .ts modules", () => { - const result = resolvePlugin("test", "test", __dirname, path.join(__dirname, "ts.ts")); - expect(result).toMatchObject({ result: true }); -}); - -test("load CJS .js modules", () => { - const result = resolvePlugin("test", "test", __dirname, path.join(__dirname, "cjs.js")); - expect(result).toMatchObject({ result: true }); -}); - -test("load transpiled ESM .js modules", () => { - const result = resolvePlugin("test", "test", __dirname, path.join(__dirname, "transpiled-esm.js")); - expect(result).toMatchObject({ result: true }); -}); - -test('"import" option', () => { - const result = resolvePlugin("test", "test", __dirname, path.join(__dirname, "import.ts"), "named"); - expect(result).toMatchObject({ result: true }); -}); From c98a98ae3d6016725efb08c70ab7f0b9f2030839 Mon Sep 17 00:00:00 2001 From: ark120202 Date: Sat, 17 Oct 2020 07:54:41 +0000 Subject: [PATCH 29/58] Resolve lualib_bundle as a dependency --- src/LuaPrinter.ts | 2 +- src/transpilation/transpilation.ts | 35 +++++++++++++++--------------- test/util.ts | 2 +- 3 files changed, 20 insertions(+), 19 deletions(-) diff --git a/src/LuaPrinter.ts b/src/LuaPrinter.ts index e4a74c0d2..b30ebb38a 100644 --- a/src/LuaPrinter.ts +++ b/src/LuaPrinter.ts @@ -164,7 +164,7 @@ export class LuaPrinter { (luaLibImport === LuaLibImportKind.Require && luaLibFeatures.size > 0) ) { // Require lualib bundle - header += 'require("lualib_bundle");\n'; + header += 'require(__TS__Resolve("/lualib/lualib_bundle"));\n'; } else if (luaLibImport === LuaLibImportKind.Inline && luaLibFeatures.size > 0) { // Inline lualib features header += "-- Lua Library inline imports\n"; diff --git a/src/transpilation/transpilation.ts b/src/transpilation/transpilation.ts index 7883ec5a5..d74b4bfe0 100644 --- a/src/transpilation/transpilation.ts +++ b/src/transpilation/transpilation.ts @@ -5,7 +5,7 @@ import { SourceNode } from "source-map"; import * as ts from "typescript"; import { CompilerOptions, isBundleEnabled, LuaTarget } from "../CompilerOptions"; import { getLuaLibBundle } from "../LuaLib"; -import { assert, cast, isNonNull, normalizeSlashes, trimExtension } from "../utils"; +import { assert, cast, isNonNull, trimExtension } from "../utils"; import { Chunk, modulesToBundleChunks, modulesToChunks } from "./chunk"; import { createResolutionErrorDiagnostic } from "./diagnostics"; import { buildModule, Module } from "./module"; @@ -55,17 +55,6 @@ export class Transpilation { public emit(): Chunk[] { this.modules.forEach(module => this.buildModule(module)); - - const lualibRequired = this.modules.some(m => m.source.toString().includes('require("lualib_bundle")')); - if (lualibRequired) { - const fileName = normalizeSlashes(path.resolve(this.rootDir, "lualib_bundle.lua")); - this.modules.unshift({ - request: fileName, - isBuilt: true, - source: new SourceNode(null, null, null, getLuaLibBundle(this.host)), - }); - } - return this.mapModulesToChunks(this.modules); } @@ -83,6 +72,17 @@ export class Transpilation { } private resolveRequestToModule(issuer: string, request: string) { + if (request === "/lualib/lualib_bundle") { + let module = this.modules.find(m => m.request === request); + if (!module) { + const source = new SourceNode(null, null, null, getLuaLibBundle(this.host)); + module = { request, isBuilt: true, source }; + this.modules.push(module); + } + + return module; + } + let resolvedPath: string; try { const result = this.resolver.resolveSync({}, path.dirname(issuer), request); @@ -103,11 +103,8 @@ export class Transpilation { // TODO: Load source map files const code = cast(this.host.readFile(resolvedPath), isNonNull); - module = { - request: resolvedPath, - isBuilt: false, - source: new SourceNode(null, null, null, code), - }; + const source = new SourceNode(null, null, null, code); + module = { request: resolvedPath, isBuilt: false, source }; this.modules.push(module); this.buildModule(module); @@ -120,6 +117,10 @@ export class Transpilation { const pluginResult = applyBailPlugin(this.plugins, p => p.getModuleId?.(module, this)); if (pluginResult !== undefined) return pluginResult; + if (module.request.startsWith("/")) { + return module.request.replace("/", ""); + } + const result = path.relative(this.rootDir, trimExtension(module.request)); // TODO: handle files on other drives assert(!path.isAbsolute(result), `Invalid path: ${result}`); diff --git a/test/util.ts b/test/util.ts index 970e2b0f6..33d12f33d 100644 --- a/test/util.ts +++ b/test/util.ts @@ -257,7 +257,7 @@ export abstract class TestBuilder { host.resolutionFileSystem = virtualFS; host.getCurrentDirectory = () => "/"; host.readFile = (fileName, encoding = "utf8") => - fileName.endsWith("lualib_bundle.lua") + fileName.includes("/lualib/") ? ts.sys.readFile(fileName, encoding) : (virtualFS.readFileSync(fileName, encoding) as string); } From 640ff33c231421aeedd27e90bdc0545e8d952fab Mon Sep 17 00:00:00 2001 From: ark120202 Date: Sat, 17 Oct 2020 08:10:57 +0000 Subject: [PATCH 30/58] Add jest-watch-typeahead --- jest.config.js | 1 + package-lock.json | 168 ++++++++++++++++++++++++++++++++++++++++++++++ package.json | 1 + 3 files changed, 170 insertions(+) diff --git a/jest.config.js b/jest.config.js index 09d82545f..7172a8984 100644 --- a/jest.config.js +++ b/jest.config.js @@ -10,6 +10,7 @@ module.exports = { "!/src/tstl.ts", ], watchPathIgnorePatterns: ["cli/watch/[^/]+$", "src/lualib"], + watchPlugins: ["jest-watch-typeahead/filename", "jest-watch-typeahead/testname"], setupFilesAfterEnv: ["/test/setup.ts"], testEnvironment: "node", diff --git a/package-lock.json b/package-lock.json index b90f71a18..790ca2520 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6914,6 +6914,174 @@ } } }, + "jest-watch-typeahead": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/jest-watch-typeahead/-/jest-watch-typeahead-0.6.1.tgz", + "integrity": "sha512-ITVnHhj3Jd/QkqQcTqZfRgjfyRhDFM/auzgVo2RKvSwi18YMvh0WvXDJFoFED6c7jd/5jxtu4kSOb9PTu2cPVg==", + "dev": true, + "requires": { + "ansi-escapes": "^4.3.1", + "chalk": "^4.0.0", + "jest-regex-util": "^26.0.0", + "jest-watcher": "^26.3.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "@jest/console": { + "version": "26.5.2", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-26.5.2.tgz", + "integrity": "sha512-lJELzKINpF1v74DXHbCRIkQ/+nUV1M+ntj+X1J8LxCgpmJZjfLmhFejiMSbjjD66fayxl5Z06tbs3HMyuik6rw==", + "dev": true, + "requires": { + "@jest/types": "^26.5.2", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^26.5.2", + "jest-util": "^26.5.2", + "slash": "^3.0.0" + } + }, + "@jest/test-result": { + "version": "26.5.2", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-26.5.2.tgz", + "integrity": "sha512-E/Zp6LURJEGSCWpoMGmCFuuEI1OWuI3hmZwmULV0GsgJBh7u0rwqioxhRU95euUuviqBDN8ruX/vP/4bwYolXw==", + "dev": true, + "requires": { + "@jest/console": "^26.5.2", + "@jest/types": "^26.5.2", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + } + }, + "@jest/types": { + "version": "26.5.2", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.5.2.tgz", + "integrity": "sha512-QDs5d0gYiyetI8q+2xWdkixVQMklReZr4ltw7GFDtb4fuJIBCE6mzj2LnitGqCuAlLap6wPyb8fpoHgwZz5fdg==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^15.0.0", + "chalk": "^4.0.0" + } + }, + "@types/istanbul-reports": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", + "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", + "dev": true, + "requires": { + "@types/istanbul-lib-report": "*" + } + }, + "@types/stack-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.0.tgz", + "integrity": "sha512-RJJrrySY7A8havqpGObOB4W92QXKJo63/jFLLgpvOtsGUqbQZ9Sbgl35KMm1DjC6j7AvmmU2bIno+3IyEaemaw==", + "dev": true + }, + "ansi-escapes": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz", + "integrity": "sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==", + "dev": true, + "requires": { + "type-fest": "^0.11.0" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true + }, + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "dev": true + }, + "jest-message-util": { + "version": "26.5.2", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-26.5.2.tgz", + "integrity": "sha512-Ocp9UYZ5Jl15C5PNsoDiGEk14A4NG0zZKknpWdZGoMzJuGAkVt10e97tnEVMYpk7LnQHZOfuK2j/izLBMcuCZw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@jest/types": "^26.5.2", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", + "micromatch": "^4.0.2", + "slash": "^3.0.0", + "stack-utils": "^2.0.2" + } + }, + "jest-regex-util": { + "version": "26.0.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-26.0.0.tgz", + "integrity": "sha512-Gv3ZIs/nA48/Zvjrl34bf+oD76JHiGDUxNOVgUjh3j890sblXryjY4rss71fPtD/njchl6PSE2hIhvyWa1eT0A==", + "dev": true + }, + "jest-util": { + "version": "26.5.2", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-26.5.2.tgz", + "integrity": "sha512-WTL675bK+GSSAYgS8z9FWdCT2nccO1yTIplNLPlP0OD8tUk/H5IrWKMMRudIQQ0qp8bb4k+1Qa8CxGKq9qnYdg==", + "dev": true, + "requires": { + "@jest/types": "^26.5.2", + "@types/node": "*", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", + "is-ci": "^2.0.0", + "micromatch": "^4.0.2" + } + }, + "jest-watcher": { + "version": "26.5.2", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-26.5.2.tgz", + "integrity": "sha512-i3m1NtWzF+FXfJ3ljLBB/WQEp4uaNhX7QcQUWMokcifFTUQBDFyUMEwk0JkJ1kopHbx7Een3KX0Q7+9koGM/Pw==", + "dev": true, + "requires": { + "@jest/test-result": "^26.5.2", + "@jest/types": "^26.5.2", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "jest-util": "^26.5.2", + "string-length": "^4.0.1" + } + }, + "stack-utils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.2.tgz", + "integrity": "sha512-0H7QK2ECz3fyZMzQ8rH0j2ykpfbnd20BFtfg/SqVC2+sCTtcw0aDTGB7dk+de4U4uUeuz6nOtJcrkFFLG1B0Rg==", + "dev": true, + "requires": { + "escape-string-regexp": "^2.0.0" + } + }, + "type-fest": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz", + "integrity": "sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==", + "dev": true + } + } + }, "jest-watcher": { "version": "26.0.1", "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-26.0.1.tgz", diff --git a/package.json b/package.json index 8dd898f38..4ed60aa5f 100644 --- a/package.json +++ b/package.json @@ -55,6 +55,7 @@ "javascript-stringify": "^2.0.1", "jest": "^26.0.1", "jest-circus": "^25.1.0", + "jest-watch-typeahead": "^0.6.1", "lua-types": "^2.8.0", "memfs": "^3.2.0", "prettier": "^2.0.5", From 52f9fd79609d283738c3211e42171c1ab5835903 Mon Sep 17 00:00:00 2001 From: ark120202 Date: Sat, 17 Oct 2020 09:42:34 +0000 Subject: [PATCH 31/58] `resolution.spec.ts` --- .../__snapshots__/resolution.spec.ts.snap | 156 ++++++++++- test/transpile/resolution.spec.ts | 246 +++++++++++------- test/util.ts | 11 +- 3 files changed, 319 insertions(+), 94 deletions(-) diff --git a/test/transpile/__snapshots__/resolution.spec.ts.snap b/test/transpile/__snapshots__/resolution.spec.ts.snap index d4228141d..84eae968d 100644 --- a/test/transpile/__snapshots__/resolution.spec.ts.snap +++ b/test/transpile/__snapshots__/resolution.spec.ts.snap @@ -1,10 +1,154 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`doesn't resolve paths out of root dir: code 1`] = ` -"local ____exports = {} -local module = require(\\"../module\\") -local ____ = module -return ____exports" +exports[`current directory index: modules 1`] = ` +"[\\"index\\"] = function() + +end, +[\\"main\\"] = function() +local ____exports = {} +require(\\"index\\") +return ____exports + +end," +`; + +exports[`entry point in nested directory: modules 1`] = ` +"[\\"module\\"] = function() + +end, +[\\"main\\"] = function() +local ____exports = {} +require(\\"module\\") +return ____exports + +end," +`; + +exports[`file in a sibling directory: modules 1`] = ` +"[\\"foo.bar\\"] = function() + +end, +[\\"main\\"] = function() +local ____exports = {} +require(\\"foo.bar\\") +return ____exports + +end," +`; + +exports[`not transpiled script file error: diagnostics 1`] = `"/main.ts(2,22): error TSTL: Resolved source file '/module.ts' is not a part of the project."`; + +exports[`not transpiled script file error: modules 1`] = ` +"[\\"main\\"] = function() +local ____exports = {} +local ____ = require(--[[ ./module ]] error(\\"Resolved source file '/module.ts' is not a part of the project.\\")) +____exports.value = ____.value +return ____exports + +end," +`; + +exports[`package resolution package with dependencies: modules 1`] = ` +"[\\"main\\"] = function() +local ____exports = {} +do + local ____lib = require(\\"node_modules.lib.index\\") + local value = ____lib.value + ____exports.value = value +end +return ____exports + +end, +[\\"node_modules.lib.index\\"] = function() +return require(\\"node_modules.lib2.index\\") +end, +[\\"node_modules.lib2.index\\"] = function() +return { value = true } +end," +`; + +exports[`package resolution package.json with exports: modules 1`] = ` +"[\\"main\\"] = function() +local ____exports = {} +do + local ____lib = require(\\"node_modules.lib.dist.index\\") + local value = ____lib.value + ____exports.value = value +end +return ____exports + +end, +[\\"node_modules.lib.dist.index\\"] = function() +return { value = true } +end," +`; + +exports[`package resolution package.json with versioned exports: modules 1`] = ` +"[\\"main\\"] = function() +local ____exports = {} +do + local ____lib = require(\\"node_modules.lib.dist.5__3\\") + local value = ____lib.value + ____exports.value = value +end +return ____exports + +end, +[\\"node_modules.lib.dist.5__3\\"] = function() +return { value = \\"5.3\\" } +end," +`; + +exports[`package resolution without package.json: modules 1`] = ` +"[\\"main\\"] = function() +local ____exports = {} +do + local ____lib = require(\\"node_modules.lib.index\\") + local value = ____lib.value + ____exports.value = value +end +return ____exports + +end, +[\\"node_modules.lib.index\\"] = function() +return { value = true } +end," `; -exports[`doesn't resolve paths out of root dir: diagnostics 1`] = `"src/main.ts(2,33): error TSTL: Cannot create require path. Module '../module' does not exist within --rootDir."`; +exports[`resolution out of rootDir .ts file: diagnostics 1`] = `"src/main.ts(2,35): error TS6059: File 'module.ts' is not under 'rootDir' 'src'. 'rootDir' is expected to contain all source files."`; + +exports[`rootDir inference: modules 1`] = ` +"[\\"module\\"] = function() + +end, +[\\"main\\"] = function() +local ____exports = {} +require(\\"module\\") +return ____exports + +end," +`; + +exports[`sibling directory index: modules 1`] = ` +"[\\"foo.index\\"] = function() + +end, +[\\"main\\"] = function() +local ____exports = {} +require(\\"foo.index\\") +return ____exports + +end," +`; + +exports[`sibling file: modules 1`] = ` +"[\\"foo\\"] = function() + +end, +[\\"main\\"] = function() +local ____exports = {} +require(\\"foo\\") +return ____exports + +end," +`; diff --git a/test/transpile/resolution.spec.ts b/test/transpile/resolution.spec.ts index 541c28c56..018ea745e 100644 --- a/test/transpile/resolution.spec.ts +++ b/test/transpile/resolution.spec.ts @@ -1,6 +1,5 @@ -import * as ts from "typescript"; -import { unresolvableRequirePath } from "../../../src/transformation/utils/diagnostics"; -import * as util from "../../util"; +import { createResolutionErrorDiagnostic } from "../../src/transpilation/diagnostics"; +import * as util from "../util"; const requireRegex = /require\("(.*?)"\)/; const expectToRequire = (expected: string): util.TapCallback => builder => { @@ -8,80 +7,166 @@ const expectToRequire = (expected: string): util.TapCallback => builder => { expect(requiredPath).toBe(expected); }; -test.each([ - { - filePath: "main.ts", - usedPath: "./folder/Module", - expected: "folder.Module", - options: { rootDir: "." }, - }, - { - filePath: "main.ts", - usedPath: "./folder/Module", - expected: "folder.Module", - options: { rootDir: "./" }, - }, - { - filePath: "src/main.ts", - usedPath: "./folder/Module", - expected: "src.folder.Module", - options: { rootDir: "." }, - }, - { - filePath: "main.ts", - usedPath: "folder/Module", - expected: "folder.Module", - options: { rootDir: ".", baseUrl: "." }, - }, - { - filePath: "main.ts", - usedPath: "folder/Module", - expected: "folder.Module", - options: { rootDir: "./", baseUrl: "." }, - }, - { - filePath: "src/main.ts", - usedPath: "./folder/Module", - expected: "folder.Module", - options: { rootDir: "src" }, - }, - { - filePath: "src/main.ts", - usedPath: "./folder/Module", - expected: "folder.Module", - options: { rootDir: "./src" }, - }, - { - filePath: "src/dir/main.ts", - usedPath: "../Module", - expected: "Module", - options: { rootDir: "./src" }, - }, - { - filePath: "src/dir/dir/main.ts", - usedPath: "../../dir/Module", - expected: "dir.Module", - options: { rootDir: "./src" }, - }, -])("resolve paths with baseUrl or rootDir (%p)", ({ filePath, usedPath, expected, options }) => { - util.testModule` - import * as module from "${usedPath}"; - module; +const expectModuleTableToMatchSnapshot: util.TapCallback = builder => { + builder.expectToHaveNoDiagnostics(); + const moduleTable = builder.getMainLuaCodeChunk().match(/(?<=\n)____modules = {\n(.+)\n}\nreturn [^\n]+$/s)?.[1]; + expect(moduleTable).not.toBeUndefined(); + expect(moduleTable).toMatchSnapshot("modules"); +}; + +test("sibling file", () => { + util.testBundle` + import "./foo"; + ` + .addExtraFile("foo.ts", "") + .tap(expectModuleTableToMatchSnapshot); +}); + +test("file in a sibling directory", () => { + util.testBundle` + import "./foo/bar.ts"; + ` + .addExtraFile("foo/bar.ts", "") + .tap(expectModuleTableToMatchSnapshot); +}); + +test("sibling directory index", () => { + util.testBundle` + import "./foo"; ` - .setMainFileName(filePath) - .setOptions(options) - .tap(expectToRequire(expected)); + .addExtraFile("foo/index.ts", "") + .tap(expectModuleTableToMatchSnapshot); }); -test("doesn't resolve paths out of root dir", () => { - util.testModule` - import * as module from "../module"; - module; +test("current directory index", () => { + util.testBundle` + import "."; + ` + .addExtraFile("index.ts", "") + .tap(expectModuleTableToMatchSnapshot); +}); + +test("rootDir inference", () => { + util.testBundle` + import "./module"; + ` + .setMainFileName("src/main.ts") + .addExtraFile("src/module.ts", "") + .tap(expectModuleTableToMatchSnapshot); +}); + +test("entry point in nested directory", () => { + util.testBundle` + import "./module"; ` .setMainFileName("src/main.ts") - .setOptions({ rootDir: "./src" }) - .disableSemanticCheck() - .expectDiagnosticsToMatchSnapshot([unresolvableRequirePath.code]); + .addExtraFile("src/module.ts", "") + .setOptions({ rootDir: "src" }) + .tap(expectModuleTableToMatchSnapshot); +}); + +describe("resolution out of rootDir", () => { + test(".lua file", () => { + util.testBundle` + export { value } from "../module"; + ` + .setOptions({ rootDir: "src" }) + .setMainFileName("src/main.ts") + .addExtraFile("module.d.ts", "export declare const value: boolean;") + .addRawFile("module.lua", "return { value = true }") + + .tap(expectToRequire("_.module")) + .expectToEqual({ value: true }); + }); + + test(".ts file", () => { + util.testBundle` + export { value } from "../module"; + ` + .setOptions({ rootDir: "src" }) + .setMainFileName("src/main.ts") + .addExtraFile("module.ts", "export const value = true;") + + .tap(expectToRequire("_.module")) + .expectDiagnosticsToMatchSnapshot([6059], true) + .expectToEqual({ value: true }); + }); +}); + +describe("package resolution", () => { + test("without package.json", () => { + util.testBundle` + export { value } from 'lib'; + ` + .addExtraFile("node_modules/lib/index.d.ts", "export const value: boolean;") + .addRawFile("node_modules/lib/index.lua", "return { value = true }") + .tap(expectModuleTableToMatchSnapshot) + .expectToEqual({ value: true }); + }); + + test("package.json with exports", () => { + util.testBundle` + export { value } from 'lib'; + ` + .addExtraFile("node_modules/lib/index.d.ts", "export const value: boolean;") + .addRawFile("node_modules/lib/dist/index.lua", "return { value = true }") + .addRawFile("node_modules/lib/package.json", JSON.stringify({ exports: { lua: "./dist/index.lua" } })) + .tap(expectModuleTableToMatchSnapshot) + .expectToEqual({ value: true }); + }); + + // https://github.com/webpack/enhanced-resolve/issues/256 + test.skip("package.json with directory exports", () => { + util.testBundle` + export { value } from 'lib/foo'; + ` + .addExtraFile("node_modules/lib/index.d.ts", 'declare module "lib/foo" { export const value: boolean; }') + .addRawFile("node_modules/lib/dist/foo.lua", "return { value = true }") + .addRawFile("node_modules/lib/package.json", JSON.stringify({ exports: { "./": { lua: "./dist/" } } })) + .tap(expectModuleTableToMatchSnapshot) + .expectToEqual({ value: true }); + }); + + test("package.json with versioned exports", () => { + util.testBundle` + export { value } from 'lib'; + ` + .addExtraFile("node_modules/lib/index.d.ts", "export const value: boolean;") + .addRawFile("node_modules/lib/dist/5.3.lua", 'return { value = "5.3" }') + .addRawFile("node_modules/lib/dist/jit.lua", 'return { value = "jit" }') + .addRawFile( + "node_modules/lib/package.json", + JSON.stringify({ exports: { "lua:5.3": "./dist/5.3.lua", "lua:jit": "./dist/jit.lua" } }) + ) + .tap(expectModuleTableToMatchSnapshot) + .expectToEqual({ value: "5.3" }); + }); + + test("package with dependencies", () => { + util.testBundle` + export { value } from 'lib'; + ` + .addExtraFile("node_modules/lib/index.d.ts", "export const value: boolean;") + .addRawFile("node_modules/lib/index.lua", 'return require(__TS__Resolve("lib2"))') + .addRawFile("node_modules/lib2/index.lua", "return { value = true }") + .tap(expectModuleTableToMatchSnapshot) + .expectToEqual({ value: true }); + }); + + test.todo("symlink"); +}); + +test("not transpiled script file error", () => { + util.testBundle` + declare function require(this: void, path: string): any; + declare function __TS__Resolve(this: void, request: string): string; + + export const { value } = require(__TS__Resolve("./module")); + ` + .addRawFile("module.ts", "export const value = true;") + .expectDiagnosticsToMatchSnapshot([createResolutionErrorDiagnostic.code], true) + .tap(expectModuleTableToMatchSnapshot) + .expectToEqual(new util.ExecutionError("Resolved source file '/module.ts' is not a part of the project.")); }); test.each([ @@ -91,7 +176,6 @@ test.each([ declare module "fake" {} `, mainCode: 'import "fake";', - expectedPath: "fake", }, { declarationStatement: ` @@ -99,7 +183,6 @@ test.each([ declare module "fake" {} `, mainCode: 'import * as fake from "fake"; fake;', - expectedPath: "fake", }, { declarationStatement: ` @@ -109,7 +192,6 @@ test.each([ } `, mainCode: 'import { x } from "fake"; x;', - expectedPath: "fake", }, { declarationStatement: ` @@ -123,20 +205,10 @@ test.each([ } `, mainCode: 'import { y } from "fake"; y;', - expectedPath: "fake", }, -])("noResolution prevents any module path resolution behavior", ({ declarationStatement, mainCode, expectedPath }) => { +])("@noResolution annotation (%p)", ({ declarationStatement, mainCode }) => { util.testModule(mainCode) .setMainFileName("src/main.ts") .addExtraFile("module.d.ts", declarationStatement) - .tap(expectToRequire(expectedPath)); -}); - -test("import = require", () => { - util.testModule` - import foo = require("./foo/bar"); - foo; - ` - .setOptions({ module: ts.ModuleKind.CommonJS }) - .tap(expectToRequire("foo.bar")); + .tap(expectToRequire("fake")); }); diff --git a/test/util.ts b/test/util.ts index 33d12f33d..6b60bd7b5 100644 --- a/test/util.ts +++ b/test/util.ts @@ -243,7 +243,10 @@ export abstract class TestBuilder { @memoize public getProgram(): ts.Program { this.hasProgram = true; - return createVirtualProgram(this.getSourceFiles(), this.options); + const program = createVirtualProgram(this.getSourceFiles(), this.options); + // https://github.com/microsoft/TypeScript/issues/41020 + program.getCommonSourceDirectory(); + return program; } @memoize @@ -431,6 +434,12 @@ class BundleTestBuilder extends AccessorTestBuilder { this.setOptions({ luaBundle: "main.lua", luaBundleEntry: this.mainFileName }); } + public setMainFileName(mainFileName: string) { + super.setMainFileName(mainFileName); + this.setOptions({ luaBundleEntry: mainFileName }); + return this; + } + public setEntryPoint(fileName: string): this { return this.setOptions({ luaBundleEntry: fileName }); } From 70a01a6b03a38b25aae1eb94b27e097b8e4b854d Mon Sep 17 00:00:00 2001 From: ark120202 Date: Sat, 17 Oct 2020 10:13:08 +0000 Subject: [PATCH 32/58] Move test fixtures into __fixtures__ directories --- jest.config.js | 2 +- test/cli/{ => __fixtures__}/errors/error.ts | 0 .../{ => __fixtures__}/watch/tsconfig.json | 0 test/cli/{ => __fixtures__}/watch/watch.ts | 0 test/cli/run.ts | 3 ++- test/cli/watch.spec.ts | 11 ++++---- .../blockScopeVariables.ts | 0 .../characterEscapeSequence.ts | 0 .../classExtension1.ts | 0 .../classExtension2.ts | 0 .../classExtension3.ts | 0 .../classExtension4.ts | 0 .../classPureAbstract.ts | 0 .../exportStatement.ts | 0 .../globalAugmentation.ts | 0 .../methodRestArguments.ts | 0 .../modulesChangedVariableExport.ts | 0 .../modulesClassExport.ts | 0 .../modulesClassWithMemberExport.ts | 0 .../modulesFunctionExport.ts | 0 .../modulesFunctionNoExport.ts | 0 .../modulesImportAll.ts | 0 .../modulesImportNamed.ts | 0 .../modulesImportRenamed.ts | 0 .../modulesImportRenamedSpecialChars.ts | 0 .../modulesImportWithoutFromClause.ts | 0 .../modulesNamespaceExport.ts | 0 .../modulesNamespaceNestedWithMemberExport.ts | 0 .../modulesNamespaceNoExport.ts | 0 .../modulesNamespaceWithMemberExport.ts | 0 .../modulesNamespaceWithMemberNoExport.ts | 0 .../modulesVariableExport.ts | 0 .../modulesVariableNoExport.ts | 0 .../namespacePhantom.ts | 0 .../returnDefault.ts | 0 .../topLevelVariables.ts | 0 .../unusedDefaultWithNamespaceImport.ts | 0 ....spec.ts.snap => translation.spec.ts.snap} | 0 ...sformation.spec.ts => translation.spec.ts} | 2 +- .../{ => __fixtures__}/bundle/index.ts | 0 .../{ => __fixtures__}/bundle/otherFile.ts | 0 .../{ => __fixtures__}/bundle/tsconfig.json | 0 .../baseurl/src/lib/nested/file.ts | 0 .../directories/baseurl/src/main.ts | 0 .../directories/basic/src/lib/file.ts | 0 .../directories/basic/src/main.ts | 0 .../load-config-import/cjs.js | 0 .../load-config-import/import.ts | 0 .../load-config-import/transpiled-esm.js | 0 .../load-config-import/ts.ts | 0 .../{ => __fixtures__}/plugins/getModuleId.ts | 2 +- .../plugins/getResolvePlugins.ts | 2 +- .../{ => __fixtures__}/plugins/printer.ts | 2 +- .../plugins/visitor-super.ts | 2 +- .../{ => __fixtures__}/plugins/visitor.ts | 2 +- .../{ => __fixtures__}/project/api.d.ts | 0 .../{ => __fixtures__}/project/index.ts | 0 .../{ => __fixtures__}/project/otherFile.ts | 0 .../{ => __fixtures__}/project/tsconfig.json | 0 .../transformers.ts} | 6 ++++- .../__snapshots__/directories.spec.ts.snap | 16 +++-------- .../__snapshots__/plugins.spec.ts.snap | 0 test/transpile/bundle.spec.ts | 8 ++---- test/transpile/directories.spec.ts | 5 ++-- test/transpile/load-config-import.spec.ts | 27 +++++++++++++++++++ .../load-config-import.spec.ts | 27 ------------------- test/transpile/{plugins => }/plugins.spec.ts | 14 +++++----- test/transpile/project.spec.ts | 5 ++-- test/transpile/run.ts | 4 ++- .../{transformers => }/transformers.spec.ts | 16 +++++------ test/transpile/transformers/utils.ts | 6 ----- test/tsconfig.json | 9 +------ 72 files changed, 75 insertions(+), 96 deletions(-) rename test/cli/{ => __fixtures__}/errors/error.ts (100%) rename test/cli/{ => __fixtures__}/watch/tsconfig.json (100%) rename test/cli/{ => __fixtures__}/watch/watch.ts (100%) rename test/translation/{transformation => __fixtures__}/blockScopeVariables.ts (100%) rename test/translation/{transformation => __fixtures__}/characterEscapeSequence.ts (100%) rename test/translation/{transformation => __fixtures__}/classExtension1.ts (100%) rename test/translation/{transformation => __fixtures__}/classExtension2.ts (100%) rename test/translation/{transformation => __fixtures__}/classExtension3.ts (100%) rename test/translation/{transformation => __fixtures__}/classExtension4.ts (100%) rename test/translation/{transformation => __fixtures__}/classPureAbstract.ts (100%) rename test/translation/{transformation => __fixtures__}/exportStatement.ts (100%) rename test/translation/{transformation => __fixtures__}/globalAugmentation.ts (100%) rename test/translation/{transformation => __fixtures__}/methodRestArguments.ts (100%) rename test/translation/{transformation => __fixtures__}/modulesChangedVariableExport.ts (100%) rename test/translation/{transformation => __fixtures__}/modulesClassExport.ts (100%) rename test/translation/{transformation => __fixtures__}/modulesClassWithMemberExport.ts (100%) rename test/translation/{transformation => __fixtures__}/modulesFunctionExport.ts (100%) rename test/translation/{transformation => __fixtures__}/modulesFunctionNoExport.ts (100%) rename test/translation/{transformation => __fixtures__}/modulesImportAll.ts (100%) rename test/translation/{transformation => __fixtures__}/modulesImportNamed.ts (100%) rename test/translation/{transformation => __fixtures__}/modulesImportRenamed.ts (100%) rename test/translation/{transformation => __fixtures__}/modulesImportRenamedSpecialChars.ts (100%) rename test/translation/{transformation => __fixtures__}/modulesImportWithoutFromClause.ts (100%) rename test/translation/{transformation => __fixtures__}/modulesNamespaceExport.ts (100%) rename test/translation/{transformation => __fixtures__}/modulesNamespaceNestedWithMemberExport.ts (100%) rename test/translation/{transformation => __fixtures__}/modulesNamespaceNoExport.ts (100%) rename test/translation/{transformation => __fixtures__}/modulesNamespaceWithMemberExport.ts (100%) rename test/translation/{transformation => __fixtures__}/modulesNamespaceWithMemberNoExport.ts (100%) rename test/translation/{transformation => __fixtures__}/modulesVariableExport.ts (100%) rename test/translation/{transformation => __fixtures__}/modulesVariableNoExport.ts (100%) rename test/translation/{transformation => __fixtures__}/namespacePhantom.ts (100%) rename test/translation/{transformation => __fixtures__}/returnDefault.ts (100%) rename test/translation/{transformation => __fixtures__}/topLevelVariables.ts (100%) rename test/translation/{transformation => __fixtures__}/unusedDefaultWithNamespaceImport.ts (100%) rename test/translation/__snapshots__/{transformation.spec.ts.snap => translation.spec.ts.snap} (100%) rename test/translation/{transformation.spec.ts => translation.spec.ts} (89%) rename test/transpile/{ => __fixtures__}/bundle/index.ts (100%) rename test/transpile/{ => __fixtures__}/bundle/otherFile.ts (100%) rename test/transpile/{ => __fixtures__}/bundle/tsconfig.json (100%) rename test/transpile/{ => __fixtures__}/directories/baseurl/src/lib/nested/file.ts (100%) rename test/transpile/{ => __fixtures__}/directories/baseurl/src/main.ts (100%) rename test/transpile/{ => __fixtures__}/directories/basic/src/lib/file.ts (100%) rename test/transpile/{ => __fixtures__}/directories/basic/src/main.ts (100%) rename test/transpile/{ => __fixtures__}/load-config-import/cjs.js (100%) rename test/transpile/{ => __fixtures__}/load-config-import/import.ts (100%) rename test/transpile/{ => __fixtures__}/load-config-import/transpiled-esm.js (100%) rename test/transpile/{ => __fixtures__}/load-config-import/ts.ts (100%) rename test/transpile/{ => __fixtures__}/plugins/getModuleId.ts (90%) rename test/transpile/{ => __fixtures__}/plugins/getResolvePlugins.ts (91%) rename test/transpile/{ => __fixtures__}/plugins/printer.ts (90%) rename test/transpile/{ => __fixtures__}/plugins/visitor-super.ts (91%) rename test/transpile/{ => __fixtures__}/plugins/visitor.ts (87%) rename test/transpile/{ => __fixtures__}/project/api.d.ts (100%) rename test/transpile/{ => __fixtures__}/project/index.ts (100%) rename test/transpile/{ => __fixtures__}/project/otherFile.ts (100%) rename test/transpile/{ => __fixtures__}/project/tsconfig.json (100%) rename test/transpile/{transformers/fixtures.ts => __fixtures__/transformers.ts} (86%) rename test/transpile/{plugins => }/__snapshots__/plugins.spec.ts.snap (100%) create mode 100644 test/transpile/load-config-import.spec.ts delete mode 100644 test/transpile/load-config-import/load-config-import.spec.ts rename test/transpile/{plugins => }/plugins.spec.ts (63%) rename test/transpile/{transformers => }/transformers.spec.ts (54%) delete mode 100644 test/transpile/transformers/utils.ts diff --git a/jest.config.js b/jest.config.js index 7172a8984..b7c677eee 100644 --- a/jest.config.js +++ b/jest.config.js @@ -9,7 +9,7 @@ module.exports = { // https://github.com/facebook/jest/issues/5274 "!/src/tstl.ts", ], - watchPathIgnorePatterns: ["cli/watch/[^/]+$", "src/lualib"], + watchPathIgnorePatterns: ["cli/__fixtures__", "src/lualib"], watchPlugins: ["jest-watch-typeahead/filename", "jest-watch-typeahead/testname"], setupFilesAfterEnv: ["/test/setup.ts"], diff --git a/test/cli/errors/error.ts b/test/cli/__fixtures__/errors/error.ts similarity index 100% rename from test/cli/errors/error.ts rename to test/cli/__fixtures__/errors/error.ts diff --git a/test/cli/watch/tsconfig.json b/test/cli/__fixtures__/watch/tsconfig.json similarity index 100% rename from test/cli/watch/tsconfig.json rename to test/cli/__fixtures__/watch/tsconfig.json diff --git a/test/cli/watch/watch.ts b/test/cli/__fixtures__/watch/watch.ts similarity index 100% rename from test/cli/watch/watch.ts rename to test/cli/__fixtures__/watch/watch.ts diff --git a/test/cli/run.ts b/test/cli/run.ts index cee7e19b2..aa804a83f 100644 --- a/test/cli/run.ts +++ b/test/cli/run.ts @@ -3,8 +3,9 @@ import * as path from "path"; jest.setTimeout(20000); -const cliPath = path.join(__dirname, "../../src/tstl.ts"); +export const resolveFixture = (name: string) => path.resolve(__dirname, "__fixtures__", name); +const cliPath = path.join(__dirname, "../../src/tstl.ts"); const defaultArgs = ["--skipLibCheck", "--types", "node"]; export function forkCli(args: string[]): ChildProcess { return fork(cliPath, [...defaultArgs, ...args], { diff --git a/test/cli/watch.spec.ts b/test/cli/watch.spec.ts index 35d2f2f59..679a3f9ae 100644 --- a/test/cli/watch.spec.ts +++ b/test/cli/watch.spec.ts @@ -1,6 +1,5 @@ import * as fs from "fs-extra"; -import * as path from "path"; -import { forkCli } from "./run"; +import { forkCli, resolveFixture } from "./run"; let testsCleanup: Array<() => void> = []; afterEach(() => { @@ -26,7 +25,7 @@ function forkWatchProcess(args: string[]): void { testsCleanup.push(() => child.kill()); } -const watchedFile = path.join(__dirname, "./watch/watch.ts"); +const watchedFile = resolveFixture("watch/watch.ts"); const watchedFileOut = watchedFile.replace(".ts", ".lua"); afterEach(() => fs.removeSync(watchedFileOut)); @@ -48,17 +47,17 @@ async function compileChangeAndCompare(filePath: string, content: string): Promi } test("should watch single file", async () => { - forkWatchProcess([path.join(__dirname, "./watch/watch.ts")]); + forkWatchProcess([resolveFixture("watch/watch.ts")]); await compileChangeAndCompare(watchedFile, "const value = 1;"); }); test("should watch project", async () => { - forkWatchProcess(["--project", path.join(__dirname, "./watch")]); + forkWatchProcess(["--project", resolveFixture("watch")]); await compileChangeAndCompare(watchedFile, "const value = 1;"); }); test("should watch config file", async () => { - const configFilePath = path.join(__dirname, "./watch/tsconfig.json"); + const configFilePath = resolveFixture("watch/tsconfig.json"); forkWatchProcess(["--project", configFilePath]); await compileChangeAndCompare(configFilePath, '{ "tstl": { "luaTarget": "5.3" } }'); }); diff --git a/test/translation/transformation/blockScopeVariables.ts b/test/translation/__fixtures__/blockScopeVariables.ts similarity index 100% rename from test/translation/transformation/blockScopeVariables.ts rename to test/translation/__fixtures__/blockScopeVariables.ts diff --git a/test/translation/transformation/characterEscapeSequence.ts b/test/translation/__fixtures__/characterEscapeSequence.ts similarity index 100% rename from test/translation/transformation/characterEscapeSequence.ts rename to test/translation/__fixtures__/characterEscapeSequence.ts diff --git a/test/translation/transformation/classExtension1.ts b/test/translation/__fixtures__/classExtension1.ts similarity index 100% rename from test/translation/transformation/classExtension1.ts rename to test/translation/__fixtures__/classExtension1.ts diff --git a/test/translation/transformation/classExtension2.ts b/test/translation/__fixtures__/classExtension2.ts similarity index 100% rename from test/translation/transformation/classExtension2.ts rename to test/translation/__fixtures__/classExtension2.ts diff --git a/test/translation/transformation/classExtension3.ts b/test/translation/__fixtures__/classExtension3.ts similarity index 100% rename from test/translation/transformation/classExtension3.ts rename to test/translation/__fixtures__/classExtension3.ts diff --git a/test/translation/transformation/classExtension4.ts b/test/translation/__fixtures__/classExtension4.ts similarity index 100% rename from test/translation/transformation/classExtension4.ts rename to test/translation/__fixtures__/classExtension4.ts diff --git a/test/translation/transformation/classPureAbstract.ts b/test/translation/__fixtures__/classPureAbstract.ts similarity index 100% rename from test/translation/transformation/classPureAbstract.ts rename to test/translation/__fixtures__/classPureAbstract.ts diff --git a/test/translation/transformation/exportStatement.ts b/test/translation/__fixtures__/exportStatement.ts similarity index 100% rename from test/translation/transformation/exportStatement.ts rename to test/translation/__fixtures__/exportStatement.ts diff --git a/test/translation/transformation/globalAugmentation.ts b/test/translation/__fixtures__/globalAugmentation.ts similarity index 100% rename from test/translation/transformation/globalAugmentation.ts rename to test/translation/__fixtures__/globalAugmentation.ts diff --git a/test/translation/transformation/methodRestArguments.ts b/test/translation/__fixtures__/methodRestArguments.ts similarity index 100% rename from test/translation/transformation/methodRestArguments.ts rename to test/translation/__fixtures__/methodRestArguments.ts diff --git a/test/translation/transformation/modulesChangedVariableExport.ts b/test/translation/__fixtures__/modulesChangedVariableExport.ts similarity index 100% rename from test/translation/transformation/modulesChangedVariableExport.ts rename to test/translation/__fixtures__/modulesChangedVariableExport.ts diff --git a/test/translation/transformation/modulesClassExport.ts b/test/translation/__fixtures__/modulesClassExport.ts similarity index 100% rename from test/translation/transformation/modulesClassExport.ts rename to test/translation/__fixtures__/modulesClassExport.ts diff --git a/test/translation/transformation/modulesClassWithMemberExport.ts b/test/translation/__fixtures__/modulesClassWithMemberExport.ts similarity index 100% rename from test/translation/transformation/modulesClassWithMemberExport.ts rename to test/translation/__fixtures__/modulesClassWithMemberExport.ts diff --git a/test/translation/transformation/modulesFunctionExport.ts b/test/translation/__fixtures__/modulesFunctionExport.ts similarity index 100% rename from test/translation/transformation/modulesFunctionExport.ts rename to test/translation/__fixtures__/modulesFunctionExport.ts diff --git a/test/translation/transformation/modulesFunctionNoExport.ts b/test/translation/__fixtures__/modulesFunctionNoExport.ts similarity index 100% rename from test/translation/transformation/modulesFunctionNoExport.ts rename to test/translation/__fixtures__/modulesFunctionNoExport.ts diff --git a/test/translation/transformation/modulesImportAll.ts b/test/translation/__fixtures__/modulesImportAll.ts similarity index 100% rename from test/translation/transformation/modulesImportAll.ts rename to test/translation/__fixtures__/modulesImportAll.ts diff --git a/test/translation/transformation/modulesImportNamed.ts b/test/translation/__fixtures__/modulesImportNamed.ts similarity index 100% rename from test/translation/transformation/modulesImportNamed.ts rename to test/translation/__fixtures__/modulesImportNamed.ts diff --git a/test/translation/transformation/modulesImportRenamed.ts b/test/translation/__fixtures__/modulesImportRenamed.ts similarity index 100% rename from test/translation/transformation/modulesImportRenamed.ts rename to test/translation/__fixtures__/modulesImportRenamed.ts diff --git a/test/translation/transformation/modulesImportRenamedSpecialChars.ts b/test/translation/__fixtures__/modulesImportRenamedSpecialChars.ts similarity index 100% rename from test/translation/transformation/modulesImportRenamedSpecialChars.ts rename to test/translation/__fixtures__/modulesImportRenamedSpecialChars.ts diff --git a/test/translation/transformation/modulesImportWithoutFromClause.ts b/test/translation/__fixtures__/modulesImportWithoutFromClause.ts similarity index 100% rename from test/translation/transformation/modulesImportWithoutFromClause.ts rename to test/translation/__fixtures__/modulesImportWithoutFromClause.ts diff --git a/test/translation/transformation/modulesNamespaceExport.ts b/test/translation/__fixtures__/modulesNamespaceExport.ts similarity index 100% rename from test/translation/transformation/modulesNamespaceExport.ts rename to test/translation/__fixtures__/modulesNamespaceExport.ts diff --git a/test/translation/transformation/modulesNamespaceNestedWithMemberExport.ts b/test/translation/__fixtures__/modulesNamespaceNestedWithMemberExport.ts similarity index 100% rename from test/translation/transformation/modulesNamespaceNestedWithMemberExport.ts rename to test/translation/__fixtures__/modulesNamespaceNestedWithMemberExport.ts diff --git a/test/translation/transformation/modulesNamespaceNoExport.ts b/test/translation/__fixtures__/modulesNamespaceNoExport.ts similarity index 100% rename from test/translation/transformation/modulesNamespaceNoExport.ts rename to test/translation/__fixtures__/modulesNamespaceNoExport.ts diff --git a/test/translation/transformation/modulesNamespaceWithMemberExport.ts b/test/translation/__fixtures__/modulesNamespaceWithMemberExport.ts similarity index 100% rename from test/translation/transformation/modulesNamespaceWithMemberExport.ts rename to test/translation/__fixtures__/modulesNamespaceWithMemberExport.ts diff --git a/test/translation/transformation/modulesNamespaceWithMemberNoExport.ts b/test/translation/__fixtures__/modulesNamespaceWithMemberNoExport.ts similarity index 100% rename from test/translation/transformation/modulesNamespaceWithMemberNoExport.ts rename to test/translation/__fixtures__/modulesNamespaceWithMemberNoExport.ts diff --git a/test/translation/transformation/modulesVariableExport.ts b/test/translation/__fixtures__/modulesVariableExport.ts similarity index 100% rename from test/translation/transformation/modulesVariableExport.ts rename to test/translation/__fixtures__/modulesVariableExport.ts diff --git a/test/translation/transformation/modulesVariableNoExport.ts b/test/translation/__fixtures__/modulesVariableNoExport.ts similarity index 100% rename from test/translation/transformation/modulesVariableNoExport.ts rename to test/translation/__fixtures__/modulesVariableNoExport.ts diff --git a/test/translation/transformation/namespacePhantom.ts b/test/translation/__fixtures__/namespacePhantom.ts similarity index 100% rename from test/translation/transformation/namespacePhantom.ts rename to test/translation/__fixtures__/namespacePhantom.ts diff --git a/test/translation/transformation/returnDefault.ts b/test/translation/__fixtures__/returnDefault.ts similarity index 100% rename from test/translation/transformation/returnDefault.ts rename to test/translation/__fixtures__/returnDefault.ts diff --git a/test/translation/transformation/topLevelVariables.ts b/test/translation/__fixtures__/topLevelVariables.ts similarity index 100% rename from test/translation/transformation/topLevelVariables.ts rename to test/translation/__fixtures__/topLevelVariables.ts diff --git a/test/translation/transformation/unusedDefaultWithNamespaceImport.ts b/test/translation/__fixtures__/unusedDefaultWithNamespaceImport.ts similarity index 100% rename from test/translation/transformation/unusedDefaultWithNamespaceImport.ts rename to test/translation/__fixtures__/unusedDefaultWithNamespaceImport.ts diff --git a/test/translation/__snapshots__/transformation.spec.ts.snap b/test/translation/__snapshots__/translation.spec.ts.snap similarity index 100% rename from test/translation/__snapshots__/transformation.spec.ts.snap rename to test/translation/__snapshots__/translation.spec.ts.snap diff --git a/test/translation/transformation.spec.ts b/test/translation/translation.spec.ts similarity index 89% rename from test/translation/transformation.spec.ts rename to test/translation/translation.spec.ts index 7a5088fcb..e91a5c2b8 100644 --- a/test/translation/transformation.spec.ts +++ b/test/translation/translation.spec.ts @@ -3,7 +3,7 @@ import * as path from "path"; import * as tstl from "../../src"; import * as util from "../util"; -const fixturesPath = path.join(__dirname, "./transformation"); +const fixturesPath = path.join(__dirname, "__fixtures__"); const fixtures = fs .readdirSync(fixturesPath) .filter(f => path.extname(f) === ".ts") diff --git a/test/transpile/bundle/index.ts b/test/transpile/__fixtures__/bundle/index.ts similarity index 100% rename from test/transpile/bundle/index.ts rename to test/transpile/__fixtures__/bundle/index.ts diff --git a/test/transpile/bundle/otherFile.ts b/test/transpile/__fixtures__/bundle/otherFile.ts similarity index 100% rename from test/transpile/bundle/otherFile.ts rename to test/transpile/__fixtures__/bundle/otherFile.ts diff --git a/test/transpile/bundle/tsconfig.json b/test/transpile/__fixtures__/bundle/tsconfig.json similarity index 100% rename from test/transpile/bundle/tsconfig.json rename to test/transpile/__fixtures__/bundle/tsconfig.json diff --git a/test/transpile/directories/baseurl/src/lib/nested/file.ts b/test/transpile/__fixtures__/directories/baseurl/src/lib/nested/file.ts similarity index 100% rename from test/transpile/directories/baseurl/src/lib/nested/file.ts rename to test/transpile/__fixtures__/directories/baseurl/src/lib/nested/file.ts diff --git a/test/transpile/directories/baseurl/src/main.ts b/test/transpile/__fixtures__/directories/baseurl/src/main.ts similarity index 100% rename from test/transpile/directories/baseurl/src/main.ts rename to test/transpile/__fixtures__/directories/baseurl/src/main.ts diff --git a/test/transpile/directories/basic/src/lib/file.ts b/test/transpile/__fixtures__/directories/basic/src/lib/file.ts similarity index 100% rename from test/transpile/directories/basic/src/lib/file.ts rename to test/transpile/__fixtures__/directories/basic/src/lib/file.ts diff --git a/test/transpile/directories/basic/src/main.ts b/test/transpile/__fixtures__/directories/basic/src/main.ts similarity index 100% rename from test/transpile/directories/basic/src/main.ts rename to test/transpile/__fixtures__/directories/basic/src/main.ts diff --git a/test/transpile/load-config-import/cjs.js b/test/transpile/__fixtures__/load-config-import/cjs.js similarity index 100% rename from test/transpile/load-config-import/cjs.js rename to test/transpile/__fixtures__/load-config-import/cjs.js diff --git a/test/transpile/load-config-import/import.ts b/test/transpile/__fixtures__/load-config-import/import.ts similarity index 100% rename from test/transpile/load-config-import/import.ts rename to test/transpile/__fixtures__/load-config-import/import.ts diff --git a/test/transpile/load-config-import/transpiled-esm.js b/test/transpile/__fixtures__/load-config-import/transpiled-esm.js similarity index 100% rename from test/transpile/load-config-import/transpiled-esm.js rename to test/transpile/__fixtures__/load-config-import/transpiled-esm.js diff --git a/test/transpile/load-config-import/ts.ts b/test/transpile/__fixtures__/load-config-import/ts.ts similarity index 100% rename from test/transpile/load-config-import/ts.ts rename to test/transpile/__fixtures__/load-config-import/ts.ts diff --git a/test/transpile/plugins/getModuleId.ts b/test/transpile/__fixtures__/plugins/getModuleId.ts similarity index 90% rename from test/transpile/plugins/getModuleId.ts rename to test/transpile/__fixtures__/plugins/getModuleId.ts index 6ce5f2e20..9bce8c6a2 100644 --- a/test/transpile/plugins/getModuleId.ts +++ b/test/transpile/__fixtures__/plugins/getModuleId.ts @@ -1,6 +1,6 @@ import { createHash } from "crypto"; import * as path from "path"; -import * as tstl from "../../../src"; +import * as tstl from "../../../../src"; const plugin: tstl.Plugin = { getModuleId: (module, transpilation) => diff --git a/test/transpile/plugins/getResolvePlugins.ts b/test/transpile/__fixtures__/plugins/getResolvePlugins.ts similarity index 91% rename from test/transpile/plugins/getResolvePlugins.ts rename to test/transpile/__fixtures__/plugins/getResolvePlugins.ts index 5d1ded4d7..cb58b4b89 100644 --- a/test/transpile/plugins/getResolvePlugins.ts +++ b/test/transpile/__fixtures__/plugins/getResolvePlugins.ts @@ -1,6 +1,6 @@ // @ts-expect-error Could not find a declaration file for module 'enhanced-resolve/lib/AliasPlugin'. import * as AliasPlugin from "enhanced-resolve/lib/AliasPlugin"; -import * as tstl from "../../../src"; +import * as tstl from "../../../../src"; const plugin: tstl.Plugin = { getResolvePlugins: () => [ diff --git a/test/transpile/plugins/printer.ts b/test/transpile/__fixtures__/plugins/printer.ts similarity index 90% rename from test/transpile/plugins/printer.ts rename to test/transpile/__fixtures__/plugins/printer.ts index e14f3e838..3208901d9 100644 --- a/test/transpile/plugins/printer.ts +++ b/test/transpile/__fixtures__/plugins/printer.ts @@ -1,5 +1,5 @@ import { SourceNode } from "source-map"; -import * as tstl from "../../../src"; +import * as tstl from "../../../../src"; const plugin: tstl.Plugin = { printer(program, host, fileName, ...args) { diff --git a/test/transpile/plugins/visitor-super.ts b/test/transpile/__fixtures__/plugins/visitor-super.ts similarity index 91% rename from test/transpile/plugins/visitor-super.ts rename to test/transpile/__fixtures__/plugins/visitor-super.ts index 9a8bee5f1..42847562b 100644 --- a/test/transpile/plugins/visitor-super.ts +++ b/test/transpile/__fixtures__/plugins/visitor-super.ts @@ -1,5 +1,5 @@ import * as ts from "typescript"; -import * as tstl from "../../../src"; +import * as tstl from "../../../../src"; const plugin: tstl.Plugin = { visitors: { diff --git a/test/transpile/plugins/visitor.ts b/test/transpile/__fixtures__/plugins/visitor.ts similarity index 87% rename from test/transpile/plugins/visitor.ts rename to test/transpile/__fixtures__/plugins/visitor.ts index 507b9fa3d..f74610a74 100644 --- a/test/transpile/plugins/visitor.ts +++ b/test/transpile/__fixtures__/plugins/visitor.ts @@ -1,5 +1,5 @@ import * as ts from "typescript"; -import * as tstl from "../../../src"; +import * as tstl from "../../../../src"; const plugin: tstl.Plugin = { visitors: { diff --git a/test/transpile/project/api.d.ts b/test/transpile/__fixtures__/project/api.d.ts similarity index 100% rename from test/transpile/project/api.d.ts rename to test/transpile/__fixtures__/project/api.d.ts diff --git a/test/transpile/project/index.ts b/test/transpile/__fixtures__/project/index.ts similarity index 100% rename from test/transpile/project/index.ts rename to test/transpile/__fixtures__/project/index.ts diff --git a/test/transpile/project/otherFile.ts b/test/transpile/__fixtures__/project/otherFile.ts similarity index 100% rename from test/transpile/project/otherFile.ts rename to test/transpile/__fixtures__/project/otherFile.ts diff --git a/test/transpile/project/tsconfig.json b/test/transpile/__fixtures__/project/tsconfig.json similarity index 100% rename from test/transpile/project/tsconfig.json rename to test/transpile/__fixtures__/project/tsconfig.json diff --git a/test/transpile/transformers/fixtures.ts b/test/transpile/__fixtures__/transformers.ts similarity index 86% rename from test/transpile/transformers/fixtures.ts rename to test/transpile/__fixtures__/transformers.ts index c998d6efc..80c3a1ab7 100644 --- a/test/transpile/transformers/fixtures.ts +++ b/test/transpile/__fixtures__/transformers.ts @@ -1,7 +1,11 @@ import * as assert from "assert"; import * as ts from "typescript"; import * as tstl from "../../../src"; -import { visitAndReplace } from "./utils"; + +function visitAndReplace(context: ts.TransformationContext, node: T, visitor: ts.Visitor): T { + const visit: ts.Visitor = node => visitor(node) ?? ts.visitEachChild(node, visit, context); + return ts.visitNode(node, visit); +} export const program = (program: ts.Program, options: { value: any }): ts.TransformerFactory => checker(program.getTypeChecker(), options); diff --git a/test/transpile/__snapshots__/directories.spec.ts.snap b/test/transpile/__snapshots__/directories.spec.ts.snap index 901d885f4..d846f471e 100644 --- a/test/transpile/__snapshots__/directories.spec.ts.snap +++ b/test/transpile/__snapshots__/directories.spec.ts.snap @@ -1,17 +1,9 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`should be able to resolve ({"name": "baseurl", "options": [Object]}) 1`] = ` -Array [ - "directories/baseurl/out/lualib_bundle.lua", - "directories/baseurl/out/src/lib/nested/file.lua", - "directories/baseurl/out/src/main.lua", -] -`; - exports[`should be able to resolve ({"name": "basic", "options": [Object]}) 1`] = ` Array [ "directories/basic/src/lib/file.lua", - "directories/basic/src/lualib_bundle.lua", + "directories/basic/src/lualib/lualib_bundle.lua", "directories/basic/src/main.lua", ] `; @@ -19,7 +11,7 @@ Array [ exports[`should be able to resolve ({"name": "basic", "options": [Object]}) 2`] = ` Array [ "directories/basic/out/lib/file.lua", - "directories/basic/out/lualib_bundle.lua", + "directories/basic/out/lualib/lualib_bundle.lua", "directories/basic/out/main.lua", ] `; @@ -27,7 +19,7 @@ Array [ exports[`should be able to resolve ({"name": "basic", "options": [Object]}) 3`] = ` Array [ "directories/basic/src/lib/file.lua", - "directories/basic/src/lualib_bundle.lua", + "directories/basic/src/lualib/lualib_bundle.lua", "directories/basic/src/main.lua", ] `; @@ -35,7 +27,7 @@ Array [ exports[`should be able to resolve ({"name": "basic", "options": [Object]}) 4`] = ` Array [ "directories/basic/out/lib/file.lua", - "directories/basic/out/lualib_bundle.lua", + "directories/basic/out/lualib/lualib_bundle.lua", "directories/basic/out/main.lua", ] `; diff --git a/test/transpile/plugins/__snapshots__/plugins.spec.ts.snap b/test/transpile/__snapshots__/plugins.spec.ts.snap similarity index 100% rename from test/transpile/plugins/__snapshots__/plugins.spec.ts.snap rename to test/transpile/__snapshots__/plugins.spec.ts.snap diff --git a/test/transpile/bundle.spec.ts b/test/transpile/bundle.spec.ts index 21e9f153a..85b2554d5 100644 --- a/test/transpile/bundle.spec.ts +++ b/test/transpile/bundle.spec.ts @@ -1,12 +1,8 @@ -import * as path from "path"; import * as util from "../util"; -import { transpileProjectResult } from "./run"; - -const projectDir = path.join(__dirname, "bundle"); -const inputProject = path.join(projectDir, "tsconfig.json"); +import { resolveFixture, transpileProjectResult } from "./run"; test("should transpile into one file", () => { - const { diagnostics, emittedFiles } = transpileProjectResult(inputProject); + const { diagnostics, emittedFiles } = transpileProjectResult(resolveFixture("bundle/tsconfig.json")); expect(diagnostics).not.toHaveDiagnostics(); expect(emittedFiles).toHaveLength(1); diff --git a/test/transpile/directories.spec.ts b/test/transpile/directories.spec.ts index 9141199e5..ee346c44d 100644 --- a/test/transpile/directories.spec.ts +++ b/test/transpile/directories.spec.ts @@ -1,7 +1,6 @@ -import * as path from "path"; import * as ts from "typescript"; import * as tstl from "../../src"; -import { transpileFilesResult } from "./run"; +import { resolveFixture, transpileFilesResult } from "./run"; interface DirectoryTestCase { name: string; @@ -15,7 +14,7 @@ test.each([ { name: "basic", options: { rootDir: "src", outDir: "out" } }, { name: "baseurl", options: { baseUrl: "./src/lib", rootDir: ".", outDir: "./out" } }, ])("should be able to resolve (%p)", ({ name, options: compilerOptions }) => { - const projectPath = path.join(__dirname, "directories", name); + const projectPath = resolveFixture(`directories/${name}`); jest.spyOn(process, "cwd").mockReturnValue(projectPath); const config = { diff --git a/test/transpile/load-config-import.spec.ts b/test/transpile/load-config-import.spec.ts new file mode 100644 index 000000000..fe328b053 --- /dev/null +++ b/test/transpile/load-config-import.spec.ts @@ -0,0 +1,27 @@ +import { loadConfigImport } from "../../src/transpilation/utils"; +import { resolveFixture } from "./run"; + +test("resolve relative module paths", () => { + const result = loadConfigImport("test", "test", resolveFixture("load-config-import"), "./ts.ts"); + expect(result).toMatchObject({ result: true }); +}); + +test("load .ts modules", () => { + const result = loadConfigImport("test", "test", __dirname, resolveFixture("load-config-import/ts.ts")); + expect(result).toMatchObject({ result: true }); +}); + +test("load CJS .js modules", () => { + const result = loadConfigImport("test", "test", __dirname, resolveFixture("load-config-import/cjs.js")); + expect(result).toMatchObject({ result: true }); +}); + +test("load transpiled ESM .js modules", () => { + const result = loadConfigImport("test", "test", __dirname, resolveFixture("load-config-import/transpiled-esm.js")); + expect(result).toMatchObject({ result: true }); +}); + +test('"import" option', () => { + const result = loadConfigImport("test", "test", __dirname, resolveFixture("load-config-import/import.ts"), "named"); + expect(result).toMatchObject({ result: true }); +}); diff --git a/test/transpile/load-config-import/load-config-import.spec.ts b/test/transpile/load-config-import/load-config-import.spec.ts deleted file mode 100644 index 4e1b63a53..000000000 --- a/test/transpile/load-config-import/load-config-import.spec.ts +++ /dev/null @@ -1,27 +0,0 @@ -import * as path from "path"; -import { loadConfigImport } from "../../../src/transpilation/utils"; - -test("resolve relative module paths", () => { - const result = loadConfigImport("test", "test", __dirname, "./ts.ts"); - expect(result).toMatchObject({ result: true }); -}); - -test("load .ts modules", () => { - const result = loadConfigImport("test", "test", __dirname, path.join(__dirname, "ts.ts")); - expect(result).toMatchObject({ result: true }); -}); - -test("load CJS .js modules", () => { - const result = loadConfigImport("test", "test", __dirname, path.join(__dirname, "cjs.js")); - expect(result).toMatchObject({ result: true }); -}); - -test("load transpiled ESM .js modules", () => { - const result = loadConfigImport("test", "test", __dirname, path.join(__dirname, "transpiled-esm.js")); - expect(result).toMatchObject({ result: true }); -}); - -test('"import" option', () => { - const result = loadConfigImport("test", "test", __dirname, path.join(__dirname, "import.ts"), "named"); - expect(result).toMatchObject({ result: true }); -}); diff --git a/test/transpile/plugins/plugins.spec.ts b/test/transpile/plugins.spec.ts similarity index 63% rename from test/transpile/plugins/plugins.spec.ts rename to test/transpile/plugins.spec.ts index d6de15caa..683f7da52 100644 --- a/test/transpile/plugins/plugins.spec.ts +++ b/test/transpile/plugins.spec.ts @@ -1,9 +1,9 @@ -import * as path from "path"; -import * as util from "../../util"; +import * as util from "../util"; +import { resolveFixture } from "./run"; test("printer", () => { util.testModule`` - .setOptions({ luaPlugins: [{ name: path.join(__dirname, "printer.ts") }] }) + .setOptions({ luaPlugins: [{ name: resolveFixture("plugins/printer.ts") }] }) .tap(builder => expect(builder.getMainLuaCodeChunk()).toMatch("Plugin")); }); @@ -11,7 +11,7 @@ test("visitor", () => { util.testFunction` return false; ` - .setOptions({ luaPlugins: [{ name: path.join(__dirname, "visitor.ts") }] }) + .setOptions({ luaPlugins: [{ name: resolveFixture("plugins/visitor.ts") }] }) .expectToEqual(true); }); @@ -19,7 +19,7 @@ test("visitor using super", () => { util.testFunction` return "foo"; ` - .setOptions({ luaPlugins: [{ name: path.join(__dirname, "visitor-super.ts") }] }) + .setOptions({ luaPlugins: [{ name: resolveFixture("plugins/visitor-super.ts") }] }) .expectToEqual("bar"); }); @@ -28,7 +28,7 @@ test("getModuleId", () => { export { value } from "./foo"; ` .addExtraFile("foo.ts", "export const value = true;") - .setOptions({ luaPlugins: [{ name: path.join(__dirname, "getModuleId.ts") }] }) + .setOptions({ luaPlugins: [{ name: resolveFixture("plugins/getModuleId.ts") }] }) .expectToEqual({ value: true }) .expectLuaToMatchSnapshot(); }); @@ -39,7 +39,7 @@ test("getResolvePlugins", () => { ` .addExtraFile("bar.ts", "export const value = true;") .setOptions({ - luaPlugins: [{ name: path.join(__dirname, "getResolvePlugins.ts") }], + luaPlugins: [{ name: resolveFixture("plugins/getResolvePlugins.ts") }], baseUrl: ".", paths: { foo: ["bar"] }, }) diff --git a/test/transpile/project.spec.ts b/test/transpile/project.spec.ts index 6f262dada..2abbe4dd7 100644 --- a/test/transpile/project.spec.ts +++ b/test/transpile/project.spec.ts @@ -1,8 +1,7 @@ -import * as path from "path"; -import { transpileProjectResult } from "./run"; +import { resolveFixture, transpileProjectResult } from "./run"; test("should transpile", () => { - const { diagnostics, emittedFiles } = transpileProjectResult(path.join(__dirname, "project", "tsconfig.json")); + const { diagnostics, emittedFiles } = transpileProjectResult(resolveFixture("project/tsconfig.json")); expect(diagnostics).not.toHaveDiagnostics(); expect(emittedFiles).toMatchSnapshot(); }); diff --git a/test/transpile/run.ts b/test/transpile/run.ts index 8890d7a18..5e4d1aaeb 100644 --- a/test/transpile/run.ts +++ b/test/transpile/run.ts @@ -4,13 +4,15 @@ import * as tstl from "../../src"; import { parseConfigFileWithSystem } from "../../src/cli/tsconfig"; import { normalizeSlashes } from "../../src/utils"; +export const resolveFixture = (name: string) => path.resolve(__dirname, "__fixtures__", name); + export function transpileFilesResult(rootNames: string[], options: tstl.CompilerOptions) { options.skipLibCheck = true; options.types = []; const emittedFiles: ts.OutputFile[] = []; const { diagnostics } = tstl.transpileFiles(rootNames, options, (fileName, text, writeByteOrderMark) => { - const name = normalizeSlashes(path.relative(__dirname, fileName)); + const name = normalizeSlashes(path.relative(resolveFixture(""), fileName)); emittedFiles.push({ name, text, writeByteOrderMark }); }); diff --git a/test/transpile/transformers/transformers.spec.ts b/test/transpile/transformers.spec.ts similarity index 54% rename from test/transpile/transformers/transformers.spec.ts rename to test/transpile/transformers.spec.ts index e166198bd..254541a79 100644 --- a/test/transpile/transformers/transformers.spec.ts +++ b/test/transpile/transformers.spec.ts @@ -1,11 +1,13 @@ -import * as path from "path"; -import * as util from "../../util"; +import * as util from "../util"; +import { resolveFixture } from "./run"; + +const transformersFixture = resolveFixture("transformers.ts"); test("ignore language service plugins", () => { util.testFunction` return false; ` - .setOptions({ plugins: [{ name: path.join(__dirname, "types.ts") }] }) + .setOptions({ plugins: [{ name: transformersFixture }] }) .expectToEqual(false); }); @@ -13,13 +15,13 @@ test("default type", () => { util.testFunction` return false; ` - .setOptions({ plugins: [{ transform: path.join(__dirname, "fixtures.ts"), import: "program", value: true }] }) + .setOptions({ plugins: [{ transform: transformersFixture, import: "program", value: true }] }) .expectToEqual(true); }); test("transformer resolution error", () => { util.testModule`` - .setOptions({ plugins: [{ transform: path.join(__dirname, "error.ts") }] }) + .setOptions({ plugins: [{ transform: resolveFixture("transformers/error.ts") }] }) .expectToHaveDiagnostics(); }); @@ -28,9 +30,7 @@ describe("factory types", () => { util.testFunction` return false; ` - .setOptions({ - plugins: [{ transform: path.join(__dirname, "fixtures.ts"), type, import: type, value: true }], - }) + .setOptions({ plugins: [{ transform: transformersFixture, type, import: type, value: true }] }) .expectToEqual(true); }); }); diff --git a/test/transpile/transformers/utils.ts b/test/transpile/transformers/utils.ts deleted file mode 100644 index 3e1503ba0..000000000 --- a/test/transpile/transformers/utils.ts +++ /dev/null @@ -1,6 +0,0 @@ -import * as ts from "typescript"; - -export function visitAndReplace(context: ts.TransformationContext, node: T, visitor: ts.Visitor): T { - const visit: ts.Visitor = node => visitor(node) ?? ts.visitEachChild(node, visit, context); - return ts.visitNode(node, visit); -} diff --git a/test/tsconfig.json b/test/tsconfig.json index de0b21bdd..77f05ec58 100644 --- a/test/tsconfig.json +++ b/test/tsconfig.json @@ -7,12 +7,5 @@ "paths": { "*": ["types/*"] } }, "include": [".", "../src"], - "exclude": [ - "translation/transformation", - "cli/errors", - "cli/watch", - "transpile/directories", - "transpile/outFile", - "../src/lualib" - ] + "exclude": ["translation/__fixtures__", "cli/__fixtures__", "transpile/__fixtures__/directories", "../src/lualib"] } From cf6fdff3c99356e1fd4e733f6c43122d7274696f Mon Sep 17 00:00:00 2001 From: ark120202 Date: Sat, 17 Oct 2020 10:19:18 +0000 Subject: [PATCH 33/58] Update .eslintignore and .prettierignore --- .eslintignore | 8 +++----- .prettierignore | 2 +- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/.eslintignore b/.eslintignore index 707cb8140..f5c92cd2a 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,6 +1,4 @@ /dist -/test/translation/transformation -/test/cli/errors -/test/cli/watch -/test/transpile/directories -/test/transpile/outFile +/test/translation/__fixtures__ +/test/cli/__fixtures__ +/test/transpile/__fixtures__/directories diff --git a/.prettierignore b/.prettierignore index cbfe6aa04..f38b564c0 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,3 +1,3 @@ /dist /coverage -/test/translation/transformation/characterEscapeSequence.ts +/test/translation/__fixtures__/characterEscapeSequence.ts From d129b3c3a301662b63e28e16dc96e07d2a40ee21 Mon Sep 17 00:00:00 2001 From: ark120202 Date: Sat, 17 Oct 2020 10:21:30 +0000 Subject: [PATCH 34/58] Move `lualib_bundle` chunk back to root --- src/LuaPrinter.ts | 2 +- src/transpilation/transpilation.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/LuaPrinter.ts b/src/LuaPrinter.ts index b30ebb38a..4543608ef 100644 --- a/src/LuaPrinter.ts +++ b/src/LuaPrinter.ts @@ -164,7 +164,7 @@ export class LuaPrinter { (luaLibImport === LuaLibImportKind.Require && luaLibFeatures.size > 0) ) { // Require lualib bundle - header += 'require(__TS__Resolve("/lualib/lualib_bundle"));\n'; + header += 'require(__TS__Resolve("/lualib_bundle"));\n'; } else if (luaLibImport === LuaLibImportKind.Inline && luaLibFeatures.size > 0) { // Inline lualib features header += "-- Lua Library inline imports\n"; diff --git a/src/transpilation/transpilation.ts b/src/transpilation/transpilation.ts index d74b4bfe0..8ba3a2fad 100644 --- a/src/transpilation/transpilation.ts +++ b/src/transpilation/transpilation.ts @@ -72,7 +72,7 @@ export class Transpilation { } private resolveRequestToModule(issuer: string, request: string) { - if (request === "/lualib/lualib_bundle") { + if (request === "/lualib_bundle") { let module = this.modules.find(m => m.request === request); if (!module) { const source = new SourceNode(null, null, null, getLuaLibBundle(this.host)); From 5bcafb9974a4c239e4d2012157f9e98aaadb91be Mon Sep 17 00:00:00 2001 From: ark120202 Date: Sun, 18 Oct 2020 12:50:11 +0000 Subject: [PATCH 35/58] Fix sourcemaps when lualib_bundle is used --- src/LuaPrinter.ts | 4 +++- test/unit/printer/sourcemaps.spec.ts | 16 +++++++++------- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/LuaPrinter.ts b/src/LuaPrinter.ts index 4543608ef..126e32a85 100644 --- a/src/LuaPrinter.ts +++ b/src/LuaPrinter.ts @@ -175,9 +175,11 @@ export class LuaPrinter { header += "{#SourceMapTraceback}\n"; } + // Hack: __TS__Resolve macro resolution destroys mappings of macro's node grand-parent + const headerNode = new SourceNode(null, null, null, new SourceNode(null, null, null, header)); const fileBlockNode = this.printBlock(block); - return this.concatNodes(header, fileBlockNode); + return this.concatNodes(headerNode, fileBlockNode); } protected pushIndent(): void { diff --git a/test/unit/printer/sourcemaps.spec.ts b/test/unit/printer/sourcemaps.spec.ts index 84a6596a6..83dbaed8d 100644 --- a/test/unit/printer/sourcemaps.spec.ts +++ b/test/unit/printer/sourcemaps.spec.ts @@ -51,25 +51,23 @@ test.each([ }, { code: ` - // @ts-ignore - import { Foo } from "foo"; + import { Foo } from "./module"; Foo; `, assertPatterns: [ - { luaPattern: 'require("foo")', typeScriptPattern: '"foo"' }, + { luaPattern: 'require("module")', typeScriptPattern: '"./module"' }, { luaPattern: "Foo", typeScriptPattern: "Foo" }, ], }, { code: ` - // @ts-ignore - import * as Foo from "foo"; + import * as Foo from "./module"; Foo; `, assertPatterns: [ - { luaPattern: 'require("foo")', typeScriptPattern: '"foo"' }, + { luaPattern: 'require("module")', typeScriptPattern: '"./module"' }, { luaPattern: "Foo", typeScriptPattern: "Foo" }, ], }, @@ -144,7 +142,11 @@ test.each([ ], }, ])("Source map has correct mapping (%p)", async ({ code, assertPatterns }) => { - const file = util.testModule(code).expectToHaveNoDiagnostics().getMainLuaFileResult(); + const file = util + .testModule(code) + .addExtraFile("module.ts", "export const Foo = true;") + .expectToHaveNoDiagnostics() + .getMainLuaFileResult(); const consumer = await new SourceMapConsumer(file.luaSourceMap); for (const { luaPattern, typeScriptPattern } of assertPatterns) { From 052ac1ffcf40fd8435ba606bac4283bd282ae410 Mon Sep 17 00:00:00 2001 From: ark120202 Date: Sun, 18 Oct 2020 14:32:53 +0000 Subject: [PATCH 36/58] Fix few tests --- test/cli/errors.spec.ts | 7 +-- test/cli/watch.spec.ts | 2 +- .../__snapshots__/directories.spec.ts.snap | 8 +-- .../functions/noImplicitSelfOption.spec.ts | 4 +- test/unit/identifiers.spec.ts | 56 +++++++------------ 5 files changed, 31 insertions(+), 46 deletions(-) diff --git a/test/cli/errors.spec.ts b/test/cli/errors.spec.ts index 8ff45ce14..8f5324cb8 100644 --- a/test/cli/errors.spec.ts +++ b/test/cli/errors.spec.ts @@ -1,9 +1,8 @@ import * as fs from "fs"; -import * as path from "path"; -import { runCli } from "./run"; +import { resolveFixture, runCli } from "./run"; -const srcFilePath = path.resolve(__dirname, "errors", "error.ts"); -const outFilePath = path.resolve(__dirname, "errors", "error.lua"); +const srcFilePath = resolveFixture("errors/error.ts"); +const outFilePath = resolveFixture("errors/error.lua"); const errorMessage = "Unable to convert function with no 'this' parameter to function with 'this'."; afterEach(() => { diff --git a/test/cli/watch.spec.ts b/test/cli/watch.spec.ts index 679a3f9ae..4a89e764e 100644 --- a/test/cli/watch.spec.ts +++ b/test/cli/watch.spec.ts @@ -26,7 +26,7 @@ function forkWatchProcess(args: string[]): void { } const watchedFile = resolveFixture("watch/watch.ts"); -const watchedFileOut = watchedFile.replace(".ts", ".lua"); +const watchedFileOut = resolveFixture("watch/watch.lua"); afterEach(() => fs.removeSync(watchedFileOut)); diff --git a/test/transpile/__snapshots__/directories.spec.ts.snap b/test/transpile/__snapshots__/directories.spec.ts.snap index d846f471e..eaf179a6c 100644 --- a/test/transpile/__snapshots__/directories.spec.ts.snap +++ b/test/transpile/__snapshots__/directories.spec.ts.snap @@ -3,7 +3,7 @@ exports[`should be able to resolve ({"name": "basic", "options": [Object]}) 1`] = ` Array [ "directories/basic/src/lib/file.lua", - "directories/basic/src/lualib/lualib_bundle.lua", + "directories/basic/src/lualib_bundle.lua", "directories/basic/src/main.lua", ] `; @@ -11,7 +11,7 @@ Array [ exports[`should be able to resolve ({"name": "basic", "options": [Object]}) 2`] = ` Array [ "directories/basic/out/lib/file.lua", - "directories/basic/out/lualib/lualib_bundle.lua", + "directories/basic/out/lualib_bundle.lua", "directories/basic/out/main.lua", ] `; @@ -19,7 +19,7 @@ Array [ exports[`should be able to resolve ({"name": "basic", "options": [Object]}) 3`] = ` Array [ "directories/basic/src/lib/file.lua", - "directories/basic/src/lualib/lualib_bundle.lua", + "directories/basic/src/lualib_bundle.lua", "directories/basic/src/main.lua", ] `; @@ -27,7 +27,7 @@ Array [ exports[`should be able to resolve ({"name": "basic", "options": [Object]}) 4`] = ` Array [ "directories/basic/out/lib/file.lua", - "directories/basic/out/lualib/lualib_bundle.lua", + "directories/basic/out/lualib_bundle.lua", "directories/basic/out/main.lua", ] `; diff --git a/test/unit/functions/noImplicitSelfOption.spec.ts b/test/unit/functions/noImplicitSelfOption.spec.ts index f61e37cb2..52f5ae35a 100644 --- a/test/unit/functions/noImplicitSelfOption.spec.ts +++ b/test/unit/functions/noImplicitSelfOption.spec.ts @@ -32,8 +32,8 @@ test("generates declaration files with @noSelfInFile", () => { util.assert(fooDeclaration !== undefined); util.testModule` - import { bar } from "./foo.d"; - const test: (this: void) => void = bar; + import type { bar } from "./foo"; + const test: (this: void) => void = undefined as typeof bar; ` .addExtraFile("foo.d.ts", fooDeclaration) .expectToHaveNoDiagnostics(); diff --git a/test/unit/identifiers.spec.ts b/test/unit/identifiers.spec.ts index ba2bcd8a0..a5aee7a93 100644 --- a/test/unit/identifiers.spec.ts +++ b/test/unit/identifiers.spec.ts @@ -455,18 +455,13 @@ describe("lua keyword as identifier doesn't interfere with lua's value", () => { }); test("variable (require)", () => { - const code = ` - const require = "foobar"; - export { foo } from "someModule"; - export const result = require;`; - - const lua = ` - package.loaded.someModule = {foo = "bar"} - return (function() - ${util.transpileString(code, undefined, true)} - end)().result`; - - expect(util.executeLua(lua)).toBe("foobar"); + util.testBundle` + const require = true; + import "./module"; + export const result = require; + ` + .addExtraFile("module.ts", "") + .expectToEqual({ result: true }); }); test("variable (tostring)", () => { @@ -575,19 +570,14 @@ describe("lua keyword as identifier doesn't interfere with lua's value", () => { expect(util.transpileAndExecute(code)).toBe("foobar|string"); }); - test.each(["type", "type as type"])("imported variable (%p)", importName => { - const luaHeader = ` - package.loaded.someModule = {type = "foobar"}`; - - const code = ` - import {${importName}} from "someModule"; - export const result = typeof 7 + "|" + type; - `; - - const lua = util.transpileString(code); - const result = util.executeLua(`${luaHeader} return (function() ${lua} end)().result`); - - expect(result).toBe("number|foobar"); + test.each(["type", "type as type"])("imported variable (%p)", importSpecifier => { + util.testBundle` + import { ${importSpecifier} } from "./module"; + typeof 0; + export const result = type; + ` + .addExtraFile("module.ts", "export const type = true;") + .expectToEqual({ result: true }); }); test.each([ @@ -605,16 +595,12 @@ describe("lua keyword as identifier doesn't interfere with lua's value", () => { }); test.each(["type", "type as type"])("re-exported variable with lua keyword as name (%p)", importName => { - const code = ` - export { ${importName} } from "someModule"`; - - const lua = ` - package.loaded.someModule = {type = "foobar"} - return (function() - ${util.transpileString(code)} - end)().type`; - - expect(util.executeLua(lua)).toBe("foobar"); + util.testBundle` + export { ${importName} } from "./module"; + typeof 0; + ` + .addExtraFile("module.ts", "export const type = true") + .expectToEqual({ type: true }); }); test("class", () => { From 9081fff295d996fab53f1eedb8a78b2695b2be2a Mon Sep 17 00:00:00 2001 From: ark120202 Date: Sun, 18 Oct 2020 18:02:39 +0000 Subject: [PATCH 37/58] Refactor modules.spec.ts --- src/LuaPrinter.ts | 2 +- test/unit/__snapshots__/modules.spec.ts.snap | 16 +- test/unit/modules.spec.ts | 312 ++++++++++--------- 3 files changed, 167 insertions(+), 163 deletions(-) diff --git a/src/LuaPrinter.ts b/src/LuaPrinter.ts index 9d3356120..7bb13886f 100644 --- a/src/LuaPrinter.ts +++ b/src/LuaPrinter.ts @@ -141,7 +141,7 @@ export class LuaPrinter { } public print(file: lua.File): SourceNode { - let header = ""; + let header = file.trivia; if (!this.options.noHeader) { header += "--[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]]\n"; diff --git a/test/unit/__snapshots__/modules.spec.ts.snap b/test/unit/__snapshots__/modules.spec.ts.snap index 3bf3ddcc7..766491de9 100644 --- a/test/unit/__snapshots__/modules.spec.ts.snap +++ b/test/unit/__snapshots__/modules.spec.ts.snap @@ -1,15 +1,17 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Import module names with invalid lua identifier characters ("_̀ः٠‿"): local identifier 1`] = `"______300_903_660_203F"`; +exports[`import local identifier generation (".dot"): local identifier 1`] = `"_____2Edot"`; -exports[`Import module names with invalid lua identifier characters ("dollar$"): local identifier 1`] = `"____dollar_24"`; +exports[`import local identifier generation ("_̀ः٠‿"): local identifier 1`] = `"______300_903_660_203F"`; -exports[`Import module names with invalid lua identifier characters ("hash#"): local identifier 1`] = `"____hash_23"`; +exports[`import local identifier generation ("dollar$"): local identifier 1`] = `"____dollar_24"`; -exports[`Import module names with invalid lua identifier characters ("ke-bab"): local identifier 1`] = `"____ke_2Dbab"`; +exports[`import local identifier generation ("hash#"): local identifier 1`] = `"____hash_23"`; -exports[`Import module names with invalid lua identifier characters ("s p a c e"): local identifier 1`] = `"____s_20p_20a_20c_20e"`; +exports[`import local identifier generation ("ke-bab"): local identifier 1`] = `"____ke_2Dbab"`; -exports[`Import module names with invalid lua identifier characters ("singlequote'"): local identifier 1`] = `"____singlequote_27"`; +exports[`import local identifier generation ("s p a c e"): local identifier 1`] = `"____s_20p_20a_20c_20e"`; -exports[`Import module names with invalid lua identifier characters ("ɥɣɎɌͼƛಠ"): local identifier 1`] = `"_____265_263_24E_24C_37C_19B_CA0"`; +exports[`import local identifier generation ("singlequote'"): local identifier 1`] = `"____singlequote_27"`; + +exports[`import local identifier generation ("ɥɣɎɌͼƛಠ"): local identifier 1`] = `"_____265_263_24E_24C_37C_19B_CA0"`; diff --git a/test/unit/modules.spec.ts b/test/unit/modules.spec.ts index 84e54bc7d..6500d7aea 100644 --- a/test/unit/modules.spec.ts +++ b/test/unit/modules.spec.ts @@ -1,96 +1,51 @@ import * as ts from "typescript"; import * as util from "../util"; -describe("module import/export elision", () => { - const moduleDeclaration = ` - declare module "module" { - export type Type = string; - export declare const value: string; - } - `; - - const expectToElideImport: util.TapCallback = builder => { - builder.addExtraFile("module.d.ts", moduleDeclaration).setOptions({ module: ts.ModuleKind.CommonJS }); - expect(builder.getLuaExecutionResult()).not.toBeInstanceOf(util.ExecutionError); - }; - - test("should elide named type imports", () => { - util.testModule` - import { Type } from "module"; - const foo: Type = "bar"; - `.tap(expectToElideImport); - }); - - test("should elide named value imports used only as a type", () => { +describe("export default", () => { + test("literal", () => { util.testModule` - import { value } from "module"; - const foo: typeof value = "bar"; - `.tap(expectToElideImport); - }); - - test("should elide namespace imports with unused values", () => { - util.testModule` - import * as module from "module"; - const foo: module.Type = "bar"; - `.tap(expectToElideImport); + export default true; + `.expectToEqual({ default: true }); }); - test("should elide `import =` declarations", () => { + test("class", () => { util.testModule` - import module = require("module"); - const foo: module.Type = "bar"; - `.tap(expectToElideImport); + export default class Default {} + const d = new Default(); + export const result = d.constructor.name; + ` + .setReturnExport("result") + .expectToMatchJsResult(); }); - test("should elide type exports", () => { + test("function", () => { util.testModule` - (globalThis as any).foo = true; - type foo = boolean; - export { foo }; - `.expectToEqual([]); + export default function defaultFunction() { + return true; + } + export const result = defaultFunction(); + ` + .setReturnExport("result") + .expectToMatchJsResult(); }); }); -test.each(["ke-bab", "dollar$", "singlequote'", "hash#", "s p a c e", "ɥɣɎɌͼƛಠ", "_̀ः٠‿"])( - "Import module names with invalid lua identifier characters (%p)", - name => { - util.testBundle` - import { foo } from "./${name}"; - export { foo }; - ` - .addExtraFile(`${name}.ts`, "export const foo = true;") - .expectToEqual({ foo: true }) - .tap(builder => { - const identifier = builder.getMainLuaCodeChunk().match(/local (.+) = require\(/)?.[1]; - expect(identifier).toMatchSnapshot("local identifier"); - }); - } -); - -test.each(["export default value;", "export { value as default };"])("Export Default From (%p)", exportStatement => { +test("export { value as default }", () => { util.testBundle` - export { default } from "./module"; - ` - .addExtraFile( - "module.ts", - ` - export const value = true; - ${exportStatement}; - ` - ) - .expectToEqual({ default: true }); + const value = true; + export { value as default }; + `.expectToEqual({ default: true }); }); -test("Default Import and Export Expression", () => { +test("export { default } from '...'", () => { util.testBundle` - import defaultExport from "./module"; - export const value = defaultExport; + export { default } from "./module"; ` - .addExtraFile("module.ts", "export default 1 + 2 + 3;") - .expectToEqual({ value: 6 }); + .addExtraFile("module.ts", "export default true;") + .expectToEqual({ default: true }); }); -test("Import and Export Assignment", () => { +test("import = and export =", () => { util.testBundle` import m = require("./module"); export const value = m; @@ -134,95 +89,142 @@ test("Mixed Exports, Default and Namespace Import", () => { .expectToEqual({ value: 6 }); }); -test("Export Default Function", () => { - util.testBundle` - import defaultExport from "./module"; - export const value = defaultExport(); - ` - .addExtraFile("module.ts", "export default function() { return true; }") - .expectToEqual({ value: true }); -}); - -const reassignmentTestCases = [ - "x = 1", - "x++", - "(x = 1)", - "[x] = [1]", - "[[x]] = [[1]]", - "({ x } = { x: 1 })", - "({ y: x } = { y: 1 })", - "({ x = 1 } = { x: undefined })", -]; - -test.each(reassignmentTestCases)("export specifier with reassignment afterwards (%p)", reassignment => { - util.testModule` - let x = 0; - export { x }; - ${reassignment}; - `.expectToMatchJsResult(); -}); +describe("export live bindings", () => { + const reassignmentTestCases = [ + "x = 1", + "x++", + "(x = 1)", + "[x] = [1]", + "[[x]] = [[1]]", + "({ x } = { x: 1 })", + "({ y: x } = { y: 1 })", + "({ x = 1 } = { x: undefined })", + ]; + + // https://github.com/TypeScriptToLua/TypeScriptToLua/issues/926 + test.each(reassignmentTestCases.filter(c => !c.includes(" } = { x: ")))("export variable (%p)", reassignment => { + util.testModule` + export let x = 0; + ${reassignment}; + `.expectToMatchJsResult(); + }); -test.each(reassignmentTestCases)("export specifier fork (%p)", reassignment => { - util.testModule` - let x = 0; - export { x as a }; - export { x as b }; - ${reassignment}; - `.expectToMatchJsResult(); -}); + test.each(reassignmentTestCases)("export variable as a binding (%p)", reassignment => { + util.testModule` + let x = 0; + export { x }; + ${reassignment}; + `.expectToMatchJsResult(); + }); -test("does not export shadowed identifiers", () => { - util.testModule` - export let a = 1; - { let a = 2; a = 3 }; - `.expectToMatchJsResult(); -}); + test.each(reassignmentTestCases)("export variable with multiple bindings (%p)", reassignment => { + util.testModule` + let x = 0; + export { x as a }; + export { x as b }; + ${reassignment}; + `.expectToMatchJsResult(); + }); -test("export as specifier shouldn't effect local vars", () => { - util.testModule` - let x = false; - export { x as a }; - let a = 5; - a = 6; - `.expectToMatchJsResult(); -}); + // Can't be added to reassignmentTestCases because of https://github.com/microsoft/TypeScript/issues/35881 + test("export variable (for in loop)", () => { + util.testModule` + export let foo = ''; + for (foo in { x: true }) {} + ` + .setReturnExport("foo") + .expectToMatchJsResult(); + }); -test("export modified in for in loop", () => { - util.testModule` - export let foo = ''; - for (foo in { x: true }) {} - ` - .setReturnExport("foo") - .expectToMatchJsResult(); -}); + test("export variable as a binding (for in loop)", () => { + util.testModule` + let foo = ''; + export { foo as bar }; + for (foo in { x: true }) {} + ` + .setReturnExport("bar") + .expectToEqual("x"); + }); -test("export dependency modified in for in loop", () => { - util.testModule` - let foo = ''; - export { foo as bar }; - for (foo in { x: true }) {} - ` - .setReturnExport("bar") - .expectToEqual("x"); -}); + test("does not update shadowed names", () => { + util.testModule` + export let a = 1; + { let a = 2; a = 3 }; + `.expectToMatchJsResult(); + }); -test("export default class with future reference", () => { - util.testModule` - export default class Default {} - const d = new Default(); - export const result = d.constructor.name; - ` - .setReturnExport("result") - .expectToMatchJsResult(); + test("renamed export specifier shouldn't effect local vars", () => { + util.testModule` + let x = false; + export { x as a }; + let a = 5; + a = 6; + `.expectToMatchJsResult(); + }); }); -test("export default function with future reference", () => { - util.testModule` - export default function defaultFunction() { - return true; +describe("import and export elision", () => { + const moduleDeclaration = ` + declare module "module" { + export type Type = string; + export declare const value: string; } - export const result = defaultFunction(); - ` - .setReturnExport("result") - .expectToMatchJsResult(); + `; + + const expectToElideImport: util.TapCallback = builder => { + builder.addExtraFile("module.d.ts", moduleDeclaration).setOptions({ module: ts.ModuleKind.CommonJS }); + expect(builder.getLuaExecutionResult()).not.toBeInstanceOf(util.ExecutionError); + }; + + test("should elide named type imports", () => { + util.testModule` + import { Type } from "module"; + const foo: Type = "bar"; + `.tap(expectToElideImport); + }); + + test("should elide named value imports used only as a type", () => { + util.testModule` + import { value } from "module"; + const foo: typeof value = "bar"; + `.tap(expectToElideImport); + }); + + test("should elide namespace imports with unused values", () => { + util.testModule` + import * as module from "module"; + const foo: module.Type = "bar"; + `.tap(expectToElideImport); + }); + + test("should elide `import =` declarations", () => { + util.testModule` + import module = require("module"); + const foo: module.Type = "bar"; + `.tap(expectToElideImport); + }); + + test("should elide type exports", () => { + util.testModule` + (globalThis as any).foo = true; + type foo = boolean; + export { foo }; + `.expectToEqual([]); + }); }); + +test.each(["ke-bab", "dollar$", "singlequote'", "hash#", "s p a c e", "ɥɣɎɌͼƛಠ", "_̀ः٠‿", ".dot"])( + "import local identifier generation (%p)", + name => { + util.testBundle` + import { foo } from "./${name}"; + export { foo }; + ` + .addExtraFile(`${name}.ts`, "export const foo = true;") + .expectToEqual({ foo: true }) + .tap(builder => { + const identifier = builder.getMainLuaCodeChunk().match(/local (.+) = require\(/)?.[1]; + expect(identifier).toMatchSnapshot("local identifier"); + }); + } +); From 1252265e6426d33122db955d7c09b574096d2e7a Mon Sep 17 00:00:00 2001 From: ark120202 Date: Mon, 19 Oct 2020 06:06:26 +0000 Subject: [PATCH 38/58] Move some translation tests to unit --- .../__fixtures__/exportStatement.ts | 6 - .../modulesChangedVariableExport.ts | 2 - .../modulesClassWithMemberExport.ts | 3 - .../__fixtures__/modulesImportAll.ts | 3 - .../__fixtures__/modulesImportNamed.ts | 3 - .../__fixtures__/modulesImportRenamed.ts | 3 - .../modulesImportRenamedSpecialChars.ts | 11 - .../modulesImportWithoutFromClause.ts | 1 - .../__fixtures__/modulesVariableExport.ts | 1 - .../__fixtures__/modulesVariableNoExport.ts | 1 - .../__fixtures__/topLevelVariables.ts | 11 - .../unusedDefaultWithNamespaceImport.ts | 2 - .../__snapshots__/translation.spec.ts.snap | 122 --------- test/unit/assignments.spec.ts | 45 ++-- test/unit/modules.spec.ts | 246 +++++++++++++----- 15 files changed, 209 insertions(+), 251 deletions(-) delete mode 100644 test/translation/__fixtures__/exportStatement.ts delete mode 100644 test/translation/__fixtures__/modulesChangedVariableExport.ts delete mode 100644 test/translation/__fixtures__/modulesClassWithMemberExport.ts delete mode 100644 test/translation/__fixtures__/modulesImportAll.ts delete mode 100644 test/translation/__fixtures__/modulesImportNamed.ts delete mode 100644 test/translation/__fixtures__/modulesImportRenamed.ts delete mode 100644 test/translation/__fixtures__/modulesImportRenamedSpecialChars.ts delete mode 100644 test/translation/__fixtures__/modulesImportWithoutFromClause.ts delete mode 100644 test/translation/__fixtures__/modulesVariableExport.ts delete mode 100644 test/translation/__fixtures__/modulesVariableNoExport.ts delete mode 100644 test/translation/__fixtures__/topLevelVariables.ts delete mode 100644 test/translation/__fixtures__/unusedDefaultWithNamespaceImport.ts diff --git a/test/translation/__fixtures__/exportStatement.ts b/test/translation/__fixtures__/exportStatement.ts deleted file mode 100644 index 7aa965422..000000000 --- a/test/translation/__fixtures__/exportStatement.ts +++ /dev/null @@ -1,6 +0,0 @@ -const xyz = 4; -export { xyz }; -export { xyz as uwv }; -export * from "xyz"; -export { abc, def } from "xyz"; -export { abc as def } from "xyz"; diff --git a/test/translation/__fixtures__/modulesChangedVariableExport.ts b/test/translation/__fixtures__/modulesChangedVariableExport.ts deleted file mode 100644 index 009c4cd06..000000000 --- a/test/translation/__fixtures__/modulesChangedVariableExport.ts +++ /dev/null @@ -1,2 +0,0 @@ -export let foo; -foo = 1; diff --git a/test/translation/__fixtures__/modulesClassWithMemberExport.ts b/test/translation/__fixtures__/modulesClassWithMemberExport.ts deleted file mode 100644 index 73f226b68..000000000 --- a/test/translation/__fixtures__/modulesClassWithMemberExport.ts +++ /dev/null @@ -1,3 +0,0 @@ -export class TestClass { - memberFunc() {} -} diff --git a/test/translation/__fixtures__/modulesImportAll.ts b/test/translation/__fixtures__/modulesImportAll.ts deleted file mode 100644 index 28b470640..000000000 --- a/test/translation/__fixtures__/modulesImportAll.ts +++ /dev/null @@ -1,3 +0,0 @@ -import * as Test from "test"; - -Test; diff --git a/test/translation/__fixtures__/modulesImportNamed.ts b/test/translation/__fixtures__/modulesImportNamed.ts deleted file mode 100644 index d147081ff..000000000 --- a/test/translation/__fixtures__/modulesImportNamed.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { TestClass } from "test"; - -TestClass; diff --git a/test/translation/__fixtures__/modulesImportRenamed.ts b/test/translation/__fixtures__/modulesImportRenamed.ts deleted file mode 100644 index 74c82598b..000000000 --- a/test/translation/__fixtures__/modulesImportRenamed.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { TestClass as RenamedClass } from "test"; - -RenamedClass; diff --git a/test/translation/__fixtures__/modulesImportRenamedSpecialChars.ts b/test/translation/__fixtures__/modulesImportRenamedSpecialChars.ts deleted file mode 100644 index a200c07e9..000000000 --- a/test/translation/__fixtures__/modulesImportRenamedSpecialChars.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { TestClass as RenamedClass1 } from "kebab-module"; -import { TestClass as RenamedClass2 } from "dollar$module"; -import { TestClass as RenamedClass3 } from "singlequote'module"; -import { TestClass as RenamedClass4 } from "hash#module"; -import { TestClass as RenamedClass5 } from "space module"; - -RenamedClass1; -RenamedClass2; -RenamedClass3; -RenamedClass4; -RenamedClass5; diff --git a/test/translation/__fixtures__/modulesImportWithoutFromClause.ts b/test/translation/__fixtures__/modulesImportWithoutFromClause.ts deleted file mode 100644 index 08fabf934..000000000 --- a/test/translation/__fixtures__/modulesImportWithoutFromClause.ts +++ /dev/null @@ -1 +0,0 @@ -import "test"; diff --git a/test/translation/__fixtures__/modulesVariableExport.ts b/test/translation/__fixtures__/modulesVariableExport.ts deleted file mode 100644 index d407b0602..000000000 --- a/test/translation/__fixtures__/modulesVariableExport.ts +++ /dev/null @@ -1 +0,0 @@ -export const foo = "bar"; diff --git a/test/translation/__fixtures__/modulesVariableNoExport.ts b/test/translation/__fixtures__/modulesVariableNoExport.ts deleted file mode 100644 index 4f4b4c843..000000000 --- a/test/translation/__fixtures__/modulesVariableNoExport.ts +++ /dev/null @@ -1 +0,0 @@ -const foo = "bar"; diff --git a/test/translation/__fixtures__/topLevelVariables.ts b/test/translation/__fixtures__/topLevelVariables.ts deleted file mode 100644 index 5782c4cf4..000000000 --- a/test/translation/__fixtures__/topLevelVariables.ts +++ /dev/null @@ -1,11 +0,0 @@ -const obj = { value1: 1, value2: 2 }; -const value1 = obj.value1; -const { value2 } = obj; - -let noValueLet; -let obj2 = { value3: 1, value4: 2 }; -let value3 = obj2.value3; -let { value4 } = obj2; - -function fun1(): void {} -const fun2 = () => {}; diff --git a/test/translation/__fixtures__/unusedDefaultWithNamespaceImport.ts b/test/translation/__fixtures__/unusedDefaultWithNamespaceImport.ts deleted file mode 100644 index 5b66a4b22..000000000 --- a/test/translation/__fixtures__/unusedDefaultWithNamespaceImport.ts +++ /dev/null @@ -1,2 +0,0 @@ -import def, * as x from "module"; -x; diff --git a/test/translation/__snapshots__/translation.spec.ts.snap b/test/translation/__snapshots__/translation.spec.ts.snap index f533103f6..d1891cebe 100644 --- a/test/translation/__snapshots__/translation.spec.ts.snap +++ b/test/translation/__snapshots__/translation.spec.ts.snap @@ -56,32 +56,6 @@ function ClassB.prototype.____constructor(self) end" `; -exports[`Transformation (exportStatement) 1`] = ` -"local ____exports = {} -local xyz = 4 -____exports.xyz = xyz -____exports.uwv = xyz -do - local ____export = require(\\"xyz\\") - for ____exportKey, ____exportValue in pairs(____export) do - ____exports[____exportKey] = ____exportValue - end -end -do - local ____xyz = require(\\"xyz\\") - local abc = ____xyz.abc - local def = ____xyz.def - ____exports.abc = abc - ____exports.def = def -end -do - local ____xyz = require(\\"xyz\\") - local def = ____xyz.abc - ____exports.def = def -end -return ____exports" -`; - exports[`Transformation (globalAugmentation) 1`] = ` "local ____exports = globalVariable return ____exports" @@ -97,12 +71,6 @@ function MyClass.prototype.varargsFunction(self, a, ...) end" `; -exports[`Transformation (modulesChangedVariableExport) 1`] = ` -"local ____exports = {} -____exports.foo = 1 -return ____exports" -`; - exports[`Transformation (modulesClassExport) 1`] = ` "require(\\"lualib_bundle\\"); local ____exports = {} @@ -114,19 +82,6 @@ end return ____exports" `; -exports[`Transformation (modulesClassWithMemberExport) 1`] = ` -"require(\\"lualib_bundle\\"); -local ____exports = {} -____exports.TestClass = __TS__Class() -local TestClass = ____exports.TestClass -TestClass.name = \\"TestClass\\" -function TestClass.prototype.____constructor(self) -end -function TestClass.prototype.memberFunc(self) -end -return ____exports" -`; - exports[`Transformation (modulesFunctionExport) 1`] = ` "local ____exports = {} function ____exports.publicFunc(self) @@ -139,55 +94,6 @@ exports[`Transformation (modulesFunctionNoExport) 1`] = ` end" `; -exports[`Transformation (modulesImportAll) 1`] = ` -"local ____exports = {} -local Test = require(\\"test\\") -local ____ = Test -return ____exports" -`; - -exports[`Transformation (modulesImportNamed) 1`] = ` -"local ____exports = {} -local ____test = require(\\"test\\") -local TestClass = ____test.TestClass -local ____ = TestClass -return ____exports" -`; - -exports[`Transformation (modulesImportRenamed) 1`] = ` -"local ____exports = {} -local ____test = require(\\"test\\") -local RenamedClass = ____test.TestClass -local ____ = RenamedClass -return ____exports" -`; - -exports[`Transformation (modulesImportRenamedSpecialChars) 1`] = ` -"local ____exports = {} -local ____kebab_2Dmodule = require(\\"kebab-module\\") -local RenamedClass1 = ____kebab_2Dmodule.TestClass -local ____dollar_24module = require(\\"dollar$module\\") -local RenamedClass2 = ____dollar_24module.TestClass -local ____singlequote_27module = require(\\"singlequote'module\\") -local RenamedClass3 = ____singlequote_27module.TestClass -local ____hash_23module = require(\\"hash#module\\") -local RenamedClass4 = ____hash_23module.TestClass -local ____space_20module = require(\\"space module\\") -local RenamedClass5 = ____space_20module.TestClass -local ____ = RenamedClass1 -local ____ = RenamedClass2 -local ____ = RenamedClass3 -local ____ = RenamedClass4 -local ____ = RenamedClass5 -return ____exports" -`; - -exports[`Transformation (modulesImportWithoutFromClause) 1`] = ` -"local ____exports = {} -require(\\"test\\") -return ____exports" -`; - exports[`Transformation (modulesNamespaceExport) 1`] = ` "local ____exports = {} ____exports.TestSpace = {} @@ -232,14 +138,6 @@ end return ____exports" `; -exports[`Transformation (modulesVariableExport) 1`] = ` -"local ____exports = {} -____exports.foo = \\"bar\\" -return ____exports" -`; - -exports[`Transformation (modulesVariableNoExport) 1`] = `"foo = \\"bar\\""`; - exports[`Transformation (namespacePhantom) 1`] = ` "function nsMember(self) end" @@ -250,23 +148,3 @@ exports[`Transformation (returnDefault) 1`] = ` return end" `; - -exports[`Transformation (topLevelVariables) 1`] = ` -"obj = {value1 = 1, value2 = 2} -value1 = obj.value1 -value2 = obj.value2 -obj2 = {value3 = 1, value4 = 2} -value3 = obj2.value3 -value4 = obj2.value4 -function fun1(self) -end -fun2 = function() -end" -`; - -exports[`Transformation (unusedDefaultWithNamespaceImport) 1`] = ` -"local ____exports = {} -local x = require(\\"module\\") -local ____ = x -return ____exports" -`; diff --git a/test/unit/assignments.spec.ts b/test/unit/assignments.spec.ts index d2d80f08b..d8d360517 100644 --- a/test/unit/assignments.spec.ts +++ b/test/unit/assignments.spec.ts @@ -1,23 +1,32 @@ import { unsupportedVarDeclaration } from "../../src/transformation/utils/diagnostics"; import * as util from "../util"; -test.each(["const", "let"])("%s declaration not top-level is not global", declarationKind => { - util.testModule` - { - ${declarationKind} foo = true; - } - // @ts-ignore - return "foo" in globalThis; - `.expectToEqual(false); -}); +describe("global scoping", () => { + test.each(["const", "let"])("top-level %s declaration in module is not global", declarationKind => { + util.testModule` + ${declarationKind} foo = true + export const result = "foo" in globalThis; + `.expectToMatchJsResult(); + }); -test.each(["const", "let"])("top-level %s declaration is global", declarationKind => { - util.testBundle` - import './a'; - export const result = foo; - ` - .addExtraFile("a.ts", `${declarationKind} foo = true;`) - .expectToEqual({ result: true }); + test.each(["const", "let"])("top-level %s declaration in script is global", declarationKind => { + util.testBundle` + import './script'; + export const result = foo; + ` + .addExtraFile("script.ts", `${declarationKind} foo = true;`) + .expectToEqual({ result: true }); + }); + + test.each(["const", "let"])("%s declaration not top-level is not global", declarationKind => { + util.testModule` + { + ${declarationKind} foo = true; + } + // @ts-ignore + return "foo" in globalThis; + `.expectToEqual(false); + }); }); describe("var is disallowed", () => { @@ -415,7 +424,7 @@ test.each([ * x.y ||= z is translated to x.y || (x.y = z). * x.y &&= z is translated to x.y && (x.y = z). * x.y ||= z is translated to x.y !== undefined && (x.y = z). - + Test if setter in Lua is called same nr of times as in JS. */ util.testModule` @@ -447,7 +456,7 @@ test.each([ * x.y ||= z is translated to x.y || (x.y = z). * x.y &&= z is translated to x.y && (x.y = z). * x.y ||= z is translated to x.y !== undefined && (x.y = z). - + Test if setter in Lua is called same nr of times as in JS. */ util.testModule` diff --git a/test/unit/modules.spec.ts b/test/unit/modules.spec.ts index 6500d7aea..8909a8143 100644 --- a/test/unit/modules.spec.ts +++ b/test/unit/modules.spec.ts @@ -1,11 +1,17 @@ import * as ts from "typescript"; import * as util from "../util"; +test("export const value", () => { + util.testModule` + export const value = true; + `.expectToMatchJsResult(); +}); + describe("export default", () => { test("literal", () => { util.testModule` export default true; - `.expectToEqual({ default: true }); + `.expectToMatchJsResult(); }); test("class", () => { @@ -30,63 +36,77 @@ describe("export default", () => { }); }); -test("export { value as default }", () => { - util.testBundle` - const value = true; - export { value as default }; - `.expectToEqual({ default: true }); -}); +describe("export { ... }", () => { + test("export { value }", () => { + util.testModule` + const value = true; + export { value }; + `.expectToMatchJsResult(); + }); -test("export { default } from '...'", () => { - util.testBundle` - export { default } from "./module"; - ` - .addExtraFile("module.ts", "export default true;") - .expectToEqual({ default: true }); -}); + test("export { value as result }", () => { + util.testModule` + const value = true; + export { value as result }; + `.expectToMatchJsResult(); + }); -test("import = and export =", () => { - util.testBundle` - import m = require("./module"); - export const value = m; - ` - .setOptions({ module: ts.ModuleKind.CommonJS }) - .addExtraFile("module.ts", "export = true;") - .expectToEqual({ value: true }); + test("export { value as default }", () => { + util.testModule` + const value = true; + export { value as default }; + `.expectToMatchJsResult(); + }); }); -test("Mixed Exports, Default and Named Imports", () => { - util.testBundle` - import defaultExport, { a, b, c } from "./module"; - export const value = defaultExport + b + c; - ` - .addExtraFile( - "module.ts", - ` - export const a = 1; - export const b = 2; - export const c = 3; - export default a; - ` - ) - .expectToEqual({ value: 6 }); -}); +describe("export ... from", () => { + test("export { value } from '...'", () => { + util.testBundle` + export { value } from "./module"; + ` + .addExtraFile("module.ts", "export const value = true;") + .expectToEqual({ value: true }); + }); -test("Mixed Exports, Default and Namespace Import", () => { - util.testBundle` - import defaultExport, * as ns from "./module"; - export const value = defaultExport + ns.b + ns.c; - ` - .addExtraFile( - "module.ts", - ` - export const a = 1; - export const b = 2; - export const c = 3; - export default a; - ` - ) - .expectToEqual({ value: 6 }); + test("export { value as result } from '...'", () => { + util.testBundle` + export { value as result } from "./module"; + ` + .addExtraFile("module.ts", "export const value = true;") + .expectToEqual({ result: true }); + }); + + test("export { value as result1, value as result2 } from '...'", () => { + util.testBundle` + export { value as result1, value as result2 } from "./module"; + ` + .addExtraFile("module.ts", "export const value = true;") + .expectToEqual({ result1: true, result2: true }); + }); + + test("export { default } from '...'", () => { + util.testBundle` + export { default } from "./module"; + ` + .addExtraFile("module.ts", "export default true;") + .expectToEqual({ default: true }); + }); + + test("export * from '...'", () => { + util.testBundle` + export * from "./module"; + ` + .addExtraFile( + "module.ts", + ` + export const a = "a"; + export const b = "b"; + export default "default"; + ` + ) + // TODO: Doesn't match JS + .expectToEqual({ a: "a", b: "b", default: "default" }); + }); }); describe("export live bindings", () => { @@ -163,45 +183,143 @@ describe("export live bindings", () => { }); }); +describe("import ...", () => { + test("import { value }", () => { + util.testBundle` + import { value } from "./module"; + export const result = value; + ` + .addExtraFile("module.ts", "export const value = true;") + .expectToEqual({ result: true }); + }); + + test("import { value as x }", () => { + util.testBundle` + import { value as x } from "./module"; + export const result = x; + ` + .addExtraFile("module.ts", "export const value = true;") + .expectToEqual({ result: true }); + }); + + test("import * as ns", () => { + util.testBundle` + import * as ns from "./module"; + export { ns } + ` + .addExtraFile( + "module.ts", + ` + export const a = "a"; + export const b = "b"; + export default "default"; + ` + ) + .expectToEqual({ ns: { a: "a", b: "b", default: "default" } }); + }); + + test("import defaultValue", () => { + util.testBundle` + import defaultValue from "./module"; + export const result = defaultValue; + ` + .addExtraFile("module.ts", "export default true;") + .expectToEqual({ result: true }); + }); + + test('import "..."', () => { + util.testBundle` + import { state } from "./state"; + import "./module"; + export const result = state.loaded; + ` + .addExtraFile( + "module.ts", + ` + import { state } from "./state"; + state.loaded = true; + ` + ) + .addExtraFile("state.ts", "export const state = { loaded: false };") + .expectToEqual({ result: true }); + }); +}); + +test("export =", () => { + util.testModule` + export = true; + ` + .setOptions({ module: ts.ModuleKind.CommonJS }) + .expectToMatchJsResult(); +}); + +test("import =", () => { + util.testBundle` + import module = require("./module"); + export const result = module; + ` + .addRawFile("module.lua", "return true") + .addExtraFile("module.d.ts", "export = true;") + .setOptions({ module: ts.ModuleKind.CommonJS }) + .expectToEqual({ result: true }); +}); + describe("import and export elision", () => { const moduleDeclaration = ` - declare module "module" { - export type Type = string; - export declare const value: string; - } + export type Type = string; + export const value: string; + export default value; `; const expectToElideImport: util.TapCallback = builder => { - builder.addExtraFile("module.d.ts", moduleDeclaration).setOptions({ module: ts.ModuleKind.CommonJS }); - expect(builder.getLuaExecutionResult()).not.toBeInstanceOf(util.ExecutionError); + builder.addExtraFile("module.d.ts", moduleDeclaration).expectToHaveNoDiagnostics().expectNoExecutionError(); }; test("should elide named type imports", () => { util.testModule` - import { Type } from "module"; + import { Type } from "./module"; const foo: Type = "bar"; `.tap(expectToElideImport); }); test("should elide named value imports used only as a type", () => { util.testModule` - import { value } from "module"; + import { value } from "./module"; const foo: typeof value = "bar"; `.tap(expectToElideImport); }); + test("should elide unused default imports", () => { + util.testModule` + import defaultValue from "./module"; + `.tap(expectToElideImport); + }); + + test("should elide mixed imports", () => { + util.testBundle` + import defaultValue, { value } from "./module"; + export const result = defaultValue; + ` + .addExtraFile("module.d.ts", moduleDeclaration) + .addRawFile("module.lua", "return { default = true }") + .tap(builder => expect(builder.getMainLuaCodeChunk()).not.toContain("a = ")) + .expectToEqual({ result: true }); + }); + test("should elide namespace imports with unused values", () => { util.testModule` - import * as module from "module"; + import * as module from "./module"; const foo: module.Type = "bar"; `.tap(expectToElideImport); }); test("should elide `import =` declarations", () => { util.testModule` - import module = require("module"); + import module = require("./module"); const foo: module.Type = "bar"; - `.tap(expectToElideImport); + ` + .setOptions({ module: ts.ModuleKind.CommonJS }) + .tap(expectToElideImport); }); test("should elide type exports", () => { From fbb9f23e92f6ca28aab752b13ff87d7702fa40ca Mon Sep 17 00:00:00 2001 From: ark120202 Date: Mon, 19 Oct 2020 07:58:34 +0000 Subject: [PATCH 39/58] Remove `baseUrl` tests It's not supposed to change runtime behavior by default, and should get the same treatment as `"paths"` (#591). --- .../baseurl/src/lib/nested/file.ts | 3 -- .../directories/baseurl/src/main.ts | 3 -- .../directories/{basic => }/src/lib/file.ts | 0 .../directories/{basic => }/src/main.ts | 0 .../__snapshots__/directories.spec.ts.snap | 32 +++++++------- test/transpile/directories.spec.ts | 42 ++++++++----------- 6 files changed, 33 insertions(+), 47 deletions(-) delete mode 100644 test/transpile/__fixtures__/directories/baseurl/src/lib/nested/file.ts delete mode 100644 test/transpile/__fixtures__/directories/baseurl/src/main.ts rename test/transpile/__fixtures__/directories/{basic => }/src/lib/file.ts (100%) rename test/transpile/__fixtures__/directories/{basic => }/src/main.ts (100%) diff --git a/test/transpile/__fixtures__/directories/baseurl/src/lib/nested/file.ts b/test/transpile/__fixtures__/directories/baseurl/src/lib/nested/file.ts deleted file mode 100644 index 4248a042b..000000000 --- a/test/transpile/__fixtures__/directories/baseurl/src/lib/nested/file.ts +++ /dev/null @@ -1,3 +0,0 @@ -export function test() { - return 1; -} diff --git a/test/transpile/__fixtures__/directories/baseurl/src/main.ts b/test/transpile/__fixtures__/directories/baseurl/src/main.ts deleted file mode 100644 index 1665f4e08..000000000 --- a/test/transpile/__fixtures__/directories/baseurl/src/main.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from "nested/file"; - -test(); diff --git a/test/transpile/__fixtures__/directories/basic/src/lib/file.ts b/test/transpile/__fixtures__/directories/src/lib/file.ts similarity index 100% rename from test/transpile/__fixtures__/directories/basic/src/lib/file.ts rename to test/transpile/__fixtures__/directories/src/lib/file.ts diff --git a/test/transpile/__fixtures__/directories/basic/src/main.ts b/test/transpile/__fixtures__/directories/src/main.ts similarity index 100% rename from test/transpile/__fixtures__/directories/basic/src/main.ts rename to test/transpile/__fixtures__/directories/src/main.ts diff --git a/test/transpile/__snapshots__/directories.spec.ts.snap b/test/transpile/__snapshots__/directories.spec.ts.snap index eaf179a6c..3bf126239 100644 --- a/test/transpile/__snapshots__/directories.spec.ts.snap +++ b/test/transpile/__snapshots__/directories.spec.ts.snap @@ -1,33 +1,33 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`should be able to resolve ({"name": "basic", "options": [Object]}) 1`] = ` +exports[`should be able to resolve ({"outDir": "out", "rootDir": "src"}) 1`] = ` Array [ - "directories/basic/src/lib/file.lua", - "directories/basic/src/lualib_bundle.lua", - "directories/basic/src/main.lua", + "directories/out/lib/file.lua", + "directories/out/lualib_bundle.lua", + "directories/out/main.lua", ] `; -exports[`should be able to resolve ({"name": "basic", "options": [Object]}) 2`] = ` +exports[`should be able to resolve ({"outDir": "out"}) 1`] = ` Array [ - "directories/basic/out/lib/file.lua", - "directories/basic/out/lualib_bundle.lua", - "directories/basic/out/main.lua", + "directories/out/lib/file.lua", + "directories/out/lualib_bundle.lua", + "directories/out/main.lua", ] `; -exports[`should be able to resolve ({"name": "basic", "options": [Object]}) 3`] = ` +exports[`should be able to resolve ({"rootDir": "src"}) 1`] = ` Array [ - "directories/basic/src/lib/file.lua", - "directories/basic/src/lualib_bundle.lua", - "directories/basic/src/main.lua", + "directories/src/lib/file.lua", + "directories/src/lualib_bundle.lua", + "directories/src/main.lua", ] `; -exports[`should be able to resolve ({"name": "basic", "options": [Object]}) 4`] = ` +exports[`should be able to resolve ({}) 1`] = ` Array [ - "directories/basic/out/lib/file.lua", - "directories/basic/out/lualib_bundle.lua", - "directories/basic/out/main.lua", + "directories/src/lib/file.lua", + "directories/src/lualib_bundle.lua", + "directories/src/main.lua", ] `; diff --git a/test/transpile/directories.spec.ts b/test/transpile/directories.spec.ts index ee346c44d..80d8e9d26 100644 --- a/test/transpile/directories.spec.ts +++ b/test/transpile/directories.spec.ts @@ -2,31 +2,23 @@ import * as ts from "typescript"; import * as tstl from "../../src"; import { resolveFixture, transpileFilesResult } from "./run"; -interface DirectoryTestCase { - name: string; - options: tstl.CompilerOptions; -} +const projectRoot = resolveFixture("directories"); +test.each([{}, { outDir: "out" }, { rootDir: "src" }, { rootDir: "src", outDir: "out" }])( + "should be able to resolve (%p)", + tsconfigOptions => { + jest.spyOn(process, "cwd").mockReturnValue(projectRoot); -test.each([ - { name: "basic", options: {} }, - { name: "basic", options: { outDir: "out" } }, - { name: "basic", options: { rootDir: "src" } }, - { name: "basic", options: { rootDir: "src", outDir: "out" } }, - { name: "baseurl", options: { baseUrl: "./src/lib", rootDir: ".", outDir: "./out" } }, -])("should be able to resolve (%p)", ({ name, options: compilerOptions }) => { - const projectPath = resolveFixture(`directories/${name}`); - jest.spyOn(process, "cwd").mockReturnValue(projectPath); + const config = { + compilerOptions: { ...tsconfigOptions, types: [], skipLibCheck: true }, + tstl: { luaTarget: tstl.LuaTarget.LuaJIT, luaLibImport: tstl.LuaLibImportKind.Always }, + }; - const config = { - compilerOptions: { ...compilerOptions, types: [], skipLibCheck: true }, - tstl: { luaTarget: tstl.LuaTarget.LuaJIT, luaLibImport: tstl.LuaLibImportKind.Always }, - }; + const { fileNames, options } = tstl.updateParsedConfigFile( + ts.parseJsonConfigFileContent(config, ts.sys, projectRoot) + ); - const { fileNames, options } = tstl.updateParsedConfigFile( - ts.parseJsonConfigFileContent(config, ts.sys, projectPath) - ); - - const { diagnostics, emittedFiles } = transpileFilesResult(fileNames, options); - expect(diagnostics).not.toHaveDiagnostics(); - expect(emittedFiles.map(f => f.name).sort()).toMatchSnapshot(); -}); + const { diagnostics, emittedFiles } = transpileFilesResult(fileNames, options); + expect(diagnostics).not.toHaveDiagnostics(); + expect(emittedFiles.map(f => f.name).sort()).toMatchSnapshot(); + } +); From 260c71d708f0cfdf064f4b3bf1b2c5f08f2b13d3 Mon Sep 17 00:00:00 2001 From: ark120202 Date: Tue, 20 Oct 2020 06:21:11 +0000 Subject: [PATCH 40/58] Use typescript internal api over "path" module for better windows compat --- src/transpilation/chunk/bundle.ts | 8 ++++---- src/transpilation/transpilation.ts | 8 ++++---- src/transpilation/transpile/index.ts | 15 ++------------- src/typescript-internal.d.ts | 7 ++++++- test/util.ts | 2 +- 5 files changed, 17 insertions(+), 23 deletions(-) diff --git a/src/transpilation/chunk/bundle.ts b/src/transpilation/chunk/bundle.ts index 67f25f200..6c52162c6 100644 --- a/src/transpilation/chunk/bundle.ts +++ b/src/transpilation/chunk/bundle.ts @@ -1,9 +1,9 @@ -import * as path from "path"; import { SourceNode } from "source-map"; +import * as ts from "typescript"; import { Chunk } from "."; import { CompilerOptions, isBundleEnabled } from "../../CompilerOptions"; import { escapeString } from "../../LuaPrinter"; -import { assert, normalizeSlashes } from "../../utils"; +import { assert } from "../../utils"; import { couldNotFindBundleEntryPoint } from "../diagnostics"; import { Module } from "../module"; import { Transpilation } from "../transpilation"; @@ -34,8 +34,8 @@ export function modulesToBundleChunks(transpilation: Transpilation, modules: Mod const options = transpilation.program.getCompilerOptions() as CompilerOptions; assert(isBundleEnabled(options)); - const outputPath = normalizeSlashes(path.resolve(transpilation.projectDir, options.luaBundle)); - const entryFileName = normalizeSlashes(path.resolve(transpilation.projectDir, options.luaBundleEntry)); + const outputPath = ts.getNormalizedAbsolutePath(options.luaBundle, transpilation.projectDir); + const entryFileName = ts.getNormalizedAbsolutePath(options.luaBundleEntry, transpilation.projectDir); const entryModule = modules.find(m => m.request === entryFileName); if (entryModule === undefined) { diff --git a/src/transpilation/transpilation.ts b/src/transpilation/transpilation.ts index 8ba3a2fad..36b605ea8 100644 --- a/src/transpilation/transpilation.ts +++ b/src/transpilation/transpilation.ts @@ -5,7 +5,7 @@ import { SourceNode } from "source-map"; import * as ts from "typescript"; import { CompilerOptions, isBundleEnabled, LuaTarget } from "../CompilerOptions"; import { getLuaLibBundle } from "../LuaLib"; -import { assert, cast, isNonNull, trimExtension } from "../utils"; +import { assert, cast, isNonNull, normalizeSlashes, trimExtension } from "../utils"; import { Chunk, modulesToBundleChunks, modulesToChunks } from "./chunk"; import { createResolutionErrorDiagnostic } from "./diagnostics"; import { buildModule, Module } from "./module"; @@ -39,7 +39,7 @@ export class Transpilation { this.projectDir = this.options.configFilePath !== undefined - ? path.dirname(this.options.configFilePath) + ? ts.getDirectoryPath(this.options.configFilePath) : this.host.getCurrentDirectory(); this.plugins = getPlugins(this, extraPlugins); @@ -85,10 +85,10 @@ export class Transpilation { let resolvedPath: string; try { - const result = this.resolver.resolveSync({}, path.dirname(issuer), request); + const result = this.resolver.resolveSync({}, ts.getDirectoryPath(issuer), request); assert(typeof result === "string", `Invalid resolution result: ${result}`); // https://github.com/webpack/enhanced-resolve#escaping - resolvedPath = result.replace(/\0#/g, "#"); + resolvedPath = normalizeSlashes(result.replace(/\0#/g, "#")); } catch (error) { if (!isResolveError(error)) throw error; return { error: error.message }; diff --git a/src/transpilation/transpile/index.ts b/src/transpilation/transpile/index.ts index 4d6477b34..8c3afdbac 100644 --- a/src/transpilation/transpile/index.ts +++ b/src/transpilation/transpile/index.ts @@ -1,9 +1,8 @@ -import * as path from "path"; import * as ts from "typescript"; import { CompilerOptions, validateOptions } from "../../CompilerOptions"; import { LuaPrinter } from "../../LuaPrinter"; import { createVisitorMap, transformSourceFile } from "../../transformation"; -import { assert, isNonNull } from "../../utils"; +import { isNonNull } from "../../utils"; import { applySinglePlugin } from "../plugins"; import { Transpilation } from "../transpilation"; import { getTransformers } from "./transformers"; @@ -57,17 +56,7 @@ export function emitProgramModules( transpilation.diagnostics.push(...transformDiagnostics); if (!options.noEmit && !options.emitDeclarationOnly) { const source = printer(program, transpilation.host, sourceFile.fileName, file); - - let request: string; - if (path.isAbsolute(sourceFile.fileName)) { - request = sourceFile.fileName; - } else { - const currentDirectory = transpilation.host.getCurrentDirectory(); - // Having no absolute path in path.resolve would make it fallback to real cwd - assert(path.isAbsolute(currentDirectory), `Invalid path: ${currentDirectory}`); - request = path.resolve(currentDirectory, sourceFile.fileName); - } - + const request = ts.getNormalizedAbsolutePath(sourceFile.fileName, transpilation.projectDir); transpilation.modules.push({ request, isBuilt: false, source, sourceFiles: [sourceFile] }); } }; diff --git a/src/typescript-internal.d.ts b/src/typescript-internal.d.ts index cc6df2d0b..01e7e4b5c 100644 --- a/src/typescript-internal.d.ts +++ b/src/typescript-internal.d.ts @@ -3,7 +3,12 @@ export {}; declare module "typescript" { function createDiagnosticReporter(system: System, pretty?: boolean): DiagnosticReporter; function createWatchStatusReporter(system: System, pretty?: boolean): WatchStatusReporter; - function getNormalizedAbsolutePath(fileName: string, currentDirectory: string): string; + + // https://github.com/microsoft/TypeScript/blob/master/src/compiler/path.ts + // Prefer to use these methods over "path" module, because they don't depend on runtime platform, + // preserving input path style, which works better with tests that always use "/" as cwd. + function getNormalizedAbsolutePath(fileName: string, currentDirectory: string | undefined): string; + function getDirectoryPath(path: string): string; interface System { setBlocking?(): void; diff --git a/test/util.ts b/test/util.ts index 6b60bd7b5..68387d7b6 100644 --- a/test/util.ts +++ b/test/util.ts @@ -260,7 +260,7 @@ export abstract class TestBuilder { host.resolutionFileSystem = virtualFS; host.getCurrentDirectory = () => "/"; host.readFile = (fileName, encoding = "utf8") => - fileName.includes("/lualib/") + /[\\/]lualib[\\/]/.test(fileName) ? ts.sys.readFile(fileName, encoding) : (virtualFS.readFileSync(fileName, encoding) as string); } From f728c17283a20d2fafa8d5cd46e3e5dc6c76a5ea Mon Sep 17 00:00:00 2001 From: ark120202 Date: Tue, 20 Oct 2020 06:50:15 +0000 Subject: [PATCH 41/58] Move managed-api out of transpilation module --- src/index.ts | 1 + src/{transpilation => }/managed-api/index.ts | 6 +++--- src/{transpilation => }/managed-api/utils.ts | 4 ++-- src/transpilation/index.ts | 1 - test/util.ts | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) rename src/{transpilation => }/managed-api/index.ts (92%) rename src/{transpilation => }/managed-api/utils.ts (96%) diff --git a/src/index.ts b/src/index.ts index 3d060a994..66d45e5dc 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,5 +6,6 @@ export * from "./CompilerOptions"; export * from "./LuaAST"; export { LuaLibFeature } from "./LuaLib"; export * from "./LuaPrinter"; +export * from "./managed-api"; export * from "./transformation/context"; export * from "./transpilation"; diff --git a/src/transpilation/managed-api/index.ts b/src/managed-api/index.ts similarity index 92% rename from src/transpilation/managed-api/index.ts rename to src/managed-api/index.ts index 380f9f8af..f278baa09 100644 --- a/src/transpilation/managed-api/index.ts +++ b/src/managed-api/index.ts @@ -1,7 +1,7 @@ import * as ts from "typescript"; -import { parseConfigFileWithSystem } from "../../cli/tsconfig"; -import { CompilerOptions } from "../../CompilerOptions"; -import { EmitResult, Transpiler } from "../transpiler"; +import { parseConfigFileWithSystem } from "../cli/tsconfig"; +import { CompilerOptions } from "../CompilerOptions"; +import { EmitResult, Transpiler } from "../transpilation"; import { createEmitOutputCollector, createVirtualProgram, TranspiledFile } from "./utils"; export { TranspiledFile }; diff --git a/src/transpilation/managed-api/utils.ts b/src/managed-api/utils.ts similarity index 96% rename from src/transpilation/managed-api/utils.ts rename to src/managed-api/utils.ts index 7f60eee93..90fa50f70 100644 --- a/src/transpilation/managed-api/utils.ts +++ b/src/managed-api/utils.ts @@ -1,8 +1,8 @@ import * as fs from "fs"; import * as path from "path"; import * as ts from "typescript"; -import { CompilerOptions } from "../../CompilerOptions"; -import { intersection, union } from "../../utils"; +import { CompilerOptions } from "../CompilerOptions"; +import { intersection, union } from "../utils"; const libCache = new Map(); export function createVirtualProgram(input: Record, options: CompilerOptions = {}): ts.Program { diff --git a/src/transpilation/index.ts b/src/transpilation/index.ts index 908c7189c..292d58ab4 100644 --- a/src/transpilation/index.ts +++ b/src/transpilation/index.ts @@ -1,5 +1,4 @@ export { Chunk } from "./chunk"; -export * from "./managed-api"; export { Module } from "./module"; export { Plugin } from "./plugins"; export * from "./transpile"; diff --git a/test/util.ts b/test/util.ts index 68387d7b6..98ebed5b8 100644 --- a/test/util.ts +++ b/test/util.ts @@ -9,7 +9,7 @@ import * as prettyFormat from "pretty-format"; import * as ts from "typescript"; import * as vm from "vm"; import * as tstl from "../src"; -import { createEmitOutputCollector, createVirtualProgram } from "../src/transpilation/managed-api/utils"; +import { createEmitOutputCollector, createVirtualProgram } from "../src/managed-api/utils"; export * from "./legacy-utils"; From f5cdf2c3c1bd1544c51044723e1d6c2a4bef65d9 Mon Sep 17 00:00:00 2001 From: ark120202 Date: Tue, 20 Oct 2020 06:51:48 +0000 Subject: [PATCH 42/58] Remove index from transformation module to signify that it's not public --- src/transformation/{index.ts => transform.ts} | 1 - src/transpilation/transpile/index.ts | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) rename src/transformation/{index.ts => transform.ts} (99%) diff --git a/src/transformation/index.ts b/src/transformation/transform.ts similarity index 99% rename from src/transformation/index.ts rename to src/transformation/transform.ts index 5b6fca647..58f500e36 100644 --- a/src/transformation/index.ts +++ b/src/transformation/transform.ts @@ -30,6 +30,5 @@ export function createVisitorMap(customVisitors: Visitors[]): VisitorMap { export function transformSourceFile(program: ts.Program, sourceFile: ts.SourceFile, visitorMap: VisitorMap) { const context = new TransformationContext(program, sourceFile, visitorMap); const [file] = context.transformNode(sourceFile) as [lua.File]; - return { file, diagnostics: context.diagnostics }; } diff --git a/src/transpilation/transpile/index.ts b/src/transpilation/transpile/index.ts index 8c3afdbac..b0a9bc76c 100644 --- a/src/transpilation/transpile/index.ts +++ b/src/transpilation/transpile/index.ts @@ -1,7 +1,7 @@ import * as ts from "typescript"; import { CompilerOptions, validateOptions } from "../../CompilerOptions"; import { LuaPrinter } from "../../LuaPrinter"; -import { createVisitorMap, transformSourceFile } from "../../transformation"; +import { createVisitorMap, transformSourceFile } from "../../transformation/transform"; import { isNonNull } from "../../utils"; import { applySinglePlugin } from "../plugins"; import { Transpilation } from "../transpilation"; From 3783d92dabedf824bca8a74aad7b2c65a80b92bd Mon Sep 17 00:00:00 2001 From: ark120202 Date: Tue, 20 Oct 2020 07:12:29 +0000 Subject: [PATCH 43/58] Prefer to use options on context objects --- src/transformation/utils/function-context.ts | 4 +--- src/transpilation/chunk/bundle.ts | 4 ++-- src/transpilation/transpilation.ts | 2 +- src/transpilation/transpile/index.ts | 5 ++--- src/transpilation/transpiler.ts | 4 ++-- src/tstl.ts | 2 +- 6 files changed, 9 insertions(+), 12 deletions(-) diff --git a/src/transformation/utils/function-context.ts b/src/transformation/utils/function-context.ts index 8bf05255c..d2a603139 100644 --- a/src/transformation/utils/function-context.ts +++ b/src/transformation/utils/function-context.ts @@ -1,5 +1,4 @@ import * as ts from "typescript"; -import { CompilerOptions } from "../../CompilerOptions"; import { TransformationContext } from "../context"; import { AnnotationKind, getFileAnnotations, getNodeAnnotations } from "./annotations"; import { findFirstNodeAbove, getAllCallSignatures, inferAssignedType } from "./typescript"; @@ -35,7 +34,7 @@ function getExplicitThisParameter(signatureDeclaration: ts.SignatureDeclaration) } export function getDeclarationContextType( - { program }: TransformationContext, + { program, options }: TransformationContext, signatureDeclaration: ts.SignatureDeclaration ): ContextType { const thisParameter = getExplicitThisParameter(signatureDeclaration); @@ -78,7 +77,6 @@ export function getDeclarationContextType( } // When using --noImplicitSelf and the signature is defined in a file targeted by the program apply the @noSelf rule. - const options = program.getCompilerOptions() as CompilerOptions; if (options.noImplicitSelf && program.getRootFileNames().includes(signatureDeclaration.getSourceFile().fileName)) { return ContextType.Void; } diff --git a/src/transpilation/chunk/bundle.ts b/src/transpilation/chunk/bundle.ts index 6c52162c6..819d2426c 100644 --- a/src/transpilation/chunk/bundle.ts +++ b/src/transpilation/chunk/bundle.ts @@ -1,7 +1,7 @@ import { SourceNode } from "source-map"; import * as ts from "typescript"; import { Chunk } from "."; -import { CompilerOptions, isBundleEnabled } from "../../CompilerOptions"; +import { isBundleEnabled } from "../../CompilerOptions"; import { escapeString } from "../../LuaPrinter"; import { assert } from "../../utils"; import { couldNotFindBundleEntryPoint } from "../diagnostics"; @@ -31,7 +31,7 @@ end `; export function modulesToBundleChunks(transpilation: Transpilation, modules: Module[]): Chunk[] { - const options = transpilation.program.getCompilerOptions() as CompilerOptions; + const { options } = transpilation; assert(isBundleEnabled(options)); const outputPath = ts.getNormalizedAbsolutePath(options.luaBundle, transpilation.projectDir); diff --git a/src/transpilation/transpilation.ts b/src/transpilation/transpilation.ts index 36b605ea8..b0b266e67 100644 --- a/src/transpilation/transpilation.ts +++ b/src/transpilation/transpilation.ts @@ -18,7 +18,7 @@ export class Transpilation { public modules: Module[] = []; public host: TranspilerHost; - public options = this.program.getCompilerOptions() as CompilerOptions; + public options: CompilerOptions = this.program.getCompilerOptions(); public rootDir: string; public outDir: string; public projectDir: string; diff --git a/src/transpilation/transpile/index.ts b/src/transpilation/transpile/index.ts index b0a9bc76c..bde08c206 100644 --- a/src/transpilation/transpile/index.ts +++ b/src/transpilation/transpile/index.ts @@ -1,5 +1,5 @@ import * as ts from "typescript"; -import { CompilerOptions, validateOptions } from "../../CompilerOptions"; +import { validateOptions } from "../../CompilerOptions"; import { LuaPrinter } from "../../LuaPrinter"; import { createVisitorMap, transformSourceFile } from "../../transformation/transform"; import { isNonNull } from "../../utils"; @@ -18,8 +18,7 @@ export function emitProgramModules( writeFileResult: ts.WriteFileCallback, { program, sourceFiles: targetSourceFiles, customTransformers = {} }: TranspileOptions ) { - const options = program.getCompilerOptions() as CompilerOptions; - + const { options } = transpilation; transpilation.diagnostics.push(...validateOptions(options)); if (options.noEmitOnError) { diff --git a/src/transpilation/transpiler.ts b/src/transpilation/transpiler.ts index 7c076eb53..d4b9b175a 100644 --- a/src/transpilation/transpiler.ts +++ b/src/transpilation/transpiler.ts @@ -31,9 +31,9 @@ export class Transpiler { public emit(emitOptions: EmitOptions): EmitResult { const { program, writeFile = this.host.writeFile } = emitOptions; - const options = program.getCompilerOptions(); - const transpilation = new Transpilation(this, program, emitOptions.plugins ?? []); + const { options } = transpilation; + emitProgramModules(transpilation, writeFile, emitOptions); if (options.noEmit || (options.noEmitOnError && transpilation.diagnostics.length > 0)) { return { diagnostics: transpilation.diagnostics, emitSkipped: true }; diff --git a/src/tstl.ts b/src/tstl.ts index fb011be3c..bb74fdb6d 100644 --- a/src/tstl.ts +++ b/src/tstl.ts @@ -158,7 +158,7 @@ function updateWatchCompilationHost( const transpiler = new tstl.Transpiler(); host.afterProgramCreate = builderProgram => { const program = builderProgram.getProgram(); - const options = builderProgram.getCompilerOptions() as tstl.CompilerOptions; + const options: tstl.CompilerOptions = builderProgram.getCompilerOptions(); const configFileParsingDiagnostics: ts.Diagnostic[] = updateConfigFile(options); let sourceFiles: ts.SourceFile[] | undefined; From 41dafddb74476fb8f8c4631e0dc019433d182a15 Mon Sep 17 00:00:00 2001 From: ark120202 Date: Tue, 20 Oct 2020 07:38:20 +0000 Subject: [PATCH 44/58] Move asset emission to Transpilation --- src/transpilation/chunk/{print.ts => assets.ts} | 2 +- src/transpilation/chunk/index.ts | 2 +- src/transpilation/transpilation.ts | 16 +++++++++++++--- src/transpilation/transpiler.ts | 14 ++------------ 4 files changed, 17 insertions(+), 17 deletions(-) rename src/transpilation/chunk/{print.ts => assets.ts} (98%) diff --git a/src/transpilation/chunk/print.ts b/src/transpilation/chunk/assets.ts similarity index 98% rename from src/transpilation/chunk/print.ts rename to src/transpilation/chunk/assets.ts index a6d25035d..31709ed04 100644 --- a/src/transpilation/chunk/print.ts +++ b/src/transpilation/chunk/assets.ts @@ -3,7 +3,7 @@ import { Mapping, SourceMapGenerator, SourceNode, StartOfSourceMap } from "sourc import { Chunk } from "."; import { CompilerOptions } from "../../CompilerOptions"; -export function printChunk(chunk: Chunk, options: CompilerOptions) { +export function chunkToAssets(chunk: Chunk, options: CompilerOptions) { const sourceRoot = options.sourceRoot ? // According to spec, sourceRoot is simply prepended to the source name, so the slash should be included options.sourceRoot.replace(/[\\/]+$/, "") + "/" diff --git a/src/transpilation/chunk/index.ts b/src/transpilation/chunk/index.ts index 3faaa3009..725bed952 100644 --- a/src/transpilation/chunk/index.ts +++ b/src/transpilation/chunk/index.ts @@ -5,8 +5,8 @@ import { normalizeSlashes } from "../../utils"; import { Module } from "../module"; import { Transpilation } from "../transpilation"; +export * from "./assets"; export * from "./bundle"; -export * from "./print"; export interface Chunk { outputPath: string; diff --git a/src/transpilation/transpilation.ts b/src/transpilation/transpilation.ts index b0b266e67..7b4d3bafb 100644 --- a/src/transpilation/transpilation.ts +++ b/src/transpilation/transpilation.ts @@ -6,7 +6,7 @@ import * as ts from "typescript"; import { CompilerOptions, isBundleEnabled, LuaTarget } from "../CompilerOptions"; import { getLuaLibBundle } from "../LuaLib"; import { assert, cast, isNonNull, normalizeSlashes, trimExtension } from "../utils"; -import { Chunk, modulesToBundleChunks, modulesToChunks } from "./chunk"; +import { Chunk, chunkToAssets, modulesToBundleChunks, modulesToChunks } from "./chunk"; import { createResolutionErrorDiagnostic } from "./diagnostics"; import { buildModule, Module } from "./module"; import { applyBailPlugin, applySinglePlugin, getPlugins, Plugin } from "./plugins"; @@ -53,9 +53,19 @@ export class Transpilation { }); } - public emit(): Chunk[] { + public emit(writeFile: ts.WriteFileCallback) { this.modules.forEach(module => this.buildModule(module)); - return this.mapModulesToChunks(this.modules); + + const chunks = this.mapModulesToChunks(this.modules); + + const emitBOM = this.options.emitBOM ?? false; + for (const chunk of chunks) { + const { code, sourceMap } = chunkToAssets(chunk, this.options); + writeFile(chunk.outputPath, code, emitBOM, undefined, chunk.sourceFiles); + if (sourceMap !== undefined) { + writeFile(chunk.outputPath + ".map", sourceMap, emitBOM, undefined, chunk.sourceFiles); + } + } } private buildModule(module: Module) { diff --git a/src/transpilation/transpiler.ts b/src/transpilation/transpiler.ts index d4b9b175a..c6038c780 100644 --- a/src/transpilation/transpiler.ts +++ b/src/transpilation/transpiler.ts @@ -1,6 +1,5 @@ import { FileSystem } from "enhanced-resolve"; import * as ts from "typescript"; -import { printChunk } from "./chunk"; import { Plugin } from "./plugins"; import { Transpilation } from "./transpilation"; import { emitProgramModules, TranspileOptions } from "./transpile"; @@ -39,17 +38,8 @@ export class Transpiler { return { diagnostics: transpilation.diagnostics, emitSkipped: true }; } - const chunks = transpilation.emit(); + transpilation.emit(writeFile); - const emitBOM = options.emitBOM ?? false; - for (const chunk of chunks) { - const { code, sourceMap } = printChunk(chunk, options); - writeFile(chunk.outputPath, code, emitBOM, undefined, chunk.sourceFiles); - if (sourceMap !== undefined) { - writeFile(chunk.outputPath + ".map", sourceMap, emitBOM, undefined, chunk.sourceFiles); - } - } - - return { diagnostics: transpilation.diagnostics, emitSkipped: chunks.length === 0 }; + return { diagnostics: transpilation.diagnostics, emitSkipped: false }; } } From 5e2bbdf713b803cdc274272e662f265dc1fa63b7 Mon Sep 17 00:00:00 2001 From: ark120202 Date: Tue, 20 Oct 2020 08:01:48 +0000 Subject: [PATCH 45/58] --mode lib --- src/CompilerOptions.ts | 6 ++++++ src/cli/parse.ts | 9 ++++++++- src/transpilation/transpilation.ts | 4 +++- .../__snapshots__/resolution.spec.ts.snap | 9 +++++++++ test/transpile/resolution.spec.ts | 15 +++++++++++++++ 5 files changed, 41 insertions(+), 2 deletions(-) diff --git a/src/CompilerOptions.ts b/src/CompilerOptions.ts index 92c2cbf70..b96da5fa9 100644 --- a/src/CompilerOptions.ts +++ b/src/CompilerOptions.ts @@ -24,6 +24,7 @@ export interface LuaPluginImport { } export type CompilerOptions = OmitIndexSignature & { + mode?: TranspilerMode; noImplicitSelf?: boolean; noHeader?: boolean; luaBundle?: string; @@ -36,6 +37,11 @@ export type CompilerOptions = OmitIndexSignature & { [option: string]: any; }; +export enum TranspilerMode { + App = "app", + Lib = "lib", +} + export enum LuaLibImportKind { None = "none", Always = "always", diff --git a/src/cli/parse.ts b/src/cli/parse.ts index fff0ef0d0..eedd7cb80 100644 --- a/src/cli/parse.ts +++ b/src/cli/parse.ts @@ -1,5 +1,5 @@ import * as ts from "typescript"; -import { CompilerOptions, LuaLibImportKind, LuaTarget } from "../CompilerOptions"; +import { CompilerOptions, LuaLibImportKind, LuaTarget, TranspilerMode } from "../CompilerOptions"; import * as cliDiagnostics from "./diagnostics"; export interface ParsedCommandLine extends ts.ParsedCommandLine { @@ -24,6 +24,13 @@ interface CommandLineOptionOfPrimitive extends CommandLineOptionBase { type CommandLineOption = CommandLineOptionOfEnum | CommandLineOptionOfPrimitive; export const optionDeclarations: CommandLineOption[] = [ + { + name: "mode", + description: + 'In "lib" mode, imported modules are emitted without being resolved. Visit https://typescripttolua.github.io/docs/TODO for more details.', + type: "enum", + choices: Object.values(TranspilerMode), + }, { name: "luaBundle", description: "The name of the lua file to bundle output lua to. Requires luaBundleEntry.", diff --git a/src/transpilation/transpilation.ts b/src/transpilation/transpilation.ts index 7b4d3bafb..a000d7259 100644 --- a/src/transpilation/transpilation.ts +++ b/src/transpilation/transpilation.ts @@ -3,7 +3,7 @@ import * as fs from "fs"; import * as path from "path"; import { SourceNode } from "source-map"; import * as ts from "typescript"; -import { CompilerOptions, isBundleEnabled, LuaTarget } from "../CompilerOptions"; +import { CompilerOptions, isBundleEnabled, LuaTarget, TranspilerMode } from "../CompilerOptions"; import { getLuaLibBundle } from "../LuaLib"; import { assert, cast, isNonNull, normalizeSlashes, trimExtension } from "../utils"; import { Chunk, chunkToAssets, modulesToBundleChunks, modulesToChunks } from "./chunk"; @@ -69,6 +69,8 @@ export class Transpilation { } private buildModule(module: Module) { + if (this.options.mode === TranspilerMode.Lib) return; + buildModule(module, (request, position) => { const result = this.resolveRequestToModule(module.request, request); if ("error" in result) { diff --git a/test/transpile/__snapshots__/resolution.spec.ts.snap b/test/transpile/__snapshots__/resolution.spec.ts.snap index 84eae968d..7ccd64743 100644 --- a/test/transpile/__snapshots__/resolution.spec.ts.snap +++ b/test/transpile/__snapshots__/resolution.spec.ts.snap @@ -36,6 +36,15 @@ return ____exports end," `; +exports[`mode: "lib" doesn't fail on unresolvable requests: modules 1`] = ` +"[\\"main\\"] = function() +local ____exports = {} +require(__TS__Resolve(\\"./module\\")) +return ____exports + +end," +`; + exports[`not transpiled script file error: diagnostics 1`] = `"/main.ts(2,22): error TSTL: Resolved source file '/module.ts' is not a part of the project."`; exports[`not transpiled script file error: modules 1`] = ` diff --git a/test/transpile/resolution.spec.ts b/test/transpile/resolution.spec.ts index 018ea745e..dd4726fc9 100644 --- a/test/transpile/resolution.spec.ts +++ b/test/transpile/resolution.spec.ts @@ -1,3 +1,4 @@ +import * as tstl from "../../src"; import { createResolutionErrorDiagnostic } from "../../src/transpilation/diagnostics"; import * as util from "../util"; @@ -212,3 +213,17 @@ test.each([ .addExtraFile("module.d.ts", declarationStatement) .tap(expectToRequire("fake")); }); + +describe('mode: "lib"', () => { + test("doesn't fail on unresolvable requests", () => { + util.testBundle` + import "./module"; + ` + .addExtraFile("module.d.ts", "export const foo = true;") + .setOptions({ mode: tstl.TranspilerMode.Lib }) + .expectToHaveNoDiagnostics() + .tap(expectModuleTableToMatchSnapshot); + }); + + test.todo('gets resolved with mode: "app" from different compilation'); +}); From cd9f9a673372b0992839830aac91e0e452ea0513 Mon Sep 17 00:00:00 2001 From: ark120202 Date: Tue, 20 Oct 2020 08:29:36 +0000 Subject: [PATCH 46/58] Transpiler -> Compiler --- src/CompilerOptions.ts | 6 +-- src/LuaLib.ts | 6 +-- src/LuaPrinter.ts | 6 +-- src/cli/parse.ts | 4 +- .../chunk/assets.ts | 0 .../chunk/bundle.ts | 16 +++---- .../chunk/index.ts | 8 ++-- .../compilation.ts} | 14 +++--- src/compiler/compiler.ts | 41 +++++++++++++++++ .../diagnostics.ts | 0 src/{transpilation => compiler}/index.ts | 2 +- src/{transpilation => compiler}/module.ts | 0 src/{transpilation => compiler}/plugins.ts | 16 +++---- .../transpile/index.ts | 28 ++++++------ .../transpile/transformers.ts | 22 ++++----- src/{transpilation => compiler}/utils.ts | 0 src/index.ts | 2 +- src/managed-api/index.ts | 8 ++-- src/transformation/visitors/namespace.ts | 2 +- src/transpilation/transpiler.ts | 45 ------------------- src/tstl.ts | 7 ++- .../__fixtures__/plugins/getModuleId.ts | 4 +- test/transpile/load-config-import.spec.ts | 2 +- test/transpile/resolution.spec.ts | 4 +- test/unit/bundle.spec.ts | 2 +- test/util.ts | 4 +- 26 files changed, 123 insertions(+), 126 deletions(-) rename src/{transpilation => compiler}/chunk/assets.ts (100%) rename src/{transpilation => compiler}/chunk/bundle.ts (82%) rename src/{transpilation => compiler}/chunk/index.ts (60%) rename src/{transpilation/transpilation.ts => compiler/compilation.ts} (93%) create mode 100644 src/compiler/compiler.ts rename src/{transpilation => compiler}/diagnostics.ts (100%) rename src/{transpilation => compiler}/index.ts (81%) rename src/{transpilation => compiler}/module.ts (100%) rename src/{transpilation => compiler}/plugins.ts (79%) rename src/{transpilation => compiler}/transpile/index.ts (77%) rename src/{transpilation => compiler}/transpile/transformers.ts (91%) rename src/{transpilation => compiler}/utils.ts (100%) delete mode 100644 src/transpilation/transpiler.ts diff --git a/src/CompilerOptions.ts b/src/CompilerOptions.ts index b96da5fa9..f24bf297d 100644 --- a/src/CompilerOptions.ts +++ b/src/CompilerOptions.ts @@ -1,5 +1,5 @@ import * as ts from "typescript"; -import * as diagnosticFactories from "./transpilation/diagnostics"; +import * as diagnosticFactories from "./compiler/diagnostics"; type KnownKeys = { [K in keyof T]: string extends K ? never : number extends K ? never : K } extends { [K in keyof T]: infer U; @@ -24,7 +24,7 @@ export interface LuaPluginImport { } export type CompilerOptions = OmitIndexSignature & { - mode?: TranspilerMode; + mode?: CompilerMode; noImplicitSelf?: boolean; noHeader?: boolean; luaBundle?: string; @@ -37,7 +37,7 @@ export type CompilerOptions = OmitIndexSignature & { [option: string]: any; }; -export enum TranspilerMode { +export enum CompilerMode { App = "app", Lib = "lib", } diff --git a/src/LuaLib.ts b/src/LuaLib.ts index 2b0a528cf..4f5d1fda9 100644 --- a/src/LuaLib.ts +++ b/src/LuaLib.ts @@ -1,5 +1,5 @@ import * as path from "path"; -import { TranspilerHost } from "./transpilation"; +import { CompilerHost } from "./compiler"; export enum LuaLibFeature { ArrayConcat = "ArrayConcat", @@ -105,7 +105,7 @@ const luaLibDependencies: Partial> = { SymbolRegistry: [LuaLibFeature.Symbol], }; -export function loadLuaLibFeatures(features: Iterable, host: TranspilerHost): string { +export function loadLuaLibFeatures(features: Iterable, host: CompilerHost): string { let result = ""; const loadedFeatures = new Set(); @@ -136,7 +136,7 @@ export function loadLuaLibFeatures(features: Iterable, host: Tran } let luaLibBundleContent: string; -export function getLuaLibBundle(host: TranspilerHost): string { +export function getLuaLibBundle(host: CompilerHost): string { if (luaLibBundleContent === undefined) { const lualibPath = path.resolve(__dirname, "../dist/lualib/lualib_bundle.lua"); const result = host.readFile(lualibPath); diff --git a/src/LuaPrinter.ts b/src/LuaPrinter.ts index 7bb13886f..c8f82ee52 100644 --- a/src/LuaPrinter.ts +++ b/src/LuaPrinter.ts @@ -1,11 +1,11 @@ import * as path from "path"; import { SourceNode } from "source-map"; import * as ts from "typescript"; +import { CompilerHost } from "./compiler"; import { CompilerOptions, LuaLibImportKind } from "./CompilerOptions"; import * as lua from "./LuaAST"; import { loadLuaLibFeatures, LuaLibFeature } from "./LuaLib"; import { isValidLuaIdentifier } from "./transformation/utils/safe-names"; -import { TranspilerHost } from "./transpilation"; import { assert, intersperse, invertObject, normalizeSlashes } from "./utils"; // https://www.lua.org/pil/2.4.html @@ -85,7 +85,7 @@ function isSimpleExpression(expression: lua.Expression): boolean { type SourceChunk = string | SourceNode; -export type Printer = (program: ts.Program, host: TranspilerHost, fileName: string, file: lua.File) => SourceNode; +export type Printer = (program: ts.Program, host: CompilerHost, fileName: string, file: lua.File) => SourceNode; export class LuaPrinter { private static operatorMap: Record = { @@ -120,7 +120,7 @@ export class LuaPrinter { private fileName: string; private options: CompilerOptions; - constructor(private host: TranspilerHost, program: ts.Program, fileName: string) { + constructor(private host: CompilerHost, program: ts.Program, fileName: string) { this.options = program.getCompilerOptions(); if (this.options.outDir) { diff --git a/src/cli/parse.ts b/src/cli/parse.ts index eedd7cb80..3bf4b27c9 100644 --- a/src/cli/parse.ts +++ b/src/cli/parse.ts @@ -1,5 +1,5 @@ import * as ts from "typescript"; -import { CompilerOptions, LuaLibImportKind, LuaTarget, TranspilerMode } from "../CompilerOptions"; +import { CompilerMode, CompilerOptions, LuaLibImportKind, LuaTarget } from "../CompilerOptions"; import * as cliDiagnostics from "./diagnostics"; export interface ParsedCommandLine extends ts.ParsedCommandLine { @@ -29,7 +29,7 @@ export const optionDeclarations: CommandLineOption[] = [ description: 'In "lib" mode, imported modules are emitted without being resolved. Visit https://typescripttolua.github.io/docs/TODO for more details.', type: "enum", - choices: Object.values(TranspilerMode), + choices: Object.values(CompilerMode), }, { name: "luaBundle", diff --git a/src/transpilation/chunk/assets.ts b/src/compiler/chunk/assets.ts similarity index 100% rename from src/transpilation/chunk/assets.ts rename to src/compiler/chunk/assets.ts diff --git a/src/transpilation/chunk/bundle.ts b/src/compiler/chunk/bundle.ts similarity index 82% rename from src/transpilation/chunk/bundle.ts rename to src/compiler/chunk/bundle.ts index 819d2426c..c24b54f04 100644 --- a/src/transpilation/chunk/bundle.ts +++ b/src/compiler/chunk/bundle.ts @@ -4,9 +4,9 @@ import { Chunk } from "."; import { isBundleEnabled } from "../../CompilerOptions"; import { escapeString } from "../../LuaPrinter"; import { assert } from "../../utils"; +import { Compilation } from "../compilation"; import { couldNotFindBundleEntryPoint } from "../diagnostics"; import { Module } from "../module"; -import { Transpilation } from "../transpilation"; // Override `require` to read from ____modules table. const requireOverride = ` @@ -30,27 +30,27 @@ local function require(file) end `; -export function modulesToBundleChunks(transpilation: Transpilation, modules: Module[]): Chunk[] { - const { options } = transpilation; +export function modulesToBundleChunks(compilation: Compilation, modules: Module[]): Chunk[] { + const { options } = compilation; assert(isBundleEnabled(options)); - const outputPath = ts.getNormalizedAbsolutePath(options.luaBundle, transpilation.projectDir); - const entryFileName = ts.getNormalizedAbsolutePath(options.luaBundleEntry, transpilation.projectDir); + const outputPath = ts.getNormalizedAbsolutePath(options.luaBundle, compilation.projectDir); + const entryFileName = ts.getNormalizedAbsolutePath(options.luaBundleEntry, compilation.projectDir); const entryModule = modules.find(m => m.request === entryFileName); if (entryModule === undefined) { - transpilation.diagnostics.push(couldNotFindBundleEntryPoint(options.luaBundleEntry)); + compilation.diagnostics.push(couldNotFindBundleEntryPoint(options.luaBundleEntry)); return [{ outputPath, source: new SourceNode() }]; } // For each file: [""] = function() end, - const moduleTableEntries = modules.map(m => moduleSourceNode(m, transpilation.getModuleId(m))); + const moduleTableEntries = modules.map(m => moduleSourceNode(m, compilation.getModuleId(m))); // Create ____modules table containing all entries from moduleTableEntries const moduleTable = createModuleTableNode(moduleTableEntries); // return require("") - const bootstrap = `return require(${escapeString(transpilation.getModuleId(entryModule))})\n`; + const bootstrap = `return require(${escapeString(compilation.getModuleId(entryModule))})\n`; const bundleNode = joinSourceChunks([requireOverride, moduleTable, bootstrap]); const sourceFiles = modules.flatMap(x => x.sourceFiles ?? []); diff --git a/src/transpilation/chunk/index.ts b/src/compiler/chunk/index.ts similarity index 60% rename from src/transpilation/chunk/index.ts rename to src/compiler/chunk/index.ts index 725bed952..3c0c04610 100644 --- a/src/transpilation/chunk/index.ts +++ b/src/compiler/chunk/index.ts @@ -2,8 +2,8 @@ import * as path from "path"; import { SourceNode } from "source-map"; import * as ts from "typescript"; import { normalizeSlashes } from "../../utils"; +import { Compilation } from "../compilation"; import { Module } from "../module"; -import { Transpilation } from "../transpilation"; export * from "./assets"; export * from "./bundle"; @@ -14,10 +14,10 @@ export interface Chunk { sourceFiles?: ts.SourceFile[]; } -export function modulesToChunks(transpilation: Transpilation, modules: Module[]): Chunk[] { +export function modulesToChunks(compilation: Compilation, modules: Module[]): Chunk[] { return modules.map(module => { - const moduleId = transpilation.getModuleId(module); - const outputPath = normalizeSlashes(path.resolve(transpilation.outDir, `${moduleId.replace(/\./g, "/")}.lua`)); + const moduleId = compilation.getModuleId(module); + const outputPath = normalizeSlashes(path.resolve(compilation.outDir, `${moduleId.replace(/\./g, "/")}.lua`)); return { outputPath, source: module.source, sourceFiles: module.sourceFiles }; }); } diff --git a/src/transpilation/transpilation.ts b/src/compiler/compilation.ts similarity index 93% rename from src/transpilation/transpilation.ts rename to src/compiler/compilation.ts index a000d7259..1296ab65b 100644 --- a/src/transpilation/transpilation.ts +++ b/src/compiler/compilation.ts @@ -3,21 +3,21 @@ import * as fs from "fs"; import * as path from "path"; import { SourceNode } from "source-map"; import * as ts from "typescript"; -import { CompilerOptions, isBundleEnabled, LuaTarget, TranspilerMode } from "../CompilerOptions"; +import { CompilerMode, CompilerOptions, isBundleEnabled, LuaTarget } from "../CompilerOptions"; import { getLuaLibBundle } from "../LuaLib"; import { assert, cast, isNonNull, normalizeSlashes, trimExtension } from "../utils"; import { Chunk, chunkToAssets, modulesToBundleChunks, modulesToChunks } from "./chunk"; +import { Compiler, CompilerHost } from "./compiler"; import { createResolutionErrorDiagnostic } from "./diagnostics"; import { buildModule, Module } from "./module"; import { applyBailPlugin, applySinglePlugin, getPlugins, Plugin } from "./plugins"; -import { Transpiler, TranspilerHost } from "./transpiler"; import { isResolveError } from "./utils"; -export class Transpilation { +export class Compilation { public readonly diagnostics: ts.Diagnostic[] = []; public modules: Module[] = []; - public host: TranspilerHost; + public host: CompilerHost; public options: CompilerOptions = this.program.getCompilerOptions(); public rootDir: string; public outDir: string; @@ -26,8 +26,8 @@ export class Transpilation { public plugins: Plugin[]; protected resolver: Resolver; - constructor(public transpiler: Transpiler, public program: ts.Program, extraPlugins: Plugin[]) { - this.host = transpiler.host; + constructor(public compiler: Compiler, public program: ts.Program, extraPlugins: Plugin[]) { + this.host = compiler.host; this.rootDir = // getCommonSourceDirectory ignores provided rootDir when TS6059 is emitted @@ -69,7 +69,7 @@ export class Transpilation { } private buildModule(module: Module) { - if (this.options.mode === TranspilerMode.Lib) return; + if (this.options.mode === CompilerMode.Lib) return; buildModule(module, (request, position) => { const result = this.resolveRequestToModule(module.request, request); diff --git a/src/compiler/compiler.ts b/src/compiler/compiler.ts new file mode 100644 index 000000000..ebbd628f6 --- /dev/null +++ b/src/compiler/compiler.ts @@ -0,0 +1,41 @@ +import { FileSystem } from "enhanced-resolve"; +import * as ts from "typescript"; +import { Compilation } from "./compilation"; +import { Plugin } from "./plugins"; +import { emitProgramModules, TranspileOptions } from "./transpile"; + +export interface CompilerHost extends Pick { + resolutionFileSystem?: FileSystem; +} + +export interface EmitOptions extends TranspileOptions { + writeFile?: ts.WriteFileCallback; + plugins?: Plugin[]; +} + +export interface EmitResult { + emitSkipped: boolean; + diagnostics: readonly ts.Diagnostic[]; +} + +export class Compiler { + public host: CompilerHost; + constructor({ host = ts.sys }: { host?: CompilerHost } = {}) { + this.host = host; + } + + public emit(emitOptions: EmitOptions): EmitResult { + const { program, writeFile = this.host.writeFile } = emitOptions; + const compilation = new Compilation(this, program, emitOptions.plugins ?? []); + const { options } = compilation; + + emitProgramModules(compilation, writeFile, emitOptions); + if (options.noEmit || (options.noEmitOnError && compilation.diagnostics.length > 0)) { + return { diagnostics: compilation.diagnostics, emitSkipped: true }; + } + + compilation.emit(writeFile); + + return { diagnostics: compilation.diagnostics, emitSkipped: false }; + } +} diff --git a/src/transpilation/diagnostics.ts b/src/compiler/diagnostics.ts similarity index 100% rename from src/transpilation/diagnostics.ts rename to src/compiler/diagnostics.ts diff --git a/src/transpilation/index.ts b/src/compiler/index.ts similarity index 81% rename from src/transpilation/index.ts rename to src/compiler/index.ts index 292d58ab4..c120cd43a 100644 --- a/src/transpilation/index.ts +++ b/src/compiler/index.ts @@ -1,5 +1,5 @@ export { Chunk } from "./chunk"; +export * from "./compiler"; export { Module } from "./module"; export { Plugin } from "./plugins"; export * from "./transpile"; -export * from "./transpiler"; diff --git a/src/transpilation/module.ts b/src/compiler/module.ts similarity index 100% rename from src/transpilation/module.ts rename to src/compiler/module.ts diff --git a/src/transpilation/plugins.ts b/src/compiler/plugins.ts similarity index 79% rename from src/transpilation/plugins.ts rename to src/compiler/plugins.ts index 6bd11b11d..b819b77b3 100644 --- a/src/transpilation/plugins.ts +++ b/src/compiler/plugins.ts @@ -2,8 +2,8 @@ import { Plugin as ResolvePlugin } from "enhanced-resolve"; import { Printer } from "../LuaPrinter"; import { Visitors } from "../transformation/context"; import { Chunk } from "./chunk"; +import { Compilation } from "./compilation"; import { Module } from "./module"; -import { Transpilation } from "./transpilation"; import { loadConfigImport } from "./utils"; export interface Plugin { @@ -25,35 +25,35 @@ export interface Plugin { * Provide extra [enhanced-resolve](https://github.com/webpack/enhanced-resolve) plugins, * used for `.lua` module resolution. */ - getResolvePlugins?(transpilation: Transpilation): ResolvePlugin[]; + getResolvePlugins?(compilation: Compilation): ResolvePlugin[]; /** * Transform modules into chunks. */ - mapModulesToChunks?(modules: Module[], transpilation: Transpilation): Chunk[]; + mapModulesToChunks?(modules: Module[], compilation: Compilation): Chunk[]; /** * Produce a unique identifier for a module, which would be used as `require` call parameter, * and may be used for chunk naming. */ - getModuleId?(module: Module, transpilation: Transpilation): string | undefined; + getModuleId?(module: Module, compilation: Compilation): string | undefined; } -export function getPlugins(transpilation: Transpilation, customPlugins: Plugin[]): Plugin[] { +export function getPlugins(compilation: Compilation, customPlugins: Plugin[]): Plugin[] { const pluginsFromOptions: Plugin[] = []; - for (const [index, pluginOption] of (transpilation.options.luaPlugins ?? []).entries()) { + for (const [index, pluginOption] of (compilation.options.luaPlugins ?? []).entries()) { const optionName = `tstl.luaPlugins[${index}]`; const { error: resolveError, result: factory } = loadConfigImport( "plugin", `${optionName}.name`, - transpilation.projectDir, + compilation.projectDir, pluginOption.name, pluginOption.import ); - if (resolveError) transpilation.diagnostics.push(resolveError); + if (resolveError) compilation.diagnostics.push(resolveError); if (factory === undefined) continue; const plugin = typeof factory === "function" ? factory(pluginOption) : factory; diff --git a/src/transpilation/transpile/index.ts b/src/compiler/transpile/index.ts similarity index 77% rename from src/transpilation/transpile/index.ts rename to src/compiler/transpile/index.ts index bde08c206..dce56a5c6 100644 --- a/src/transpilation/transpile/index.ts +++ b/src/compiler/transpile/index.ts @@ -3,8 +3,8 @@ import { validateOptions } from "../../CompilerOptions"; import { LuaPrinter } from "../../LuaPrinter"; import { createVisitorMap, transformSourceFile } from "../../transformation/transform"; import { isNonNull } from "../../utils"; +import { Compilation } from "../compilation"; import { applySinglePlugin } from "../plugins"; -import { Transpilation } from "../transpilation"; import { getTransformers } from "./transformers"; export interface TranspileOptions { @@ -14,12 +14,12 @@ export interface TranspileOptions { } export function emitProgramModules( - transpilation: Transpilation, + compilation: Compilation, writeFileResult: ts.WriteFileCallback, { program, sourceFiles: targetSourceFiles, customTransformers = {} }: TranspileOptions ) { - const { options } = transpilation; - transpilation.diagnostics.push(...validateOptions(options)); + const { options } = compilation; + compilation.diagnostics.push(...validateOptions(options)); if (options.noEmitOnError) { const preEmitDiagnostics = [...program.getOptionsDiagnostics(), ...program.getGlobalDiagnostics()]; @@ -39,28 +39,28 @@ export function emitProgramModules( } if (preEmitDiagnostics.length > 0) { - transpilation.diagnostics.push(...preEmitDiagnostics); + compilation.diagnostics.push(...preEmitDiagnostics); return; } } - const visitorMap = createVisitorMap(transpilation.plugins.map(p => p.visitors).filter(isNonNull)); + const visitorMap = createVisitorMap(compilation.plugins.map(p => p.visitors).filter(isNonNull)); const printer = - applySinglePlugin(transpilation.plugins, "printer") ?? + applySinglePlugin(compilation.plugins, "printer") ?? ((program, host, fileName, file) => new LuaPrinter(host, program, fileName).print(file)); const processSourceFile = (sourceFile: ts.SourceFile) => { const { file, diagnostics: transformDiagnostics } = transformSourceFile(program, sourceFile, visitorMap); - transpilation.diagnostics.push(...transformDiagnostics); + compilation.diagnostics.push(...transformDiagnostics); if (!options.noEmit && !options.emitDeclarationOnly) { - const source = printer(program, transpilation.host, sourceFile.fileName, file); - const request = ts.getNormalizedAbsolutePath(sourceFile.fileName, transpilation.projectDir); - transpilation.modules.push({ request, isBuilt: false, source, sourceFiles: [sourceFile] }); + const source = printer(program, compilation.host, sourceFile.fileName, file); + const request = ts.getNormalizedAbsolutePath(sourceFile.fileName, compilation.projectDir); + compilation.modules.push({ request, isBuilt: false, source, sourceFiles: [sourceFile] }); } }; - const transformers = getTransformers(transpilation, customTransformers, processSourceFile); + const transformers = getTransformers(compilation, customTransformers, processSourceFile); const isEmittableJsonFile = (sourceFile: ts.SourceFile) => sourceFile.flags & ts.NodeFlags.JsonFile && @@ -83,12 +83,12 @@ export function emitProgramModules( processSourceFile(file); } else { const { diagnostics } = program.emit(file, writeFile, undefined, false, transformers); - transpilation.diagnostics.push(...diagnostics); + compilation.diagnostics.push(...diagnostics); } } } else { const { diagnostics } = program.emit(undefined, writeFile, undefined, false, transformers); - transpilation.diagnostics.push(...diagnostics); + compilation.diagnostics.push(...diagnostics); // JSON files don't get through transformers and aren't written when outDir is the same as rootDir program.getSourceFiles().filter(isEmittableJsonFile).forEach(processSourceFile); diff --git a/src/transpilation/transpile/transformers.ts b/src/compiler/transpile/transformers.ts similarity index 91% rename from src/transpilation/transpile/transformers.ts rename to src/compiler/transpile/transformers.ts index a13b46056..f71a26e55 100644 --- a/src/transpilation/transpile/transformers.ts +++ b/src/compiler/transpile/transformers.ts @@ -2,12 +2,12 @@ import * as ts from "typescript"; // TODO: Don't depend on CLI? import * as cliDiagnostics from "../../cli/diagnostics"; import { CompilerOptions, TransformerImport } from "../../CompilerOptions"; +import { Compilation } from "../compilation"; import * as diagnosticFactories from "../diagnostics"; -import { Transpilation } from "../transpilation"; import { loadConfigImport } from "../utils"; export function getTransformers( - transpilation: Transpilation, + compilation: Compilation, customTransformers: ts.CustomTransformers, onSourceFile: (sourceFile: ts.SourceFile) => void ): ts.CustomTransformers { @@ -16,14 +16,14 @@ export function getTransformers( return ts.createSourceFile(sourceFile.fileName, "", ts.ScriptTarget.ESNext); }; - const transformersFromOptions = loadTransformersFromOptions(transpilation); + const transformersFromOptions = loadTransformersFromOptions(compilation); const afterDeclarations = [ ...(transformersFromOptions.afterDeclarations ?? []), ...(customTransformers.afterDeclarations ?? []), ]; - if (transpilation.options.noImplicitSelf) { + if (compilation.options.noImplicitSelf) { afterDeclarations.unshift(noImplicitSelfTransformer); } @@ -52,37 +52,37 @@ export const noImplicitSelfTransformer: ts.TransformerFactory = { before: [], after: [], afterDeclarations: [], }; - if (!transpilation.options.plugins) return customTransformers; + if (!compilation.options.plugins) return customTransformers; - for (const [index, transformerImport] of transpilation.options.plugins.entries()) { + for (const [index, transformerImport] of compilation.options.plugins.entries()) { if (!("transform" in transformerImport)) continue; const optionName = `compilerOptions.plugins[${index}]`; const { error: resolveError, result: factory } = loadConfigImport( "transformer", `${optionName}.transform`, - transpilation.projectDir, + compilation.projectDir, transformerImport.transform, transformerImport.import ); - if (resolveError) transpilation.diagnostics.push(resolveError); + if (resolveError) compilation.diagnostics.push(resolveError); if (factory === undefined) continue; const { error: loadError, transformer } = loadTransformer( optionName, - transpilation.program, + compilation.program, factory, transformerImport ); - if (loadError) transpilation.diagnostics.push(loadError); + if (loadError) compilation.diagnostics.push(loadError); if (transformer === undefined) continue; if (transformer.before) { diff --git a/src/transpilation/utils.ts b/src/compiler/utils.ts similarity index 100% rename from src/transpilation/utils.ts rename to src/compiler/utils.ts diff --git a/src/index.ts b/src/index.ts index 66d45e5dc..0566dfaf9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,10 +2,10 @@ export { version } from "./cli/information"; export { parseCommandLine, ParsedCommandLine, updateParsedConfigFile } from "./cli/parse"; export * from "./cli/report"; export { parseConfigFileWithSystem } from "./cli/tsconfig"; +export * from "./compiler"; export * from "./CompilerOptions"; export * from "./LuaAST"; export { LuaLibFeature } from "./LuaLib"; export * from "./LuaPrinter"; export * from "./managed-api"; export * from "./transformation/context"; -export * from "./transpilation"; diff --git a/src/managed-api/index.ts b/src/managed-api/index.ts index f278baa09..3d388f366 100644 --- a/src/managed-api/index.ts +++ b/src/managed-api/index.ts @@ -1,7 +1,7 @@ import * as ts from "typescript"; import { parseConfigFileWithSystem } from "../cli/tsconfig"; +import { Compiler, EmitResult } from "../compiler"; import { CompilerOptions } from "../CompilerOptions"; -import { EmitResult, Transpiler } from "../transpilation"; import { createEmitOutputCollector, createVirtualProgram, TranspiledFile } from "./utils"; export { TranspiledFile }; @@ -12,7 +12,8 @@ export function transpileFiles( writeFile?: ts.WriteFileCallback ): EmitResult { const program = ts.createProgram(rootNames, options); - const { diagnostics: transpileDiagnostics, emitSkipped } = new Transpiler().emit({ program, writeFile }); + + const { diagnostics: transpileDiagnostics, emitSkipped } = new Compiler().emit({ program, writeFile }); const diagnostics = ts.sortAndDeduplicateDiagnostics([ ...ts.getPreEmitDiagnostics(program), ...transpileDiagnostics, @@ -45,7 +46,8 @@ export function transpileVirtualProject( ): TranspileVirtualProjectResult { const program = createVirtualProgram(files, options); const collector = createEmitOutputCollector(); - const { diagnostics: transpileDiagnostics } = new Transpiler().emit({ program, writeFile: collector.writeFile }); + + const { diagnostics: transpileDiagnostics } = new Compiler().emit({ program, writeFile: collector.writeFile }); const diagnostics = ts.sortAndDeduplicateDiagnostics([ ...ts.getPreEmitDiagnostics(program), ...transpileDiagnostics, diff --git a/src/transformation/visitors/namespace.ts b/src/transformation/visitors/namespace.ts index 35190705f..7ce953a44 100644 --- a/src/transformation/visitors/namespace.ts +++ b/src/transformation/visitors/namespace.ts @@ -113,7 +113,7 @@ export const transformModuleDeclaration: FunctionVisitor = } // Set current namespace for nested NS - // Keep previous namespace to reset after block transpilation + // Keep previous namespace to reset after block transformation currentNamespaces.set(context, node); // Transform moduleblock to block and visit it diff --git a/src/transpilation/transpiler.ts b/src/transpilation/transpiler.ts deleted file mode 100644 index c6038c780..000000000 --- a/src/transpilation/transpiler.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { FileSystem } from "enhanced-resolve"; -import * as ts from "typescript"; -import { Plugin } from "./plugins"; -import { Transpilation } from "./transpilation"; -import { emitProgramModules, TranspileOptions } from "./transpile"; - -export interface TranspilerHost extends Pick { - resolutionFileSystem?: FileSystem; -} - -export interface TranspilerOptions { - host?: TranspilerHost; -} - -export interface EmitOptions extends TranspileOptions { - writeFile?: ts.WriteFileCallback; - plugins?: Plugin[]; -} - -export interface EmitResult { - emitSkipped: boolean; - diagnostics: readonly ts.Diagnostic[]; -} - -export class Transpiler { - public host: TranspilerHost; - constructor({ host = ts.sys }: TranspilerOptions = {}) { - this.host = host; - } - - public emit(emitOptions: EmitOptions): EmitResult { - const { program, writeFile = this.host.writeFile } = emitOptions; - const transpilation = new Transpilation(this, program, emitOptions.plugins ?? []); - const { options } = transpilation; - - emitProgramModules(transpilation, writeFile, emitOptions); - if (options.noEmit || (options.noEmitOnError && transpilation.diagnostics.length > 0)) { - return { diagnostics: transpilation.diagnostics, emitSkipped: true }; - } - - transpilation.emit(writeFile); - - return { diagnostics: transpilation.diagnostics, emitSkipped: false }; - } -} diff --git a/src/tstl.ts b/src/tstl.ts index bb74fdb6d..e7ad4ef92 100644 --- a/src/tstl.ts +++ b/src/tstl.ts @@ -102,8 +102,7 @@ function performCompilation( configFileParsingDiagnostics, }); - const { diagnostics: transpileDiagnostics, emitSkipped } = new tstl.Transpiler().emit({ program }); - + const { diagnostics: transpileDiagnostics, emitSkipped } = new tstl.Compiler().emit({ program }); const diagnostics = ts.sortAndDeduplicateDiagnostics([ ...ts.getPreEmitDiagnostics(program), ...transpileDiagnostics, @@ -155,7 +154,7 @@ function updateWatchCompilationHost( let hadErrorLastTime = true; const updateConfigFile = createConfigFileUpdater(optionsToExtend); - const transpiler = new tstl.Transpiler(); + const compiler = new tstl.Compiler(); host.afterProgramCreate = builderProgram => { const program = builderProgram.getProgram(); const options: tstl.CompilerOptions = builderProgram.getCompilerOptions(); @@ -176,7 +175,7 @@ function updateWatchCompilationHost( } } - const { diagnostics: emitDiagnostics } = transpiler.emit({ program, sourceFiles }); + const { diagnostics: emitDiagnostics } = compiler.emit({ program, sourceFiles }); const diagnostics = ts.sortAndDeduplicateDiagnostics([ ...configFileParsingDiagnostics, diff --git a/test/transpile/__fixtures__/plugins/getModuleId.ts b/test/transpile/__fixtures__/plugins/getModuleId.ts index 9bce8c6a2..748642fcf 100644 --- a/test/transpile/__fixtures__/plugins/getModuleId.ts +++ b/test/transpile/__fixtures__/plugins/getModuleId.ts @@ -3,10 +3,10 @@ import * as path from "path"; import * as tstl from "../../../../src"; const plugin: tstl.Plugin = { - getModuleId: (module, transpilation) => + getModuleId: (module, compilation) => createHash("sha1") .update(module.source.toString()) - .update(path.relative(transpilation.rootDir, module.request)) + .update(path.relative(compilation.rootDir, module.request)) .digest("hex"), }; diff --git a/test/transpile/load-config-import.spec.ts b/test/transpile/load-config-import.spec.ts index fe328b053..52df3cbd8 100644 --- a/test/transpile/load-config-import.spec.ts +++ b/test/transpile/load-config-import.spec.ts @@ -1,4 +1,4 @@ -import { loadConfigImport } from "../../src/transpilation/utils"; +import { loadConfigImport } from "../../src/compiler/utils"; import { resolveFixture } from "./run"; test("resolve relative module paths", () => { diff --git a/test/transpile/resolution.spec.ts b/test/transpile/resolution.spec.ts index dd4726fc9..44c06326a 100644 --- a/test/transpile/resolution.spec.ts +++ b/test/transpile/resolution.spec.ts @@ -1,5 +1,5 @@ import * as tstl from "../../src"; -import { createResolutionErrorDiagnostic } from "../../src/transpilation/diagnostics"; +import { createResolutionErrorDiagnostic } from "../../src/compiler/diagnostics"; import * as util from "../util"; const requireRegex = /require\("(.*?)"\)/; @@ -220,7 +220,7 @@ describe('mode: "lib"', () => { import "./module"; ` .addExtraFile("module.d.ts", "export const foo = true;") - .setOptions({ mode: tstl.TranspilerMode.Lib }) + .setOptions({ mode: tstl.CompilerMode.Lib }) .expectToHaveNoDiagnostics() .tap(expectModuleTableToMatchSnapshot); }); diff --git a/test/unit/bundle.spec.ts b/test/unit/bundle.spec.ts index d28f4bf25..515e2578a 100644 --- a/test/unit/bundle.spec.ts +++ b/test/unit/bundle.spec.ts @@ -1,5 +1,5 @@ import { LuaLibImportKind } from "../../src"; -import * as diagnosticFactories from "../../src/transpilation/diagnostics"; +import * as diagnosticFactories from "../../src/compiler/diagnostics"; import * as util from "../util"; test("import module -> main", () => { diff --git a/test/util.ts b/test/util.ts index 98ebed5b8..84de00e6a 100644 --- a/test/util.ts +++ b/test/util.ts @@ -254,7 +254,7 @@ export abstract class TestBuilder { const collector = createEmitOutputCollector(); const program = this.getProgram(); - const host: tstl.TranspilerHost = { ...ts.sys }; + const host: tstl.CompilerHost = { ...ts.sys }; if (!this.nativeFileSystem) { const virtualFS = Volume.fromJSON({ ...this.extraRawFiles, ...this.getSourceFiles() }, "/"); host.resolutionFileSystem = virtualFS; @@ -265,7 +265,7 @@ export abstract class TestBuilder { : (virtualFS.readFileSync(fileName, encoding) as string); } - const { diagnostics: transpileDiagnostics } = new tstl.Transpiler({ host }).emit({ + const { diagnostics: transpileDiagnostics } = new tstl.Compiler({ host }).emit({ program, customTransformers: this.customTransformers, writeFile: collector.writeFile, From 64280d5a81f1d651002a2cdfad932e4a9ba95b13 Mon Sep 17 00:00:00 2001 From: ark120202 Date: Fri, 23 Oct 2020 13:56:34 +0000 Subject: [PATCH 47/58] Return main transpiled file from `transpileString` itself --- src/managed-api/index.ts | 11 +++++------ test/legacy-utils.ts | 11 ++++++----- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/managed-api/index.ts b/src/managed-api/index.ts index 3d388f366..ccd92ea32 100644 --- a/src/managed-api/index.ts +++ b/src/managed-api/index.ts @@ -2,6 +2,7 @@ import * as ts from "typescript"; import { parseConfigFileWithSystem } from "../cli/tsconfig"; import { Compiler, EmitResult } from "../compiler"; import { CompilerOptions } from "../CompilerOptions"; +import { assert } from "../utils"; import { createEmitOutputCollector, createVirtualProgram, TranspiledFile } from "./utils"; export { TranspiledFile }; @@ -56,15 +57,13 @@ export function transpileVirtualProject( return { diagnostics: [...diagnostics], transpiledFiles: collector.files }; } -export interface TranspileStringResult { +export interface TranspileStringResult extends TranspiledFile { 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(({ sourceFiles }) => sourceFiles.some(f => f.fileName === "main.ts")), - }; + const file = transpiledFiles.find(({ sourceFiles }) => sourceFiles.some(f => f.fileName === "main.ts")); + assert(file !== undefined); + return { ...file, diagnostics }; } diff --git a/test/legacy-utils.ts b/test/legacy-utils.ts index 83894fda8..4cdf0b82a 100644 --- a/test/legacy-utils.ts +++ b/test/legacy-utils.ts @@ -3,19 +3,20 @@ import * as fs from "fs"; import * as path from "path"; import * as ts from "typescript"; import * as tstl from "../src"; +import { assert } from "./util"; export function transpileString( str: string | { [filename: string]: string }, options: tstl.CompilerOptions = {}, ignoreDiagnostics = true ): string { - const { diagnostics, file } = transpileStringResult(str, options); - expect(file.lua).toBeDefined(); + const { diagnostics, lua } = transpileStringResult(str, options); + assert(lua !== undefined); const errors = diagnostics.filter(d => !ignoreDiagnostics || d.source === "typescript-to-lua"); expect(errors).not.toHaveDiagnostics(); - return file.lua!.trim(); + return lua.trim(); } function transpileStringsAsProject(input: Record, options: tstl.CompilerOptions = {}) { @@ -33,7 +34,7 @@ function transpileStringsAsProject(input: Record, options: tstl. function transpileStringResult( input: string | Record, options: tstl.CompilerOptions = {} -): Required { +): tstl.TranspileStringResult { const { diagnostics, transpiledFiles } = transpileStringsAsProject( typeof input === "string" ? { "main.ts": input } : input, options @@ -44,7 +45,7 @@ function transpileStringResult( throw new Error('Program should have a file named "main"'); } - return { diagnostics, file }; + return { ...file, diagnostics }; } const lualibContent = fs.readFileSync(path.resolve(__dirname, "../dist/lualib/lualib_bundle.lua"), "utf8"); From c042185e9d2c3007373875a964537915e156f70a Mon Sep 17 00:00:00 2001 From: ark120202 Date: Fri, 23 Oct 2020 13:59:06 +0000 Subject: [PATCH 48/58] `transpileDiagnostics` -> `emitDiagnostics` --- src/managed-api/index.ts | 14 ++++---------- src/tstl.ts | 7 ++----- test/util.ts | 4 ++-- 3 files changed, 8 insertions(+), 17 deletions(-) diff --git a/src/managed-api/index.ts b/src/managed-api/index.ts index ccd92ea32..fea25729c 100644 --- a/src/managed-api/index.ts +++ b/src/managed-api/index.ts @@ -14,11 +14,8 @@ export function transpileFiles( ): EmitResult { const program = ts.createProgram(rootNames, options); - const { diagnostics: transpileDiagnostics, emitSkipped } = new Compiler().emit({ program, writeFile }); - const diagnostics = ts.sortAndDeduplicateDiagnostics([ - ...ts.getPreEmitDiagnostics(program), - ...transpileDiagnostics, - ]); + const { diagnostics: emitDiagnostics, emitSkipped } = new Compiler().emit({ program, writeFile }); + const diagnostics = ts.sortAndDeduplicateDiagnostics([...ts.getPreEmitDiagnostics(program), ...emitDiagnostics]); return { diagnostics: [...diagnostics], emitSkipped }; } @@ -48,11 +45,8 @@ export function transpileVirtualProject( const program = createVirtualProgram(files, options); const collector = createEmitOutputCollector(); - const { diagnostics: transpileDiagnostics } = new Compiler().emit({ program, writeFile: collector.writeFile }); - const diagnostics = ts.sortAndDeduplicateDiagnostics([ - ...ts.getPreEmitDiagnostics(program), - ...transpileDiagnostics, - ]); + const { diagnostics: emitDiagnostics } = new Compiler().emit({ program, writeFile: collector.writeFile }); + const diagnostics = ts.sortAndDeduplicateDiagnostics([...ts.getPreEmitDiagnostics(program), ...emitDiagnostics]); return { diagnostics: [...diagnostics], transpiledFiles: collector.files }; } diff --git a/src/tstl.ts b/src/tstl.ts index e7ad4ef92..36d3eb72c 100644 --- a/src/tstl.ts +++ b/src/tstl.ts @@ -102,11 +102,8 @@ function performCompilation( configFileParsingDiagnostics, }); - const { diagnostics: transpileDiagnostics, emitSkipped } = new tstl.Compiler().emit({ program }); - const diagnostics = ts.sortAndDeduplicateDiagnostics([ - ...ts.getPreEmitDiagnostics(program), - ...transpileDiagnostics, - ]); + const { diagnostics: emitDiagnostics, emitSkipped } = new tstl.Compiler().emit({ program }); + const diagnostics = ts.sortAndDeduplicateDiagnostics([...ts.getPreEmitDiagnostics(program), ...emitDiagnostics]); diagnostics.forEach(reportDiagnostic); const exitCode = diff --git a/test/util.ts b/test/util.ts index 84de00e6a..91d6738f3 100644 --- a/test/util.ts +++ b/test/util.ts @@ -265,7 +265,7 @@ export abstract class TestBuilder { : (virtualFS.readFileSync(fileName, encoding) as string); } - const { diagnostics: transpileDiagnostics } = new tstl.Compiler({ host }).emit({ + const { diagnostics: emitDiagnostics } = new tstl.Compiler({ host }).emit({ program, customTransformers: this.customTransformers, writeFile: collector.writeFile, @@ -273,7 +273,7 @@ export abstract class TestBuilder { const diagnostics = ts.sortAndDeduplicateDiagnostics([ ...ts.getPreEmitDiagnostics(program), - ...transpileDiagnostics, + ...emitDiagnostics, ]); return { diagnostics: [...diagnostics], transpiledFiles: collector.files }; From db9b60a56727da767a8282d52865a4bb908f2cfb Mon Sep 17 00:00:00 2001 From: ark120202 Date: Fri, 23 Oct 2020 16:45:45 +0000 Subject: [PATCH 49/58] Make Compilation a public API --- src/compiler/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/compiler/index.ts b/src/compiler/index.ts index c120cd43a..3e88465cc 100644 --- a/src/compiler/index.ts +++ b/src/compiler/index.ts @@ -1,4 +1,5 @@ export { Chunk } from "./chunk"; +export * from "./compilation"; export * from "./compiler"; export { Module } from "./module"; export { Plugin } from "./plugins"; From 1d0383015c45c2ec86fce44b05d6dd93ff9b6dcf Mon Sep 17 00:00:00 2001 From: ark120202 Date: Thu, 19 Nov 2020 18:24:18 +0000 Subject: [PATCH 50/58] Rename `Module.request` to `Module.fileName` --- src/compiler/chunk/bundle.ts | 2 +- src/compiler/compilation.ts | 16 ++++++++-------- src/compiler/diagnostics.ts | 2 +- src/compiler/module.ts | 2 +- src/compiler/transpile/index.ts | 4 ++-- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/compiler/chunk/bundle.ts b/src/compiler/chunk/bundle.ts index c24b54f04..497b374e4 100644 --- a/src/compiler/chunk/bundle.ts +++ b/src/compiler/chunk/bundle.ts @@ -37,7 +37,7 @@ export function modulesToBundleChunks(compilation: Compilation, modules: Module[ const outputPath = ts.getNormalizedAbsolutePath(options.luaBundle, compilation.projectDir); const entryFileName = ts.getNormalizedAbsolutePath(options.luaBundleEntry, compilation.projectDir); - const entryModule = modules.find(m => m.request === entryFileName); + const entryModule = modules.find(m => m.fileName === entryFileName); if (entryModule === undefined) { compilation.diagnostics.push(couldNotFindBundleEntryPoint(options.luaBundleEntry)); return [{ outputPath, source: new SourceNode() }]; diff --git a/src/compiler/compilation.ts b/src/compiler/compilation.ts index 1296ab65b..9fa7a3544 100644 --- a/src/compiler/compilation.ts +++ b/src/compiler/compilation.ts @@ -72,7 +72,7 @@ export class Compilation { if (this.options.mode === CompilerMode.Lib) return; buildModule(module, (request, position) => { - const result = this.resolveRequestToModule(module.request, request); + const result = this.resolveRequestToModule(module.fileName, request); if ("error" in result) { const diagnostic = createResolutionErrorDiagnostic(result.error, module, position); this.diagnostics.push(diagnostic); @@ -85,10 +85,10 @@ export class Compilation { private resolveRequestToModule(issuer: string, request: string) { if (request === "/lualib_bundle") { - let module = this.modules.find(m => m.request === request); + let module = this.modules.find(m => m.fileName === request); if (!module) { const source = new SourceNode(null, null, null, getLuaLibBundle(this.host)); - module = { request, isBuilt: true, source }; + module = { fileName: request, isBuilt: true, source }; this.modules.push(module); } @@ -106,7 +106,7 @@ export class Compilation { return { error: error.message }; } - let module = this.modules.find(m => m.request === resolvedPath); + let module = this.modules.find(m => m.fileName === resolvedPath); if (!module) { if (!resolvedPath.endsWith(".lua")) { const messageText = `Resolved source file '${resolvedPath}' is not a part of the project.`; @@ -116,7 +116,7 @@ export class Compilation { // TODO: Load source map files const code = cast(this.host.readFile(resolvedPath), isNonNull); const source = new SourceNode(null, null, null, code); - module = { request: resolvedPath, isBuilt: false, source }; + module = { fileName: resolvedPath, isBuilt: false, source }; this.modules.push(module); this.buildModule(module); @@ -129,11 +129,11 @@ export class Compilation { const pluginResult = applyBailPlugin(this.plugins, p => p.getModuleId?.(module, this)); if (pluginResult !== undefined) return pluginResult; - if (module.request.startsWith("/")) { - return module.request.replace("/", ""); + if (module.fileName.startsWith("/")) { + return module.fileName.replace("/", ""); } - const result = path.relative(this.rootDir, trimExtension(module.request)); + const result = path.relative(this.rootDir, trimExtension(module.fileName)); // TODO: handle files on other drives assert(!path.isAbsolute(result), `Invalid path: ${result}`); return result diff --git a/src/compiler/diagnostics.ts b/src/compiler/diagnostics.ts index 5a3b3f294..475f9828e 100644 --- a/src/compiler/diagnostics.ts +++ b/src/compiler/diagnostics.ts @@ -43,7 +43,7 @@ const sourceFileStub = ts.createSourceFile("", "", ts.ScriptTarget.ES3); export const createResolutionErrorDiagnostic = createSerialDiagnosticFactory( (messageText: string, module: Module, position: ts.ReadonlyTextRange) => ({ messageText, - file: { ...sourceFileStub, fileName: module.request, text: module.source.toString() }, + file: { ...sourceFileStub, fileName: module.fileName, text: module.source.toString() }, start: position.pos, length: position.end - position.pos, }) diff --git a/src/compiler/module.ts b/src/compiler/module.ts index 27e4984c9..b7dad05be 100644 --- a/src/compiler/module.ts +++ b/src/compiler/module.ts @@ -3,7 +3,7 @@ import * as ts from "typescript"; import { escapeString, unescapeLuaString } from "../LuaPrinter"; export interface Module { - request: string; + fileName: string; isBuilt: boolean; source: SourceNode; sourceFiles?: ts.SourceFile[]; diff --git a/src/compiler/transpile/index.ts b/src/compiler/transpile/index.ts index dce56a5c6..542fc19ed 100644 --- a/src/compiler/transpile/index.ts +++ b/src/compiler/transpile/index.ts @@ -54,9 +54,9 @@ export function emitProgramModules( compilation.diagnostics.push(...transformDiagnostics); if (!options.noEmit && !options.emitDeclarationOnly) { + const fileName = ts.getNormalizedAbsolutePath(sourceFile.fileName, compilation.projectDir); const source = printer(program, compilation.host, sourceFile.fileName, file); - const request = ts.getNormalizedAbsolutePath(sourceFile.fileName, compilation.projectDir); - compilation.modules.push({ request, isBuilt: false, source, sourceFiles: [sourceFile] }); + compilation.modules.push({ fileName, isBuilt: false, source, sourceFiles: [sourceFile] }); } }; From 9ccb503c301f44548d3f8700d1dbbe2be6eef86e Mon Sep 17 00:00:00 2001 From: ark120202 Date: Thu, 19 Nov 2020 22:01:40 +0000 Subject: [PATCH 51/58] Fix Compilation fail to find unchanged source files in watch mode --- src/compiler/compilation.ts | 59 ++++++++++---- src/compiler/compiler.ts | 21 +++++ .../watch/{ => basic}/tsconfig.json | 0 .../__fixtures__/watch/{ => basic}/watch.ts | 0 .../__fixtures__/watch/multiple-files/a.ts | 1 + .../__fixtures__/watch/multiple-files/b.ts | 1 + .../watch/multiple-files/tsconfig.json | 1 + test/cli/run.ts | 12 +-- test/cli/watch.spec.ts | 81 ++++++++++++++++--- .../__snapshots__/resolution.spec.ts.snap | 24 ++++++ test/transpile/resolution.spec.ts | 18 +++++ 11 files changed, 187 insertions(+), 31 deletions(-) rename test/cli/__fixtures__/watch/{ => basic}/tsconfig.json (100%) rename test/cli/__fixtures__/watch/{ => basic}/watch.ts (100%) create mode 100644 test/cli/__fixtures__/watch/multiple-files/a.ts create mode 100644 test/cli/__fixtures__/watch/multiple-files/b.ts create mode 100644 test/cli/__fixtures__/watch/multiple-files/tsconfig.json diff --git a/src/compiler/compilation.ts b/src/compiler/compilation.ts index 9fa7a3544..f3570005a 100644 --- a/src/compiler/compilation.ts +++ b/src/compiler/compilation.ts @@ -24,7 +24,8 @@ export class Compilation { public projectDir: string; public plugins: Plugin[]; - protected resolver: Resolver; + protected tsResolver: Resolver; + protected luaResolver: Resolver; constructor(public compiler: Compiler, public program: ts.Program, extraPlugins: Plugin[]) { this.host = compiler.host; @@ -44,16 +45,23 @@ export class Compilation { this.plugins = getPlugins(this, extraPlugins); - this.resolver = ResolverFactory.createResolver({ - extensions: [".lua", ".ts", ".tsx", ".js", ".jsx"], - conditionNames: ["lua", `lua:${this.options.luaTarget ?? LuaTarget.Universal}`], - fileSystem: this.host.resolutionFileSystem ?? fs, - useSyncFileSystemCalls: true, - plugins: this.plugins.flatMap(p => p.getResolvePlugins?.(this) ?? []), - }); + const createResolver = (extensions: string[]) => + ResolverFactory.createResolver({ + extensions, + conditionNames: ["lua", `lua:${this.options.luaTarget ?? LuaTarget.Universal}`], + fileSystem: this.host.resolutionFileSystem ?? fs, + useSyncFileSystemCalls: true, + plugins: this.plugins.flatMap(p => p.getResolvePlugins?.(this) ?? []), + }); + + // We want to prioritize .ts files from current program, but .lua files otherwise + // TODO: It's not very efficient, maybe it could be solved with a plugin? + this.tsResolver = createResolver([".ts", ".tsx", ".js", ".jsx"]); + this.luaResolver = createResolver([".lua"]); } public emit(writeFile: ts.WriteFileCallback) { + this.modules.forEach(module => this.compiler.addModuleToCache(module)); this.modules.forEach(module => this.buildModule(module)); const chunks = this.mapModulesToChunks(this.modules); @@ -95,18 +103,40 @@ export class Compilation { return module; } - let resolvedPath: string; - try { - const result = this.resolver.resolveSync({}, ts.getDirectoryPath(issuer), request); + function resolveUsingResolver(resolver: Resolver) { + const result = resolver.resolveSync({}, ts.getDirectoryPath(issuer), request); assert(typeof result === "string", `Invalid resolution result: ${result}`); // https://github.com/webpack/enhanced-resolve#escaping - resolvedPath = normalizeSlashes(result.replace(/\0#/g, "#")); + return normalizeSlashes(result.replace(/\0#/g, "#")); + } + + let resolvedPath: string | undefined; + let resolvedTsPath: string | undefined; + + try { + resolvedTsPath = resolveUsingResolver(this.tsResolver); + if (this.compiler.findModuleInCache(resolvedTsPath)) { + resolvedPath = resolvedTsPath; + } } catch (error) { if (!isResolveError(error)) throw error; - return { error: error.message }; } - let module = this.modules.find(m => m.fileName === resolvedPath); + if (resolvedPath === undefined) { + try { + resolvedPath = resolveUsingResolver(this.luaResolver); + } catch (error) { + if (!isResolveError(error)) throw error; + + if (resolvedTsPath !== undefined) { + resolvedPath = resolvedTsPath; + } else { + return { error: error.message }; + } + } + } + + let module = this.compiler.findModuleInCache(resolvedPath); if (!module) { if (!resolvedPath.endsWith(".lua")) { const messageText = `Resolved source file '${resolvedPath}' is not a part of the project.`; @@ -119,6 +149,7 @@ export class Compilation { module = { fileName: resolvedPath, isBuilt: false, source }; this.modules.push(module); + this.compiler.addModuleToCache(module); this.buildModule(module); } diff --git a/src/compiler/compiler.ts b/src/compiler/compiler.ts index ebbd628f6..9f0ec0c2a 100644 --- a/src/compiler/compiler.ts +++ b/src/compiler/compiler.ts @@ -1,6 +1,7 @@ import { FileSystem } from "enhanced-resolve"; import * as ts from "typescript"; import { Compilation } from "./compilation"; +import { Module } from "./module"; import { Plugin } from "./plugins"; import { emitProgramModules, TranspileOptions } from "./transpile"; @@ -29,6 +30,14 @@ export class Compiler { const compilation = new Compilation(this, program, emitOptions.plugins ?? []); const { options } = compilation; + // Clean-up module cache from modules created from deleted source files, so they won't be hit during resolution + const programSourceFiles = program.getSourceFiles(); + const validModulesInCache = [...this.moduleCache.values()].filter( + module => !module.sourceFiles?.some(sourceFile => !programSourceFiles.includes(sourceFile)) + ); + this.moduleCache.clear(); + validModulesInCache.forEach(m => this.addModuleToCache(m)); + emitProgramModules(compilation, writeFile, emitOptions); if (options.noEmit || (options.noEmitOnError && compilation.diagnostics.length > 0)) { return { diagnostics: compilation.diagnostics, emitSkipped: true }; @@ -38,4 +47,16 @@ export class Compiler { return { diagnostics: compilation.diagnostics, emitSkipped: false }; } + + // A module cache that persists between compilations, so it can generate ids for these modules, + // without re-reading files every time + private moduleCache = new Map(); + + public addModuleToCache(module: Module) { + this.moduleCache.set(module.fileName, module); + } + + public findModuleInCache(fileName: string) { + return this.moduleCache.get(fileName); + } } diff --git a/test/cli/__fixtures__/watch/tsconfig.json b/test/cli/__fixtures__/watch/basic/tsconfig.json similarity index 100% rename from test/cli/__fixtures__/watch/tsconfig.json rename to test/cli/__fixtures__/watch/basic/tsconfig.json diff --git a/test/cli/__fixtures__/watch/watch.ts b/test/cli/__fixtures__/watch/basic/watch.ts similarity index 100% rename from test/cli/__fixtures__/watch/watch.ts rename to test/cli/__fixtures__/watch/basic/watch.ts diff --git a/test/cli/__fixtures__/watch/multiple-files/a.ts b/test/cli/__fixtures__/watch/multiple-files/a.ts new file mode 100644 index 000000000..e6da568dd --- /dev/null +++ b/test/cli/__fixtures__/watch/multiple-files/a.ts @@ -0,0 +1 @@ +import "./b"; diff --git a/test/cli/__fixtures__/watch/multiple-files/b.ts b/test/cli/__fixtures__/watch/multiple-files/b.ts new file mode 100644 index 000000000..2e583315c --- /dev/null +++ b/test/cli/__fixtures__/watch/multiple-files/b.ts @@ -0,0 +1 @@ +export const result = true; diff --git a/test/cli/__fixtures__/watch/multiple-files/tsconfig.json b/test/cli/__fixtures__/watch/multiple-files/tsconfig.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/test/cli/__fixtures__/watch/multiple-files/tsconfig.json @@ -0,0 +1 @@ +{} diff --git a/test/cli/run.ts b/test/cli/run.ts index aa804a83f..54e96f7c2 100644 --- a/test/cli/run.ts +++ b/test/cli/run.ts @@ -14,19 +14,21 @@ export function forkCli(args: string[]): ChildProcess { }); } -export interface CliResult { +export interface CliOutput { exitCode: number; output: string; } -export async function runCli(args: string[]): Promise { - const child = forkCli(args); - +export async function collectCliOutput(child: ChildProcess) { let output = ""; child.stdout!.on("data", (data: Buffer) => (output += data.toString())); child.stderr!.on("data", (data: Buffer) => (output += data.toString())); - return new Promise(resolve => { + return new Promise(resolve => { child.on("close", exitCode => resolve({ exitCode, output })); }); } + +export async function runCli(args: string[]) { + return collectCliOutput(forkCli(args)); +} diff --git a/test/cli/watch.spec.ts b/test/cli/watch.spec.ts index 4a89e764e..9372c7fa5 100644 --- a/test/cli/watch.spec.ts +++ b/test/cli/watch.spec.ts @@ -1,5 +1,8 @@ import * as fs from "fs-extra"; -import { forkCli, resolveFixture } from "./run"; +import { promisify } from "util"; +import { collectCliOutput, forkCli, resolveFixture } from "./run"; + +const delay = promisify(setTimeout); let testsCleanup: Array<() => void> = []; afterEach(() => { @@ -20,25 +23,36 @@ async function waitForFileExists(filePath: string): Promise { }); } -function forkWatchProcess(args: string[]): void { +function forkWatchProcess(args: string[]) { const child = forkCli(["--watch", ...args]); testsCleanup.push(() => child.kill()); + return child; } -const watchedFile = resolveFixture("watch/watch.ts"); -const watchedFileOut = resolveFixture("watch/watch.lua"); +const changedTestFiles = new Set(); +function changeTestFile(fileName: string, content: string) { + if (!changedTestFiles.has(fileName)) { + changedTestFiles.add(fileName); + const originalContent = fs.readFileSync(fileName); + testsCleanup.push(() => { + changedTestFiles.delete(fileName); + fs.writeFileSync(fileName, originalContent); + }); + } + + fs.writeFileSync(fileName, content); +} -afterEach(() => fs.removeSync(watchedFileOut)); +const watchedFile = resolveFixture("watch/basic/watch.ts"); +const watchedFileOut = resolveFixture("watch/basic/watch.lua"); async function compileChangeAndCompare(filePath: string, content: string): Promise { + testsCleanup.push(() => fs.removeSync(watchedFileOut)); await waitForFileExists(watchedFileOut); const initialResultLua = fs.readFileSync(watchedFileOut, "utf8"); fs.unlinkSync(watchedFileOut); - - const originalContent = fs.readFileSync(filePath, "utf8"); - fs.writeFileSync(filePath, content); - testsCleanup.push(() => fs.writeFileSync(filePath, originalContent)); + changeTestFile(filePath, content); await waitForFileExists(watchedFileOut); const updatedResultLua = fs.readFileSync(watchedFileOut, "utf8"); @@ -47,17 +61,60 @@ async function compileChangeAndCompare(filePath: string, content: string): Promi } test("should watch single file", async () => { - forkWatchProcess([resolveFixture("watch/watch.ts")]); + forkWatchProcess([watchedFile]); await compileChangeAndCompare(watchedFile, "const value = 1;"); }); test("should watch project", async () => { - forkWatchProcess(["--project", resolveFixture("watch")]); + forkWatchProcess(["--project", resolveFixture("watch/basic")]); await compileChangeAndCompare(watchedFile, "const value = 1;"); }); test("should watch config file", async () => { - const configFilePath = resolveFixture("watch/tsconfig.json"); + const configFilePath = resolveFixture("watch/basic/tsconfig.json"); forkWatchProcess(["--project", configFilePath]); await compileChangeAndCompare(configFilePath, '{ "tstl": { "luaTarget": "5.3" } }'); }); + +test("should watch multiple files", async () => { + const mtimeSnapshots: Array<{ a: number; b: number }> = []; + const takeMtimeSnapshot = () => { + mtimeSnapshots.push({ + a: fs.statSync(resolveFixture("watch/multiple-files/a.lua")).mtimeMs, + b: fs.statSync(resolveFixture("watch/multiple-files/b.lua")).mtimeMs, + }); + }; + + testsCleanup.push(() => fs.unlinkSync(resolveFixture("watch/multiple-files/b.lua"))); + testsCleanup.push(() => fs.unlinkSync(resolveFixture("watch/multiple-files/a.lua"))); + + const child = forkWatchProcess(["--project", resolveFixture("watch/multiple-files")]); + const childOutputPromise = collectCliOutput(child); + + await waitForFileExists(resolveFixture("watch/multiple-files/a.lua")); + await waitForFileExists(resolveFixture("watch/multiple-files/b.lua")); + await delay(300); + takeMtimeSnapshot(); + + for (let index = 0; index < 3; index++) { + fs.unlinkSync(resolveFixture("watch/multiple-files/a.lua")); + changeTestFile(resolveFixture("watch/multiple-files/a.ts"), `import "./b"; // ${index}`); + + await waitForFileExists(resolveFixture("watch/multiple-files/a.lua")); + await delay(300); + takeMtimeSnapshot(); + } + + child.kill(); + const { output } = await childOutputPromise; + expect(output.match(/Found 0 errors\. Watching for file changes\./g)).toHaveLength(4); + + // TODO: First watch event re-writes all files + expect(mtimeSnapshots[0].b).not.toEqual(mtimeSnapshots[1].b); + expect(mtimeSnapshots[1].b).toEqual(mtimeSnapshots[2].b); + expect(mtimeSnapshots[1].b).toEqual(mtimeSnapshots[3].b); + + expect(mtimeSnapshots[0].a).not.toEqual(mtimeSnapshots[1].a); + expect(mtimeSnapshots[1].a).not.toEqual(mtimeSnapshots[2].a); + expect(mtimeSnapshots[2].a).not.toEqual(mtimeSnapshots[3].a); +}); diff --git a/test/transpile/__snapshots__/resolution.spec.ts.snap b/test/transpile/__snapshots__/resolution.spec.ts.snap index 7ccd64743..da53915dc 100644 --- a/test/transpile/__snapshots__/resolution.spec.ts.snap +++ b/test/transpile/__snapshots__/resolution.spec.ts.snap @@ -124,6 +124,30 @@ return { value = true } end," `; +exports[`prioritize .lua files over external .ts: modules 1`] = ` +"[\\"main\\"] = function() +local ____exports = {} +require(\\"foo\\") +return ____exports + +end, +[\\"foo\\"] = function() + +end," +`; + +exports[`prioritize internal .ts files over .lua: modules 1`] = ` +"[\\"foo\\"] = function() + +end, +[\\"main\\"] = function() +local ____exports = {} +require(\\"foo\\") +return ____exports + +end," +`; + exports[`resolution out of rootDir .ts file: diagnostics 1`] = `"src/main.ts(2,35): error TS6059: File 'module.ts' is not under 'rootDir' 'src'. 'rootDir' is expected to contain all source files."`; exports[`rootDir inference: modules 1`] = ` diff --git a/test/transpile/resolution.spec.ts b/test/transpile/resolution.spec.ts index 44c06326a..126a90b28 100644 --- a/test/transpile/resolution.spec.ts +++ b/test/transpile/resolution.spec.ts @@ -66,6 +66,24 @@ test("entry point in nested directory", () => { .tap(expectModuleTableToMatchSnapshot); }); +test("prioritize internal .ts files over .lua", () => { + util.testBundle` + import "./foo"; + ` + .addExtraFile("foo.ts", "") + .addRawFile("foo.lua", "") + .tap(expectModuleTableToMatchSnapshot); +}); + +test("prioritize .lua files over external .ts", () => { + util.testBundle` + import "./foo"; + ` + .addRawFile("foo.ts", "") + .addRawFile("foo.lua", "") + .tap(expectModuleTableToMatchSnapshot); +}); + describe("resolution out of rootDir", () => { test(".lua file", () => { util.testBundle` From 18fe206acd814d0f9ed6c517fcadc216eb91ae00 Mon Sep 17 00:00:00 2001 From: ark120202 Date: Thu, 19 Nov 2020 22:12:49 +0000 Subject: [PATCH 52/58] Rename plugin application functions --- src/compiler/compilation.ts | 8 ++++---- src/compiler/plugins.ts | 19 +++++-------------- src/compiler/transpile/index.ts | 4 ++-- src/utils.ts | 11 ++++++++++- 4 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/compiler/compilation.ts b/src/compiler/compilation.ts index f3570005a..55521e82b 100644 --- a/src/compiler/compilation.ts +++ b/src/compiler/compilation.ts @@ -5,12 +5,12 @@ import { SourceNode } from "source-map"; import * as ts from "typescript"; import { CompilerMode, CompilerOptions, isBundleEnabled, LuaTarget } from "../CompilerOptions"; import { getLuaLibBundle } from "../LuaLib"; -import { assert, cast, isNonNull, normalizeSlashes, trimExtension } from "../utils"; +import { assert, cast, isNonNull, mapAndFind, normalizeSlashes, trimExtension } from "../utils"; import { Chunk, chunkToAssets, modulesToBundleChunks, modulesToChunks } from "./chunk"; import { Compiler, CompilerHost } from "./compiler"; import { createResolutionErrorDiagnostic } from "./diagnostics"; import { buildModule, Module } from "./module"; -import { applyBailPlugin, applySinglePlugin, getPlugins, Plugin } from "./plugins"; +import { getPlugins, getUniquePluginProperty, Plugin } from "./plugins"; import { isResolveError } from "./utils"; export class Compilation { @@ -157,7 +157,7 @@ export class Compilation { } public getModuleId(module: Module) { - const pluginResult = applyBailPlugin(this.plugins, p => p.getModuleId?.(module, this)); + const pluginResult = mapAndFind(this.plugins, p => p.getModuleId?.(module, this)); if (pluginResult !== undefined) return pluginResult; if (module.fileName.startsWith("/")) { @@ -175,7 +175,7 @@ export class Compilation { private mapModulesToChunks(modules: Module[]): Chunk[] { return ( - applySinglePlugin(this.plugins, "mapModulesToChunks")?.(modules, this) ?? + getUniquePluginProperty(this.plugins, "mapModulesToChunks")?.(modules, this) ?? (isBundleEnabled(this.options) ? modulesToBundleChunks(this, modules) : modulesToChunks(this, modules)) ); } diff --git a/src/compiler/plugins.ts b/src/compiler/plugins.ts index b819b77b3..4052182b3 100644 --- a/src/compiler/plugins.ts +++ b/src/compiler/plugins.ts @@ -63,20 +63,11 @@ export function getPlugins(compilation: Compilation, customPlugins: Plugin[]): P return [...customPlugins, ...pluginsFromOptions]; } -export function applyBailPlugin(plugins: Plugin[], callback: (plugin: Plugin) => T | undefined): T | undefined { - for (const plugin of plugins) { - const result = callback(plugin); - if (result !== undefined) { - return result; - } - } -} - -export function applySinglePlugin

(plugins: Plugin[], property: P): Plugin[P] | undefined { - const results = plugins.filter(p => p[property] !== undefined); - if (results.length === 1) { - return results[0][property]; - } else if (results.length > 1) { +export function getUniquePluginProperty

(plugins: Plugin[], property: P): Plugin[P] | undefined { + const applicablePlugins = plugins.filter(p => p[property] !== undefined); + if (applicablePlugins.length === 1) { + return applicablePlugins[0][property]; + } else if (applicablePlugins.length > 1) { throw new Error(`Only one plugin can specify '${property}'`); } } diff --git a/src/compiler/transpile/index.ts b/src/compiler/transpile/index.ts index 542fc19ed..5440edfcf 100644 --- a/src/compiler/transpile/index.ts +++ b/src/compiler/transpile/index.ts @@ -4,7 +4,7 @@ import { LuaPrinter } from "../../LuaPrinter"; import { createVisitorMap, transformSourceFile } from "../../transformation/transform"; import { isNonNull } from "../../utils"; import { Compilation } from "../compilation"; -import { applySinglePlugin } from "../plugins"; +import { getUniquePluginProperty } from "../plugins"; import { getTransformers } from "./transformers"; export interface TranspileOptions { @@ -46,7 +46,7 @@ export function emitProgramModules( const visitorMap = createVisitorMap(compilation.plugins.map(p => p.visitors).filter(isNonNull)); const printer = - applySinglePlugin(compilation.plugins, "printer") ?? + getUniquePluginProperty(compilation.plugins, "printer") ?? ((program, host, fileName, file) => new LuaPrinter(host, program, fileName).print(file)); const processSourceFile = (sourceFile: ts.SourceFile) => { diff --git a/src/utils.ts b/src/utils.ts index 8a8330f8e..7e4966888 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,6 +1,6 @@ -import * as ts from "typescript"; import * as nativeAssert from "assert"; import * as path from "path"; +import * as ts from "typescript"; export function castArray(value: T | T[]): T[]; export function castArray(value: T | readonly T[]): readonly T[]; @@ -19,6 +19,15 @@ export const intersection = (first: readonly T[], ...rest: Array(record: Record): Record => Object.fromEntries(Object.entries(record).map(([key, value]) => [value, key])); +export function mapAndFind(elements: T[], callback: (plugin: T) => U | undefined): U | undefined { + for (const element of elements) { + const result = callback(element); + if (result !== undefined) { + return result; + } + } +} + type DiagnosticFactory = (...args: any) => Partial & Pick; export const createDiagnosticFactoryWithCode = (code: number, create: T) => Object.assign( From 06969016be3f54ea3b671156a3e62eb9aca6de11 Mon Sep 17 00:00:00 2001 From: ark120202 Date: Thu, 19 Nov 2020 22:15:18 +0000 Subject: [PATCH 53/58] Use snapshot for printer plugin test --- test/transpile/__fixtures__/plugins/getModuleId.ts | 2 +- test/transpile/__fixtures__/plugins/printer.ts | 2 +- test/transpile/__snapshots__/plugins.spec.ts.snap | 2 ++ test/transpile/plugins.spec.ts | 2 +- 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/test/transpile/__fixtures__/plugins/getModuleId.ts b/test/transpile/__fixtures__/plugins/getModuleId.ts index 748642fcf..e01410355 100644 --- a/test/transpile/__fixtures__/plugins/getModuleId.ts +++ b/test/transpile/__fixtures__/plugins/getModuleId.ts @@ -6,7 +6,7 @@ const plugin: tstl.Plugin = { getModuleId: (module, compilation) => createHash("sha1") .update(module.source.toString()) - .update(path.relative(compilation.rootDir, module.request)) + .update(path.relative(compilation.rootDir, module.fileName)) .digest("hex"), }; diff --git a/test/transpile/__fixtures__/plugins/printer.ts b/test/transpile/__fixtures__/plugins/printer.ts index 3208901d9..c0489acf8 100644 --- a/test/transpile/__fixtures__/plugins/printer.ts +++ b/test/transpile/__fixtures__/plugins/printer.ts @@ -4,7 +4,7 @@ import * as tstl from "../../../../src"; const plugin: tstl.Plugin = { printer(program, host, fileName, ...args) { const result = new tstl.LuaPrinter(host, program, fileName).print(...args); - return new SourceNode(null, null, null, ["-- Plugin\n", result.source]); + return new SourceNode(null, null, null, ["-- Plugin\n", result.toString()]); }, }; diff --git a/test/transpile/__snapshots__/plugins.spec.ts.snap b/test/transpile/__snapshots__/plugins.spec.ts.snap index d7363f2f6..f3e6fe0c2 100644 --- a/test/transpile/__snapshots__/plugins.spec.ts.snap +++ b/test/transpile/__snapshots__/plugins.spec.ts.snap @@ -40,3 +40,5 @@ end, } return require(\\"9124a2db32045398a4f097c567d1fe6fcb7ab900\\")" `; + +exports[`printer 1`] = `"-- Plugin"`; diff --git a/test/transpile/plugins.spec.ts b/test/transpile/plugins.spec.ts index 683f7da52..94e827324 100644 --- a/test/transpile/plugins.spec.ts +++ b/test/transpile/plugins.spec.ts @@ -4,7 +4,7 @@ import { resolveFixture } from "./run"; test("printer", () => { util.testModule`` .setOptions({ luaPlugins: [{ name: resolveFixture("plugins/printer.ts") }] }) - .tap(builder => expect(builder.getMainLuaCodeChunk()).toMatch("Plugin")); + .expectLuaToMatchSnapshot(); }); test("visitor", () => { From 5d44f065b7f3ef69ec52a59db6c5909d214431fa Mon Sep 17 00:00:00 2001 From: ark120202 Date: Thu, 19 Nov 2020 22:31:05 +0000 Subject: [PATCH 54/58] Move `executeCommandLine` to `src/cli/execute.ts` --- src/cli/execute.ts | 191 ++++++++++++++++++++++++++++++++++++++++++++ src/tstl.ts | 192 +-------------------------------------------- 2 files changed, 192 insertions(+), 191 deletions(-) create mode 100644 src/cli/execute.ts diff --git a/src/cli/execute.ts b/src/cli/execute.ts new file mode 100644 index 000000000..dbb9d12f6 --- /dev/null +++ b/src/cli/execute.ts @@ -0,0 +1,191 @@ +import * as ts from "typescript"; +import * as tstl from ".."; +import * as cliDiagnostics from "./diagnostics"; +import { getHelpString, versionString } from "./information"; +import { parseCommandLine } from "./parse"; +import { createDiagnosticReporter } from "./report"; +import { createConfigFileUpdater, locateConfigFile, parseConfigFileWithSystem } from "./tsconfig"; + +const shouldBePretty = ({ pretty }: ts.CompilerOptions = {}) => + pretty !== undefined ? (pretty as boolean) : ts.sys.writeOutputIsTTY?.() ?? false; + +let reportDiagnostic = createDiagnosticReporter(false); +function updateReportDiagnostic(options?: ts.CompilerOptions): void { + reportDiagnostic = createDiagnosticReporter(shouldBePretty(options)); +} + +function createWatchStatusReporter(options?: ts.CompilerOptions): ts.WatchStatusReporter { + return ts.createWatchStatusReporter(ts.sys, shouldBePretty(options)); +} + +export function executeCommandLine(args: string[]): void { + if (args.length > 0 && args[0].startsWith("-")) { + const firstOption = args[0].slice(args[0].startsWith("--") ? 2 : 1).toLowerCase(); + if (firstOption === "build" || firstOption === "b") { + return performBuild(args.slice(1)); + } + } + + const commandLine = parseCommandLine(args); + + if (commandLine.options.build) { + reportDiagnostic(cliDiagnostics.optionBuildMustBeFirstCommandLineArgument()); + return ts.sys.exit(ts.ExitStatus.DiagnosticsPresent_OutputsSkipped); + } + + // TODO: ParsedCommandLine.errors isn't meant to contain warnings. Once root-level options + // support would be dropped it should be changed to `commandLine.errors.length > 0`. + if (commandLine.errors.some(e => e.category === ts.DiagnosticCategory.Error)) { + commandLine.errors.forEach(reportDiagnostic); + return ts.sys.exit(ts.ExitStatus.DiagnosticsPresent_OutputsSkipped); + } + + if (commandLine.options.version) { + console.log(versionString); + return ts.sys.exit(ts.ExitStatus.Success); + } + + if (commandLine.options.help) { + console.log(versionString); + console.log(getHelpString()); + return ts.sys.exit(ts.ExitStatus.Success); + } + + const configFileName = locateConfigFile(commandLine); + if (typeof configFileName === "object") { + reportDiagnostic(configFileName); + return ts.sys.exit(ts.ExitStatus.DiagnosticsPresent_OutputsSkipped); + } + + const commandLineOptions = commandLine.options; + if (configFileName) { + const configParseResult = parseConfigFileWithSystem(configFileName, commandLineOptions); + + updateReportDiagnostic(configParseResult.options); + if (configParseResult.options.watch) { + createWatchOfConfigFile(configFileName, commandLineOptions); + } else { + performCompilation( + configParseResult.fileNames, + configParseResult.projectReferences, + configParseResult.options, + ts.getConfigFileParsingDiagnostics(configParseResult) + ); + } + } else { + updateReportDiagnostic(commandLineOptions); + if (commandLineOptions.watch) { + createWatchOfFilesAndCompilerOptions(commandLine.fileNames, commandLineOptions); + } else { + performCompilation(commandLine.fileNames, commandLine.projectReferences, commandLineOptions); + } + } +} + +function performBuild(_args: string[]): void { + console.log("Option '--build' is not supported."); + return ts.sys.exit(ts.ExitStatus.DiagnosticsPresent_OutputsSkipped); +} + +function performCompilation( + rootNames: string[], + projectReferences: readonly ts.ProjectReference[] | undefined, + options: tstl.CompilerOptions, + configFileParsingDiagnostics?: readonly ts.Diagnostic[] +): void { + const program = ts.createProgram({ + rootNames, + options, + projectReferences, + configFileParsingDiagnostics, + }); + + const { diagnostics: emitDiagnostics, emitSkipped } = new tstl.Compiler().emit({ program }); + const diagnostics = ts.sortAndDeduplicateDiagnostics([...ts.getPreEmitDiagnostics(program), ...emitDiagnostics]); + + diagnostics.forEach(reportDiagnostic); + const exitCode = + diagnostics.length === 0 + ? ts.ExitStatus.Success + : emitSkipped + ? ts.ExitStatus.DiagnosticsPresent_OutputsSkipped + : ts.ExitStatus.DiagnosticsPresent_OutputsGenerated; + + return ts.sys.exit(exitCode); +} + +function createWatchOfConfigFile(configFileName: string, optionsToExtend: tstl.CompilerOptions): void { + const watchCompilerHost = ts.createWatchCompilerHost( + configFileName, + optionsToExtend, + ts.sys, + ts.createSemanticDiagnosticsBuilderProgram, + undefined, + createWatchStatusReporter(optionsToExtend) + ); + + updateWatchCompilationHost(watchCompilerHost, optionsToExtend); + ts.createWatchProgram(watchCompilerHost); +} + +function createWatchOfFilesAndCompilerOptions(rootFiles: string[], options: tstl.CompilerOptions): void { + const watchCompilerHost = ts.createWatchCompilerHost( + rootFiles, + options, + ts.sys, + ts.createSemanticDiagnosticsBuilderProgram, + undefined, + createWatchStatusReporter(options) + ); + + updateWatchCompilationHost(watchCompilerHost, options); + ts.createWatchProgram(watchCompilerHost); +} + +function updateWatchCompilationHost( + host: ts.WatchCompilerHost, + optionsToExtend: tstl.CompilerOptions +): void { + let hadErrorLastTime = true; + const updateConfigFile = createConfigFileUpdater(optionsToExtend); + + const compiler = new tstl.Compiler(); + host.afterProgramCreate = builderProgram => { + const program = builderProgram.getProgram(); + const options: tstl.CompilerOptions = builderProgram.getCompilerOptions(); + const configFileParsingDiagnostics: ts.Diagnostic[] = updateConfigFile(options); + + let sourceFiles: ts.SourceFile[] | undefined; + if (!tstl.isBundleEnabled(options) && !hadErrorLastTime) { + sourceFiles = []; + while (true) { + const currentFile = builderProgram.getSemanticDiagnosticsOfNextAffectedFile(); + if (!currentFile) break; + + if ("fileName" in currentFile.affected) { + sourceFiles.push(currentFile.affected); + } else { + sourceFiles.push(...currentFile.affected.getSourceFiles()); + } + } + } + + const { diagnostics: emitDiagnostics } = compiler.emit({ program, sourceFiles }); + + const diagnostics = ts.sortAndDeduplicateDiagnostics([ + ...configFileParsingDiagnostics, + ...program.getOptionsDiagnostics(), + ...program.getSyntacticDiagnostics(), + ...program.getGlobalDiagnostics(), + ...program.getSemanticDiagnostics(), + ...emitDiagnostics, + ]); + + diagnostics.forEach(reportDiagnostic); + + const errors = diagnostics.filter(d => d.category === ts.DiagnosticCategory.Error); + hadErrorLastTime = errors.length > 0; + + host.onWatchStatusChange!(cliDiagnostics.watchErrorSummary(errors.length), host.getNewLine(), options); + }; +} diff --git a/src/tstl.ts b/src/tstl.ts index 36d3eb72c..f4abd8306 100644 --- a/src/tstl.ts +++ b/src/tstl.ts @@ -1,196 +1,6 @@ #!/usr/bin/env node import * as ts from "typescript"; -import * as tstl from "."; -import * as cliDiagnostics from "./cli/diagnostics"; -import { getHelpString, versionString } from "./cli/information"; -import { parseCommandLine } from "./cli/parse"; -import { createDiagnosticReporter } from "./cli/report"; -import { createConfigFileUpdater, locateConfigFile, parseConfigFileWithSystem } from "./cli/tsconfig"; -import { isBundleEnabled } from "./CompilerOptions"; - -const shouldBePretty = ({ pretty }: ts.CompilerOptions = {}) => - pretty !== undefined ? (pretty as boolean) : ts.sys.writeOutputIsTTY?.() ?? false; - -let reportDiagnostic = createDiagnosticReporter(false); -function updateReportDiagnostic(options?: ts.CompilerOptions): void { - reportDiagnostic = createDiagnosticReporter(shouldBePretty(options)); -} - -function createWatchStatusReporter(options?: ts.CompilerOptions): ts.WatchStatusReporter { - return ts.createWatchStatusReporter(ts.sys, shouldBePretty(options)); -} - -function executeCommandLine(args: string[]): void { - if (args.length > 0 && args[0].startsWith("-")) { - const firstOption = args[0].slice(args[0].startsWith("--") ? 2 : 1).toLowerCase(); - if (firstOption === "build" || firstOption === "b") { - return performBuild(args.slice(1)); - } - } - - const commandLine = parseCommandLine(args); - - if (commandLine.options.build) { - reportDiagnostic(cliDiagnostics.optionBuildMustBeFirstCommandLineArgument()); - return ts.sys.exit(ts.ExitStatus.DiagnosticsPresent_OutputsSkipped); - } - - // TODO: ParsedCommandLine.errors isn't meant to contain warnings. Once root-level options - // support would be dropped it should be changed to `commandLine.errors.length > 0`. - if (commandLine.errors.some(e => e.category === ts.DiagnosticCategory.Error)) { - commandLine.errors.forEach(reportDiagnostic); - return ts.sys.exit(ts.ExitStatus.DiagnosticsPresent_OutputsSkipped); - } - - if (commandLine.options.version) { - console.log(versionString); - return ts.sys.exit(ts.ExitStatus.Success); - } - - if (commandLine.options.help) { - console.log(versionString); - console.log(getHelpString()); - return ts.sys.exit(ts.ExitStatus.Success); - } - - const configFileName = locateConfigFile(commandLine); - if (typeof configFileName === "object") { - reportDiagnostic(configFileName); - return ts.sys.exit(ts.ExitStatus.DiagnosticsPresent_OutputsSkipped); - } - - const commandLineOptions = commandLine.options; - if (configFileName) { - const configParseResult = parseConfigFileWithSystem(configFileName, commandLineOptions); - - updateReportDiagnostic(configParseResult.options); - if (configParseResult.options.watch) { - createWatchOfConfigFile(configFileName, commandLineOptions); - } else { - performCompilation( - configParseResult.fileNames, - configParseResult.projectReferences, - configParseResult.options, - ts.getConfigFileParsingDiagnostics(configParseResult) - ); - } - } else { - updateReportDiagnostic(commandLineOptions); - if (commandLineOptions.watch) { - createWatchOfFilesAndCompilerOptions(commandLine.fileNames, commandLineOptions); - } else { - performCompilation(commandLine.fileNames, commandLine.projectReferences, commandLineOptions); - } - } -} - -function performBuild(_args: string[]): void { - console.log("Option '--build' is not supported."); - return ts.sys.exit(ts.ExitStatus.DiagnosticsPresent_OutputsSkipped); -} - -function performCompilation( - rootNames: string[], - projectReferences: readonly ts.ProjectReference[] | undefined, - options: tstl.CompilerOptions, - configFileParsingDiagnostics?: readonly ts.Diagnostic[] -): void { - const program = ts.createProgram({ - rootNames, - options, - projectReferences, - configFileParsingDiagnostics, - }); - - const { diagnostics: emitDiagnostics, emitSkipped } = new tstl.Compiler().emit({ program }); - const diagnostics = ts.sortAndDeduplicateDiagnostics([...ts.getPreEmitDiagnostics(program), ...emitDiagnostics]); - - diagnostics.forEach(reportDiagnostic); - const exitCode = - diagnostics.length === 0 - ? ts.ExitStatus.Success - : emitSkipped - ? ts.ExitStatus.DiagnosticsPresent_OutputsSkipped - : ts.ExitStatus.DiagnosticsPresent_OutputsGenerated; - - return ts.sys.exit(exitCode); -} - -function createWatchOfConfigFile(configFileName: string, optionsToExtend: tstl.CompilerOptions): void { - const watchCompilerHost = ts.createWatchCompilerHost( - configFileName, - optionsToExtend, - ts.sys, - ts.createSemanticDiagnosticsBuilderProgram, - undefined, - createWatchStatusReporter(optionsToExtend) - ); - - updateWatchCompilationHost(watchCompilerHost, optionsToExtend); - ts.createWatchProgram(watchCompilerHost); -} - -function createWatchOfFilesAndCompilerOptions(rootFiles: string[], options: tstl.CompilerOptions): void { - const watchCompilerHost = ts.createWatchCompilerHost( - rootFiles, - options, - ts.sys, - ts.createSemanticDiagnosticsBuilderProgram, - undefined, - createWatchStatusReporter(options) - ); - - updateWatchCompilationHost(watchCompilerHost, options); - ts.createWatchProgram(watchCompilerHost); -} - -function updateWatchCompilationHost( - host: ts.WatchCompilerHost, - optionsToExtend: tstl.CompilerOptions -): void { - let hadErrorLastTime = true; - const updateConfigFile = createConfigFileUpdater(optionsToExtend); - - const compiler = new tstl.Compiler(); - host.afterProgramCreate = builderProgram => { - const program = builderProgram.getProgram(); - const options: tstl.CompilerOptions = builderProgram.getCompilerOptions(); - const configFileParsingDiagnostics: ts.Diagnostic[] = updateConfigFile(options); - - let sourceFiles: ts.SourceFile[] | undefined; - if (!isBundleEnabled(options) && !hadErrorLastTime) { - sourceFiles = []; - while (true) { - const currentFile = builderProgram.getSemanticDiagnosticsOfNextAffectedFile(); - if (!currentFile) break; - - if ("fileName" in currentFile.affected) { - sourceFiles.push(currentFile.affected); - } else { - sourceFiles.push(...currentFile.affected.getSourceFiles()); - } - } - } - - const { diagnostics: emitDiagnostics } = compiler.emit({ program, sourceFiles }); - - const diagnostics = ts.sortAndDeduplicateDiagnostics([ - ...configFileParsingDiagnostics, - ...program.getOptionsDiagnostics(), - ...program.getSyntacticDiagnostics(), - ...program.getGlobalDiagnostics(), - ...program.getSemanticDiagnostics(), - ...emitDiagnostics, - ]); - - diagnostics.forEach(reportDiagnostic); - - const errors = diagnostics.filter(d => d.category === ts.DiagnosticCategory.Error); - hadErrorLastTime = errors.length > 0; - - host.onWatchStatusChange!(cliDiagnostics.watchErrorSummary(errors.length), host.getNewLine(), options); - }; -} +import { executeCommandLine } from "./cli/execute"; function checkNodeVersion(): void { const [major, minor] = process.version.slice(1).split(".").map(Number); From 7dbb0f74333172aa726f8fde84890220c5bfa295 Mon Sep 17 00:00:00 2001 From: ark120202 Date: Thu, 19 Nov 2020 22:51:22 +0000 Subject: [PATCH 55/58] Fix resolution error diagnostic source file missing some methods --- src/compiler/diagnostics.ts | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/compiler/diagnostics.ts b/src/compiler/diagnostics.ts index 475f9828e..1cba59d1b 100644 --- a/src/compiler/diagnostics.ts +++ b/src/compiler/diagnostics.ts @@ -39,12 +39,13 @@ export const usingLuaBundleWithInlineMightGenerateDuplicateCode = createSerialDi "It is recommended to use 'luaLibImport: \"require\"'.", })); -const sourceFileStub = ts.createSourceFile("", "", ts.ScriptTarget.ES3); +const endOfFileToken = ts.factory.createToken(ts.SyntaxKind.EndOfFileToken); export const createResolutionErrorDiagnostic = createSerialDiagnosticFactory( - (messageText: string, module: Module, position: ts.ReadonlyTextRange) => ({ - messageText, - file: { ...sourceFileStub, fileName: module.fileName, text: module.source.toString() }, - start: position.pos, - length: position.end - position.pos, - }) + (messageText: string, module: Module, position: ts.ReadonlyTextRange) => { + const file = ts.factory.createSourceFile([], endOfFileToken, ts.NodeFlags.None); + file.fileName = module.fileName; + file.text = module.source.toString(); + + return { messageText, file, start: position.pos, length: position.end - position.pos }; + } ); From 2eda42a3e416578fcfaa53263819709c5e0edb42 Mon Sep 17 00:00:00 2001 From: ark120202 Date: Thu, 19 Nov 2020 23:08:50 +0000 Subject: [PATCH 56/58] Move chunk to files mapping to `chunkToAssets` --- src/compiler/chunk/assets.ts | 10 +++++++++- src/compiler/compilation.ts | 11 +++-------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/compiler/chunk/assets.ts b/src/compiler/chunk/assets.ts index 31709ed04..a436df773 100644 --- a/src/compiler/chunk/assets.ts +++ b/src/compiler/chunk/assets.ts @@ -1,9 +1,11 @@ import * as path from "path"; import { Mapping, SourceMapGenerator, SourceNode, StartOfSourceMap } from "source-map"; +import * as ts from "typescript"; import { Chunk } from "."; import { CompilerOptions } from "../../CompilerOptions"; export function chunkToAssets(chunk: Chunk, options: CompilerOptions) { + const writeByteOrderMark = options.emitBOM ?? false; const sourceRoot = options.sourceRoot ? // According to spec, sourceRoot is simply prepended to the source name, so the slash should be included options.sourceRoot.replace(/[\\/]+$/, "") + "/" @@ -22,7 +24,13 @@ export function chunkToAssets(chunk: Chunk, options: CompilerOptions) { code = code.replace("{#SourceMapTraceback}", printStackTraceOverride(chunk.source)); } - return { code, sourceMap }; + const assets: ts.OutputFile[] = []; + assets.push({ name: chunk.outputPath, text: code, writeByteOrderMark }); + if (sourceMap !== undefined) { + assets.push({ name: chunk.outputPath + ".map", text: sourceMap, writeByteOrderMark }); + } + + return assets; } function printInlineSourceMap(sourceMap: SourceMapGenerator): string { diff --git a/src/compiler/compilation.ts b/src/compiler/compilation.ts index 55521e82b..87c6b5ee1 100644 --- a/src/compiler/compilation.ts +++ b/src/compiler/compilation.ts @@ -64,14 +64,9 @@ export class Compilation { this.modules.forEach(module => this.compiler.addModuleToCache(module)); this.modules.forEach(module => this.buildModule(module)); - const chunks = this.mapModulesToChunks(this.modules); - - const emitBOM = this.options.emitBOM ?? false; - for (const chunk of chunks) { - const { code, sourceMap } = chunkToAssets(chunk, this.options); - writeFile(chunk.outputPath, code, emitBOM, undefined, chunk.sourceFiles); - if (sourceMap !== undefined) { - writeFile(chunk.outputPath + ".map", sourceMap, emitBOM, undefined, chunk.sourceFiles); + for (const chunk of this.mapModulesToChunks(this.modules)) { + for (const asset of chunkToAssets(chunk, this.options)) { + writeFile(asset.name, asset.text, asset.writeByteOrderMark, undefined, chunk.sourceFiles); } } } From 7f6f2f05942d3252f6a1f7b6d4c24d00311e35ee Mon Sep 17 00:00:00 2001 From: ark120202 Date: Thu, 19 Nov 2020 23:36:37 +0000 Subject: [PATCH 57/58] Move out-of-project dependency error to lua resolver error handler --- src/compiler/compilation.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/compiler/compilation.ts b/src/compiler/compilation.ts index 87c6b5ee1..2de08af13 100644 --- a/src/compiler/compilation.ts +++ b/src/compiler/compilation.ts @@ -124,7 +124,7 @@ export class Compilation { if (!isResolveError(error)) throw error; if (resolvedTsPath !== undefined) { - resolvedPath = resolvedTsPath; + return { error: `Resolved source file '${resolvedTsPath}' is not a part of the project.` }; } else { return { error: error.message }; } @@ -133,11 +133,6 @@ export class Compilation { let module = this.compiler.findModuleInCache(resolvedPath); if (!module) { - if (!resolvedPath.endsWith(".lua")) { - const messageText = `Resolved source file '${resolvedPath}' is not a part of the project.`; - return { error: messageText }; - } - // TODO: Load source map files const code = cast(this.host.readFile(resolvedPath), isNonNull); const source = new SourceNode(null, null, null, code); From f1377387389c16a53416c8cea4ffef60f3819efa Mon Sep 17 00:00:00 2001 From: ark120202 Date: Thu, 19 Nov 2020 23:41:54 +0000 Subject: [PATCH 58/58] Add JSDoc to `Module` and `Chunk` declarations --- src/compiler/chunk/index.ts | 4 ++++ src/compiler/module.ts | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/src/compiler/chunk/index.ts b/src/compiler/chunk/index.ts index 3c0c04610..d371a4deb 100644 --- a/src/compiler/chunk/index.ts +++ b/src/compiler/chunk/index.ts @@ -8,6 +8,10 @@ import { Module } from "../module"; export * from "./assets"; export * from "./bundle"; +/** + * A chunk of Lua code to be emitted. + * Usually composed of one or multiple `Module` instances. + */ export interface Chunk { outputPath: string; source: SourceNode; diff --git a/src/compiler/module.ts b/src/compiler/module.ts index b7dad05be..b576b90f6 100644 --- a/src/compiler/module.ts +++ b/src/compiler/module.ts @@ -2,6 +2,10 @@ import { SourceNode } from "source-map"; import * as ts from "typescript"; import { escapeString, unescapeLuaString } from "../LuaPrinter"; +/** + * Source code of a single input Lua file. + * May be constructed from transpiled `.ts` source files, or from real `.lua` files. + */ export interface Module { fileName: string; isBuilt: boolean;