JS: Implement import resolution

This commit is contained in:
Asger F
2025-04-29 12:52:15 +02:00
parent ed4864edf7
commit 6725cb5b8c
11 changed files with 394 additions and 93 deletions

View File

@@ -2,6 +2,7 @@
import javascript
private import semmle.javascript.internal.CachedStages
private import semmle.javascript.internal.paths.PathExprResolver
/**
* An ECMAScript 2015 module.
@@ -725,22 +726,7 @@ abstract class ReExportDeclaration extends ExportDeclaration {
cached
Module getReExportedModule() {
Stages::Imports::ref() and
result.getFile() = this.getEnclosingModule().resolve(this.getImportedPath())
or
result = this.resolveFromTypeRoot()
}
/**
* Gets a module in a `node_modules/@types/` folder that matches the imported module name.
*/
private Module resolveFromTypeRoot() {
result.getFile() =
min(TypeRootFolder typeRoot |
|
typeRoot.getModuleFile(this.getImportedPath().getStringValue())
order by
typeRoot.getSearchPriority(this.getFile().getParentContainer())
)
result.getFile() = ImportPathResolver::resolveExpr(this.getImportedPath())
}
}

View File

@@ -6,6 +6,7 @@
import javascript
private import semmle.javascript.internal.CachedStages
private import semmle.javascript.internal.paths.PathExprResolver
/**
* A module, which may either be an ECMAScript 2015-style module,
@@ -138,39 +139,17 @@ abstract class Import extends AstNode {
/**
* Gets the module the path of this import resolves to.
*/
Module resolveImportedPath() {
result.getFile() = this.getEnclosingModule().resolve(this.getImportedPath())
}
Module resolveImportedPath() { result.getFile() = this.getTargetFile() }
/**
* Gets a module with a `@providesModule` JSDoc tag that matches
* the imported path.
* Gets the module the path of this import resolves to.
*/
private Module resolveAsProvidedModule() {
exists(JSDocTag tag |
tag.getTitle() = "providesModule" and
tag.getParent().getComment().getTopLevel() = result and
tag.getDescription().trim() = this.getImportedPath().getValue()
)
}
File getTargetFile() { result = ImportPathResolver::resolveExpr(this.getImportedPath()) }
/**
* Gets a module in a `node_modules/@types/` folder that matches the imported module name.
* DEPRECATED. Use `getImportedModule()` instead.
*/
private Module resolveFromTypeRoot() {
result.getFile() =
min(TypeRootFolder typeRoot |
|
typeRoot.getModuleFile(this.getImportedPath().getValue())
order by
typeRoot.getSearchPriority(this.getFile().getParentContainer())
)
}
/**
* Gets the imported module, as determined by the TypeScript compiler, if any.
*/
private Module resolveFromTypeScriptSymbol() {
deprecated Module resolveFromTypeScriptSymbol() {
exists(CanonicalName symbol |
ast_node_symbol(this, symbol) and
ast_node_symbol(result, symbol)
@@ -190,13 +169,7 @@ abstract class Import extends AstNode {
Stages::Imports::ref() and
if exists(this.resolveExternsImport())
then result = this.resolveExternsImport()
else (
result = this.resolveAsProvidedModule() or
result = this.resolveImportedPath() or
result = this.resolveFromTypeRoot() or
result = this.resolveFromTypeScriptSymbol() or
result = resolveNeighbourPackage(this.getImportedPath().getValue())
)
else result = this.resolveImportedPath()
}
/**
@@ -204,28 +177,3 @@ abstract class Import extends AstNode {
*/
abstract DataFlow::Node getImportedModuleNode();
}
/**
* Gets a module imported from another package in the same repository.
*
* No support for importing from folders inside the other package.
*/
private Module resolveNeighbourPackage(PathString importPath) {
exists(PackageJson json | importPath = json.getPackageName() and result = json.getMainModule())
or
exists(string package |
result.getFile().getParentContainer() = getPackageFolder(package) and
importPath = package + "/" + [result.getFile().getBaseName(), result.getFile().getStem()]
)
}
/**
* Gets the folder for a package that has name `package` according to a package.json file in the resulting folder.
*/
pragma[noinline]
private Folder getPackageFolder(string package) {
exists(PackageJson json |
json.getPackageName() = package and
result = json.getFile().getParentContainer()
)
}

View File

@@ -3,6 +3,7 @@
*/
private import javascript
private import semmle.javascript.internal.paths.PathMapping
/**
* A TypeScript configuration file, usually named `tsconfig.json`.
@@ -178,3 +179,44 @@ private module ResolverConfig implements Folder::ResolveSig {
}
private module Resolver = Folder::Resolve<ResolverConfig>;
/**
* Gets a tsconfig file to use as fallback for handling paths in `c`.
*
* This holds for files and folders where no tsconfig seems to include it,
* but it has one or more tsconfig files in parent directories.
*/
private TSConfig getFallbackTSConfig(Container c) {
not c = any(TSConfig t).getAnIncludedContainer() and
(
c = result.getFolder()
or
result = getFallbackTSConfig(c.getParentContainer())
)
}
private class TSConfigPathMapping extends PathMapping, TSConfig {
override File getAnAffectedFile() {
result = this.getAnIncludedContainer()
or
this = getFallbackTSConfig(result)
}
override predicate hasExactPathMapping(string pattern, Container newContext, string newPath) {
exists(TSConfig tsconfig |
tsconfig = this.getExtendedTSConfig*() and
tsconfig.hasExactPathMapping(pattern, newPath) and
newContext = tsconfig.getBaseUrlFolderOrOwnFolder()
)
}
override predicate hasPrefixPathMapping(string pattern, Container newContext, string newPath) {
exists(TSConfig tsconfig |
tsconfig = this.getExtendedTSConfig*() and
tsconfig.hasPrefixPathMapping(pattern, newPath) and
newContext = tsconfig.getBaseUrlFolderOrOwnFolder()
)
}
override predicate hasBaseUrl(Container base) { base = this.getBaseUrlFolder() }
}

View File

@@ -0,0 +1,35 @@
private import javascript
/**
* A path expression that can be constant-folded by concatenating subexpressions.
*/
abstract class PathConcatenation extends Expr {
/** Gets the separator to insert between paths */
string getSeparator() { result = "" }
/** Gets the `n`th operand to concatenate. */
abstract Expr getOperand(int n);
}
private class AddExprConcatenation extends PathConcatenation, AddExpr {
override Expr getOperand(int n) {
n = 0 and result = this.getLeftOperand()
or
n = 1 and result = this.getRightOperand()
}
}
private class TemplateConcatenation extends PathConcatenation, TemplateLiteral {
override Expr getOperand(int n) { result = this.getElement(n) }
}
private class JoinCallConcatenation extends PathConcatenation, CallExpr {
JoinCallConcatenation() {
this.getReceiver().(VarAccess).getName() = "path" and
this.getCalleeName() = ["join", "resolve"]
}
override Expr getOperand(int n) { result = this.getArgument(n) }
override string getSeparator() { result = "/" }
}

View File

@@ -0,0 +1,249 @@
private import javascript
private import semmle.javascript.internal.paths.PackageJsonEx
private import semmle.javascript.internal.paths.JSPaths
private import semmle.javascript.internal.paths.PathMapping
private import semmle.javascript.internal.paths.PathConcatenation
private import semmle.javascript.dataflow.internal.DataFlowNode
/**
* Gets the file to import when an imported path resolves to the given `folder`.
*/
File getFileFromFolderImport(Folder folder) {
result = folder.getJavaScriptFileOrTypings("index")
or
// Note that unlike "exports" paths, "main" and "module" also take effect when the package
// is imported via a relative path, e.g. `require("..")` targeting a folder with a package.json file.
exists(PackageJsonEx pkg |
pkg.getFolder() = folder and
result = pkg.getMainFileOrBestGuess()
)
}
private Variable dirnameVar() { result.getName() = "__dirname" }
private Variable filenameVar() { result.getName() = "__filename" }
private signature predicate exprSig(Expr e);
module ResolveExpr<exprSig/1 shouldResolveExpr> {
/** Holds if we need the constant string-value of `node`. */
private predicate needsConstantFolding(EarlyStageNode node) {
exists(Expr e |
shouldResolveExpr(e) and
node = TValueNode(e)
)
or
exists(EarlyStageNode needsFolding | needsConstantFolding(needsFolding) |
DataFlow::Impl::earlyStageImmediateFlowStep(node, needsFolding)
or
exists(PathConcatenation joiner |
needsFolding = TValueNode(joiner) and
node = TValueNode(joiner.getOperand(_))
)
)
}
/** Gets the constant-value of `node` */
language[monotonicAggregates]
private string getValue(EarlyStageNode node) {
needsConstantFolding(node) and
(
exists(Expr e | node = TValueNode(e) |
result = e.getStringValue()
or
e = dirnameVar().getAnAccess() and
result = "./" // Ensure the path gets interpreted relative to the current directory
or
e = filenameVar().getAnAccess() and
result = "./" + e.getFile().getBaseName()
)
or
exists(EarlyStageNode pred |
DataFlow::Impl::earlyStageImmediateFlowStep(pred, node) and
result = getValue(pred)
)
or
exists(PathConcatenation join |
node = TValueNode(join) and
result =
strictconcat(int n, EarlyStageNode child, string sep |
child = TValueNode(join.getOperand(n)) and sep = join.getSeparator()
|
getValue(child), sep order by n
)
)
)
}
final private class FinalExpr = Expr;
private class RelevantExpr extends FinalExpr {
RelevantExpr() { shouldResolveExpr(this) }
/** Gets the string-value of this path. */
string getValue() { result = getValue(TValueNode(this)) }
/** Gets a path mapping affecting this path. */
pragma[nomagic]
PathMapping getAPathMapping() { result.getAnAffectedFile() = this.getFile() }
/** Gets the NPM package name from the beginning of this path. */
pragma[nomagic]
string getPackagePrefix() { result = this.getValue().(FilePath).getPackagePrefix() }
}
/**
* Holds if `expr` matches a path mapping, and should thus be resolved as `newPath` relative to `base`.
*/
pragma[nomagic]
private predicate resolveViaPathMapping(RelevantExpr expr, Container base, string newPath) {
// Handle path mappings such as `{ "paths": { "@/*": "./src/*" }}` in a tsconfig.json file
exists(PathMapping mapping, string value |
mapping = expr.getAPathMapping() and
value = expr.getValue()
|
mapping.hasExactPathMapping(value, base, newPath)
or
exists(string pattern, string suffix, string mappedPath |
mapping.hasPrefixPathMapping(pattern, base, mappedPath) and
value = pattern + suffix and
newPath = mappedPath + suffix
)
)
or
// Handle imports referring to a package by name, where we have a package.json
// file for that package in the codebase. This is treated separately from PathMapping for performance
// reasons, as there can be a large number of packages which affect all files in the project.
//
// This part only handles the "exports" property of package.json. "main" and "modules" are
// handled further down because their semantics are easier to handle there.
exists(PackageJsonEx pkg, string packageName, string remainder |
packageName = expr.getPackagePrefix() and
pkg.getDeclaredPackageName() = packageName and
remainder = expr.getValue().suffix(packageName.length()).regexpReplaceAll("^[/\\\\]", "")
|
// "exports": { ".": "./foo.js" }
// "exports": { "./foo.js": "./foo/impl.js" }
pkg.hasExactPathMappingTo(remainder, base) and
newPath = ""
or
// "exports": { "./*": "./foo/*" }
exists(string prefix |
pkg.hasPrefixPathMappingTo(prefix, base) and
remainder = prefix + newPath
)
)
}
pragma[noopt]
private predicate relativePathExpr(RelevantExpr expr, Container base, FilePath path) {
expr instanceof RelevantExpr and
path = expr.getValue() and
path.isDotRelativePath() and
exists(File file |
file = expr.getFile() and
base = file.getParentContainer()
)
}
pragma[nomagic]
private Container getJSDocProvidedModule(string moduleName) {
exists(JSDocTag tag |
tag.getTitle() = "providesModule" and
tag.getDescription().trim() = moduleName and
tag.getFile() = result
)
}
/**
* Holds if `expr` should be resolved as `path` relative to `base`.
*/
pragma[nomagic]
private predicate shouldResolve(RelevantExpr expr, Container base, FilePath path) {
// Relative paths are resolved from their enclosing folder
relativePathExpr(expr, base, path)
or
resolveViaPathMapping(expr, base, path)
or
// Resolve from baseUrl of relevant tsconfig.json file
path = expr.getValue() and
not path.isDotRelativePath() and
expr.getAPathMapping().hasBaseUrl(base)
or
// If the path starts with the name of a package, resolve relative to the directory of that package.
// Note that `getFileFromFolderImport` may subsequently redirect this to the package's "main",
// so we don't have to deal with that here.
exists(PackageJson pkg, string packageName |
packageName = expr.getPackagePrefix() and
pkg.getDeclaredPackageName() = packageName and
path = expr.getValue().suffix(packageName.length()) and
base = pkg.getFolder()
)
or
base = getJSDocProvidedModule(expr.getValue()) and
path = ""
}
private module ResolverConfig implements Folder::ResolveSig {
predicate shouldResolve(Container base, string path) { shouldResolve(_, base, path) }
predicate getAnAdditionalChild = JSPaths::getAnAdditionalChild/2;
}
private module Resolver = Folder::Resolve<ResolverConfig>;
private Container resolvePathExpr1(RelevantExpr expr) {
exists(Container base, string path |
shouldResolve(expr, base, path) and
result = Resolver::resolve(base, path)
)
}
File resolveExpr(RelevantExpr expr) {
result = resolvePathExpr1(expr)
or
result = getFileFromFolderImport(resolvePathExpr1(expr))
}
module Debug {
class PathExprToDebug extends RelevantExpr {
PathExprToDebug() { this.getValue() = "vs/nls" }
}
query PathExprToDebug pathExprs() { any() }
query string getPackagePrefixFromPathExpr_(PathExprToDebug expr) {
result = expr.getPackagePrefix()
}
query predicate resolveViaPathMapping_(PathExprToDebug expr, Container base, string newPath) {
resolveViaPathMapping(expr, base, newPath)
}
query predicate shouldResolve_(PathExprToDebug expr, Container base, string newPath) {
shouldResolve(expr, base, newPath)
}
query Container resolvePathExpr1_(PathExprToDebug expr) { result = resolvePathExpr1(expr) }
query File resolveExpr_(PathExprToDebug expr) { result = resolveExpr(expr) }
// Some predicates that are usually small enough that they don't need restriction
query File getPackageMainFile(PackageJsonEx pkg) { result = pkg.getMainFile() }
query predicate guessPackageJsonMain1_ = guessPackageJsonMain1/1;
query predicate guessPackageJsonMain2_ = guessPackageJsonMain2/1;
query predicate getFileFromFolderImport_ = getFileFromFolderImport/1;
}
}
private predicate isImportPathExpr(Expr e) {
e = any(Import imprt).getImportedPath()
or
e = any(ReExportDeclaration decl).getImportedPath()
}
/** Resolves paths in imports and re-exports. */
module ImportPathResolver = ResolveExpr<isImportPathExpr/1>;

View File

@@ -0,0 +1,31 @@
/**
* Provides an extensible mechanism for modeling path mappings.
*/
private import javascript
private import semmle.javascript.TSConfig
/**
* A `tsconfig.json`-like configuration object that can affect import resolution via path mappings.
*/
abstract class PathMapping extends Locatable {
/**
* Gets a file affected by this path mapping.
*/
abstract File getAnAffectedFile();
/**
* Holds if imports paths exactly matching `pattern` should be redirected to `newPath`
* resolved relative to `newContext`.
*/
predicate hasExactPathMapping(string pattern, Container newContext, string newPath) { none() }
/**
* Holds if imports paths starting with `pattern` should have the matched prefix replaced by `newPath`
* and then resolved relative to `newContext`.
*/
predicate hasPrefixPathMapping(string pattern, Container newContext, string newPath) { none() }
/** Holds if non-relative paths in affected files should be resolved relative to `base`. */
predicate hasBaseUrl(Container base) { none() }
}

View File

@@ -94,14 +94,12 @@ requireImport
| a.js:3:6:3:23 | require('./sub/c') | ./sub/c | sub/c.js:1:1:4:0 | <toplevel> |
| a.js:4:6:4:29 | require ... /d.js') | ./sub/../d.js | d.js:1:1:7:15 | <toplevel> |
| a.js:7:1:7:18 | require('./sub/c') | ./sub/c | sub/c.js:1:1:4:0 | <toplevel> |
| a.js:10:1:10:18 | require(__dirname) | | index.js:1:1:3:0 | <toplevel> |
| a.js:11:1:11:25 | require ... + '/e') | /e | e.js:1:1:6:0 | <toplevel> |
| a.js:12:1:12:28 | require ... + 'c') | ./sub/c | sub/c.js:1:1:4:0 | <toplevel> |
| b.js:1:1:1:18 | require('./sub/c') | ./sub/c | sub/c.js:1:1:4:0 | <toplevel> |
| d.js:7:1:7:14 | require('foo') | foo | sub/f.js:1:1:4:17 | <toplevel> |
| index.js:2:1:2:41 | require ... b.js")) | /index.js/../b.js | b.js:1:1:8:0 | <toplevel> |
| mjs-files/require-from-js.js:1:12:1:36 | require ... on-me') | ./depend-on-me | mjs-files/depend-on-me.mjs:1:1:7:1 | <toplevel> |
| mjs-files/require-from-js.js:2:12:2:39 | require ... me.js') | ./depend-on-me.js | mjs-files/depend-on-me.js:1:1:8:0 | <toplevel> |
| mjs-files/require-from-js.js:2:12:2:39 | require ... me.js') | ./depend-on-me.js | mjs-files/depend-on-me.mjs:1:1:7:1 | <toplevel> |
| mjs-files/require-from-js.js:3:12:3:40 | require ... e.mjs') | ./depend-on-me.mjs | mjs-files/depend-on-me.mjs:1:1:7:1 | <toplevel> |
| reexport/b.js:1:11:1:24 | require("./a") | ./a | reexport/a.js:1:1:3:1 | <toplevel> |
| sub/c.js:1:1:1:15 | require('../a') | ../a | a.js:1:1:14:0 | <toplevel> |

View File

@@ -4,4 +4,4 @@ import "../lib/split.d.ts"; // $ importTarget=DeclarationFiles/lib/split.d.ts
import "../lib/typescript"; // $ importTarget=DeclarationFiles/lib/typescript.ts
import "../lib/typescript.js"; // $ importTarget=DeclarationFiles/lib/typescript.ts
import "../lib/typescript.d.ts"; // $ importTarget=DeclarationFiles/lib/typescript.d.ts SPURIOUS: importTarget=DeclarationFiles/lib/typescript.ts
import "../lib/typescript.d.ts"; // $ importTarget=DeclarationFiles/lib/typescript.d.ts

View File

@@ -1,10 +1,10 @@
import "@/both" // $ importTarget=Fallback/lib1/both.ts
import "@/both" // $ importTarget=Fallback/lib1/both.ts SPURIOUS: importTarget=Fallback/lib2/both.ts
import "@/only1" // $ importTarget=Fallback/lib1/only1.ts
import "@/only2" // $ importTarget=Fallback/lib2/only2.ts
import "@/differentExtension" // $ importTarget=Fallback/lib2/differentExtension.ts
import "@/differentExtension.js" // $ importTarget=Fallback/lib2/differentExtension.ts
import "@/differentExtension" // $ importTarget=Fallback/lib2/differentExtension.ts SPURIOUS: importTarget=Fallback/lib1/differentExtension.js
import "@/differentExtension.js" // $ importTarget=Fallback/lib2/differentExtension.ts SPURIOUS: importTarget=Fallback/lib1/differentExtension.js
import "@/subdir" // $ importTarget=Fallback/lib1/subdir/index.ts
import "@/subdir/both" // $ importTarget=Fallback/lib1/subdir/both.ts
import "@/subdir" // $ importTarget=Fallback/lib1/subdir/index.ts SPURIOUS: importTarget=Fallback/lib2/subdir/index.ts
import "@/subdir/both" // $ importTarget=Fallback/lib1/subdir/both.ts SPURIOUS: importTarget=Fallback/lib2/subdir/both.ts
import "@/subdir/only1" // $ importTarget=Fallback/lib1/subdir/only1.ts
import "@/subdir/only2" // $ importTarget=Fallback/lib2/subdir/only2.ts

View File

@@ -1,23 +1,23 @@
import './PackageWithMain/main'; // $ importTarget=PackageWithMain/main.js
import '@example/package-with-main'; // $ importTarget=PackageWithMain/main.js
import './PackageWithModuleMain'; // $ MISSING: importTarget=PackageWithModuleMain/main.js
import './PackageWithModuleMain'; // $ importTarget=PackageWithModuleMain/main.js
import '@example/package-with-module-main'; // $ importTarget=PackageWithModuleMain/main.js
import './PackageWithExports'; // Not a valid import
import './PackageWithExports/fake-file'; // Not a valid import
import './PackageWithExports/star/foo'; // Not a valid import
import '@example/package-with-exports'; // $ importTarget=PackageWithExports/main.js
import '@example/package-with-exports/fake-file'; // $ MISSING: importTarget=PackageWithExports/fake-file-impl.js
import '@example/package-with-exports/star/foo'; // $ MISSING: importTarget=PackageWithExports/star-impl/foo.js
import '@example/package-with-exports/fake-file'; // $ importTarget=PackageWithExports/fake-file-impl.js
import '@example/package-with-exports/star/foo'; // $ importTarget=PackageWithExports/star-impl/foo.js
import './PackageIndexFile'; // $ importTarget=PackageIndexFile/index.js
import '@example/package-with-index-file'; // $ importTarget=PackageIndexFile/index.js
import './PackageGuess1'; // $ MISSING: importTarget=PackageGuess1/src/index.ts
import './PackageGuess1'; // $ importTarget=PackageGuess1/src/index.ts
import '@example/package-guess1'; // $ importTarget=PackageGuess1/src/index.ts
import './PackageGuess2'; // $ MISSING: importTarget=PackageGuess2/blah/stuff.ts
import './PackageGuess2'; // $ importTarget=PackageGuess2/blah/stuff.ts
import '@example/package-guess2'; // $ importTarget=PackageGuess2/blah/stuff.ts
import './PackageWithMainExt'; // $ importTarget=PackageWithMainExt/lib/main.ts
@@ -25,11 +25,11 @@ import '@example/package-with-main-ext'; // $ importTarget=PackageWithMainExt/li
import './TSConfigOutDir/customOutDir/foo.js'; // $ importTarget=TSConfigOutDir/src/foo.ts
import './MainIsFolder'; // $ MISSING: importTarget=MainIsFolder/src/index.ts
import './MainIsFolder'; // $ importTarget=MainIsFolder/src/index.ts
import '@example/main-is-folder'; // $ importTarget=MainIsFolder/src/index.ts
import './DistContainsSrc'; // $ MISSING: importTarget=DistContainsSrc/src/foo.ts
import './DistContainsSrc'; // $ importTarget=DistContainsSrc/src/foo.ts
import './MinifiedMain'; // $ MISSING: importTarget=MinifiedMain/src/library.ts
import './MinifiedMain'; // $ importTarget=MinifiedMain/src/library.ts
import './RootDir/my-out/foo.js'; // $ importTarget=RootDir/my-root/foo.ts

View File

@@ -40,7 +40,6 @@
| DeclarationFiles/src/main.ts:5:1:5:27 | import ... cript"; | DeclarationFiles/lib/typescript.ts |
| DeclarationFiles/src/main.ts:6:1:6:30 | import ... pt.js"; | DeclarationFiles/lib/typescript.ts |
| DeclarationFiles/src/main.ts:7:1:7:32 | import ... .d.ts"; | DeclarationFiles/lib/typescript.d.ts |
| DeclarationFiles/src/main.ts:7:1:7:32 | import ... .d.ts"; | DeclarationFiles/lib/typescript.ts |
| DirnameImports/main.js:4:1:4:33 | require ... et.js') | DirnameImports/target.js |
| DirnameImports/main.js:5:1:5:40 | require ... et.js') | DirnameImports/nested/target.js |
| DirnameImports/main.js:6:1:6:45 | require ... es.ts') | import-packages.ts |
@@ -69,12 +68,17 @@
| 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 |
| Fallback/src/main.ts:1:1:1:15 | import "@/both" | Fallback/lib1/both.ts |
| Fallback/src/main.ts:1:1:1:15 | import "@/both" | Fallback/lib2/both.ts |
| Fallback/src/main.ts:2:1:2:16 | import "@/only1" | Fallback/lib1/only1.ts |
| Fallback/src/main.ts:3:1:3:16 | import "@/only2" | Fallback/lib2/only2.ts |
| Fallback/src/main.ts:4:1:4:29 | import ... ension" | Fallback/lib1/differentExtension.js |
| Fallback/src/main.ts:4:1:4:29 | import ... ension" | Fallback/lib2/differentExtension.ts |
| Fallback/src/main.ts:5:1:5:32 | import ... ion.js" | Fallback/lib1/differentExtension.js |
| Fallback/src/main.ts:5:1:5:32 | import ... ion.js" | Fallback/lib2/differentExtension.ts |
| Fallback/src/main.ts:7:1:7:17 | import "@/subdir" | Fallback/lib1/subdir/index.ts |
| Fallback/src/main.ts:7:1:7:17 | import "@/subdir" | Fallback/lib2/subdir/index.ts |
| Fallback/src/main.ts:8:1:8:22 | import ... r/both" | Fallback/lib1/subdir/both.ts |
| Fallback/src/main.ts:8:1:8:22 | import ... r/both" | Fallback/lib2/subdir/both.ts |
| Fallback/src/main.ts:9:1:9:23 | import ... /only1" | Fallback/lib1/subdir/only1.ts |
| Fallback/src/main.ts:10:1:10:23 | import ... /only2" | Fallback/lib2/subdir/only2.ts |
| JSDocProvide/main.js:1:1:1:43 | import ... r/baz'; | JSDocProvide/lib.js |
@@ -98,14 +102,22 @@
| NodeModules/subfolder/src/main.ts:2:1:2:13 | import 'bar'; | NodeModules/subfolder/node_modules/bar/index.js |
| import-packages.ts:1:1:1:32 | import ... /main'; | PackageWithMain/main.js |
| import-packages.ts:2:1:2:36 | import ... -main'; | PackageWithMain/main.js |
| import-packages.ts:4:1:4:33 | import ... eMain'; | PackageWithModuleMain/main.js |
| import-packages.ts:5:1:5:43 | import ... -main'; | PackageWithModuleMain/main.js |
| import-packages.ts:10:1:10:39 | import ... ports'; | PackageWithExports/main.js |
| import-packages.ts:11:1:11:49 | import ... -file'; | PackageWithExports/fake-file-impl.js |
| import-packages.ts:12:1:12:48 | import ... r/foo'; | PackageWithExports/star-impl/foo.js |
| import-packages.ts:14:1:14:28 | import ... xFile'; | PackageIndexFile/index.js |
| import-packages.ts:15:1:15:42 | import ... -file'; | PackageIndexFile/index.js |
| import-packages.ts:17:1:17:25 | import ... uess1'; | PackageGuess1/src/index.ts |
| import-packages.ts:18:1:18:33 | import ... uess1'; | PackageGuess1/src/index.ts |
| import-packages.ts:20:1:20:25 | import ... uess2'; | PackageGuess2/blah/stuff.ts |
| 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 |
| import-packages.ts:28:1:28:24 | import ... older'; | MainIsFolder/src/index.ts |
| import-packages.ts:29:1:29:33 | import ... older'; | MainIsFolder/src/index.ts |
| import-packages.ts:31:1:31:27 | import ... nsSrc'; | DistContainsSrc/src/foo.ts |
| import-packages.ts:33:1:33:24 | import ... dMain'; | MinifiedMain/src/library.ts |
| import-packages.ts:35:1:35:33 | import ... oo.js'; | RootDir/my-root/foo.ts |