@@ -144,26 +144,24 @@ abstract class PathString extends string {
144144 Path resolveUpTo ( int n , Folder root ) {
145145 n = 0 and result .getContainer ( ) = root and root = getARootFolder ( )
146146 or
147- exists ( Path base | base = resolveUpTo ( n - 1 , root ) |
148- exists ( string next | next = getComponent ( n - 1 ) |
149- // handle empty components and the special "." folder
150- ( next = "" or next = "." ) and
151- result = base
152- or
153- // handle the special ".." folder
154- next = ".." and result = base .( ConsPath ) .getParent ( )
155- or
156- // special handling for Windows drive letters when resolving absolute path:
157- // the extractor populates "C:/" as a folder that has path "C:/" but name ""
158- n = 1 and
159- next .regexpMatch ( "[A-Za-z]:" ) and
160- root .getBaseName ( ) = "" and
161- root .toString ( ) = next .toUpperCase ( ) + "/" and
162- result = base
163- or
164- // default case
165- result = TConsPath ( base , next )
166- )
147+ exists ( Path base , string next | next = getComponent ( this , n - 1 , base , root ) |
148+ // handle empty components and the special "." folder
149+ ( next = "" or next = "." ) and
150+ result = base
151+ or
152+ // handle the special ".." folder
153+ next = ".." and result = base .( ConsPath ) .getParent ( )
154+ or
155+ // special handling for Windows drive letters when resolving absolute path:
156+ // the extractor populates "C:/" as a folder that has path "C:/" but name ""
157+ n = 1 and
158+ next .regexpMatch ( "[A-Za-z]:" ) and
159+ root .getBaseName ( ) = "" and
160+ root .toString ( ) = next .toUpperCase ( ) + "/" and
161+ result = base
162+ or
163+ // default case
164+ result = TConsPath ( base , next )
167165 )
168166 }
169167
@@ -174,6 +172,105 @@ abstract class PathString extends string {
174172 Path resolve ( Folder root ) { result = resolveUpTo ( getNumComponent ( ) , root ) }
175173}
176174
175+ /**
176+ * Gets the `i`th component of the path `str`, where `base` is the resolved path one level up.
177+ * Supports that the root directory might be compiled output from TypeScript.
178+ */
179+ private string getComponent ( PathString str , int n , Path base , Folder root ) {
180+ base = str .resolveUpTo ( n , root ) and
181+ (
182+ result = str .getComponent ( n )
183+ or
184+ result = TypeScriptOutDir:: getOriginalTypeScriptFolder ( str .getComponent ( n ) , base .getContainer ( ) )
185+ )
186+ }
187+
188+ /**
189+ * Predicates for resolving imports to compiled TypeScript.
190+ */
191+ private module TypeScriptOutDir {
192+ /**
193+ * Gets a folder of TypeScript files that is compiled to JavaScript files in `outdir` relative to a `parent`.
194+ */
195+ string getOriginalTypeScriptFolder ( string outdir , Folder parent ) {
196+ exists ( JSONObject tsconfig |
197+ tsconfig .getFile ( ) .getBaseName ( ) = "tsconfig.json" and
198+ tsconfig .isTopLevel ( ) and
199+ tsconfig .getFile ( ) .getParentContainer ( ) = parent
200+ |
201+ outdir =
202+ tsconfig
203+ .getPropValue ( "compilerOptions" )
204+ .( JSONObject )
205+ .getPropValue ( "outDir" )
206+ .( JSONString )
207+ .getValue ( ) and
208+ result = getEffectiveRootDirFromTSConfig ( tsconfig )
209+ )
210+ }
211+
212+ /**
213+ * Gets the directory that contains the TypeScript source files.
214+ * Based on the tsconfig.json file `tsconfig`.
215+ */
216+ pragma [ inline]
217+ private string getEffectiveRootDirFromTSConfig ( JSONObject tsconfig ) {
218+ // if an explicit "rootDir" option exists, then use that.
219+ result = getRootDir ( tsconfig )
220+ or
221+ // otherwise, infer from "includes"
222+ not exists ( getRootDir ( tsconfig ) ) and
223+ (
224+ // if unique root folder in "includes", then use that.
225+ result = unique( | | getARootDirFromInclude ( tsconfig ) )
226+ or
227+ // otherwise use "." if the includes are split over multiple folders.
228+ exists ( getARootDirFromInclude ( tsconfig ) ) and
229+ not exists ( unique( | | getARootDirFromInclude ( tsconfig ) ) ) and
230+ result = "."
231+ )
232+ }
233+
234+ /**
235+ * Gets the first folder from `path`.
236+ */
237+ bindingset [ path]
238+ private string getRootFolderFromPath ( string path ) {
239+ not exists ( path .indexOf ( "/" ) ) and result = path
240+ or
241+ result = path .substring ( 0 , path .indexOf ( "/" , 0 , 0 ) )
242+ }
243+
244+ /**
245+ * Gets a root directory containing TypeScript files based on the "include" option from tsconfig.json.
246+ * Can have multiple results if the includes are from multiple folders.
247+ */
248+ pragma [ inline]
249+ private string getARootDirFromInclude ( JSONObject tsconfig ) {
250+ result =
251+ getRootFolderFromPath ( tsconfig
252+ .getPropValue ( "include" )
253+ .( JSONArray )
254+ .getElementValue ( _)
255+ .( JSONString )
256+ .getValue ( ) )
257+ }
258+
259+ /**
260+ * Gets the value of the "rootDir" option from a tsconfig.json.
261+ */
262+ pragma [ inline]
263+ private string getRootDir ( JSONObject tsconfig ) {
264+ result =
265+ tsconfig
266+ .getPropValue ( "compilerOptions" )
267+ .( JSONObject )
268+ .getPropValue ( "rootDir" )
269+ .( JSONString )
270+ .getValue ( )
271+ }
272+ }
273+
177274/**
178275 * An expression whose value represents a (relative or absolute) file system path.
179276 *
0 commit comments