-
Notifications
You must be signed in to change notification settings - Fork 1.7k
JS: Overhaul import resolution #19391
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 1 commit
Commits
Show all changes
26 commits
Select commit
Hold shift + click to select a range
eb05996
Move getAChildContainer one scope up
asgerf 2ce01bf
Add Folder::Resolve as a generalisation of Folder::Append
asgerf ec9d15b
JS: Make shared Folder module visible
asgerf 8c0b0c4
JS: Ensure json files are extracted properly in tests
asgerf 359525b
JS: Extract more tsconfig.json patterns
asgerf 565cb43
JS: Add test
asgerf 17aa522
JS: Add some helpers
asgerf ef32a03
JS: Extract from methods from PathString into a non-abstract base class
asgerf 59e1cbc
JS: Add tsconfig class
asgerf bb91df8
JS: Add helper for doing path resolution with JS rules
asgerf f542956
JS: Add internal extension of PackageJson class
asgerf ed4864e
JS: Add two more helpers to FilePath class
asgerf 6725cb5
JS: Implement import resolution
asgerf e4420f6
JS: Move babel-root-import test
asgerf d724874
JS: Implement babel-plugin-root-import as a PathMapping
asgerf a195d07
JS: Resolve Angular2 templateUrl with ResolveExpr instead of PathExpr
asgerf c293f03
JS: Remove a dependency on getImportedPath()
asgerf fe055ad
JS: Use PackageJsonEx instead of resolveMainModule
asgerf ed2a832
JS: Deprecate PathExpr and related classes
asgerf be5de9c
JS: Update test output
asgerf 5de2c93
JS: Rename getTargetFile to getImportedFile and remove its deprecated…
asgerf 70a5ec5
JS: Add package.json files in tests relying on node_modules
asgerf b0f73f1
JS: Update test output now that we import .d.ts files more liberally
asgerf f3e0cfd
Apply suggestions from code review
asgerf 5c9218f
JS: Add comment about 'path' heuristic
asgerf 1f308ee
JS: Explain use of monotonicAggregates
asgerf File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,180 @@ | ||
/** | ||
* Provides a class for working with `tsconfig.json` files. | ||
*/ | ||
|
||
private import javascript | ||
|
||
/** | ||
* A TypeScript configuration file, usually named `tsconfig.json`. | ||
*/ | ||
class TSConfig extends JsonObject { | ||
TSConfig() { | ||
this.getJsonFile().getBaseName().matches("%tsconfig%.json") and | ||
this.isTopLevel() | ||
} | ||
|
||
/** Gets the folder containing this file. */ | ||
Folder getFolder() { result = this.getJsonFile().getParentContainer() } | ||
|
||
/** Gets the `compilerOptions` object. */ | ||
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 = Resolver::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 = Resolver::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 the effective baseUrl folder for this tsconfig file, or its enclosing folder if there is no baseUrl. */ | ||
Folder getBaseUrlFolderOrOwnFolder() { | ||
result = this.getBaseUrlFolder() | ||
or | ||
not exists(this.getBaseUrlFolder()) and | ||
result = this.getFolder() | ||
} | ||
|
||
/** 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 refenced by a path the `include` property, possibly | ||
* inherited from an extended tsconfig file. | ||
* | ||
* Does not include all the files within includes directories, use `getAnIncludedContainer` for that. | ||
*/ | ||
Container getAnIncludePathTarget() { | ||
result = Resolver::resolve(this.getFolder(), this.getAnIncludePath()) | ||
or | ||
not exists(this.getPropValue("include")) and | ||
result = this.getExtendedTSConfig().getAnIncludePathTarget() | ||
} | ||
|
||
/** | ||
* Gets a file or folder inside the directory tree mentioned in the `include` property. | ||
*/ | ||
Container getAnIncludedContainer() { | ||
result = this.getAnIncludePathTarget() | ||
or | ||
result = this.getAnIncludedContainer().getAChildContainer() | ||
} | ||
|
||
/** Gets the path mentioned in the `rootDir` property. */ | ||
string getRootDirPath() { result = this.getCompilerOptions().getPropStringValue("rootDir") } | ||
|
||
private Container getOwnRootDir() { | ||
result = Resolver::resolve(this.getFolder(), this.getRootDirPath()) | ||
} | ||
|
||
/** Gets the file or folder referenced by the `rootDir` property. */ | ||
Container getRootDir() { | ||
result = this.getOwnRootDir() | ||
or | ||
not exists(this.getRootDirPath()) and | ||
result = this.getExtendedTSConfig().getOwnRootDir() | ||
} | ||
|
||
private string getATopLevelIncludePath() { | ||
result = this.getAnIncludePath().(FilePath).getComponent(0) | ||
} | ||
|
||
private string getUniqueTopLevelIncludePath() { | ||
result = unique( | | this.getATopLevelIncludePath()) | ||
} | ||
|
||
/** | ||
* Gets the folder referred to by the `rootDir` property, or if absent, an effective root dir | ||
* derived from `include` paths. | ||
*/ | ||
Container getEffectiveRootDir() { | ||
result = this.getRootDir() | ||
or | ||
not exists(this.getRootDir()) and | ||
( | ||
result = this.getFolder().getFolder(this.getUniqueTopLevelIncludePath()) | ||
or | ||
not exists(this.getUniqueTopLevelIncludePath()) and | ||
exists(this.getATopLevelIncludePath()) and | ||
result = this.getFolder() | ||
or | ||
not exists(this.getATopLevelIncludePath()) and | ||
result = this.getExtendedTSConfig().getEffectiveRootDir() | ||
) | ||
} | ||
|
||
private JsonObject getPathMappings() { result = this.getCompilerOptions().getPropValue("paths") } | ||
|
||
/** | ||
* Holds if this has a path mapping from `pattern` to `newPath`. | ||
* | ||
* For example, `"paths": { "@/*": "./src/*" }` maps the `@/*` pattern to `./src/*`. | ||
* | ||
* Does not include path mappings from extended tsconfig files. | ||
*/ | ||
predicate hasPathMapping(string pattern, string newPath) { | ||
this.getPathMappings().getPropStringValue(pattern) = newPath | ||
or | ||
this.getPathMappings().getPropValue(pattern).(JsonArray).getElementStringValue(_) = newPath | ||
} | ||
|
||
/** | ||
* Holds if this has an exact path mapping from `pattern` to `newPath`. | ||
* | ||
* For example, `"paths": { "@": "./src/index.ts" }` maps the `@` path to `./src/index.ts`. | ||
* | ||
* Does not include path mappings from extended tsconfig files. | ||
*/ | ||
predicate hasExactPathMapping(string pattern, string newPath) { | ||
this.hasPathMapping(pattern, newPath) and | ||
not pattern.matches("%*%") | ||
} | ||
|
||
/** | ||
* Holds if this has a path mapping from the `pattern` prefix to the `newPath` prefix. | ||
* The trailing `*` is not included. | ||
* | ||
* For example, `"paths": { "@/*": "./src/*" }` maps the `@/` pattern to `./src/`. | ||
* | ||
* Does not include path mappings from extended tsconfig files. | ||
*/ | ||
predicate hasPrefixPathMapping(string pattern, string newPath) { | ||
this.hasPathMapping(pattern + "*", newPath + "*") | ||
} | ||
} | ||
|
||
/** For resolving paths in a tsconfig file, except `paths` mappings. */ | ||
private module ResolverConfig implements Folder::ResolveSig { | ||
predicate shouldResolve(Container base, string path) { | ||
exists(TSConfig cfg | | ||
base = cfg.getFolder() and | ||
path = | ||
[cfg.getExtendsPath(), cfg.getBaseUrlPath(), cfg.getRootDirPath(), cfg.getAnIncludePath()] | ||
) | ||
} | ||
|
||
predicate allowGlobs() { any() } // "include" can use globs | ||
} | ||
|
||
private module Resolver = Folder::Resolve<ResolverConfig>; |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.