Skip to content

Commit 3ff3b49

Browse files
committed
Base case module resolution
1 parent 07e5a78 commit 3ff3b49

14 files changed

Lines changed: 239 additions & 366 deletions

File tree

package-lock.json

Lines changed: 47 additions & 306 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
"node": ">=12.13.0"
4242
},
4343
"dependencies": {
44+
"enhanced-resolve": "^5.8.2",
4445
"resolve": "^1.15.1",
4546
"source-map": "^0.7.3",
4647
"typescript": ">=4.0.2"

src/LuaPrinter.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ export class LuaPrinter {
125125
constructor(private emitHost: EmitHost, program: ts.Program, fileName: string) {
126126
this.options = program.getCompilerOptions();
127127

128+
// TODO remove?
128129
if (this.options.outDir) {
129130
const relativeFileName = path.relative(program.getCommonSourceDirectory(), fileName);
130131
if (this.options.sourceRoot) {

src/transpilation/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ const libCache: { [key: string]: ts.SourceFile } = {};
4545
/** @internal */
4646
export function createVirtualProgram(input: Record<string, string>, options: CompilerOptions = {}): ts.Program {
4747
const compilerHost: ts.CompilerHost = {
48-
fileExists: () => true,
48+
fileExists: fileName => fileName in input || ts.sys.fileExists(fileName),
4949
getCanonicalFileName: fileName => fileName,
5050
getCurrentDirectory: () => "",
5151
getDefaultLibFileName: ts.getDefaultLibFileName,

src/transpilation/output-collector.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import * as ts from "typescript";
22
import { intersection, union } from "../utils";
33

44
export interface TranspiledFile {
5+
outPath: string;
56
sourceFiles: ts.SourceFile[];
67
lua?: string;
78
luaSourceMap?: string;
@@ -18,7 +19,7 @@ export function createEmitOutputCollector() {
1819
const writeFile: ts.WriteFileCallback = (fileName, data, _bom, _onError, sourceFiles = []) => {
1920
let file = files.find(f => intersection(f.sourceFiles, sourceFiles).length > 0);
2021
if (!file) {
21-
file = { sourceFiles: [...sourceFiles] };
22+
file = { outPath: fileName, sourceFiles: [...sourceFiles] };
2223
files.push(file);
2324
} else {
2425
file.sourceFiles = union(file.sourceFiles, sourceFiles);

src/transpilation/resolve.ts

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import * as path from "path";
2+
import * as resolve from "enhanced-resolve";
3+
import * as ts from "typescript";
4+
import * as fs from "fs";
5+
import { EmitHost, ProcessedFile } from "./utils";
6+
7+
const resolver = resolve.ResolverFactory.createResolver({
8+
extensions: [".lua", ".ts"],
9+
fileSystem: { ...new resolve.CachedInputFileSystem(fs) },
10+
useSyncFileSystemCalls: true,
11+
});
12+
13+
export function resolveDependencies(program: ts.Program, files: ProcessedFile[], emitHost: EmitHost): ProcessedFile[] {
14+
const outFiles = [];
15+
16+
for (const file of files) {
17+
outFiles.push(file, ...resolveFileDependencies(file, program.getCommonSourceDirectory(), emitHost));
18+
}
19+
20+
return outFiles;
21+
}
22+
23+
function resolveFileDependencies(file: ProcessedFile, rootDir: string, emitHost: EmitHost): ProcessedFile[] {
24+
const fileDir = path.dirname(file.fileName);
25+
const dependencies: ProcessedFile[] = [];
26+
for (const required of findRequiredPaths(file.code)) {
27+
const resolvedDependency = resolveDependency(fileDir, required);
28+
if (resolvedDependency) {
29+
const dependencyContent = emitHost.readFile(resolvedDependency);
30+
if (dependencyContent === undefined) {
31+
throw `TODO: FAILED TO READ ${resolvedDependency}`;
32+
}
33+
34+
let relativePath = path.relative(fileDir, resolvedDependency);
35+
let outPath = resolvedDependency;
36+
if (relativePath.includes("..")) {
37+
relativePath = path.relative(rootDir, resolvedDependency);
38+
outPath = path.join(fileDir, relativePath);
39+
}
40+
const requirePath = relativePath.replace(".lua", "").replace(/\\/g, ".");
41+
file.code = file.code.replace(`require("${required}")`, `require("${requirePath}")`);
42+
43+
const dependency = {
44+
fileName: outPath,
45+
code: dependencyContent,
46+
};
47+
48+
dependencies.push(dependency, ...resolveFileDependencies(dependency, rootDir, emitHost));
49+
} else {
50+
//throw `TODO: COULD NOT RESOLVE ${required}`;
51+
}
52+
}
53+
return dependencies;
54+
}
55+
56+
function findRequiredPaths(code: string): string[] {
57+
const paths: string[] = [];
58+
const pattern = /require\("(.+)"\)/g;
59+
// eslint-disable-next-line @typescript-eslint/ban-types
60+
let match: RegExpExecArray | null;
61+
while ((match = pattern.exec(code))) {
62+
paths.push(match[1]);
63+
}
64+
65+
return paths;
66+
}
67+
68+
function resolveDependency(fromDirectory: string, dependency: string): string | undefined {
69+
try {
70+
const resolveResult = resolver.resolveSync({}, fromDirectory, dependency.replace(".", "/"));
71+
if (resolveResult) {
72+
return resolveResult;
73+
}
74+
} catch {
75+
// TODO
76+
}
77+
78+
return undefined;
79+
}

src/transpilation/transpiler.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { isBundleEnabled } from "../CompilerOptions";
44
import { getLuaLibBundle } from "../LuaLib";
55
import { normalizeSlashes, trimExtension } from "../utils";
66
import { getBundleResult } from "./bundle";
7+
import { resolveDependencies } from "./resolve";
78
import { getProgramTranspileResult, TranspileOptions } from "./transpile";
89
import { EmitFile, EmitHost, ProcessedFile } from "./utils";
910

@@ -33,7 +34,10 @@ export class Transpiler {
3334
writeFile,
3435
emitOptions
3536
);
36-
const { emitPlan } = this.getEmitPlan(program, diagnostics, freshFiles);
37+
38+
const resolvedFiles = resolveDependencies(program, freshFiles, this.emitHost);
39+
40+
const { emitPlan } = this.getEmitPlan(program, diagnostics, resolvedFiles);
3741

3842
const options = program.getCompilerOptions();
3943
const emitBOM = options.emitBOM ?? false;

test/transpile/module-resolution.spec.ts

Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,49 @@ import * as util from "../util";
33

44
const projectPath = path.resolve(__dirname, "module-resolution", "project-with-node-modules");
55

6-
test("moduleResolution", () => {
7-
util.testProject(path.join(projectPath, "tsconfig.json"))
8-
.setMainFileName(path.join(projectPath, "main.ts"))
9-
.debug()
10-
.expectToEqual({});
11-
})
6+
const projectWithNodeModules = util
7+
.testProject(path.join(projectPath, "tsconfig.json"))
8+
.setMainFileName(path.join(projectPath, "main.ts"));
9+
10+
test("can resolve global dependencies with declarations", () => {
11+
// Declarations in the node_modules directory
12+
expect(projectWithNodeModules.getLuaExecutionResult().globalWithDeclarationsResults).toEqual({
13+
foo: "foo from lua global with decls",
14+
bar: "bar from lua global with decls: global with declarations!",
15+
baz: "baz from lua global with decls",
16+
});
17+
});
18+
19+
test("can resolve global dependencies with hand-written declarations", () => {
20+
// No declarations in the node_modules directory, but written by hand in project dir
21+
expect(projectWithNodeModules.getLuaExecutionResult().globalWithoutDeclarationsResults).toEqual({
22+
foo: "foo from lua global without decls",
23+
bar: "bar from lua global without decls: global without declarations!",
24+
baz: "baz from lua global without decls",
25+
});
26+
});
27+
28+
test("can resolve module dependencies with declarations", () => {
29+
// Declarations in the node_modules directory
30+
expect(projectWithNodeModules.getLuaExecutionResult().moduleWithDeclarationsResults).toEqual({
31+
foo: "foo from lua module with decls",
32+
bar: "bar from lua module with decls: module with declarations!",
33+
baz: "baz from lua module with decls",
34+
});
35+
});
36+
37+
test("can resolve module dependencies with hand-written declarations", () => {
38+
// Declarations in the node_modules directory
39+
expect(projectWithNodeModules.getLuaExecutionResult().moduleWithoutDeclarationsResults).toEqual({
40+
foo: "foo from lua module without decls",
41+
bar: "bar from lua module without decls: module without declarations!",
42+
baz: "baz from lua module without decls",
43+
});
44+
});
45+
46+
test("can resolve package depencency with a dependency on another package", () => {
47+
// Declarations in the node_modules directory
48+
expect(projectWithNodeModules.getLuaExecutionResult().moduleWithDependencyResult).toEqual(
49+
"Calling dependency: foo from lua module with decls"
50+
);
51+
});
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
/** @noSelfInFile */
22
declare function fooGlobalWithoutDecls(): string;
33
declare function barGlobalWithoutDecls(param: string): string;
4-
declare function bazGlobalWithoutDecls(): string;
4+
declare function bazGlobalWithoutDecls(): string;

test/transpile/module-resolution/project-with-node-modules/lua-module-without-decls.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@ declare module "lua-module-without-decls" {
66

77
declare module "lua-module-without-decls/baz" {
88
function baz(this: void): string;
9-
}
9+
}

0 commit comments

Comments
 (0)