diff --git a/src/transpilation/resolve.ts b/src/transpilation/resolve.ts index ccf0bdfa4..ff4ba1e84 100644 --- a/src/transpilation/resolve.ts +++ b/src/transpilation/resolve.ts @@ -147,6 +147,16 @@ class ResolutionContext { const fileFromPath = this.getFileFromPath(resolvedPath); if (fileFromPath) return fileFromPath; + if (this.options.paths && this.options.baseUrl) { + // If no file found yet and paths are present, try to find project file via paths mappings + const fileFromPaths = this.tryGetModuleNameFromPaths( + dependencyPath, + this.options.paths, + this.options.baseUrl + ); + if (fileFromPaths) return fileFromPaths; + } + // Not a TS file in our project sources, use resolver to check if we can find dependency try { const resolveResult = resolver.resolveSync({}, fileDirectory, dependencyPath); @@ -222,6 +232,40 @@ class ResolutionContext { } } } + + // Taken from TS and modified: https://github.com/microsoft/TypeScript/blob/88a1e3a1dd8d2d86e844ff1c16d5f041cebcfdb9/src/compiler/moduleSpecifiers.ts#L562 + private tryGetModuleNameFromPaths(relativeToBaseUrl: string, paths: ts.MapLike, baseUrl: string) { + const relativeImport = removeTrailingDirectorySeparator(normalizeSlashes(relativeToBaseUrl)); + for (const [importPattern, targetPatterns] of Object.entries(paths)) { + const pattern = removeFileExtension(normalizeSlashes(importPattern)); + const indexOfStar = pattern.indexOf("*"); + if (indexOfStar !== -1) { + // Try to match * to relativeImport + const prefix = pattern.substring(0, indexOfStar); + const suffix = pattern.substring(indexOfStar + 1); + if ( + (relativeImport.length >= prefix.length + suffix.length && + relativeImport.startsWith(prefix) && + relativeImport.endsWith(suffix)) || + (!suffix && relativeImport === removeTrailingDirectorySeparator(prefix)) + ) { + // If import matches *, extract the matched * path + const matchedStar = relativeImport.substring(prefix.length, relativeImport.length - suffix.length); + // Try to resolve to the target patterns with filled in * pattern + for (const target of targetPatterns) { + const file = this.getFileFromPath(path.join(baseUrl, target.replace("*", matchedStar))); + if (file) return file; + } + } + } else if (pattern === relativeImport) { + // If there is no * pattern, check for exact matches and try those targets + for (const target of targetPatterns) { + const file = this.getFileFromPath(path.join(baseUrl, target)); + if (file) return file; + } + } + } + } } export function resolveDependencies(program: ts.Program, files: ProcessedFile[], emitHost: EmitHost): ResolutionResult { @@ -340,3 +384,11 @@ function fallbackResolve(required: string, sourceRootDir: string, fileDir: strin function luaRequireToPath(requirePath: string): string { return requirePath.replace(/\./g, path.sep); } + +function removeFileExtension(path: string) { + return path.includes(".") ? trimExtension(path) : path; +} + +function removeTrailingDirectorySeparator(path: string) { + return path.endsWith("/") || path.endsWith("\\") ? path.substring(0, -1) : path; +} diff --git a/test/transpile/__snapshots__/module-resolution.spec.ts.snap b/test/transpile/__snapshots__/module-resolution.spec.ts.snap new file mode 100644 index 000000000..5cb03e86b --- /dev/null +++ b/test/transpile/__snapshots__/module-resolution.spec.ts.snap @@ -0,0 +1,17 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`supports complicated paths configuration 1`] = ` +Array [ + "/monorepo-complicated/dist/packages/tstl-program/packages/mypackage/src/bar.lua", + "/monorepo-complicated/dist/packages/tstl-program/packages/mypackage/src/index.lua", + "/monorepo-complicated/dist/packages/tstl-program/packages/myprogram/src/main.lua", +] +`; + +exports[`supports paths configuration 1`] = ` +Array [ + "/monorepo-with-paths/myprogram/dist/mypackage/bar.lua", + "/monorepo-with-paths/myprogram/dist/mypackage/index.lua", + "/monorepo-with-paths/myprogram/dist/myprogram/main.lua", +] +`; diff --git a/test/transpile/module-resolution.spec.ts b/test/transpile/module-resolution.spec.ts index c18990225..348b502d2 100644 --- a/test/transpile/module-resolution.spec.ts +++ b/test/transpile/module-resolution.spec.ts @@ -2,7 +2,8 @@ import * as path from "path"; import * as tstl from "../../src"; import * as util from "../util"; import * as ts from "typescript"; -import { BuildMode, transpileProject } from "../../src"; +import { BuildMode } from "../../src"; +import { normalizeSlashes } from "../../src/utils"; describe("basic module resolution", () => { const projectPath = path.resolve(__dirname, "module-resolution", "project-with-node-modules"); @@ -236,8 +237,8 @@ describe("module resolution project with dependencies built by tstl library mode const projectPath = path.resolve(__dirname, "module-resolution", "project-with-tstl-library-modules"); // First compile dependencies into node_modules. NOTE: Actually writing to disk, very slow - transpileProject(path.join(projectPath, "dependency1-ts", "tsconfig.json")); - transpileProject(path.join(projectPath, "dependency2-ts", "tsconfig.json")); + tstl.transpileProject(path.join(projectPath, "dependency1-ts", "tsconfig.json")); + tstl.transpileProject(path.join(projectPath, "dependency2-ts", "tsconfig.json")); const expectedResult = { dependency1IndexResult: "function in dependency 1 index: dependency1OtherFileFunc in dependency1/d1otherfile", @@ -538,3 +539,54 @@ test("lualib_module with parent directory import (#1307)", () => { FEATURE_CONSTANT: 456, }); }); + +test("supports paths configuration", () => { + // Package root + const baseProjectPath = path.resolve(__dirname, "module-resolution", "monorepo-with-paths"); + // myprogram package + const projectPath = path.join(baseProjectPath, "myprogram"); + const projectTsConfig = path.join(projectPath, "tsconfig.json"); + const mainFile = path.join(projectPath, "main.ts"); + + const luaResult = util + .testProject(projectTsConfig) + .setMainFileName(mainFile) + .expectToHaveNoDiagnostics() + .getLuaResult(); + + expect(snapshotPaths(luaResult.transpiledFiles)).toMatchSnapshot(); + + // Bundle to have all files required to execute and check result + util.testProject(projectTsConfig) + .setMainFileName(mainFile) + .setOptions({ luaBundle: "bundle.lua", luaBundleEntry: mainFile }) + .expectToEqual({ foo: 314, bar: 271 }); +}); + +test("supports complicated paths configuration", () => { + // Package root + const baseProjectPath = path.resolve(__dirname, "module-resolution", "monorepo-complicated"); + // myprogram package + const projectPath = path.join(baseProjectPath, "packages", "myprogram"); + const projectTsConfig = path.join(projectPath, "tsconfig.json"); + const mainFile = path.join(projectPath, "src", "main.ts"); + + const luaResult = util + .testProject(projectTsConfig) + .setMainFileName(mainFile) + .expectToHaveNoDiagnostics() + .getLuaResult(); + + expect(snapshotPaths(luaResult.transpiledFiles)).toMatchSnapshot(); + + // Bundle to have all files required to execute + util.testProject(projectTsConfig) + .setMainFileName(mainFile) + .setOptions({ luaBundle: "bundle.lua", luaBundleEntry: mainFile }) + .debug() + .expectToEqual({ foo: 314, bar: 271 }); +}); + +function snapshotPaths(files: tstl.TranspiledFile[]) { + return files.map(f => normalizeSlashes(f.outPath).split("module-resolution")[1]).sort(); +} diff --git a/test/transpile/module-resolution/monorepo-complicated/packages/mypackage/src/bar.ts b/test/transpile/module-resolution/monorepo-complicated/packages/mypackage/src/bar.ts new file mode 100644 index 000000000..fef4e9ed6 --- /dev/null +++ b/test/transpile/module-resolution/monorepo-complicated/packages/mypackage/src/bar.ts @@ -0,0 +1 @@ +export const bar = 271; diff --git a/test/transpile/module-resolution/monorepo-complicated/packages/mypackage/src/index.ts b/test/transpile/module-resolution/monorepo-complicated/packages/mypackage/src/index.ts new file mode 100644 index 000000000..c932fa9f5 --- /dev/null +++ b/test/transpile/module-resolution/monorepo-complicated/packages/mypackage/src/index.ts @@ -0,0 +1 @@ +export const foo = 314; diff --git a/test/transpile/module-resolution/monorepo-complicated/packages/mypackage/tsconfig.json b/test/transpile/module-resolution/monorepo-complicated/packages/mypackage/tsconfig.json new file mode 100644 index 000000000..5ac343336 --- /dev/null +++ b/test/transpile/module-resolution/monorepo-complicated/packages/mypackage/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "../../dist/packages/tstl-module" + }, + "include": ["./src/**/*.ts"] +} diff --git a/test/transpile/module-resolution/monorepo-complicated/packages/myprogram/src/main.ts b/test/transpile/module-resolution/monorepo-complicated/packages/myprogram/src/main.ts new file mode 100644 index 000000000..973b93922 --- /dev/null +++ b/test/transpile/module-resolution/monorepo-complicated/packages/myprogram/src/main.ts @@ -0,0 +1,4 @@ +import { foo } from "mypackage"; +import { bar } from "mypackage/bar"; + +export { foo, bar }; diff --git a/test/transpile/module-resolution/monorepo-complicated/packages/myprogram/tsconfig.json b/test/transpile/module-resolution/monorepo-complicated/packages/myprogram/tsconfig.json new file mode 100644 index 000000000..a0ba7c18e --- /dev/null +++ b/test/transpile/module-resolution/monorepo-complicated/packages/myprogram/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "../../dist/packages/tstl-program" + }, + "include": ["./src/**/*.ts"] +} diff --git a/test/transpile/module-resolution/monorepo-complicated/tsconfig.base.json b/test/transpile/module-resolution/monorepo-complicated/tsconfig.base.json new file mode 100644 index 000000000..3f961c78a --- /dev/null +++ b/test/transpile/module-resolution/monorepo-complicated/tsconfig.base.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "rootDir": ".", + "baseUrl": ".", + "paths": { + "mypackage": ["packages/mypackage/src/index.ts"], + "mypackage/*": ["packages/mypackage/src/*"] + } + } +} diff --git a/test/transpile/module-resolution/monorepo-with-paths/mypackage/bar.ts b/test/transpile/module-resolution/monorepo-with-paths/mypackage/bar.ts new file mode 100644 index 000000000..fef4e9ed6 --- /dev/null +++ b/test/transpile/module-resolution/monorepo-with-paths/mypackage/bar.ts @@ -0,0 +1 @@ +export const bar = 271; diff --git a/test/transpile/module-resolution/monorepo-with-paths/mypackage/index.ts b/test/transpile/module-resolution/monorepo-with-paths/mypackage/index.ts new file mode 100644 index 000000000..c932fa9f5 --- /dev/null +++ b/test/transpile/module-resolution/monorepo-with-paths/mypackage/index.ts @@ -0,0 +1 @@ +export const foo = 314; diff --git a/test/transpile/module-resolution/monorepo-with-paths/mypackage/tsconfig.json b/test/transpile/module-resolution/monorepo-with-paths/mypackage/tsconfig.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/test/transpile/module-resolution/monorepo-with-paths/mypackage/tsconfig.json @@ -0,0 +1 @@ +{} diff --git a/test/transpile/module-resolution/monorepo-with-paths/myprogram/main.ts b/test/transpile/module-resolution/monorepo-with-paths/myprogram/main.ts new file mode 100644 index 000000000..973b93922 --- /dev/null +++ b/test/transpile/module-resolution/monorepo-with-paths/myprogram/main.ts @@ -0,0 +1,4 @@ +import { foo } from "mypackage"; +import { bar } from "mypackage/bar"; + +export { foo, bar }; diff --git a/test/transpile/module-resolution/monorepo-with-paths/myprogram/tsconfig.json b/test/transpile/module-resolution/monorepo-with-paths/myprogram/tsconfig.json new file mode 100644 index 000000000..d5f59e8b5 --- /dev/null +++ b/test/transpile/module-resolution/monorepo-with-paths/myprogram/tsconfig.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "baseUrl": "..", + "outDir": "dist", + "paths": { + "mypackage": ["mypackage"] + } + } +}