Skip to content

Commit 2ce01bf

Browse files
committed
Add Folder::Resolve as a generalisation of Folder::Append
1 parent eb05996 commit 2ce01bf

File tree

1 file changed

+123
-26
lines changed

1 file changed

+123
-26
lines changed

shared/util/codeql/util/FileSystem.qll

Lines changed: 123 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -232,46 +232,143 @@ module Make<InputSig Input> {
232232

233233
/** Provides the `append` predicate for appending a relative path onto a folder. */
234234
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+
235287
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)
239291
}
240292

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)
243309
or
244-
relativePath = "" and
245-
result = 0
310+
result = getAnAdditionalChild(base, name)
246311
}
247312

248313
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+
)
253351
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, _)
263360
)
264361
}
265362

266363
/**
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.
268368
*/
269369
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))
275372
}
276373
}
277374
}

0 commit comments

Comments
 (0)