diff --git a/javascript/ql/lib/semmle/javascript/internal/PathResolution.qll b/javascript/ql/lib/semmle/javascript/internal/PathResolution.qll index 56ad357a92e..3b3aea019f3 100644 --- a/javascript/ql/lib/semmle/javascript/internal/PathResolution.qll +++ b/javascript/ql/lib/semmle/javascript/internal/PathResolution.qll @@ -73,6 +73,16 @@ private module AutomaticFileExtensions { } module PathResolution { + /** + * Provides an extension point for mapping build directories to corresonding source directories. + */ + class AdditionalBuildPathMapping extends Unit { + /** + * Gets a source directory whose contents are compiled to `base/buildDir`. + */ + Container getSourceFromBuildTarget(Container base, string buildDir) { none() } + } + /** * Holds if `path` is a relative path, in the sense that it must be resolved relative to * its enclosing directory. @@ -352,7 +362,12 @@ module PathResolution { predicate shouldResolve(Container base, string path) { shouldResolve(_, base, path) } - predicate getAnAdditionalChild = AutomaticFileExtensions::getAnAdditionalChild/2; + // predicate getAnAdditionalChild = AutomaticFileExtensions::getAnAdditionalChild/2; + Container getAnAdditionalChild(Container base, string name) { + result = AutomaticFileExtensions::getAnAdditionalChild(base, name) + or + result = ReverseBuildDir::getAnAdditionalChild(base, name) + } } private module ResolvePathExpr = ResolvePaths; @@ -382,13 +397,7 @@ module PathResolution { Container getAnAdditionalChild(Container base, string name) { result = AutomaticFileExtensions::getAnAdditionalChild(base, name) or - // When resolving an output-like folder that doesn't exist, try resolving from 'src' - exists(Folder folder | base = folder | - folder = any(PackageJson pkg).getJsonFile().getParentContainer() and - name = getABuildOutputFolderName() and - not exists(folder.getChildContainer(name)) and - result = folder.getChildContainer(getASrcFolderName()) - ) + result = ReverseBuildDir::getAnAdditionalChild(base, name) } } @@ -401,6 +410,33 @@ module PathResolution { ) } + /** + * Provides a `getAnAdditionalChild` predicate for mapping files inside a build directory + * back to their corresponding source files. + */ + private module ReverseBuildDir { + Container getAnAdditionalChild(Container base, string name) { + // Redirect './bar' to 'foo' given a tsconfig like: + // { include: ["foo"], compilerOptions: { outDir: "./bar" }} + exists(TSConfig tsconfig | + name = + tsconfig.getCompilerOptions().getPropStringValue("outDir").regexpReplaceAll("^\\./", "") and + base = tsconfig.getFolder() and + result = tsconfig.getAnIncludedBaseContainer() + ) + or + // Heuristic version of the above based on commonly used source and build folder names + exists(Folder folder | base = folder | + folder = any(PackageJson pkg).getJsonFile().getParentContainer() and + name = getABuildOutputFolderName() and + not exists(folder.getChildContainer(name)) and + result = folder.getChildContainer(getASrcFolderName()) + ) + or + result = any(AdditionalBuildPathMapping b).getSourceFromBuildTarget(base, name) + } + } + /** * Gets a folder name that is a common source folder name. */ @@ -465,7 +501,7 @@ module PathResolution { final private class FinalPathExpr = PathExpr; class PathExprToDebug extends FinalPathExpr { - PathExprToDebug() { this.getValue() = "~/core/handlers/RequestHandler" } + PathExprToDebug() { this.getValue() = "ai" } } query PathExprToDebug pathExprs() { any() } diff --git a/javascript/ql/test/library-tests/PathResolution/TSConfigOutDir/src/foo.ts b/javascript/ql/test/library-tests/PathResolution/TSConfigOutDir/src/foo.ts new file mode 100644 index 00000000000..ad1d380d6cc --- /dev/null +++ b/javascript/ql/test/library-tests/PathResolution/TSConfigOutDir/src/foo.ts @@ -0,0 +1 @@ +export const x = 1; diff --git a/javascript/ql/test/library-tests/PathResolution/TSConfigOutDir/tsconfig.json b/javascript/ql/test/library-tests/PathResolution/TSConfigOutDir/tsconfig.json new file mode 100644 index 00000000000..09d14078cb6 --- /dev/null +++ b/javascript/ql/test/library-tests/PathResolution/TSConfigOutDir/tsconfig.json @@ -0,0 +1,7 @@ +{ + "include": ["src"], + "compilerOptions": { + // Use unusual name to test independently of naming heuristics + "outDir": "./customOutDir" + } +} diff --git a/javascript/ql/test/library-tests/PathResolution/import-packages.ts b/javascript/ql/test/library-tests/PathResolution/import-packages.ts index c1399db994e..0a4e0e40086 100644 --- a/javascript/ql/test/library-tests/PathResolution/import-packages.ts +++ b/javascript/ql/test/library-tests/PathResolution/import-packages.ts @@ -22,3 +22,5 @@ import '@example/package-guess2'; // $ importTarget=PackageGuess2/blah/stuff.ts import './PackageWithMainExt'; // $ importTarget=PackageWithMainExt/lib/main.ts import '@example/package-with-main-ext'; // $ importTarget=PackageWithMainExt/lib/main.ts + +import './TSConfigOutDir/customOutDir/foo.js'; // $ importTarget=TSConfigOutDir/src/foo.ts diff --git a/javascript/ql/test/library-tests/PathResolution/test.expected b/javascript/ql/test/library-tests/PathResolution/test.expected index 3f139ac7b3d..8581258c23f 100644 --- a/javascript/ql/test/library-tests/PathResolution/test.expected +++ b/javascript/ql/test/library-tests/PathResolution/test.expected @@ -74,3 +74,4 @@ | import-packages.ts:21:1:21:33 | import ... uess2'; | PackageGuess2/blah/stuff.ts | | import-packages.ts:23:1:23:30 | import ... inExt'; | PackageWithMainExt/lib/main.ts | | import-packages.ts:24:1:24:40 | import ... n-ext'; | PackageWithMainExt/lib/main.ts | +| import-packages.ts:26:1:26:46 | import ... oo.js'; | TSConfigOutDir/src/foo.ts |