From 55a91ebaef97541e6d38f3ebccb24d93139fa8a8 Mon Sep 17 00:00:00 2001 From: Asger F Date: Fri, 4 Apr 2025 00:08:18 +0200 Subject: [PATCH] JS: Path resolution for tsconfig --- .../ql/lib/semmle/javascript/Modules.qll | 4 +- .../javascript/internal/PathResolution.qll | 281 ++++++++++++++++++ .../PathResolution/BaseUrl/src/main.ts | 26 +- .../PathResolution/Extended/src/main.ts | 26 +- .../PathResolution/NoBaseUrl/src/main.ts | 12 +- .../PathResolution/test.expected | 32 ++ .../test/library-tests/PathResolution/test.ql | 1 + 7 files changed, 349 insertions(+), 33 deletions(-) create mode 100644 javascript/ql/lib/semmle/javascript/internal/PathResolution.qll diff --git a/javascript/ql/lib/semmle/javascript/Modules.qll b/javascript/ql/lib/semmle/javascript/Modules.qll index 209ac0d1370..35c783796fa 100644 --- a/javascript/ql/lib/semmle/javascript/Modules.qll +++ b/javascript/ql/lib/semmle/javascript/Modules.qll @@ -6,6 +6,7 @@ import javascript private import semmle.javascript.internal.CachedStages +private import semmle.javascript.internal.PathResolution /** * A module, which may either be an ECMAScript 2015-style module, @@ -139,7 +140,8 @@ abstract class Import extends AstNode { * Gets the module the path of this import resolves to. */ Module resolveImportedPath() { - result.getFile() = this.getEnclosingModule().resolve(this.getImportedPath()) + result.getFile() = PathResolution::resolvePathExpr(this.getImportedPath()) + // result.getFile() = this.getEnclosingModule().resolve(this.getImportedPath()) } /** diff --git a/javascript/ql/lib/semmle/javascript/internal/PathResolution.qll b/javascript/ql/lib/semmle/javascript/internal/PathResolution.qll new file mode 100644 index 00000000000..985d877a6ac --- /dev/null +++ b/javascript/ql/lib/semmle/javascript/internal/PathResolution.qll @@ -0,0 +1,281 @@ +private import javascript + +signature module ResolvePathsSig { + /** + * Holds if `path` should be resolved to a file or folder, relative to `base`. + */ + predicate shouldResolve(Folder base, string path); + + /** + * Gets an additional file or folder to consider a child of `base`. + */ + default Container getAnAdditionalChild(Container base, string name) { none() } +} + +pragma[inline] +private Container getChild(Container parent, string name) { + result = parent.getFile(name) + or + result = parent.getFolder(name) +} + +/** + * Provides a mechanism for resolving relative file paths. + * + * Absolute paths are not handled. + */ +module ResolvePaths { + private import Config + + private string getPathSegment(string path, int n) { + shouldResolve(_, path) and + result = path.replaceAll("\\", "/").splitAt("/", n) + } + + private int getNumPathSegment(string path) { + result = strictcount(int n | exists(getPathSegment(path, n))) + } + + private Container resolve(Container base, string path, int n) { + shouldResolve(base, path) and n = 0 and result = base + or + exists(Container cur, string segment | + cur = resolve(base, path, n - 1) and + segment = getPathSegment(path, n - 1) + | + result = getChild(cur, segment) + or + result = getAnAdditionalChild(cur, segment) + or + segment = [".", ""] and + result = cur + or + segment = ".." and + result = cur.getParentContainer() + ) + } + + /** + * Gets the file or folder that `path` resolves to when resolved from `base`. + * + * Only has results for the `base`, `path` pairs provided by `shouldResolve` + * in the instantiation of this module. + */ + Container resolve(Container base, string path) { + result = resolve(base, path, getNumPathSegment(path)) + } +} + +module PathResolution { + class TSConfig extends JsonObject { + TSConfig() { + this.getJsonFile().getBaseName().matches("%tsconfig%.json") and + this.isTopLevel() + } + + Folder getFolder() { result = this.getJsonFile().getParentContainer() } + + JsonObject getCompilerOptions() { result = this.getPropValue("compilerOptions") } + + /** Gets the string value in the `extends` property. */ + string getExtendsPath() { result = this.getPropStringValue("extends") } + + /** Gets the file referred to by the `extends` property. */ + File getExtendedFile() { + result = TSConfigResolve::resolve(this.getFolder(), this.getExtendsPath()) + } + + /** Gets the `TSConfig` file referred to by the `extends` property. */ + TSConfig getExtendedTSConfig() { result.getJsonFile() = this.getExtendedFile() } + + /** Gets the string value in the `baseUrl` property. */ + string getBaseUrlPath() { result = this.getCompilerOptions().getPropStringValue("baseUrl") } + + /** Gets the folder referred to by the `baseUrl` property in this file, not taking `extends` into account. */ + Folder getOwnBaseUrlFolder() { + result = TSConfigResolve::resolve(this.getFolder(), this.getBaseUrlPath()) + } + + /** Gets the effective baseUrl folder for this tsconfig file. */ + Folder getBaseUrlFolder() { + result = this.getOwnBaseUrlFolder() + or + not exists(this.getOwnBaseUrlFolder()) and + result = this.getExtendedTSConfig().getBaseUrlFolder() + } + + /** Gets a path mentioned in the `include` property. */ + string getAnIncludePath() { + result = this.getPropStringValue("include") + or + result = this.getPropValue("include").(JsonArray).getElementStringValue(_) + } + + /** + * Gets a file or folder mentioned in the `include` property. + * + * Does not include all the files within includes directories. + */ + Container getAnIncludedBaseContainer() { + result = TSConfigResolve::resolve(this.getFolder(), this.getAnIncludePath()) + or + result = this.getExtendedTSConfig().getAnIncludedBaseContainer() + } + + private predicate isPrimaryTSConfig() { + this.getJsonFile().getBaseName() = "tsconfig.json" + or + // Fallback in case we can't find the primary tsconfig file + not exists(this.getFolder().getFile("tsconfig.json")) and + not this = any(TSConfig tsc).getExtendedTSConfig() + } + + /** + * Gets a file or folder inside the directory tree mentioned in the `include` property. + */ + Container getAnAffectedFile() { + this.isPrimaryTSConfig() and + result = this.getAnIncludedBaseContainer() + or + result = this.getAnAffectedFile().getAChildContainer() + } + + JsonObject getPathMappings() { result = this.getCompilerOptions().getPropValue("paths") } + + predicate hasPathMapping(string pattern, string newPath) { + this.getPathMappings().getPropStringValue(pattern) = newPath + or + // TODO: track priority + this.getPathMappings().getPropValue(pattern).(JsonArray).getElementStringValue(_) = newPath + } + + predicate hasExactPathMapping(string pattern, string newPath) { + this.hasPathMapping(pattern, newPath) and + not pattern.matches("%*%") + } + + predicate hasPrefixPathMapping(string pattern, string newPath) { + this.hasPathMapping(pattern + "*", newPath + "*") + } + } + + private module TSConfigResolveConfig implements ResolvePathsSig { + predicate shouldResolve(Folder base, string path) { + exists(TSConfig cfg | + base = cfg.getFolder() and + path = [cfg.getExtendsPath(), cfg.getBaseUrlPath(), cfg.getAnIncludePath()] + ) + } + } + + private module TSConfigResolve = ResolvePaths; + + bindingset[path] + private predicate isRelativePath(string path) { path.regexpMatch("\\.\\.?[/\\\\].*") } + + private module ResolvePathMappingConfig implements ResolvePathsSig { + additional predicate shouldResolve(TSConfig cfg, Container base, string path) { + (cfg.hasExactPathMapping(_, path) or cfg.hasPrefixPathMapping(_, path)) and + if isRelativePath(path) + then base = cfg.getFolder() // relative paths are resolved relative to tsconfig.json + else base = cfg.getBaseUrlFolder() // non-relative paths are resolved relative to the baseUrl + } + + predicate shouldResolve(Folder base, string path) { shouldResolve(_, base, path) } + } + + private module ResolvePathMapping = ResolvePaths; + + private Container resolvePathMapping(TSConfig cfg, string path) { + exists(Container base | + ResolvePathMappingConfig::shouldResolve(cfg, base, path) and + result = ResolvePathMapping::resolve(base, path) + ) + } + + private TSConfig getTSConfigFromPathExpr(PathExpr expr) { + result.getAnAffectedFile() = expr.getFile() + } + + pragma[nomagic] + private predicate replacedPath1(PathExpr expr, Container base, string newPath) { + expr = any(Import imprt).getImportedPath() and + exists(TSConfig config, string value, string mappedPath | + config = getTSConfigFromPathExpr(expr).getExtendedTSConfig*() and + value = expr.getValue() + | + config.hasExactPathMapping(value, mappedPath) and + base = resolvePathMapping(config, mappedPath) and + newPath = "" + or + exists(string pattern | + config.hasPrefixPathMapping(pattern, mappedPath) and + value = pattern + any(string s) and + base = resolvePathMapping(config, mappedPath) and + newPath = value.suffix(pattern.length()) + ) + ) + } + + pragma[nomagic] + private predicate replacedPath(PathExpr expr, Container base, string newPath) { + replacedPath1(expr, base, newPath) + or + // resolve from baseUrl + expr = any(Import imprt).getImportedPath() and + not replacedPath1(expr, _, _) and + newPath = expr.getValue() and + newPath.charAt(0) != "." and + base = getTSConfigFromPathExpr(expr).getBaseUrlFolder() + } + + private module ResolvePathExprConfig implements ResolvePathsSig { + additional predicate shouldResolve(PathExpr expr, Folder base, string path) { + expr = any(Import imprt).getImportedPath() and + ( + replacedPath(expr, base, path) + or + not replacedPath(expr, _, _) and + isRelativePath(expr.getValue()) and + base = expr.getFile().getParentContainer() and + path = expr.getValue() + ) + } + + predicate shouldResolve(Folder base, string path) { shouldResolve(_, base, path) } + + private predicate extensionCompilesTo(string original, string compilesTo) { + original = "ts" and + compilesTo = "js" + or + original = "tsx" and + compilesTo = ["jsx", "js"] + } + + Container getAnAdditionalChild(Container base, string name) { + result = base.(Folder).getJavaScriptFile(name) + or + exists(string stem, string addedExt | + result = base.(Folder).getJavaScriptFile(stem) and + extensionCompilesTo(result.getExtension(), addedExt) and + name = result.getStem() + "." + addedExt and + not exists(base.(Folder).getFile(name)) + ) + } + } + + private module ResolvePathExpr = ResolvePaths; + + private Container resolvePathExpr1(PathExpr expr) { + exists(Container base, string path | + ResolvePathExprConfig::shouldResolve(expr, base, path) and + result = ResolvePathExpr::resolve(base, path) + ) + } + + File resolvePathExpr(PathExpr expr) { + result = resolvePathExpr1(expr) + or + result = resolvePathExpr1(expr).(Folder).getJavaScriptFile("index") + } +} diff --git a/javascript/ql/test/library-tests/PathResolution/BaseUrl/src/main.ts b/javascript/ql/test/library-tests/PathResolution/BaseUrl/src/main.ts index 667f0757f8e..3299bfbd3e3 100644 --- a/javascript/ql/test/library-tests/PathResolution/BaseUrl/src/main.ts +++ b/javascript/ql/test/library-tests/PathResolution/BaseUrl/src/main.ts @@ -8,22 +8,22 @@ import "../lib/index.ts"; // $ importTarget=BaseUrl/lib/index.ts import "../lib/index.js"; // $ importTarget=BaseUrl/lib/index.ts // Import relative to baseUrl -import "lib/file"; // $ MISSING: importTarget=BaseUrl/lib/file.ts -import "lib/file.ts"; // $ MISSING: importTarget=BaseUrl/lib/file.ts -import "lib/file.js"; // $ MISSING: importTarget=BaseUrl/lib/file.ts -import "lib"; // $ MISSING: importTarget=BaseUrl/lib/index.ts -import "lib/index"; // $ MISSING: importTarget=BaseUrl/lib/index.ts -import "lib/index.ts"; // $ MISSING: importTarget=BaseUrl/lib/index.ts -import "lib/index.js"; // $ MISSING: importTarget=BaseUrl/lib/index.ts +import "lib/file"; // $ importTarget=BaseUrl/lib/file.ts +import "lib/file.ts"; // $ importTarget=BaseUrl/lib/file.ts +import "lib/file.js"; // $ importTarget=BaseUrl/lib/file.ts +import "lib"; // $ importTarget=BaseUrl/lib/index.ts +import "lib/index"; // $ importTarget=BaseUrl/lib/index.ts +import "lib/index.ts"; // $ importTarget=BaseUrl/lib/index.ts +import "lib/index.js"; // $ importTarget=BaseUrl/lib/index.ts // Import matching "@/*" path mapping -import "@/file"; // $ MISSING: importTarget=BaseUrl/lib/file.ts -import "@/file.ts"; // $ MISSING: importTarget=BaseUrl/lib/file.ts -import "@/file.js"; // $ MISSING: importTarget=BaseUrl/lib/file.ts +import "@/file"; // $ importTarget=BaseUrl/lib/file.ts +import "@/file.ts"; // $ importTarget=BaseUrl/lib/file.ts +import "@/file.js"; // $ importTarget=BaseUrl/lib/file.ts import "@"; // $ MISSING: importTarget=BaseUrl/lib/nostar.ts -import "@/index"; // $ MISSING: importTarget=BaseUrl/lib/index.ts -import "@/index.ts"; // $ MISSING: importTarget=BaseUrl/lib/index.ts -import "@/index.js"; // $ MISSING: importTarget=BaseUrl/lib/index.ts +import "@/index"; // $ importTarget=BaseUrl/lib/index.ts +import "@/index.ts"; // $ importTarget=BaseUrl/lib/index.ts +import "@/index.js"; // $ importTarget=BaseUrl/lib/index.ts // Import matching "@/*.xyz" path mapping. Note that this is not actually supported by TypeScript. import "@/file.xyz"; diff --git a/javascript/ql/test/library-tests/PathResolution/Extended/src/main.ts b/javascript/ql/test/library-tests/PathResolution/Extended/src/main.ts index 3afdd24fd17..54be7c326e9 100644 --- a/javascript/ql/test/library-tests/PathResolution/Extended/src/main.ts +++ b/javascript/ql/test/library-tests/PathResolution/Extended/src/main.ts @@ -8,22 +8,22 @@ import "../lib/index.ts"; // $ importTarget=Extended/lib/index.ts import "../lib/index.js"; // $ importTarget=Extended/lib/index.ts // Import relative to baseUrl -import "lib/file"; // $ MISSING: importTarget=Extended/lib/file.ts -import "lib/file.ts"; // $ MISSING: importTarget=Extended/lib/file.ts -import "lib/file.js"; // $ MISSING: importTarget=Extended/lib/file.ts -import "lib"; // $ MISSING: importTarget=Extended/lib/index.ts -import "lib/index"; // $ MISSING: importTarget=Extended/lib/index.ts -import "lib/index.ts"; // $ MISSING: importTarget=Extended/lib/index.ts -import "lib/index.js"; // $ MISSING: importTarget=Extended/lib/index.ts +import "lib/file"; // $ importTarget=Extended/lib/file.ts +import "lib/file.ts"; // $ importTarget=Extended/lib/file.ts +import "lib/file.js"; // $ importTarget=Extended/lib/file.ts +import "lib"; // $ importTarget=Extended/lib/index.ts +import "lib/index"; // $ importTarget=Extended/lib/index.ts +import "lib/index.ts"; // $ importTarget=Extended/lib/index.ts +import "lib/index.js"; // $ importTarget=Extended/lib/index.ts // Import matching "@/*" path mapping -import "@/file"; // $ MISSING: importTarget=Extended/lib/file.ts -import "@/file.ts"; // $ MISSING: importTarget=Extended/lib/file.ts -import "@/file.js"; // $ MISSING: importTarget=Extended/lib/file.ts +import "@/file"; // $ importTarget=Extended/lib/file.ts +import "@/file.ts"; // $ importTarget=Extended/lib/file.ts +import "@/file.js"; // $ importTarget=Extended/lib/file.ts import "@"; // $ MISSING: importTarget=Extended/lib/nostar.ts -import "@/index"; // $ MISSING: importTarget=Extended/lib/index.ts -import "@/index.ts"; // $ MISSING: importTarget=Extended/lib/index.ts -import "@/index.js"; // $ MISSING: importTarget=Extended/lib/index.ts +import "@/index"; // $ importTarget=Extended/lib/index.ts +import "@/index.ts"; // $ importTarget=Extended/lib/index.ts +import "@/index.js"; // $ importTarget=Extended/lib/index.ts // Import matching "@/*.xyz" path mapping. Note that this is not actually supported by TypeScript. import "@/file.xyz"; diff --git a/javascript/ql/test/library-tests/PathResolution/NoBaseUrl/src/main.ts b/javascript/ql/test/library-tests/PathResolution/NoBaseUrl/src/main.ts index 422ed5f3ecd..25f0a7a2c4d 100644 --- a/javascript/ql/test/library-tests/PathResolution/NoBaseUrl/src/main.ts +++ b/javascript/ql/test/library-tests/PathResolution/NoBaseUrl/src/main.ts @@ -17,13 +17,13 @@ import "lib/index.ts"; import "lib/index.js"; // Import matching "@/*" path mapping -import "@/file"; // $ MISSING: importTarget=NoBaseUrl/lib/file.ts -import "@/file.ts"; // $ MISSING: importTarget=NoBaseUrl/lib/file.ts -import "@/file.js"; // $ MISSING: importTarget=NoBaseUrl/lib/file.ts +import "@/file"; // $ importTarget=NoBaseUrl/lib/file.ts +import "@/file.ts"; // $ importTarget=NoBaseUrl/lib/file.ts +import "@/file.js"; // $ importTarget=NoBaseUrl/lib/file.ts import "@"; // $ MISSING: importTarget=NoBaseUrl/lib/nostar.ts -import "@/index"; // $ MISSING: importTarget=NoBaseUrl/lib/index.ts -import "@/index.ts"; // $ MISSING: importTarget=NoBaseUrl/lib/index.ts -import "@/index.js"; // $ MISSING: importTarget=NoBaseUrl/lib/index.ts +import "@/index"; // $ importTarget=NoBaseUrl/lib/index.ts +import "@/index.ts"; // $ importTarget=NoBaseUrl/lib/index.ts +import "@/index.js"; // $ importTarget=NoBaseUrl/lib/index.ts // Import matching "@/*.xyz" path mapping. Note that this is not actually supported by TypeScript. import "@/file.xyz"; diff --git a/javascript/ql/test/library-tests/PathResolution/test.expected b/javascript/ql/test/library-tests/PathResolution/test.expected index 29f89584f60..28a8bdbe74d 100644 --- a/javascript/ql/test/library-tests/PathResolution/test.expected +++ b/javascript/ql/test/library-tests/PathResolution/test.expected @@ -5,6 +5,19 @@ | BaseUrl/src/main.ts:6:1:6:22 | import ... index"; | BaseUrl/lib/index.ts | | BaseUrl/src/main.ts:7:1:7:25 | import ... ex.ts"; | BaseUrl/lib/index.ts | | BaseUrl/src/main.ts:8:1:8:25 | import ... ex.js"; | BaseUrl/lib/index.ts | +| BaseUrl/src/main.ts:11:1:11:18 | import "lib/file"; | BaseUrl/lib/file.ts | +| BaseUrl/src/main.ts:12:1:12:21 | import ... le.ts"; | BaseUrl/lib/file.ts | +| BaseUrl/src/main.ts:13:1:13:21 | import ... le.js"; | BaseUrl/lib/file.ts | +| BaseUrl/src/main.ts:14:1:14:13 | import "lib"; | BaseUrl/lib/index.ts | +| BaseUrl/src/main.ts:15:1:15:19 | import "lib/index"; | BaseUrl/lib/index.ts | +| BaseUrl/src/main.ts:16:1:16:22 | import ... ex.ts"; | BaseUrl/lib/index.ts | +| BaseUrl/src/main.ts:17:1:17:22 | import ... ex.js"; | BaseUrl/lib/index.ts | +| BaseUrl/src/main.ts:20:1:20:16 | import "@/file"; | BaseUrl/lib/file.ts | +| BaseUrl/src/main.ts:21:1:21:19 | import "@/file.ts"; | BaseUrl/lib/file.ts | +| BaseUrl/src/main.ts:22:1:22:19 | import "@/file.js"; | BaseUrl/lib/file.ts | +| BaseUrl/src/main.ts:24:1:24:17 | import "@/index"; | BaseUrl/lib/index.ts | +| BaseUrl/src/main.ts:25:1:25:20 | import "@/index.ts"; | BaseUrl/lib/index.ts | +| BaseUrl/src/main.ts:26:1:26:20 | import "@/index.js"; | BaseUrl/lib/index.ts | | Extended/src/main.ts:2:1:2:21 | import ... /file"; | Extended/lib/file.ts | | Extended/src/main.ts:3:1:3:24 | import ... le.ts"; | Extended/lib/file.ts | | Extended/src/main.ts:4:1:4:24 | import ... le.js"; | Extended/lib/file.ts | @@ -12,6 +25,19 @@ | Extended/src/main.ts:6:1:6:22 | import ... index"; | Extended/lib/index.ts | | Extended/src/main.ts:7:1:7:25 | import ... ex.ts"; | Extended/lib/index.ts | | Extended/src/main.ts:8:1:8:25 | import ... ex.js"; | Extended/lib/index.ts | +| Extended/src/main.ts:11:1:11:18 | import "lib/file"; | Extended/lib/file.ts | +| Extended/src/main.ts:12:1:12:21 | import ... le.ts"; | Extended/lib/file.ts | +| Extended/src/main.ts:13:1:13:21 | import ... le.js"; | Extended/lib/file.ts | +| Extended/src/main.ts:14:1:14:13 | import "lib"; | Extended/lib/index.ts | +| Extended/src/main.ts:15:1:15:19 | import "lib/index"; | Extended/lib/index.ts | +| Extended/src/main.ts:16:1:16:22 | import ... ex.ts"; | Extended/lib/index.ts | +| Extended/src/main.ts:17:1:17:22 | import ... ex.js"; | Extended/lib/index.ts | +| Extended/src/main.ts:20:1:20:16 | import "@/file"; | Extended/lib/file.ts | +| Extended/src/main.ts:21:1:21:19 | import "@/file.ts"; | Extended/lib/file.ts | +| Extended/src/main.ts:22:1:22:19 | import "@/file.js"; | Extended/lib/file.ts | +| Extended/src/main.ts:24:1:24:17 | import "@/index"; | Extended/lib/index.ts | +| Extended/src/main.ts:25:1:25:20 | import "@/index.ts"; | Extended/lib/index.ts | +| Extended/src/main.ts:26:1:26:20 | import "@/index.js"; | Extended/lib/index.ts | | NoBaseUrl/src/main.ts:2:1:2:21 | import ... /file"; | NoBaseUrl/lib/file.ts | | NoBaseUrl/src/main.ts:3:1:3:24 | import ... le.ts"; | NoBaseUrl/lib/file.ts | | NoBaseUrl/src/main.ts:4:1:4:24 | import ... le.js"; | NoBaseUrl/lib/file.ts | @@ -19,3 +45,9 @@ | NoBaseUrl/src/main.ts:6:1:6:22 | import ... index"; | NoBaseUrl/lib/index.ts | | NoBaseUrl/src/main.ts:7:1:7:25 | import ... ex.ts"; | NoBaseUrl/lib/index.ts | | NoBaseUrl/src/main.ts:8:1:8:25 | import ... ex.js"; | NoBaseUrl/lib/index.ts | +| NoBaseUrl/src/main.ts:20:1:20:16 | import "@/file"; | NoBaseUrl/lib/file.ts | +| NoBaseUrl/src/main.ts:21:1:21:19 | import "@/file.ts"; | NoBaseUrl/lib/file.ts | +| NoBaseUrl/src/main.ts:22:1:22:19 | import "@/file.js"; | NoBaseUrl/lib/file.ts | +| NoBaseUrl/src/main.ts:24:1:24:17 | import "@/index"; | NoBaseUrl/lib/index.ts | +| NoBaseUrl/src/main.ts:25:1:25:20 | import "@/index.ts"; | NoBaseUrl/lib/index.ts | +| NoBaseUrl/src/main.ts:26:1:26:20 | import "@/index.js"; | NoBaseUrl/lib/index.ts | diff --git a/javascript/ql/test/library-tests/PathResolution/test.ql b/javascript/ql/test/library-tests/PathResolution/test.ql index a3cf0884659..7cc9c348829 100644 --- a/javascript/ql/test/library-tests/PathResolution/test.ql +++ b/javascript/ql/test/library-tests/PathResolution/test.ql @@ -1,4 +1,5 @@ import javascript +import semmle.javascript.internal.PathResolution query predicate importTarget(Import imprt, string value) { imprt.getImportedModule().getFile().getRelativePath() = value