@@ -232,46 +232,143 @@ module Make<InputSig Input> {
232
232
233
233
/** Provides the `append` predicate for appending a relative path onto a folder. */
234
234
module Append< shouldAppendSig / 2 shouldAppend> {
235
+ private module Config implements ResolveSig {
236
+ predicate shouldResolve ( Container c , string name ) { shouldAppend ( c , name ) }
237
+ }
238
+
239
+ predicate append = Resolve< Config > :: resolve / 2 ;
240
+ }
241
+
242
+ /**
243
+ * Signature for modules to pass to `Resolve`.
244
+ */
245
+ signature module ResolveSig {
246
+ /**
247
+ * Holds if `path` should be resolved to a file or folder, relative to `base`.
248
+ */
249
+ predicate shouldResolve ( Container base , string path ) ;
250
+
251
+ /**
252
+ * Gets an additional file or folder to consider a child of `base`.
253
+ */
254
+ default Container getAnAdditionalChild ( Container base , string name ) { none ( ) }
255
+
256
+ /**
257
+ * Holds if `component` may be treated as `.` if it does not match a child.
258
+ */
259
+ default predicate isOptionalPathComponent ( string component ) { none ( ) }
260
+
261
+ /**
262
+ * Holds if globs should be interpreted in the paths being resolved.
263
+ *
264
+ * The following types of globs are supported:
265
+ * - `*` (matches any child)
266
+ * - `**` (matches any child recursively)
267
+ * - Complex patterns like `foo-*.txt` are also supported
268
+ */
269
+ default predicate allowGlobs ( ) { none ( ) }
270
+
271
+ /**
272
+ * Gets an alternative path segment to try if `segment` did not match a child.
273
+ *
274
+ * The motivating use-case is to map compiler-generated file names back to their sources files,
275
+ * for example, `foo.min.js` could be mapped to `foo.ts`.
276
+ */
277
+ bindingset [ segment]
278
+ default string rewritePathSegment ( string segment ) { none ( ) }
279
+ }
280
+
281
+ /**
282
+ * Provides a mechanism for resolving file paths relative to a given directory.
283
+ */
284
+ module Resolve< ResolveSig Config> {
285
+ private import Config
286
+
235
287
pragma [ nomagic]
236
- private string getComponent ( string relativePath , int i ) {
237
- shouldAppend ( _, relativePath ) and
238
- result = relativePath .replaceAll ( "\\" , "/" ) .regexpFind ( "[^/]+ ", i , _ )
288
+ private string getPathSegment ( string path , int n ) {
289
+ shouldResolve ( _, path ) and
290
+ result = path .replaceAll ( "\\" , "/" ) .splitAt ( "/ ", n )
239
291
}
240
292
241
- private int getNumberOfComponents ( string relativePath ) {
242
- result = strictcount ( int i | exists ( getComponent ( relativePath , i ) ) | i )
293
+ pragma [ nomagic]
294
+ private string getPathSegmentAsGlobRegexp ( string segment ) {
295
+ allowGlobs ( ) and
296
+ segment = getPathSegment ( _, _) and
297
+ segment .matches ( "%*%" ) and
298
+ not segment = [ "*" , "**" ] and // these are special-cased
299
+ result = segment .regexpReplaceAll ( "[^a-zA-Z0-9*]" , "\\\\$0" ) .replaceAll ( "*" , ".*" )
300
+ }
301
+
302
+ pragma [ nomagic]
303
+ private int getNumPathSegment ( string path ) {
304
+ result = strictcount ( int n | exists ( getPathSegment ( path , n ) ) )
305
+ }
306
+
307
+ private Container getChild ( Container base , string name ) {
308
+ result = getAChildContainer ( base , name )
243
309
or
244
- relativePath = "" and
245
- result = 0
310
+ result = getAnAdditionalChild ( base , name )
246
311
}
247
312
248
313
pragma [ nomagic]
249
- private Container appendStep ( Folder f , string relativePath , int i ) {
250
- i = - 1 and
251
- shouldAppend ( f , relativePath ) and
252
- result = f
314
+ private Container resolve ( Container base , string path , int n ) {
315
+ shouldResolve ( base , path ) and n = 0 and result = base
316
+ or
317
+ exists ( Container current , string segment |
318
+ current = resolve ( base , path , n - 1 ) and
319
+ segment = getPathSegment ( path , n - 1 )
320
+ |
321
+ result = getChild ( current , segment )
322
+ or
323
+ segment = [ "." , "" ] and
324
+ result = current
325
+ or
326
+ segment = ".." and
327
+ result = current .getParentContainer ( )
328
+ or
329
+ not exists ( getChild ( current , segment ) ) and
330
+ (
331
+ isOptionalPathComponent ( segment ) and
332
+ result = current
333
+ or
334
+ result = getChild ( current , rewritePathSegment ( segment ) )
335
+ )
336
+ or
337
+ allowGlobs ( ) and
338
+ (
339
+ segment = "*" and
340
+ result = getChild ( current , _)
341
+ or
342
+ segment = "**" and // allow empty match
343
+ result = current
344
+ or
345
+ exists ( string name |
346
+ result = getChild ( current , name ) and
347
+ name .regexpMatch ( getPathSegmentAsGlobRegexp ( segment ) )
348
+ )
349
+ )
350
+ )
253
351
or
254
- exists ( Container mid , string comp |
255
- mid = appendStep ( f , relativePath , i - 1 ) and
256
- comp = getComponent ( relativePath , i ) and
257
- if comp = ".."
258
- then result = mid .getParentContainer ( )
259
- else
260
- if comp = "."
261
- then result = mid
262
- else result = getAChildContainer ( mid , comp )
352
+ exists ( Container current , string segment |
353
+ current = resolve ( base , path , n ) and
354
+ segment = getPathSegment ( path , n )
355
+ |
356
+ // Follow child without advancing 'n'
357
+ allowGlobs ( ) and
358
+ segment = "**" and
359
+ result = getChild ( current , _)
263
360
)
264
361
}
265
362
266
363
/**
267
- * Gets the file or folder obtained by appending `relativePath` onto `f`.
364
+ * Gets the file or folder that `path` resolves to when resolved from `base`.
365
+ *
366
+ * Only has results for the `(base, path)` pairs provided by `shouldResolve`
367
+ * in the instantiation of this module.
268
368
*/
269
369
pragma [ nomagic]
270
- Container append ( Folder f , string relativePath ) {
271
- exists ( int last |
272
- last = getNumberOfComponents ( relativePath ) - 1 and
273
- result = appendStep ( f , relativePath , last )
274
- )
370
+ Container resolve ( Container base , string path ) {
371
+ result = resolve ( base , path , getNumPathSegment ( path ) )
275
372
}
276
373
}
277
374
}
0 commit comments