Skip to content

Commit c080110

Browse files
committed
All tests working
1 parent 83b0dea commit c080110

File tree

19 files changed

+103
-67
lines changed

19 files changed

+103
-67
lines changed

src/transformation/utils/diagnostics.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -124,10 +124,6 @@ export const invalidAmbientIdentifierName = createErrorDiagnosticFactory(
124124
(text: string) => `Invalid ambient identifier name '${text}'. Ambient identifiers must be valid lua identifiers.`
125125
);
126126

127-
export const unresolvableRequirePath = createErrorDiagnosticFactory(
128-
(path: string) => `Cannot create require path. Module '${path}' does not exist within --rootDir.`
129-
);
130-
131127
export const unsupportedVarDeclaration = createErrorDiagnosticFactory(
132128
"`var` declarations are not supported. Use `let` or `const` instead."
133129
);

src/transpilation/bundle.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,7 @@ local function require(file)
3333
end
3434
`;
3535

36-
export function getBundleResult(
37-
program: ts.Program,
38-
files: ProcessedFile[]
39-
): [ts.Diagnostic[], EmitFile] {
36+
export function getBundleResult(program: ts.Program, files: ProcessedFile[]): [ts.Diagnostic[], EmitFile] {
4037
const diagnostics: ts.Diagnostic[] = [];
4138

4239
const options = program.getCompilerOptions() as CompilerOptions;

src/transpilation/diagnostics.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,15 @@ import { createSerialDiagnosticFactory } from "../utils";
44
const createDiagnosticFactory = <TArgs extends any[]>(getMessage: (...args: TArgs) => string) =>
55
createSerialDiagnosticFactory((...args: TArgs) => ({ messageText: getMessage(...args) }));
66

7+
export const couldNotResolveRequire = createDiagnosticFactory(
8+
(require: string, containingFile: string) =>
9+
`Could not resolve require path '${require}' in file ${containingFile}.`
10+
);
11+
12+
export const couldNotReadDependency = createDiagnosticFactory(
13+
(dependency: string) => `Could not read content of resolved dependency ${dependency}.`
14+
);
15+
716
export const toLoadItShouldBeTranspiled = createDiagnosticFactory(
817
(kind: string, transform: string) =>
918
`To load "${transform}" ${kind} it should be transpiled or "ts-node" should be installed.`

src/transpilation/resolve.ts

Lines changed: 65 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ import * as ts from "typescript";
44
import * as fs from "fs";
55
import { EmitHost, ProcessedFile } from "./utils";
66
import { SourceNode } from "source-map";
7-
import { getEmitPathRelativeToOutDir, getSourceDir } from "./transpiler";
7+
import { getEmitPathRelativeToOutDir, getProjectRoot, getSourceDir } from "./transpiler";
8+
import { formatPathToLuaPath } from "../utils";
9+
import { couldNotReadDependency, couldNotResolveRequire } from "./diagnostics";
810

911
const resolver = resolve.ResolverFactory.createResolver({
1012
extensions: [".lua"],
@@ -13,19 +15,38 @@ const resolver = resolve.ResolverFactory.createResolver({
1315
useSyncFileSystemCalls: true,
1416
});
1517

16-
export function resolveDependencies(program: ts.Program, files: ProcessedFile[], emitHost: EmitHost): ProcessedFile[] {
17-
const outFiles = [];
18+
const projectFiles = new Map<string, string>();
19+
20+
interface ResolutionResult {
21+
resolvedFiles: ProcessedFile[];
22+
diagnostics: ts.Diagnostic[];
23+
}
24+
25+
export function resolveDependencies(program: ts.Program, files: ProcessedFile[], emitHost: EmitHost): ResolutionResult {
26+
const outFiles: ProcessedFile[] = [];
27+
const diagnostics: ts.Diagnostic[] = [];
28+
29+
const projectRoot = getProjectRoot(program);
30+
for (const sourceFile of program.getSourceFiles()) {
31+
const filePath = path.isAbsolute(sourceFile.fileName)
32+
? path.normalize(sourceFile.fileName)
33+
: path.resolve(projectRoot, sourceFile.fileName);
34+
projectFiles.set(filePath, sourceFile.text);
35+
}
1836

1937
for (const file of files) {
20-
outFiles.push(file, ...resolveFileDependencies(file, program, emitHost));
38+
const resolutionResult = resolveFileDependencies(file, program, emitHost);
39+
outFiles.push(file, ...resolutionResult.resolvedFiles);
40+
diagnostics.push(...resolutionResult.diagnostics);
2141
}
2242

23-
return outFiles;
43+
return { resolvedFiles: outFiles, diagnostics };
2444
}
2545

26-
function resolveFileDependencies(file: ProcessedFile, program: ts.Program, emitHost: EmitHost): ProcessedFile[] {
46+
function resolveFileDependencies(file: ProcessedFile, program: ts.Program, emitHost: EmitHost): ResolutionResult {
2747
const projectRootDir = getSourceDir(program);
2848
const dependencies: ProcessedFile[] = [];
49+
const diagnostics: ts.Diagnostic[] = [];
2950
for (const required of findRequiredPaths(file.code)) {
3051
// Do no resolve lualib
3152
if (required === "lualib_bundle") {
@@ -34,20 +55,21 @@ function resolveFileDependencies(file: ProcessedFile, program: ts.Program, emitH
3455

3556
// Do not resolve noResolution paths
3657
if (required.startsWith("@NoResolution:")) {
37-
const path = required.replace("@NoResolution:", "")
58+
const path = required.replace("@NoResolution:", "");
3859
replaceRequireInCode(file, required, path);
3960
replaceRequireInSourceMap(file, required, path);
4061
continue;
4162
}
42-
63+
4364
// Try to resolve the import starting from the directory `file` is in
4465
const fileDir = path.dirname(file.fileName);
45-
const resolvedDependency = resolveDependency(fileDir, projectRootDir, required, emitHost);
66+
const resolvedDependency = resolveDependency(fileDir, projectRootDir, required);
4667
if (resolvedDependency) {
4768
// If dependency resolved successfully, read its content
48-
const dependencyContent = emitHost.readFile(resolvedDependency);
69+
const dependencyContent = projectFiles.get(resolvedDependency) ?? emitHost.readFile(resolvedDependency);
4970
if (dependencyContent === undefined) {
50-
throw `TODO: FAILED TO READ ${resolvedDependency}`;
71+
diagnostics.push(couldNotReadDependency(resolvedDependency));
72+
continue;
5173
}
5274

5375
// Figure out resolved require path and dependency output path
@@ -57,28 +79,34 @@ function resolveFileDependencies(file: ProcessedFile, program: ts.Program, emitH
5779
replaceRequireInSourceMap(file, required, resolvedRequire);
5880

5981
// If dependency is not part of sources, add dependency to output and resolve its dependencies recursively
60-
if (!program.getSourceFile(resolvedDependency)) {
82+
if (!projectFiles.has(resolvedDependency)) {
6183
const dependency = {
6284
fileName: resolvedDependency,
6385
code: dependencyContent,
6486
};
65-
dependencies.push(dependency, ...resolveFileDependencies(dependency, program, emitHost));
87+
const nestedDependencies = resolveFileDependencies(dependency, program, emitHost);
88+
dependencies.push(dependency, ...nestedDependencies.resolvedFiles);
89+
diagnostics.push(...nestedDependencies.diagnostics);
6690
}
6791
} else {
68-
console.error(`Failed to resolve ${required} referenced in ${file.fileName}.`);
69-
console.error(projectRootDir);
92+
// Could not resolve dependency, add a diagnostic and make some fallback path
93+
diagnostics.push(couldNotResolveRequire(required, path.relative(projectRootDir, file.fileName)));
94+
95+
const fallbackRequire = fallbackResolve(required, projectRootDir, fileDir);
96+
replaceRequireInCode(file, required, fallbackRequire);
97+
replaceRequireInSourceMap(file, required, fallbackRequire);
7098
}
7199
}
72-
return dependencies;
100+
return { resolvedFiles: dependencies, diagnostics };
73101
}
74102

75103
function replaceRequireInCode(file: ProcessedFile, originalRequire: string, newRequire: string) {
76-
const requirePath = newRequire.replace(".lua", "").replace(/\\/g, ".");
104+
const requirePath = formatPathToLuaPath(newRequire.replace(".lua", ""));
77105
file.code = file.code.replace(`require("${originalRequire}")`, `require("${requirePath}")`);
78106
}
79107

80108
function replaceRequireInSourceMap(file: ProcessedFile, originalRequire: string, newRequire: string) {
81-
const requirePath = newRequire.replace(".lua", "").replace(/\\/g, ".");
109+
const requirePath = formatPathToLuaPath(newRequire.replace(".lua", ""));
82110
if (file.sourceMapNode) {
83111
replaceInSourceMap(file.sourceMapNode, file.sourceMapNode, `"${originalRequire}"`, `"${requirePath}"`);
84112
}
@@ -114,23 +142,22 @@ function findRequiredPaths(code: string): string[] {
114142
return paths;
115143
}
116144

117-
function resolveDependency(fileDirectory: string, rootDirectory: string, dependency: string, emitHost: EmitHost): string | undefined {
145+
function resolveDependency(fileDirectory: string, rootDirectory: string, dependency: string): string | undefined {
118146
// Check if file is a TS file in the project
119-
const dependencyPath = dependency;
120-
const resolvedPath = path.resolve(fileDirectory, dependencyPath);
121-
const resolvedFile = resolvedPath + ".ts";
147+
const resolvedPath = path.resolve(fileDirectory, dependency);
122148

123-
if (emitHost.fileExists(resolvedFile)) {
124-
return resolvedPath + ".ts";
149+
const resolvedFile = resolvedPath + ".ts";
150+
if (projectFiles.has(resolvedFile)) {
151+
return resolvedFile;
125152
}
126153

127-
const projectIndexPath = path.resolve(fileDirectory, dependencyPath, "index.ts");
128-
if (emitHost.fileExists(projectIndexPath)) {
154+
const projectIndexPath = path.resolve(resolvedPath, "index.ts");
155+
if (projectFiles.has(projectIndexPath)) {
129156
return projectIndexPath;
130157
}
131158

132159
try {
133-
const resolveResult = resolver.resolveSync({}, rootDirectory, dependencyPath);
160+
const resolveResult = resolver.resolveSync({}, rootDirectory, dependency);
134161
if (resolveResult) {
135162
return resolveResult;
136163
}
@@ -140,3 +167,14 @@ function resolveDependency(fileDirectory: string, rootDirectory: string, depende
140167

141168
return undefined;
142169
}
170+
171+
// Transform an import path to a lua require that is probably not correct, but can be used as fallback when regular resolution fails
172+
function fallbackResolve(required: string, projectRootDir: string, fileDir: string): string {
173+
return formatPathToLuaPath(
174+
path
175+
.normalize(path.join(path.relative(projectRootDir, fileDir), required))
176+
.split(path.sep)
177+
.filter(s => s !== "." && s !== "..")
178+
.join(path.sep)
179+
);
180+
}

src/transpilation/transpiler.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,20 +58,25 @@ export class Transpiler {
5858

5959
const lualibRequired = files.some(f => f.code.includes('require("lualib_bundle")'));
6060
if (lualibRequired) {
61-
const fileName = normalizeSlashes(path.resolve(getEmitOutDir(program), "lualib_bundle.lua"));
61+
// Add lualib bundle to source dir 'virtually', will be moved to correct output dir in emitPlan
62+
const fileName = normalizeSlashes(path.resolve(getSourceDir(program), "lualib_bundle.lua"));
6263
files.unshift({ fileName, code: getLuaLibBundle(this.emitHost) });
6364
}
6465

6566
// Resolve imported modules and modify output Lua requires
66-
const resolvedFiles = resolveDependencies(program, files, this.emitHost);
67+
const resolutionResult = resolveDependencies(program, files, this.emitHost);
68+
diagnostics.push(...resolutionResult.diagnostics);
6769

6870
let emitPlan: EmitFile[];
6971
if (isBundleEnabled(options)) {
70-
const [bundleDiagnostics, bundleFile] = getBundleResult(program, resolvedFiles);
72+
const [bundleDiagnostics, bundleFile] = getBundleResult(program, resolutionResult.resolvedFiles);
7173
diagnostics.push(...bundleDiagnostics);
7274
emitPlan = [bundleFile];
7375
} else {
74-
emitPlan = resolvedFiles.map(file => ({ ...file, outputPath: getEmitPath(file.fileName, program) }));
76+
emitPlan = resolutionResult.resolvedFiles.map(file => ({
77+
...file,
78+
outputPath: getEmitPath(file.fileName, program),
79+
}));
7580
}
7681

7782
return { emitPlan };

test/translation/transformation.spec.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import * as fs from "fs";
22
import * as path from "path";
33
import * as tstl from "../../src";
44
import { annotationDeprecated } from "../../src/transformation/utils/diagnostics";
5+
import { couldNotResolveRequire } from "../../src/transpilation/diagnostics";
56
import * as util from "../util";
67

78
const fixturesPath = path.join(__dirname, "./transformation");
@@ -14,7 +15,7 @@ const fixtures = fs
1415
test.each(fixtures)("Transformation (%s)", (_name, content) => {
1516
util.testModule(content)
1617
.setOptions({ luaLibImport: tstl.LuaLibImportKind.Require })
17-
.ignoreDiagnostics([annotationDeprecated.code])
18+
.ignoreDiagnostics([annotationDeprecated.code, couldNotResolveRequire.code])
1819
.disableSemanticCheck()
1920
.expectLuaToMatchSnapshot();
2021
});

test/transpile/__snapshots__/directories.spec.ts.snap

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,5 @@
11
// Jest Snapshot v1, https://goo.gl/fbAQLP
22

3-
exports[`should be able to resolve ({"name": "baseurl", "options": [Object]}) 1`] = `
4-
Array [
5-
"directories/baseurl/out/lualib_bundle.lua",
6-
"directories/baseurl/out/src/lib/nested/file.lua",
7-
"directories/baseurl/out/src/main.lua",
8-
]
9-
`;
10-
113
exports[`should be able to resolve ({"name": "basic", "options": [Object]}) 1`] = `
124
Array [
135
"directories/basic/src/lib/file.lua",

test/transpile/directories.spec.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ test.each<DirectoryTestCase>([
1313
{ name: "basic", options: { outDir: "out" } },
1414
{ name: "basic", options: { rootDir: "src" } },
1515
{ name: "basic", options: { rootDir: "src", outDir: "out" } },
16-
{ name: "baseurl", options: { baseUrl: "./src/lib", rootDir: ".", outDir: "./out" } },
1716
])("should be able to resolve (%p)", ({ name, options: compilerOptions }) => {
1817
const projectPath = path.join(__dirname, "directories", name);
1918
jest.spyOn(process, "cwd").mockReturnValue(projectPath);

test/transpile/directories/baseurl/src/lib/nested/file.ts

Lines changed: 0 additions & 3 deletions
This file was deleted.

test/transpile/directories/baseurl/src/main.ts

Lines changed: 0 additions & 3 deletions
This file was deleted.

0 commit comments

Comments
 (0)