@@ -4,7 +4,9 @@ import * as ts from "typescript";
44import * as fs from "fs" ;
55import { EmitHost , ProcessedFile } from "./utils" ;
66import { 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
911const 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
75103function 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
80108function 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+ }
0 commit comments