Compare commits

...

85 Commits

Author SHA1 Message Date
Asger F
21e3dc7aa5 JS: Use type resolution for CG augmentation 2025-04-23 14:30:59 +02:00
Asger F
ca75ee161a Perf improvements 2025-04-23 14:30:58 +02:00
Asger F
81c7b4a9f1 test update 2025-04-23 14:30:57 +02:00
Asger F
83b6ae4007 fixup 2025-04-23 14:30:56 +02:00
Asger F
69893db0dc JS: Do not resolve path mappings in advance 2025-04-23 14:30:54 +02:00
Asger F
38742010a7 JS: Relative path mappings are also resolved from baseUrl 2025-04-23 14:30:53 +02:00
Asger F
9998bbe6a8 Update TSConfig.qll 2025-04-23 14:30:52 +02:00
Asger F
63eb74e0e9 Update TSConfig.qll 2025-04-23 14:30:51 +02:00
Asger F
4b2e1d9c13 JS: More fallback logic 2025-04-23 14:30:49 +02:00
Asger F
e774c8ed6b JS: Support more complex globs 2025-04-23 14:30:48 +02:00
Asger F
b552e92c38 JS: Resolve to both .js and .d.ts file when there is no .ts file 2025-04-23 14:30:47 +02:00
Asger F
2cb430e86c Update TSConfig.qll 2025-04-23 14:30:46 +02:00
Asger F
36b8a14e39 Update test.ql 2025-04-23 14:30:44 +02:00
Asger F
fce9749b97 Move getExtendedTSConfig() into hasXPathMappingTo 2025-04-23 14:30:43 +02:00
Asger F
bcd5c686eb JS: Remove referenced paths 2025-04-23 14:30:42 +02:00
Asger F
15b9eee644 Update PathResolver.qll 2025-04-23 14:30:40 +02:00
Asger F
e5ea8de78b JS: Allow globs when resolving in tsconfig 2025-04-23 14:30:39 +02:00
Asger F
94c9d98412 JS: Reinstate resolveFromTypeScriptSymbol for testing 2025-04-23 14:30:38 +02:00
Asger F
bc6720e65a WIP stuff 2025-04-23 14:30:37 +02:00
Asger F
b7c5b5cf61 JS: Add optional path components 2025-04-23 14:28:08 +02:00
Asger F
7c2bb6098f Update PathExprResolver.qll 2025-04-23 14:28:07 +02:00
Asger F
5e0091b8f1 Additional heuristic for guessing package main 2025-04-23 14:28:06 +02:00
Asger F
39980e79c5 Move some logic for main resolution 2025-04-23 14:28:04 +02:00
Asger F
bdf2d66d04 more debug code 2025-04-23 14:28:03 +02:00
Asger F
2f257f1b86 Remove negative depenency on resolveViaPathMapping 2025-04-23 14:28:02 +02:00
Asger F
3fe16f09f5 sdf 2025-04-23 14:28:01 +02:00
Asger F
41aea8828d Refactor 2025-04-23 14:28:00 +02:00
Asger F
46f1af4ea4 More factoring out 2025-04-23 14:27:58 +02:00
Asger F
7957070055 Factor out PathResolver and TSConfig 2025-04-23 14:27:57 +02:00
Asger F
d3fd5b1673 Rename path resolver 2025-04-23 14:27:56 +02:00
Asger F
e8dc128dab sdfg 2025-04-23 14:27:55 +02:00
Asger F
7a5a021a82 Create VirtualPaths.qll 2025-04-23 14:27:53 +02:00
Asger F
d48b5d9174 JS: Add test for __dirname using copilot and fix a bug 2025-04-23 14:27:52 +02:00
Asger F
2b7f2488d4 Update test.expected 2025-04-23 14:27:51 +02:00
Asger F
1a41aa7296 JS: Consider package root to be a potential source dir 2025-04-23 14:27:50 +02:00
Asger F
8ffea7d1fe JS: Add test for 'src' inside 'dist' 2025-04-23 14:27:49 +02:00
Asger F
e0b241a206 Update PathResolution.qll 2025-04-23 14:27:47 +02:00
Asger F
4d6f53ed9a Resolve "exports" paths more aggressively 2025-04-23 14:27:46 +02:00
Asger F
5b0d7f5990 Factor out isRelevantPathExpr 2025-04-23 14:27:45 +02:00
Asger F
0a15b1b0ba JS: Handle __dirname (TODO: test) 2025-04-23 14:27:44 +02:00
Asger F
848ac641a5 JS: Handle case where main resolves to a folder 2025-04-23 14:27:43 +02:00
Asger F
8d658ea27a asdf 2025-04-23 14:27:41 +02:00
Asger F
cd03ef9f02 safd 2025-04-23 14:27:40 +02:00
Asger F
76ad3e6705 JS: Fix automatic file extensions in main file resolution 2025-04-23 14:27:39 +02:00
Asger F
1f0a43340f Update PathResolution.qll 2025-04-23 14:27:38 +02:00
Asger F
971193ae44 More tests 2025-04-23 14:27:36 +02:00
Asger F
920f618101 Expose for testing purposes 2025-04-23 14:27:35 +02:00
Asger F
f13e8c517f Support exports with star globs 2025-04-23 14:27:34 +02:00
Asger F
a835aec81b JS: Proper main/module/exports handling 2025-04-23 14:27:33 +02:00
Asger F
86a882303d JS: Add getDeclaredPackageName() 2025-04-23 14:27:32 +02:00
Asger F
26a8e0b572 JS: Handle package.json-specified main files 2025-04-23 14:27:30 +02:00
Asger F
95ea1b25f4 JS: Add package.json with 'exports' 2025-04-23 14:27:29 +02:00
Asger F
c807664578 JS: Add test for importing package.json with main 2025-04-23 14:27:28 +02:00
Asger F
92337d701e Keep old resolveImportPath for comparison 2025-04-23 14:27:27 +02:00
Asger F
a849d3fb2d Simplify a bit 2025-04-23 14:27:25 +02:00
Asger F
233ed4f113 Handle '..' path 2025-04-23 14:27:24 +02:00
Asger F
55a91ebaef JS: Path resolution for tsconfig 2025-04-23 14:27:23 +02:00
Asger F
190dfce9c5 JS: Ensure tsconfig.json files are extracted in qltest
QLTests still use the Main.java entry point, which didn't extract tsconfig.json and didn't allow non-standard JSON syntax such as trailing commas that are allowed in tsconfig.json files.
2025-04-23 14:27:21 +02:00
Asger F
ec4b3ef202 JS: Stop resolving imports from TS symbols 2025-04-23 14:27:20 +02:00
Asger F
a889e981ba JS: Add tests for multiple path replacements 2025-04-23 14:27:19 +02:00
Asger F
aa0f7d8ec3 JS: Add test for baseUrl and path mappings in tsconfig files 2025-04-23 14:27:18 +02:00
Asger F
ffe9d542f8 JS: Set TS mode to basic 2025-04-23 14:27:16 +02:00
Asger F
abfbc2e294 JS: Also check contextual type 2025-04-23 14:27:15 +02:00
Asger F
cada794cf1 JS: Some test updates 2025-04-23 14:27:14 +02:00
Asger F
69077ff9b6 JS: Remove some dependencies on type extraction 2025-04-23 14:27:13 +02:00
Asger F
fe06115180 JS: Use in MissingAwait 2025-04-23 14:27:11 +02:00
Asger F
05212e6042 JS: Use hasUnderlyingStringOrAnyType in Nest model (TODO: refactor) 2025-04-23 14:27:10 +02:00
Asger F
0109ab6a70 JS: Use sanitizing primitive type in Nest model 2025-04-23 14:27:09 +02:00
Asger F
9978657a1c JS: Use sanitizing primitive types in ViewComponentInput 2025-04-23 14:27:07 +02:00
Asger F
6ed900504c JS: Update jQuery model 2025-04-23 14:27:06 +02:00
Asger F
da7387ee63 JS: Use in TypeAnnotation.getClass and hasUnderlyingType predicates 2025-04-23 14:27:05 +02:00
Asger F
406754c646 JS: Resolve JSDocLocalTypeAccess to a variable in scope 2025-04-23 14:27:04 +02:00
Asger F
52c729b161 JS: Use underlying types in DataFlow::Node 2025-04-23 14:27:02 +02:00
Asger F
21f4349cc6 Create TypeResolution.qll 2025-04-23 14:27:01 +02:00
Asger F
a374b04f0f Create UnderlyingTypes.qll 2025-04-23 14:27:00 +02:00
Asger F
45ed331115 Create NameResolution.qll 2025-04-23 14:26:59 +02:00
Asger F
7586631934 JS: Add test 2025-04-23 14:26:57 +02:00
Asger F
f8be64b313 JS: Add helper for getting local type names 2025-04-23 14:26:55 +02:00
Asger F
0f981b4a1e JS: Avoid accidental recursion with API graphs 2025-04-23 14:26:54 +02:00
Asger F
bcf26ef537 JS: Make Closure concepts based on AST instead 2025-04-23 14:26:52 +02:00
Asger F
372606a93d JS: Do not ignore variables from ambient declarations 2025-04-23 14:26:51 +02:00
Asger F
f18335da5b JS: Add ImportSpecifier.getImportDeclaration() 2025-04-23 14:26:50 +02:00
Asger F
4443dec443 JS: Exclude externs from CallGraph meta-query 2025-04-23 14:26:48 +02:00
Asger F
6d58293478 Disable noisy meta query 2025-04-23 14:26:47 +02:00
Asger F
ae55f2c80f Add meta query 2025-04-23 14:26:45 +02:00
138 changed files with 2849 additions and 296 deletions

View File

@@ -237,7 +237,7 @@ public class AutoBuild {
this.outputConfig = new ExtractorOutputConfig(LegacyLanguage.JAVASCRIPT);
this.trapCache = ITrapCache.fromExtractorOptions();
this.typeScriptMode =
getEnumFromEnvVar("LGTM_INDEX_TYPESCRIPT", TypeScriptMode.class, TypeScriptMode.FULL);
getEnumFromEnvVar("LGTM_INDEX_TYPESCRIPT", TypeScriptMode.class, TypeScriptMode.BASIC);
this.defaultEncoding = getEnvVar("LGTM_INDEX_DEFAULT_ENCODING");
this.installDependencies = Boolean.valueOf(getEnvVar("LGTM_INDEX_TYPESCRIPT_INSTALL_DEPS"));
this.virtualSourceRoot = makeVirtualSourceRoot();

View File

@@ -29,7 +29,7 @@ public class JSONExtractor implements IExtractor {
private final boolean tolerateParseErrors;
public JSONExtractor(ExtractorConfig config) {
this.tolerateParseErrors = config.isTolerateParseErrors();
this.tolerateParseErrors = true;
}
@Override

View File

@@ -303,7 +303,10 @@ public class Main {
addIncludesFor(includes, FileType.JS);
// extract TypeScript if `--typescript` or `--typescript-full` was specified
if (getTypeScriptMode(ap) != TypeScriptMode.NONE) addIncludesFor(includes, FileType.TYPESCRIPT);
if (getTypeScriptMode(ap) != TypeScriptMode.NONE) {
addIncludesFor(includes, FileType.TYPESCRIPT);
includes.add("**/tsconfig*.json");
}
// add explicit include patterns
for (String pattern : ap.getZeroOrMore(P_INCLUDE))

View File

@@ -426,7 +426,6 @@ public class ScopeManager {
// cases where we turn on the 'declKind' flags
@Override
public Void visit(FunctionDeclaration nd, Void v) {
if (nd.hasDeclareKeyword() && !isInTypeScriptDeclarationFile()) return null;
// strict mode functions are block-scoped, non-strict mode ones aren't
if (blockscope == isStrict) visit(nd.getId(), DeclKind.var);
return null;
@@ -434,7 +433,6 @@ public class ScopeManager {
@Override
public Void visit(ClassDeclaration nd, Void c) {
if (nd.hasDeclareKeyword() && !isInTypeScriptDeclarationFile()) return null;
if (blockscope) visit(nd.getClassDef().getId(), DeclKind.varAndType);
return null;
}
@@ -483,7 +481,6 @@ public class ScopeManager {
@Override
public Void visit(VariableDeclaration nd, Void v) {
if (nd.hasDeclareKeyword() && !isInTypeScriptDeclarationFile()) return null;
// in block scoping mode, only process 'let'; in non-block scoping
// mode, only process non-'let'
if (blockscope == nd.isBlockScoped(ecmaVersion)) visit(nd.getDeclarations());
@@ -518,8 +515,7 @@ public class ScopeManager {
@Override
public Void visit(NamespaceDeclaration nd, Void c) {
if (blockscope) return null;
boolean isAmbientOutsideDtsFile = nd.hasDeclareKeyword() && !isInTypeScriptDeclarationFile();
boolean hasVariable = nd.isInstantiated() && !isAmbientOutsideDtsFile;
boolean hasVariable = nd.isInstantiated();
visit(nd.getName(), hasVariable ? DeclKind.varAndNamespace : DeclKind.namespace);
return null;
}

View File

@@ -649,11 +649,13 @@ module API {
/** Gets a node corresponding to an import of module `m` without taking into account types from models. */
Node getAModuleImportRaw(string m) {
result = Impl::MkModuleImport(m) or
result = Impl::MkModuleImport(m).(Node).getMember("default")
result = Impl::MkModuleImport(m).(Node).getMember("default") or
result = Impl::MkTypeUse(m, "")
}
/** Gets a node whose type has the given qualified name, not including types from models. */
Node getANodeOfTypeRaw(string moduleName, string exportedName) {
exportedName != "" and
result = Impl::MkTypeUse(moduleName, exportedName).(Node).getInstance()
or
exportedName = "" and
@@ -749,18 +751,14 @@ module API {
MkModuleImport(string m) {
imports(_, m)
or
any(TypeAnnotation n).hasQualifiedName(m, _)
or
any(Type t).hasUnderlyingType(m, _)
any(TypeAnnotation n).hasUnderlyingType(m, _)
} or
MkClassInstance(DataFlow::ClassNode cls) { needsDefNode(cls) } or
MkDef(DataFlow::Node nd) { rhs(_, _, nd) } or
MkUse(DataFlow::Node nd) { use(_, _, nd) } or
/** A use of a TypeScript type. */
MkTypeUse(string moduleName, string exportName) {
any(TypeAnnotation n).hasQualifiedName(moduleName, exportName)
or
any(Type t).hasUnderlyingType(moduleName, exportName)
any(TypeAnnotation n).hasUnderlyingType(moduleName, exportName)
} or
MkSyntheticCallbackArg(DataFlow::Node src, int bound, DataFlow::InvokeNode nd) {
trackUseNode(src, true, bound, "").flowsTo(nd.getCalleeNode())

View File

@@ -5,17 +5,49 @@
import javascript
module Closure {
/** A call to `goog.require` */
class RequireCallExpr extends CallExpr {
RequireCallExpr() { this.getCallee().(PropAccess).getQualifiedName() = "goog.require" }
/** Gets the imported namespace name. */
string getClosureNamespace() { result = this.getArgument(0).getStringValue() }
}
/** A call to `goog.provide` */
class ProvideCallExpr extends CallExpr {
ProvideCallExpr() { this.getCallee().(PropAccess).getQualifiedName() = "goog.provide" }
/** Gets the imported namespace name. */
string getClosureNamespace() { result = this.getArgument(0).getStringValue() }
}
/** A call to `goog.module` or `goog.declareModuleId`. */
private class ModuleDeclarationCall extends CallExpr {
private string kind;
ModuleDeclarationCall() {
this.getCallee().(PropAccess).getQualifiedName() = kind and
kind = ["goog.module", "goog.declareModuleId"]
}
/** Gets the declared namespace. */
string getClosureNamespace() { result = this.getArgument(0).getStringValue() }
/** Gets the string `goog.module` or `goog.declareModuleId` depending on which method is being called. */
string getModuleKind() { result = kind }
}
/**
* A reference to a Closure namespace.
*/
class ClosureNamespaceRef extends DataFlow::Node instanceof ClosureNamespaceRef::Range {
deprecated class ClosureNamespaceRef extends DataFlow::Node instanceof ClosureNamespaceRef::Range {
/**
* Gets the namespace being referenced.
*/
string getClosureNamespace() { result = super.getClosureNamespace() }
}
module ClosureNamespaceRef {
deprecated module ClosureNamespaceRef {
/**
* A reference to a Closure namespace.
*
@@ -32,10 +64,10 @@ module Closure {
/**
* A data flow node that returns the value of a closure namespace.
*/
class ClosureNamespaceAccess extends ClosureNamespaceRef instanceof ClosureNamespaceAccess::Range {
}
deprecated class ClosureNamespaceAccess extends ClosureNamespaceRef instanceof ClosureNamespaceAccess::Range
{ }
module ClosureNamespaceAccess {
deprecated module ClosureNamespaceAccess {
/**
* A data flow node that returns the value of a closure namespace.
*
@@ -47,7 +79,7 @@ module Closure {
/**
* A call to a method on the `goog.` namespace, as a closure reference.
*/
abstract private class DefaultNamespaceRef extends DataFlow::MethodCallNode,
abstract deprecated private class DefaultNamespaceRef extends DataFlow::MethodCallNode,
ClosureNamespaceRef::Range
{
DefaultNamespaceRef() { this = DataFlow::globalVarRef("goog").getAMethodCall() }
@@ -59,14 +91,14 @@ module Closure {
* Holds if `node` is the data flow node corresponding to the expression in
* a top-level expression statement.
*/
private predicate isTopLevelExpr(DataFlow::Node node) {
deprecated private predicate isTopLevelExpr(DataFlow::Node node) {
any(TopLevel tl).getAChildStmt().(ExprStmt).getExpr().flow() = node
}
/**
* A top-level call to `goog.provide`.
*/
private class DefaultClosureProvideCall extends DefaultNamespaceRef {
deprecated private class DefaultClosureProvideCall extends DefaultNamespaceRef {
DefaultClosureProvideCall() {
this.getMethodName() = "provide" and
isTopLevelExpr(this)
@@ -76,13 +108,14 @@ module Closure {
/**
* A top-level call to `goog.provide`.
*/
class ClosureProvideCall extends ClosureNamespaceRef, DataFlow::MethodCallNode instanceof DefaultClosureProvideCall
deprecated class ClosureProvideCall extends ClosureNamespaceRef, DataFlow::MethodCallNode instanceof DefaultClosureProvideCall
{ }
/**
* A call to `goog.require`.
*/
private class DefaultClosureRequireCall extends DefaultNamespaceRef, ClosureNamespaceAccess::Range
deprecated private class DefaultClosureRequireCall extends DefaultNamespaceRef,
ClosureNamespaceAccess::Range
{
DefaultClosureRequireCall() { this.getMethodName() = "require" }
}
@@ -90,13 +123,13 @@ module Closure {
/**
* A call to `goog.require`.
*/
class ClosureRequireCall extends ClosureNamespaceAccess, DataFlow::MethodCallNode instanceof DefaultClosureRequireCall
deprecated class ClosureRequireCall extends ClosureNamespaceAccess, DataFlow::MethodCallNode instanceof DefaultClosureRequireCall
{ }
/**
* A top-level call to `goog.module` or `goog.declareModuleId`.
*/
private class DefaultClosureModuleDeclaration extends DefaultNamespaceRef {
deprecated private class DefaultClosureModuleDeclaration extends DefaultNamespaceRef {
DefaultClosureModuleDeclaration() {
(this.getMethodName() = "module" or this.getMethodName() = "declareModuleId") and
isTopLevelExpr(this)
@@ -106,41 +139,29 @@ module Closure {
/**
* A top-level call to `goog.module` or `goog.declareModuleId`.
*/
class ClosureModuleDeclaration extends ClosureNamespaceRef, DataFlow::MethodCallNode instanceof DefaultClosureModuleDeclaration
deprecated class ClosureModuleDeclaration extends ClosureNamespaceRef, DataFlow::MethodCallNode instanceof DefaultClosureModuleDeclaration
{ }
private GlobalVariable googVariable() { variables(result, "goog", any(GlobalScope sc)) }
pragma[nomagic]
private MethodCallExpr googModuleDeclExpr() {
result.getReceiver() = googVariable().getAnAccess() and
result.getMethodName() = ["module", "declareModuleId"]
}
pragma[nomagic]
private MethodCallExpr googModuleDeclExprInContainer(StmtContainer container) {
result = googModuleDeclExpr() and
container = result.getContainer()
}
pragma[noinline]
private ClosureRequireCall getARequireInTopLevel(ClosureModule m) { result.getTopLevel() = m }
private RequireCallExpr getARequireInTopLevel(ClosureModule m) { result.getTopLevel() = m }
/**
* A module using the Closure module system, declared using `goog.module()` or `goog.declareModuleId()`.
*/
class ClosureModule extends Module {
ClosureModule() { exists(googModuleDeclExprInContainer(this)) }
private ModuleDeclarationCall decl;
ClosureModule() { decl.getTopLevel() = this }
/**
* Gets the call to `goog.module` or `goog.declareModuleId` in this module.
*/
ClosureModuleDeclaration getModuleDeclaration() { result.getTopLevel() = this }
deprecated ClosureModuleDeclaration getModuleDeclaration() { result.getTopLevel() = this }
/**
* Gets the namespace of this module.
*/
string getClosureNamespace() { result = this.getModuleDeclaration().getClosureNamespace() }
string getClosureNamespace() { result = decl.getClosureNamespace() }
override Module getAnImportedModule() {
result.(ClosureModule).getClosureNamespace() =
@@ -156,7 +177,7 @@ module Closure {
* Has no result for ES6 modules using `goog.declareModuleId`.
*/
Variable getExportsVariable() {
this.getModuleDeclaration().getMethodName() = "module" and
decl.getModuleKind() = "goog.module" and
result = this.getScope().getVariable("exports")
}
@@ -185,15 +206,15 @@ module Closure {
ClosureScript() {
not this instanceof ClosureModule and
(
any(ClosureProvideCall provide).getTopLevel() = this
any(ProvideCallExpr provide).getTopLevel() = this
or
any(ClosureRequireCall require).getTopLevel() = this
any(RequireCallExpr require).getTopLevel() = this
)
}
/** Gets the identifier of a namespace required by this module. */
string getARequiredNamespace() {
exists(ClosureRequireCall require |
exists(RequireCallExpr require |
require.getTopLevel() = this and
result = require.getClosureNamespace()
)
@@ -201,7 +222,7 @@ module Closure {
/** Gets the identifer of a namespace provided by this module. */
string getAProvidedNamespace() {
exists(ClosureProvideCall require |
exists(ProvideCallExpr require |
require.getTopLevel() = this and
result = require.getClosureNamespace()
)
@@ -213,7 +234,13 @@ module Closure {
*/
pragma[noinline]
predicate isClosureNamespace(string name) {
exists(string namespace | namespace = any(ClosureNamespaceRef ref).getClosureNamespace() |
exists(string namespace |
namespace =
[
any(RequireCallExpr ref).getClosureNamespace(),
any(ModuleDeclarationCall c).getClosureNamespace()
]
|
name = namespace.substring(0, namespace.indexOf("."))
or
name = namespace

View File

@@ -174,6 +174,9 @@ private class LiteralImportPath extends PathExpr, ConstantString {
* ```
*/
class ImportSpecifier extends Expr, @import_specifier {
/** Gets the import declaration in which this specifier appears. */
ImportDeclaration getImportDeclaration() { result.getASpecifier() = this }
/** Gets the imported symbol; undefined for default and namespace import specifiers. */
Identifier getImported() { result = this.getChildExpr(0) }

View File

@@ -4,6 +4,7 @@
import javascript
private import semmle.javascript.internal.CachedStages
private import semmle.javascript.internal.TypeResolution
/**
* A program element that is either an expression or a type annotation.
@@ -1017,7 +1018,11 @@ class InvokeExpr extends @invokeexpr, Expr {
* Note that the resolved function may be overridden in a subclass and thus is not
* necessarily the actual target of this invocation at runtime.
*/
Function getResolvedCallee() { result = this.getResolvedCalleeName().getImplementation() }
Function getResolvedCallee() {
TypeResolution::callTarget(this, result)
or
result = this.getResolvedCalleeName().getImplementation()
}
}
/**

View File

@@ -73,6 +73,19 @@ class Folder extends Container, Impl::Folder {
)
}
/**
* Gets an implementation file and/or a typings file from this folder that has the given `stem`.
* This could be a single `.ts` file or a pair of `.js` and `.d.ts` files.
*/
File getJavaScriptFileOrTypings(string stem) {
exists(File jsFile | jsFile = this.getJavaScriptFile(stem) |
result = jsFile
or
not jsFile.getFileType().isTypeScript() and
result = this.getFile(stem + ".d.ts")
)
}
/** Gets a subfolder contained in this folder. */
Folder getASubFolder() { result = this.getAChildContainer() }
}

View File

@@ -34,7 +34,7 @@ module AccessPath {
not this.accessesGlobal(_) and
not this instanceof DataFlow::PropRead and
not this instanceof PropertyProjection and
not this instanceof Closure::ClosureNamespaceAccess and
not this.asExpr() instanceof Closure::RequireCallExpr and
not this = DataFlow::parameterNode(any(ImmediatelyInvokedFunctionExpr iife).getAParameter()) and
not FlowSteps::identityFunctionStep(_, this)
}
@@ -139,8 +139,8 @@ module AccessPath {
result = join(fromReference(prop.getBase(), root), "[number]")
)
or
exists(Closure::ClosureNamespaceAccess acc | node = acc |
result = acc.getClosureNamespace() and
exists(Closure::RequireCallExpr req | node = req.flow() |
result = req.getClosureNamespace() and
root.isGlobal()
)
or

View File

@@ -33,6 +33,9 @@ class JSDoc extends @jsdoc, Locatable {
result.getTitle() = title
}
/** Gets the element to which this JSDoc comment is attached */
Documentable getDocumentedElement() { result.getDocumentation() = this }
override string toString() { result = this.getComment().toString() }
}
@@ -299,6 +302,41 @@ class JSDocIdentifierTypeExpr extends @jsdoc_identifier_type_expr, JSDocTypeExpr
override predicate isRawFunction() { this.getName() = "Function" }
}
private AstNode getAncestorInScope(Documentable doc) {
any(JSDocLocalTypeAccess t).getJSDocComment() = doc.getDocumentation() and // restrict to cases where we need this
result = doc.getParent()
or
exists(AstNode mid |
mid = getAncestorInScope(doc) and
not mid = any(Scope s).getScopeElement() and
result = mid.getParent()
)
}
private Scope getScope(Documentable doc) { result.getScopeElement() = getAncestorInScope(doc) }
pragma[nomagic]
private predicate shouldResolveName(TopLevel top, string name) {
exists(JSDocLocalTypeAccess access |
access.getName() = name and
access.getTopLevel() = top
)
}
private LexicalName getOwnLocal(Scope scope, string name, DeclarationSpace space) {
scope = result.getScope() and
name = result.getName() and
space = result.getDeclarationSpace() and
shouldResolveName(scope.getScopeElement().getTopLevel(), name) // restrict size of predicate
}
private LexicalName resolveLocal(Scope scope, string name, DeclarationSpace space) {
result = getOwnLocal(scope, name, space)
or
result = resolveLocal(scope.getOuterScope(), name, space) and
not exists(getOwnLocal(scope, name, space))
}
/**
* An unqualified identifier in a JSDoc type expression.
*
@@ -311,6 +349,12 @@ class JSDocIdentifierTypeExpr extends @jsdoc_identifier_type_expr, JSDocTypeExpr
*/
class JSDocLocalTypeAccess extends JSDocIdentifierTypeExpr {
JSDocLocalTypeAccess() { not this = any(JSDocQualifiedTypeAccess a).getNameNode() }
/** Gets a variable, type-name, or namespace that this expression may resolve to. */
LexicalName getALexicalName() {
result =
resolveLocal(getScope(this.getJSDocComment().getDocumentedElement()), this.getName(), _)
}
}
/**
@@ -371,7 +415,7 @@ class JSDocNamedTypeExpr extends JSDocTypeExpr {
* - `foo.bar.Baz` has prefix `foo` and suffix `.bar.Baz`.
* - `Baz` has prefix `Baz` and an empty suffix.
*/
predicate hasNameParts(string prefix, string suffix) {
deprecated predicate hasNameParts(string prefix, string suffix) {
not this = any(JSDocQualifiedTypeAccess a).getBase() and // restrict size of predicate
exists(string regex, string name | regex = "([^.]+)(.*)" |
name = this.getRawName() and
@@ -379,46 +423,6 @@ class JSDocNamedTypeExpr extends JSDocTypeExpr {
suffix = name.regexpCapture(regex, 2)
)
}
pragma[noinline]
pragma[nomagic]
private predicate hasNamePartsAndEnv(string prefix, string suffix, JSDoc::Environment env) {
// Force join ordering
this.hasNameParts(prefix, suffix) and
env.isContainerInScope(this.getContainer())
}
/**
* Gets the qualified name of this name by resolving its prefix, if any.
*/
cached
private string resolvedName() {
exists(string prefix, string suffix, JSDoc::Environment env |
this.hasNamePartsAndEnv(prefix, suffix, env) and
result = env.resolveAlias(prefix) + suffix
)
}
override predicate hasQualifiedName(string globalName) {
globalName = this.resolvedName()
or
not exists(this.resolvedName()) and
globalName = this.getRawName()
}
override DataFlow::ClassNode getClass() {
exists(string name |
this.hasQualifiedName(name) and
result.hasQualifiedName(name)
)
or
// Handle case where a local variable has a reference to the class,
// but the class doesn't have a globally qualified name.
exists(string alias, JSDoc::Environment env |
this.hasNamePartsAndEnv(alias, "", env) and
result.getAClassReference().flowsTo(env.getNodeFromAlias(alias))
)
}
}
/**
@@ -447,12 +451,6 @@ class JSDocAppliedTypeExpr extends @jsdoc_applied_type_expr, JSDocTypeExpr {
* For example, in `Array<string>`, `string` is the only argument type.
*/
JSDocTypeExpr getAnArgument() { result = this.getArgument(_) }
override predicate hasQualifiedName(string globalName) {
this.getHead().hasQualifiedName(globalName)
}
override DataFlow::ClassNode getClass() { result = this.getHead().getClass() }
}
/**
@@ -472,8 +470,6 @@ class JSDocNullableTypeExpr extends @jsdoc_nullable_type_expr, JSDocTypeExpr {
predicate isPrefix() { jsdoc_prefix_qualifier(this) }
override JSDocTypeExpr getAnUnderlyingType() { result = this.getTypeExpr().getAnUnderlyingType() }
override DataFlow::ClassNode getClass() { result = this.getTypeExpr().getClass() }
}
/**
@@ -493,8 +489,6 @@ class JSDocNonNullableTypeExpr extends @jsdoc_non_nullable_type_expr, JSDocTypeE
predicate isPrefix() { jsdoc_prefix_qualifier(this) }
override JSDocTypeExpr getAnUnderlyingType() { result = this.getTypeExpr().getAnUnderlyingType() }
override DataFlow::ClassNode getClass() { result = this.getTypeExpr().getClass() }
}
/**
@@ -599,8 +593,6 @@ class JSDocOptionalParameterTypeExpr extends @jsdoc_optional_type_expr, JSDocTyp
override JSDocTypeExpr getAnUnderlyingType() {
result = this.getUnderlyingType().getAnUnderlyingType()
}
override DataFlow::ClassNode getClass() { result = this.getUnderlyingType().getClass() }
}
/**
@@ -635,7 +627,7 @@ module JSDoc {
/**
* A statement container which may declare JSDoc name aliases.
*/
class Environment extends StmtContainer {
deprecated class Environment extends StmtContainer {
/**
* Gets the fully qualified name aliased by the given unqualified name
* within this container.
@@ -685,7 +677,7 @@ module JSDoc {
}
pragma[noinline]
private predicate isTypenamePrefix(string name) {
deprecated private predicate isTypenamePrefix(string name) {
any(JSDocNamedTypeExpr expr).hasNameParts(name, _)
}
}

View File

@@ -6,6 +6,7 @@
import javascript
private import semmle.javascript.internal.CachedStages
private import semmle.javascript.internal.paths.PathExprResolver as PathExprResolver
/**
* A module, which may either be an ECMAScript 2015-style module,
@@ -142,6 +143,13 @@ abstract class Import extends AstNode {
result.getFile() = this.getEnclosingModule().resolve(this.getImportedPath())
}
/**
* Gets the module the path of this import resolves to.
*/
Module resolveImportedPathNew() {
result.getFile() = PathExprResolver::resolvePathExpr(this.getImportedPath())
}
/**
* Gets a module with a `@providesModule` JSDoc tag that matches
* the imported path.
@@ -170,7 +178,7 @@ abstract class Import extends AstNode {
/**
* Gets the imported module, as determined by the TypeScript compiler, if any.
*/
private Module resolveFromTypeScriptSymbol() {
Module resolveFromTypeScriptSymbol() {
exists(CanonicalName symbol |
ast_node_symbol(this, symbol) and
ast_node_symbol(result, symbol)
@@ -192,9 +200,9 @@ abstract class Import extends AstNode {
then result = this.resolveExternsImport()
else (
result = this.resolveAsProvidedModule() or
result = this.resolveImportedPath() or
result = this.resolveImportedPathNew() or
result = this.resolveFromTypeRoot() or
result = this.resolveFromTypeScriptSymbol() or
// result = this.resolveFromTypeScriptSymbol() or
result = resolveNeighbourPackage(this.getImportedPath().getValue())
)
}
@@ -210,7 +218,7 @@ abstract class Import extends AstNode {
*
* No support for importing from folders inside the other package.
*/
private Module resolveNeighbourPackage(PathString importPath) {
Module resolveNeighbourPackage(PathString importPath) {
exists(PackageJson json | importPath = json.getPackageName() and result = json.getMainModule())
or
exists(string package |

View File

@@ -12,12 +12,21 @@ class PackageJson extends JsonObject {
this.isTopLevel()
}
/** Gets the folder containing this `package.json` file. */
Folder getFolder() { result = this.getJsonFile().getParentContainer() }
/**
* Gets the name of this package as it appears in the `name` field.
*/
pragma[nomagic]
string getDeclaredPackageName() { result = this.getPropStringValue("name") }
/**
* Gets the name of this package.
* If the package is located under the package `pkg1` and its relative path is `foo/bar`, then the resulting package name will be `pkg1/foo/bar`.
*/
string getPackageName() {
result = this.getPropStringValue("name")
result = this.getDeclaredPackageName()
or
exists(
PackageJson parentPkg, Container currentDir, Container parentDir, string parentPkgName,

View File

@@ -4,6 +4,8 @@
import javascript
private import internal.StmtContainers
private import internal.NameResolution
private import internal.UnderlyingTypes
/**
* A type annotation, either in the form of a TypeScript type or a JSDoc comment.
@@ -75,14 +77,38 @@ class TypeAnnotation extends @type_annotation, NodeInStmtContainer {
TypeAnnotation getAnUnderlyingType() { result = this }
/**
* DEPRECATED. Use `hasUnderlyingType` instead.
*
* Holds if this is a reference to the type with qualified name `globalName` relative to the global scope.
*/
predicate hasQualifiedName(string globalName) { none() }
deprecated predicate hasQualifiedName(string globalName) {
UnderlyingTypes::nodeHasUnderlyingType(this, globalName)
}
/**
* DEPRECATED. Use `hasUnderlyingType` instead.
*
* Holds if this is a reference to the type exported from `moduleName` under the name `exportedName`.
*/
predicate hasQualifiedName(string moduleName, string exportedName) { none() }
deprecated predicate hasQualifiedName(string moduleName, string exportedName) {
UnderlyingTypes::nodeHasUnderlyingType(this, moduleName, exportedName)
}
/**
* Holds if this is a reference to the type with qualified name `globalName` relative to the global scope,
* or is declared as a subtype thereof, or is a union or intersection containing such a type.
*/
final predicate hasUnderlyingType(string globalName) {
UnderlyingTypes::nodeHasUnderlyingType(this, globalName)
}
/**
* Holds if this is a reference to the type exported from `moduleName` under the name `exportedName`,
* or is declared as a subtype thereof, or is a union or intersection containing such a type.
*/
final predicate hasUnderlyingType(string moduleName, string exportedName) {
UnderlyingTypes::nodeHasUnderlyingType(this, moduleName, exportedName)
}
/** Gets the statement in which this type appears. */
Stmt getEnclosingStmt() { none() }
@@ -107,5 +133,5 @@ class TypeAnnotation extends @type_annotation, NodeInStmtContainer {
*
* This unfolds nullability modifiers and generic type applications.
*/
DataFlow::ClassNode getClass() { none() }
final DataFlow::ClassNode getClass() { UnderlyingTypes::nodeHasUnderlyingClassType(this, result) }
}

View File

@@ -1,4 +1,5 @@
import javascript
private import semmle.javascript.internal.UnderlyingTypes
/**
* A statement that defines a namespace, that is, a namespace declaration or enum declaration.
@@ -575,10 +576,6 @@ class TypeExpr extends ExprOrType, @typeexpr, TypeAnnotation {
override Function getEnclosingFunction() { result = ExprOrType.super.getEnclosingFunction() }
override TopLevel getTopLevel() { result = ExprOrType.super.getTopLevel() }
override DataFlow::ClassNode getClass() {
result.getAstNode() = this.getType().(ClassType).getClass()
}
}
/**
@@ -698,58 +695,9 @@ class TypeAccess extends @typeaccess, TypeExpr, TypeRef {
*/
TypeName getTypeName() { ast_node_symbol(this, result) }
override predicate hasQualifiedName(string globalName) {
this.getTypeName().hasQualifiedName(globalName)
or
exists(LocalTypeAccess local | local = this |
not exists(local.getLocalTypeName()) and // Without a local type name, the type is looked up in the global scope.
globalName = local.getName()
)
}
override predicate hasQualifiedName(string moduleName, string exportedName) {
this.getTypeName().hasQualifiedName(moduleName, exportedName)
or
exists(ImportDeclaration imprt, ImportSpecifier spec |
moduleName = getImportName(imprt) and
spec = imprt.getASpecifier()
|
spec.getImportedName() = exportedName and
this = spec.getLocal().(TypeDecl).getLocalTypeName().getAnAccess()
or
(spec instanceof ImportNamespaceSpecifier or spec instanceof ImportDefaultSpecifier) and
this =
spec.getLocal().(LocalNamespaceDecl).getLocalNamespaceName().getAMemberAccess(exportedName)
)
or
exists(ImportEqualsDeclaration imprt |
moduleName = getImportName(imprt.getImportedEntity()) and
this =
imprt
.getIdentifier()
.(LocalNamespaceDecl)
.getLocalNamespaceName()
.getAMemberAccess(exportedName)
)
}
override string getAPrimaryQlClass() { result = "TypeAccess" }
}
/**
* Gets a suitable name for the library imported by `imprt`.
*
* For relative imports, this is the snapshot-relative path to the imported module.
* For non-relative imports, it is the import path itself.
*/
private string getImportName(Import imprt) {
exists(string path | path = imprt.getImportedPath().getValue() |
if path.regexpMatch("[./].*")
then result = imprt.getImportedModule().getFile().getRelativePath()
else result = path
)
}
/** An identifier that is used as part of a type, such as `Date`. */
class LocalTypeAccess extends @local_type_access, TypeAccess, Identifier, LexicalAccess {
override predicate isStringy() { this.getName() = "String" }
@@ -822,14 +770,6 @@ class GenericTypeExpr extends @generic_typeexpr, TypeExpr {
/** Gets the number of type arguments. This is always at least one. */
int getNumTypeArgument() { result = count(this.getATypeArgument()) }
override predicate hasQualifiedName(string globalName) {
this.getTypeAccess().hasQualifiedName(globalName)
}
override predicate hasQualifiedName(string moduleName, string exportedName) {
this.getTypeAccess().hasQualifiedName(moduleName, exportedName)
}
override string getAPrimaryQlClass() { result = "GenericTypeExpr" }
}

View File

@@ -27,6 +27,12 @@ class Scope extends @scope {
result = this.getAVariable() and
result.getName() = name
}
/** Gets the local type name with the given name declared in this scope. */
LocalTypeName getLocalTypeName(string name) {
result.getScope() = this and
result.getName() = name
}
}
/**

View File

@@ -3,6 +3,7 @@
*/
private import javascript
private import semmle.javascript.internal.TypeResolution
/**
* An input to a view component, such as React props.
@@ -14,34 +15,11 @@ abstract class ViewComponentInput extends DataFlow::Node {
private class ViewComponentInputAsThreatModelSource extends ThreatModelSource::Range instanceof ViewComponentInput
{
ViewComponentInputAsThreatModelSource() { not isSafeType(this.asExpr().getType()) }
ViewComponentInputAsThreatModelSource() {
not TypeResolution::valueHasSanitizingPrimitiveType(this.asExpr())
}
final override string getThreatModel() { result = "view-component-input" }
final override string getSourceType() { result = ViewComponentInput.super.getSourceType() }
}
private predicate isSafeType(Type t) {
t instanceof NumberLikeType
or
t instanceof BooleanLikeType
or
t instanceof UndefinedType
or
t instanceof NullType
or
t instanceof VoidType
or
hasSafeTypes(t, t.(UnionType).getNumElementType())
or
isSafeType(t.(IntersectionType).getAnElementType())
}
/** Hold if the first `n` components of `t` are safe types. */
private predicate hasSafeTypes(UnionType t, int n) {
isSafeType(t.getElementType(0)) and
n = 1
or
isSafeType(t.getElementType(n - 1)) and
hasSafeTypes(t, n - 1)
}

View File

@@ -27,6 +27,9 @@ private import internal.PreCallGraphStep
private import semmle.javascript.internal.CachedStages
private import semmle.javascript.dataflow.internal.DataFlowPrivate as Private
private import semmle.javascript.dataflow.internal.VariableOrThis
private import semmle.javascript.internal.NameResolution
private import semmle.javascript.internal.UnderlyingTypes
private import semmle.javascript.internal.TypeResolution
module DataFlow {
/**
@@ -189,26 +192,6 @@ module DataFlow {
FlowSteps::identityFunctionStep(result, this)
}
/**
* Gets the static type of this node as determined by the TypeScript type system.
*/
private Type getType() {
exists(AST::ValueNode node |
this = TValueNode(node) and
ast_node_type(node, result)
)
or
exists(BindingPattern pattern |
this = lvalueNode(pattern) and
ast_node_type(pattern, result)
)
or
exists(MethodDefinition def |
this = TThisNode(def.getInit()) and
ast_node_type(def.getDeclaringClass(), result)
)
}
/**
* Gets the type annotation describing the type of this node,
* provided that a static type could not be found.
@@ -229,6 +212,15 @@ module DataFlow {
)
}
private NameResolution::Node getNameResolutionNode() {
this = valueNode(result)
or
exists(PropertyPattern pattern |
result = pattern.getValuePattern() and
this = TPropNode(pattern)
)
}
/**
* Holds if this node is annotated with the given named type,
* or is declared as a subtype thereof, or is a union or intersection containing such a type.
@@ -236,9 +228,13 @@ module DataFlow {
cached
predicate hasUnderlyingType(string globalName) {
Stages::TypeTracking::ref() and
this.getType().hasUnderlyingType(globalName)
or
this.getFallbackTypeAnnotation().getAnUnderlyingType().hasQualifiedName(globalName)
exists(NameResolution::Node type |
TypeResolution::valueHasType(this.getNameResolutionNode(), type)
or
TypeResolution::contextualType(this.getNameResolutionNode(), type)
|
UnderlyingTypes::nodeHasUnderlyingType(type, globalName)
)
}
/**
@@ -248,9 +244,16 @@ module DataFlow {
cached
predicate hasUnderlyingType(string moduleName, string typeName) {
Stages::TypeTracking::ref() and
this.getType().hasUnderlyingType(moduleName, typeName)
or
this.getFallbackTypeAnnotation().getAnUnderlyingType().hasQualifiedName(moduleName, typeName)
moduleName != "global" and
exists(NameResolution::Node type |
TypeResolution::valueHasType(this.getNameResolutionNode(), type)
or
// Also check contextual type as this helps when tracking facts between SourceNode only.
// For example, for `var x: T = getFoo()` we need `getFoo()` to have the type T.
TypeResolution::contextualType(this.getNameResolutionNode(), type)
|
UnderlyingTypes::nodeHasUnderlyingType(type, moduleName, typeName)
)
}
/**

View File

@@ -5,6 +5,8 @@
import javascript
private import semmle.javascript.security.dataflow.ServerSideUrlRedirectCustomizations
private import semmle.javascript.dataflow.internal.PreCallGraphStep
private import semmle.javascript.internal.NameResolution
private import semmle.javascript.internal.TypeResolution
/**
* Provides classes and predicates for reasoning about [Nest](https://nestjs.com/).
@@ -133,7 +135,9 @@ module NestJS {
hasSanitizingPipe(this, false)
or
hasSanitizingPipe(this, true) and
isSanitizingType(this.getParameter().getType().unfold())
// Note: we could consider types with class-validator decorators to be sanitized here, but instead we consider the root
// object to be tainted, but omit taint steps for the individual properties names that have sanitizing decorators. See ClassValidator.qll.
TypeResolution::isSanitizingPrimitiveType(this.getParameter().getTypeAnnotation())
}
}
@@ -209,19 +213,6 @@ module NestJS {
dependsOnType = true
}
/**
* Holds if a parameter of type `t` is considered sanitized, provided it has been checked by `ValidationPipe`
* (which relies on metadata emitted by the TypeScript compiler).
*/
private predicate isSanitizingType(Type t) {
t instanceof NumberType
or
t instanceof BooleanType
//
// Note: we could consider types with class-validator decorators to be sanitized here, but instead we consider the root
// object to be tainted, but omit taint steps for the individual properties names that have sanitizing decorators. See ClassValidator.qll.
}
/**
* A user-defined pipe class, for example:
* ```js
@@ -237,7 +228,7 @@ module NestJS {
CustomPipeClass() {
exists(ClassDefinition cls |
this = cls.flow() and
cls.getASuperInterface().hasQualifiedName("@nestjs/common", "PipeTransform")
cls.getASuperInterface().hasUnderlyingType("@nestjs/common", "PipeTransform")
)
}
@@ -327,14 +318,6 @@ module NestJS {
}
}
private predicate isStringType(Type type) {
type instanceof StringType
or
type instanceof AnyType
or
isStringType(type.(PromiseType).getElementType().unfold())
}
/**
* A return value from a route handler, seen as an argument to `res.send()`.
*
@@ -353,10 +336,10 @@ module NestJS {
ReturnValueAsResponseSend() {
handler.isReturnValueReflected() and
this = handler.getAReturn() and
// Only returned strings are sinks
not exists(Type type |
type = this.asExpr().getType() and
not isStringType(type.unfold())
// Only returned strings are sinks. If we can find a type for the return value, it must be string-like.
not exists(NameResolution::Node type |
TypeResolution::valueHasType(this.asExpr(), type) and
not TypeResolution::hasUnderlyingStringOrAnyType(type)
)
}

View File

@@ -60,9 +60,7 @@ predicate isPackageUsed(string package) {
or
package = any(JS::Import imp).getImportedPath().getValue()
or
any(JS::TypeName t).hasQualifiedName(package, _)
or
any(JS::TypeAnnotation t).hasQualifiedName(package, _)
any(JS::TypeAnnotation t).hasUnderlyingType(package, _)
or
exists(JS::PackageJson json | json.getPackageName() = package)
}
@@ -138,7 +136,7 @@ API::Node getExtraNodeFromType(string type) {
parseRelevantTypeString(type, package, qualifiedName)
|
qualifiedName = "" and
result = [API::moduleImport(package), API::moduleExport(package)]
result = [API::Internal::getAModuleImportRaw(package), API::moduleExport(package)]
or
// Access instance of a type based on type annotations
result = API::Internal::getANodeOfTypeRaw(package, qualifiedName)

View File

@@ -60,11 +60,7 @@ private predicate neverReturnsJQuery(string name) {
decl.getBaseName() = "jQuery" and
decl.getName() = name
|
not decl.getDocumentation()
.getATagByTitle("return")
.getType()
.getAnUnderlyingType()
.hasQualifiedName("jQuery")
not decl.getDocumentation().getATagByTitle("return").getType().hasUnderlyingType("jQuery")
)
}
@@ -414,6 +410,8 @@ module JQuery {
this = DataFlow::moduleImport(["jquery", "zepto", "cash-dom"])
or
this.hasUnderlyingType("JQueryStatic")
or
this.hasUnderlyingType("jquery", "")
}
}
}

View File

@@ -0,0 +1,468 @@
/**
* Provides name resolution and propagates type information.
*/
private import javascript
/**
* Provides name resolution and propagates type information.
*/
module NameResolution {
private class NodeBase =
@expr or @typeexpr or @lexical_name or @toplevel or @function_decl_stmt or @class_decl_stmt or
@namespace_declaration or @enum_declaration or @interface_declaration or
@type_alias_declaration or @jsdoc_type_expr;
/**
* A node in a graph which we use to perform name and type resolution.
*/
class Node extends NodeBase {
string toString() {
result = this.(AstNode).toString()
or
result = this.(LexicalName).toString()
or
result = this.(JSDocTypeExpr).toString()
}
Location getLocation() {
result = this.(AstNode).getLocation()
or
result = this.(LocalVariable).getLocation()
or
result = this.(JSDocTypeExpr).getLocation()
}
}
private signature predicate nodeSig(Node node);
/**
* A module top-level, or a `module {}` or `enum {}` statement.
*/
private class ModuleLike extends AstNode {
ModuleLike() {
this instanceof Module
or
this instanceof NamespaceDefinition // `module {}` or `enum {}` statement
}
}
/**
* Holds if values/namespaces/types in `node1` can flow to values/namespaces/types in `node2`.
*/
private predicate commonStep(Node node1, Node node2) {
// Import paths are part of the graph and has an incoming edge from the imported module, if found.
// This ensures we can also use the PathExpr as a source when working with external (unresolved) modules.
exists(Import imprt |
node1 = imprt.getImportedModule() and
node2 = imprt.getImportedPath()
)
or
exists(ImportNamespaceSpecifier spec |
node1 = spec.getImportDeclaration().getImportedPath() and
node2 = spec.getLocal()
)
or
exists(ExportNamespaceSpecifier spec |
node1 = spec.getExportDeclaration().(ReExportDeclaration).getImportedPath() and
node2 = spec
)
or
exists(ExportAssignDeclaration assign |
node1 = assign.getExpression() and
node2 = assign.getContainer()
)
or
exists(ImportEqualsDeclaration imprt |
node1 = imprt.getImportedEntity() and
node2 = imprt.getIdentifier()
)
or
exists(ExternalModuleReference ref |
node1 = ref.getImportedPath() and
node2 = ref
)
or
exists(ImportTypeExpr imprt |
node1 = imprt.getPathExpr() and // TODO: ImportTypeExpr does not seem to be resolved to a Module
node2 = imprt
)
or
exists(ClassOrInterface cls |
node1 = cls and
node2 = cls.getIdentifier()
)
or
exists(NamespaceDefinition def |
node1 = def and
node2 = def.getIdentifier()
)
or
exists(Function fun |
node1 = fun and
node2 = fun.getIdentifier()
)
or
exists(EnumMember def |
node1 = def.getInitializer() and
node2 = def.getIdentifier()
)
or
exists(TypeAliasDeclaration alias |
node1 = alias.getDefinition() and
node2 = alias.getIdentifier()
)
or
exists(VariableDeclarator decl |
node1 = decl.getInit() and
node2 = decl.getBindingPattern()
)
or
exists(ParenthesizedTypeExpr type |
node1 = type.getElementType() and
node2 = type
)
or
exists(ParenthesisExpr expr |
node1 = expr.getExpression() and
node2 = expr
)
or
exists(NonNullAssertion assertion |
// For the time being we don't use this for nullness analysis, so just
// propagate through these assertions.
node1 = assertion.getExpression() and
node2 = assertion
)
or
exists(FunctionTypeExpr fun |
node1 = fun.getFunction() and
node2 = fun
)
or
exists(TypeofTypeExpr type |
node1 = type.getExpressionName() and
node2 = type
)
or
exists(Closure::RequireCallExpr req |
node1.(Closure::ClosureModule).getClosureNamespace() = req.getClosureNamespace() and
node2 = req
)
or
exists(Closure::ClosureModule mod |
node1 = mod.getExportsVariable() and
node2 = mod
)
or
exists(ImmediatelyInvokedFunctionExpr fun, int i |
node1 = fun.getArgument(i) and
node2 = fun.getParameter(i)
)
}
/**
* Holds if there is a read from `node1` to `node2` that accesses the member `name`.
*/
predicate readStep(Node node1, string name, Node node2) {
exists(QualifiedTypeAccess access |
node1 = access.getQualifier() and
name = access.getIdentifier().getName() and
node2 = access
)
or
exists(QualifiedNamespaceAccess access |
node1 = access.getQualifier() and
name = access.getIdentifier().getName() and
node2 = access
)
or
exists(QualifiedVarTypeAccess access |
node1 = access.getQualifier() and
name = access.getIdentifier().getName() and
node2 = access
)
or
exists(PropAccess access |
node1 = access.getBase() and
name = access.getPropertyName() and
node2 = access
)
or
exists(ObjectPattern pattern |
node1 = pattern and
node2 = pattern.getPropertyPatternByName(name).getValuePattern()
)
or
exists(ImportSpecifier spec |
node1 = spec.getImportDeclaration().getImportedPath() and
name = spec.getImportedName() and
node2 = spec.getLocal()
)
or
exists(SelectiveReExportDeclaration exprt, ExportSpecifier spec |
spec = exprt.getASpecifier() and
node1 = exprt.getImportedPath() and
name = spec.getLocalName() and
node2 = spec.getLocal()
)
or
exists(JSDocQualifiedTypeAccess expr |
node1 = expr.getBase() and
name = expr.getName() and
node2 = expr
)
}
private signature module TypeResolutionInputSig {
/**
* Holds if flow is permitted through the given variable.
*/
predicate isRelevantVariable(LexicalName var);
}
/**
* A local variable with exactly one definition, not counting implicit initialization.
*/
private class EffectivelyConstantVariable extends LocalVariable {
EffectivelyConstantVariable() {
count(SsaExplicitDefinition ssa | ssa.getSourceVariable() = this) <= 1 // count may be zero if ambient
}
}
/** Configuration for propagating values and namespaces */
private module ValueConfig implements TypeResolutionInputSig {
predicate isRelevantVariable(LexicalName var) {
var instanceof EffectivelyConstantVariable
or
// We merge the namespace and value declaration spaces as it seems there is
// no need to distinguish them in practice.
var instanceof LocalNamespaceName
}
}
/**
* Associates information about values, such as references to a class, module, or namespace.
*/
module ValueFlow = FlowImpl<ValueConfig>;
private module TypeConfig implements TypeResolutionInputSig {
predicate isRelevantVariable(LexicalName var) { var instanceof LocalTypeName }
}
/**
* Associates nodes with information about types.
*/
module TypeFlow = FlowImpl<TypeConfig>;
private module FlowImpl<TypeResolutionInputSig S> {
/**
* Gets the exported member of `mod` named `name`.
*/
Node getModuleExport(ModuleLike mod, string name) {
exists(ExportDeclaration exprt |
mod = exprt.getContainer() and
exprt.exportsAs(result, name) and
S::isRelevantVariable(result)
)
or
exists(ExportNamespaceSpecifier spec |
result = spec and
mod = spec.getContainer() and
name = spec.getExportedName()
)
or
exists(SelectiveReExportDeclaration exprt, ExportSpecifier spec |
// `export { A as B } from 'blah'`
// This is not covered by `exportsAs` above because neither A or B is a LexicalName
// (both are property names) so it doesn't fit the interface of `exportsAs`.
spec = exprt.getASpecifier() and
mod = exprt.getContainer() and
name = spec.getExportedName() and
result = spec.getLocal()
)
or
exists(EnumDeclaration enum |
mod = enum and
result = enum.getMemberByName(name).getIdentifier()
)
or
storeToVariable(result, name, mod.(Closure::ClosureModule).getExportsVariable())
}
/**
* Holds if `value` is stored in `target.prop`. Only needs to recognise assignments
* that are also recognised by JSDoc tooling such as the Closure compiler.
*/
private predicate storeToVariable(Expr value, string prop, LocalVariable target) {
exists(AssignExpr assign |
// exports.name = value
assign.getLhs().(PropAccess).accesses(target.getAnAccess(), prop) and
value = assign.getRhs()
)
or
// exports = { name: value }
value = target.getAnAssignedExpr().(ObjectExpr).getPropertyByName(prop).getInit()
}
/** Steps that only apply for this configuration. */
private predicate specificStep(Node node1, Node node2) {
exists(LexicalName var | S::isRelevantVariable(var) |
node1.(LexicalDecl).getALexicalName() = var and
node2 = var
or
node1 = var and
node2.(LexicalAccess).getALexicalName() = var
or
node1 = var and
node2.(JSDocLocalTypeAccess).getALexicalName() = var
)
or
exists(Node base, string name, ModuleLike mod |
readStep(base, name, node2) and
base = trackModule(mod) and
node1 = getModuleExport(mod, name)
)
}
/**
* Holds if data should propagate from `node1` to `node2`.
*/
pragma[inline]
predicate step(Node node1, Node node2) {
commonStep(node1, node2)
or
specificStep(node1, node2)
}
/** Helps track flow from a particular set of source nodes. */
module Track<nodeSig/1 isSource> {
/** Gets the set of nodes reachable from `source`. */
Node track(Node source) {
isSource(source) and
result = source
or
step(track(source), result)
}
}
signature class AstNodeSig extends AstNode;
/** Helps track flow from a particular set of source nodes. */
module TrackNode<AstNodeSig Source> {
/** Gets the set of nodes reachable from `source`. */
Node track(Source source) {
result = source
or
step(track(source), result)
}
}
}
/**
* Gets a node to which the given module flows.
*/
predicate trackModule = ValueFlow::TrackNode<ModuleLike>::track/1;
/**
* Holds if `moduleName` appears to start with a package name, as opposed to a relative file import.
*/
bindingset[moduleName]
private predicate isExternalModuleName(string moduleName) {
not moduleName.regexpMatch("^(\\.|/).*")
}
bindingset[name]
private string normalizeModuleName(string name) {
result =
name.regexpReplaceAll("^node:", "")
.regexpReplaceAll("\\.[jt]sx?$", "")
.regexpReplaceAll("/(index)?$", "")
}
/** Appends a name onto a qualified name */
bindingset[a, b]
string append(string a, string b) {
if b = "default"
then result = a
else (
(if a = "" or b = "" then result = a + b else result = a + "." + b) and
result.length() < 100
)
}
private predicate needsQualifiedName(Node node) {
node = any(JSDocLocalTypeAccess t).getALexicalName().(Variable)
or
exists(Node prev | needsQualifiedName(prev) |
ValueFlow::step(node, prev)
or
readStep(node, _, prev)
)
}
/**
* Holds if `node` is a reference to the given module, or a qualified name rooted in that module.
*
* If `qualifiedName` is empty, `node` refers to the module itself.
*
* If `mod` is the string `"global"`, `node` refers to a global access path.
*
* Unlike `trackModule`, this is intended to track uses of external packages.
*/
predicate nodeRefersToModule(Node node, string mod, string qualifiedName) {
exists(PathExpr path |
path = any(Import imprt).getImportedPath() or
path = any(ReExportDeclaration e).getImportedPath()
|
node = path and
mod = normalizeModuleName(path.getValue()) and
isExternalModuleName(mod) and
qualifiedName = ""
)
or
mod = "global" and
exists(LocalNamespaceAccess access |
node = access and
not exists(access.getLocalNamespaceName()) and
access.getName() = qualifiedName
)
or
mod = "global" and
exists(JSDocLocalTypeAccess access |
node = access and
not exists(access.getALexicalName()) and
access.getName() = qualifiedName
)
or
mod = "global" and
exists(GlobalVarAccess access |
node = access and
needsQualifiedName(access) and // restrict number of qualified names we generate
access.getName() = qualifiedName
)
or
mod = "global" and
qualifiedName = node.(Closure::RequireCallExpr).getClosureNamespace()
or
// Additionally track through bulk re-exports (`export * from 'mod`).
// These are normally handled by 'exportAs' which supports various shadowing rules,
// but has no effect when the ultimate re-exported module is not resolved to a Module.
// We propagate external module refs through bulk re-exports and ignore shadowing rules.
exists(BulkReExportDeclaration reExport |
nodeRefersToModule(reExport.getImportedPath(), mod, qualifiedName) and
node = reExport.getContainer()
)
or
exists(Node mid |
nodeRefersToModule(mid, mod, qualifiedName) and
ValueFlow::step(mid, node)
)
or
exists(Node mid, string prefix, string step |
nodeRefersToModule(mid, mod, prefix) and
readStep(mid, step, node) and
qualifiedName = append(prefix, step)
)
}
}

View File

@@ -0,0 +1,389 @@
private import javascript
private import semmle.javascript.internal.NameResolution::NameResolution
private import semmle.javascript.internal.UnderlyingTypes
private import semmle.javascript.dataflow.internal.sharedlib.SummaryTypeTracker as SummaryTypeTracker
module TypeResolution {
predicate trackClassValue = ValueFlow::TrackNode<ClassDefinition>::track/1;
predicate trackType = TypeFlow::TrackNode<TypeDefinition>::track/1;
Node trackFunctionType(Function fun) {
result = fun
or
exists(Node mid | mid = trackFunctionType(fun) |
TypeFlow::step(mid, result)
or
UnderlyingTypes::underlyingTypeStep(mid, result)
)
}
predicate trackFunctionValue = ValueFlow::TrackNode<Function>::track/1;
/**
* Gets the representative for the type containing the given member.
*
* For non-static members this is simply the enclosing type declaration.
*
* For static members we use the class's `Variable` as representative for the type of the class object.
*/
private Node getMemberBase(MemberDeclaration member) {
if member.isStatic()
then result = member.getDeclaringClass().getVariable()
else result = member.getDeclaringType()
}
/**
* Holds if `host` is a type with a `content` of type `memberType`.
*/
private predicate typeMember(Node host, DataFlow::Content content, Node memberType) {
exists(MemberDeclaration decl | host = getMemberBase(decl) |
exists(FieldDeclaration field |
decl = field and
content.asPropertyName() = field.getName() and
memberType = field.getTypeAnnotation()
)
or
exists(MethodDeclaration method |
decl = method and
content.asPropertyName() = method.getName() and
memberType = method.getBody() // use the Function as representative for the function type
)
or
decl instanceof IndexSignature and
memberType = decl.(IndexSignature).getBody().getReturnTypeAnnotation() and
content.isUnknownArrayElement()
)
or
// Ad-hoc support for array types. We don't support generics in general currently, we just special-case arrays.
content.isUnknownArrayElement() and
(
memberType = host.(ArrayTypeExpr).getElementType()
or
exists(GenericTypeExpr type |
host = type and
type.getTypeAccess().(LocalTypeAccess).getName() = ["Array", "ReadonlyArray"] and
memberType = type.getTypeArgument(0)
)
or
exists(JSDocAppliedTypeExpr type |
host = type and
type.getHead().(JSDocLocalTypeAccess).getName() = "Array" and
memberType = type.getArgument(0)
)
)
or
// Inherit members from base types
exists(ClassOrInterface baseType | typeMember(baseType, content, memberType) |
host.(ClassDefinition).getSuperClass() = trackClassValue(baseType)
or
host.(ClassOrInterface).getASuperInterface() = trackType(baseType)
)
}
/**
* Holds `use` refers to `host`, and `host` has type members.
*
* Currently steps through unions and intersections, which acts as a basic
* approximation to the unions/intersection of objects.
*/
private predicate typeMemberHostReaches(Node host, Node use) {
typeMember(host, _, _) and
use = host
or
exists(Node mid | typeMemberHostReaches(host, mid) |
TypeFlow::step(mid, use)
or
UnderlyingTypes::underlyingTypeStep(mid, use)
)
}
/**
* Holds if there is a read from from `object` to `member` that reads `contents`.
*/
private predicate valueReadStep(Node object, DataFlow::ContentSet contents, Node member) {
member.(PropAccess).accesses(object, contents.asPropertyName())
or
object.(ObjectPattern).getPropertyPatternByName(contents.asPropertyName()).getValuePattern() =
member
or
SummaryTypeTracker::basicLoadStep(object.(AST::ValueNode).flow(),
member.(AST::ValueNode).flow(), contents)
}
predicate callTarget(InvokeExpr call, Function target) {
exists(ClassDefinition cls |
valueHasType(call.(NewExpr).getCallee(), trackClassValue(cls)) and
target = cls.getConstructor().getBody()
)
or
valueHasType(call.(InvokeExpr).getCallee(), trackFunctionValue(target))
or
valueHasType(call.(InvokeExpr).getCallee(), trackFunctionType(target)) and
(
call instanceof NewExpr and
target = any(ConstructorTypeExpr t).getFunction()
or
call instanceof CallExpr and
target = any(PlainFunctionTypeExpr t).getFunction()
)
or
exists(InterfaceDefinition interface, CallSignature sig |
valueHasType(call.(InvokeExpr).getCallee(), trackType(interface)) and
sig = interface.getACallSignature() and
target = sig.getBody()
|
call instanceof NewExpr and
sig instanceof ConstructorCallSignature
or
call instanceof CallExpr and
sig instanceof FunctionCallSignature
)
}
private predicate functionReturnType(Function func, Node returnType) {
returnType = func.getReturnTypeAnnotation()
or
not exists(func.getReturnTypeAnnotation()) and
exists(Function functionType |
contextualType(func, trackFunctionType(functionType)) and
returnType = functionType.getReturnTypeAnnotation()
)
}
bindingset[name]
private predicate isPromiseTypeName(string name) {
name.regexpMatch(".?(Promise|Thenable)(Like)?")
}
private Node unwrapPromiseType(Node promiseType) {
exists(GenericTypeExpr type |
promiseType = type and
isPromiseTypeName(type.getTypeAccess().(LocalTypeAccess).getName()) and
result = type.getTypeArgument(0)
)
or
exists(JSDocAppliedTypeExpr type |
promiseType = type and
isPromiseTypeName(type.getHead().(JSDocLocalTypeAccess).getName()) and
result = type.getArgument(0)
)
}
predicate contextualType(Node value, Node type) {
exists(LocalVariable v |
type = v.getADeclaration().getTypeAnnotation() and
value = v.getAnAssignedExpr()
)
or
exists(InvokeExpr call, Function target, int i |
callTarget(call, target) and
value = call.getArgument(i) and
type = target.getParameter(i).getTypeAnnotation()
)
or
exists(Function lambda, Node returnType |
value = lambda.getAReturnedExpr() and
functionReturnType(lambda, returnType)
|
not lambda.isAsyncOrGenerator() and
type = returnType
or
lambda.isAsync() and
type = unwrapPromiseType(returnType)
)
or
exists(ObjectExpr object, Node objectType, Node host, string name |
contextualType(object, objectType) and
typeMemberHostReaches(host, objectType) and
typeMember(host, any(DataFlow::Content c | c.asPropertyName() = name), type) and
value = object.getPropertyByName(name).getInit()
)
or
exists(ArrayExpr array, Node arrayType, Node host |
contextualType(array, arrayType) and
typeMemberHostReaches(host, arrayType) and
typeMember(host, any(DataFlow::Content c | c.isUnknownArrayElement()), type) and
value = array.getAnElement()
)
}
/**
* Holds if `value` has the given `type`.
*/
predicate valueHasType(Node value, Node type) {
value.(BindingPattern).getTypeAnnotation() = type
or
exists(VarDecl decl |
// ValueFlow::step is restricted to variables with at most one assignment. Allow the type annotation
// of a variable to propagate to its uses, even if the variable has multiple assignments.
type = decl.getTypeAnnotation() and
value = decl.getVariable().(LocalVariable).getAnAccess()
)
or
exists(MemberDeclaration member |
value.(ThisExpr).getBindingContainer() = member.getInit() and
type = getMemberBase(member)
)
or
exists(ClassDefinition cls |
value = cls and
type = cls.getVariable()
)
or
exists(FunctionDeclStmt fun |
value = fun and
type = fun.getVariable()
)
or
exists(Function target | callTarget(value, target) |
type = target.getReturnTypeAnnotation()
or
exists(ClassDefinition cls |
target = cls.getConstructor().getBody() and
type = cls
)
)
or
// Contextual typing for parameters
exists(Function lambda, Function functionType, int i |
contextualType(lambda, trackFunctionType(functionType))
or
exists(InterfaceDefinition interface |
contextualType(lambda, trackType(interface)) and
functionType = interface.getACallSignature().getBody()
)
|
value = lambda.getParameter(i) and
not exists(value.(Parameter).getTypeAnnotation()) and
type = functionType.getParameter(i).getTypeAnnotation()
)
or
exists(Node mid | valueHasType(mid, type) | ValueFlow::step(mid, value))
or
exists(Node mid, Node midType, DataFlow::ContentSet contents, Node host |
valueReadStep(mid, contents, value) and
valueHasType(mid, midType) and
typeMemberHostReaches(host, midType) and
typeMember(host, contents.getAReadContent(), type)
)
}
signature predicate nodeSig(Node node);
/**
* Tracks types that have a certain property, in the sense that:
* - an intersection type has the property if any member has the property
* - a union type has the property if all its members have the property
*/
module TrackMustProp<nodeSig/1 directlyHasProperty> {
predicate hasProperty(Node node) {
directlyHasProperty(node)
or
exists(Node mid |
hasProperty(mid) and
TypeFlow::step(mid, node)
)
or
unionHasProp(node)
or
hasProperty(node.(IntersectionTypeExpr).getAnElementType())
or
exists(ConditionalTypeExpr cond |
node = cond and
hasProperty(cond.getTrueType()) and
hasProperty(cond.getFalseType())
)
}
private predicate unionHasProp(UnionTypeExpr node, int n) {
hasProperty(node.getElementType(0)) and n = 1
or
unionHasProp(node, n - 1) and
hasProperty(node.getElementType(n - 1))
}
private predicate unionHasProp(UnionTypeExpr node) {
unionHasProp(node, node.getNumElementType())
}
}
module ValueHasProperty<nodeSig/1 typeHasProperty> {
predicate valueHasProperty(Node value) {
exists(Node type |
valueHasType(value, type) and
typeHasProperty(type)
)
}
}
private predicate isSanitizingPrimitiveTypeBase(Node node) {
node.(TypeExpr).isNumbery()
or
node.(TypeExpr).isBooleany()
or
node.(TypeExpr).isNull()
or
node.(TypeExpr).isUndefined()
or
node.(TypeExpr).isVoid()
or
node.(TypeExpr).isNever()
or
node instanceof LiteralTypeExpr
or
node = any(EnumMember m).getIdentifier() // enum members are constant
or
node instanceof EnumDeclaration // enums are unions of constants
}
/**
* Holds if `node` refers to a type that is considered untaintable (if actually enforced at runtime).
*
* Specifically, the types `number`, `boolean`, `null`, `undefined`, `void`, `never`, as well as literal types (`"foo"`)
* and enums and enum members have this property.
*/
predicate isSanitizingPrimitiveType =
TrackMustProp<isSanitizingPrimitiveTypeBase/1>::hasProperty/1;
/**
* Holds if `value` has a type that is considered untaintable (if actually enforced at runtime).
*
* See `isSanitizingPrimitiveType`.
*/
predicate valueHasSanitizingPrimitiveType =
ValueHasProperty<isSanitizingPrimitiveType/1>::valueHasProperty/1;
private predicate isPromiseBase(Node node) { exists(unwrapPromiseType(node)) }
/**
* Holds if the given type is a Promise object. Does not hold for unions unless all parts of the union are promises.
*/
predicate isPromiseType = TrackMustProp<isPromiseBase/1>::hasProperty/1;
/**
* Holds if the given value has a type that implied it is a Promise object. Does not hold for unions unless all parts of the union are promises.
*/
predicate valueHasPromiseType = ValueHasProperty<isPromiseType/1>::valueHasProperty/1;
/**
* Holds if `type` contains `string` or `any`, possibly wrapped in a promise.
*/
predicate hasUnderlyingStringOrAnyType(Node type) {
type.(TypeAnnotation).isStringy()
or
type.(TypeAnnotation).isAny()
or
type instanceof StringLiteralTypeExpr
or
type instanceof TemplateLiteralTypeExpr
or
exists(Node mid | hasUnderlyingStringOrAnyType(mid) |
TypeFlow::step(mid, type)
or
UnderlyingTypes::underlyingTypeStep(mid, type)
or
type = unwrapPromiseType(mid)
)
}
}

View File

@@ -0,0 +1,128 @@
/**
* Provides name resolution and propagates type information.
*/
private import javascript
private import semmle.javascript.internal.NameResolution::NameResolution
/**
* Provides name resolution and propagates type information.
*/
module UnderlyingTypes {
private predicate subtypeStep(Node node1, Node node2) {
exists(ClassOrInterface cls |
(
node1 = cls.getSuperClass() or
node1 = cls.getASuperInterface()
) and
node2 = cls
)
}
predicate underlyingTypeStep(Node node1, Node node2) {
exists(UnionOrIntersectionTypeExpr type |
node1 = type.getAnElementType() and
node2 = type
)
or
exists(ReadonlyTypeExpr type |
node1 = type.getElementType() and
node2 = type
)
or
exists(OptionalTypeExpr type |
node1 = type.getElementType() and
node2 = type
)
or
exists(GenericTypeExpr type |
node1 = type.getTypeAccess() and
node2 = type
)
or
exists(ExpressionWithTypeArguments e |
node1 = e.getExpression() and
node2 = e
)
or
exists(JSDocUnionTypeExpr type |
node1 = type.getAnAlternative() and
node2 = type
)
or
exists(JSDocNonNullableTypeExpr type |
node1 = type.getTypeExpr() and
node2 = type
)
or
exists(JSDocNullableTypeExpr type |
node1 = type.getTypeExpr() and
node2 = type
)
or
exists(JSDocAppliedTypeExpr type |
node1 = type.getHead() and
node2 = type
)
or
exists(JSDocOptionalParameterTypeExpr type |
node1 = type.getUnderlyingType() and
node2 = type
)
}
predicate nodeHasUnderlyingType(Node node, string mod, string name) {
nodeRefersToModule(node, mod, name)
or
exists(JSDocLocalTypeAccess type |
node = type and
not exists(type.getALexicalName()) and
not type = any(JSDocQualifiedTypeAccess t).getBase() and
name = type.getName() and
mod = "global"
)
or
exists(LocalTypeAccess type |
node = type and
not exists(type.getLocalTypeName()) and
name = type.getName() and
mod = "global"
)
or
exists(Node mid | nodeHasUnderlyingType(mid, mod, name) |
TypeFlow::step(mid, node)
or
underlyingTypeStep(mid, node)
or
subtypeStep(mid, node)
)
}
pragma[nomagic]
predicate nodeHasUnderlyingType(Node node, string name) {
nodeHasUnderlyingType(node, "global", name)
}
predicate nodeHasUnderlyingClassType(Node node, DataFlow::ClassNode cls) {
node = cls.getAstNode()
or
exists(string name |
classHasGlobalName(cls, name) and
nodeHasUnderlyingType(node, name)
)
or
exists(Node mid | nodeHasUnderlyingClassType(mid, cls) |
TypeFlow::step(mid, node)
or
underlyingTypeStep(mid, node)
// Note: unlike for external types, we do not use subtype steps here.
// The caller is responsible for handling the class hierarchy.
)
}
pragma[nomagic]
private predicate classHasGlobalName(DataFlow::ClassNode cls, string name) {
cls.flowsTo(AccessPath::getAnAssignmentTo(name)) and
not cls.getTopLevel().isExterns() // don't propagate externs classes
}
}

View File

@@ -0,0 +1,143 @@
private import javascript
signature module ResolverSig {
class FileNode {
FileNode getChild(string name);
FileNode getParent();
}
/**
* Holds if `path` should be resolved to a file or folder, relative to `base`.
*/
predicate shouldResolve(FileNode base, string path);
}
/**
* Provides a mechanism for resolving relative file paths.
*
* Absolute paths are not handled.
*/
module Resolver<ResolverSig S> {
private import S
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 FileNode resolve(FileNode base, string path, int n) {
shouldResolve(base, path) and n = 0 and result = base
or
exists(FileNode cur, string segment |
cur = resolve(base, path, n - 1) and
segment = getPathSegment(path, n - 1)
|
result = cur.getChild(segment)
or
segment = [".", ""] and
result = cur
or
segment = ".." and
result = cur.getParent()
)
}
/**
* 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.
*/
FileNode resolve(FileNode base, string path) {
result = resolve(base, path, getNumPathSegment(path))
}
}
class ContainerAsFileNode instanceof Container {
string toString() { result = super.toString() }
Location getLocation() { result = this.(File).getLocation() }
ContainerAsFileNode getChild(string name) { result = this.(Folder).getChildContainer(name) }
ContainerAsFileNode getParent() { result = super.getParentContainer() }
}
signature module VirtualFileSystemSig {
Container getAnAdditionalChild(Container base, string path);
}
module VirtualFileSystem<VirtualFileSystemSig S> {
private import S
private string getPathSegment(string path, int n) {
exists(getAnAdditionalChild(_, path)) and
result = path.replaceAll("\\", "/").splitAt("/", n)
}
private int getNumPathSegment(string path) {
result = strictcount(int n | exists(getPathSegment(path, n)))
}
private newtype TFileNode =
MkContainer(Container container) or
MkVirtualChild(Container base, string path, int n) {
exists(getAnAdditionalChild(base, path)) and
n in [1 .. getNumPathSegment(path) - 1] // any interior point in the path (not beginning or end)
}
class FileNode extends TFileNode {
Container asContainer() { this = MkContainer(result) }
Container getNearestContainer() {
result = this.asContainer() or this = MkVirtualChild(result, _, _)
}
/** Gets the name of this virtual folder */
private string getVirtualChildName() {
exists(string path, int n |
this = MkVirtualChild(_, path, n) and
result = getPathSegment(path, n - 1)
)
}
/** Gets the parent of this virtual folder */
private FileNode getVirtualParent() {
exists(Container base, string path, int n | this = MkVirtualChild(base, path, n) |
n = 0 and
result.asContainer() = base
or
result = MkVirtualChild(base, path, n - 1)
)
}
FileNode getParent() {
result.asContainer() = this.asContainer().getParentContainer()
or
result = this.getVirtualParent()
}
FileNode getChild(string name) {
result.asContainer() = this.asContainer().(Folder).getChildContainer(name)
or
result.getVirtualChildName() = name and
result.getParent() = this
}
string toString() {
result = this.asContainer().toString()
or
exists(Container base, string path, int n |
this = MkVirtualChild(base, path, n) and
result = base + "[" + path + "]n=" + n
)
}
Location getLocation() { result = this.asContainer().(File).getLocation() }
}
}

View File

@@ -0,0 +1,86 @@
private import javascript
private import semmle.javascript.internal.paths.PathResolver
private import semmle.javascript.internal.paths.PathExprResolver
/**
* Extension of `PackageJson` with some internal path-resolution predicates.
*/
class PackageJsonEx extends PackageJson {
private JsonValue getAPartOfExportsSection(string pattern) {
result = this.getPropValue("exports") and
pattern = ""
or
exists(string prop, string prevPath |
result = this.getAPartOfExportsSection(prevPath).getPropValue(prop) and
if prop.matches("./%") then pattern = prop.suffix(2) else pattern = prevPath
)
}
predicate hasPathMapping(string pattern, string newPath) {
this.getAPartOfExportsSection(pattern).getStringValue() = newPath
}
predicate hasExactPathMapping(string pattern, string newPath) {
this.getAPartOfExportsSection(pattern).getStringValue() = newPath and
not pattern.matches("%*%")
}
predicate hasPrefixPathMapping(string pattern, string newPath) {
this.hasPathMapping(pattern + "*", newPath + "*")
}
predicate hasExactPathMappingTo(string pattern, Container target) {
exists(string newPath |
this.hasExactPathMapping(pattern, newPath) and
target = Resolver::resolve(this.getFolder(), newPath)
)
}
predicate hasPrefixPathMappingTo(string pattern, Container target) {
exists(string newPath |
this.hasPrefixPathMapping(pattern, newPath) and
target = Resolver::resolve(this.getFolder(), newPath)
)
}
string getMainPath() { result = this.getPropStringValue(["main", "module"]) }
File getMainFile() {
exists(Container main | main = Resolver::resolve(this.getFolder(), this.getMainPath()) |
result = main
or
result = main.(Folder).getJavaScriptFileOrTypings("index")
)
}
string getAPathInFilesArray() {
result = this.getPropValue("files").(JsonArray).getElementStringValue(_)
}
Container getAFileInFilesArray() {
result = Resolver::resolve(this.getFolder(), this.getAPathInFilesArray())
}
}
private module ResolverConfig implements PathResolverSig {
additional predicate shouldResolve(PackageJsonEx pkg, Container base, string path) {
base = pkg.getFolder() and
(
pkg.hasExactPathMapping(_, path)
or
pkg.hasPrefixPathMapping(_, path)
or
path = pkg.getMainPath()
or
path = pkg.getAPathInFilesArray()
)
}
predicate shouldResolve(Container base, string path) { shouldResolve(_, base, path) }
predicate getAnAdditionalChild = JSPaths::getAnAdditionalChild/2;
predicate isOptionalPathComponent(string segment) { segment = ["cjs", "mjs", "js"] }
}
private module Resolver = PathResolver<ResolverConfig>;

View File

@@ -0,0 +1,311 @@
private import javascript
private import semmle.javascript.internal.paths.PathResolver
private import semmle.javascript.internal.paths.TSConfig
private import semmle.javascript.internal.paths.PackageJsonEx
final private class FinalPathExpr = PathExpr;
private class RelevantPathExpr extends FinalPathExpr {
pragma[nomagic]
RelevantPathExpr() { this = any(Import imprt).getImportedPath() }
}
/**
* 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())
)
}
/**
* Gets the TSConfig file relevant for resolving `expr`.
*/
pragma[nomagic]
private TSConfig getTSConfigFromPathExpr(RelevantPathExpr expr) {
result.getAnIncludedContainer() = expr.getFile()
or
result = getFallbackTSConfig(expr.getFile())
}
/**
* Holds if `path` is relative, in the sense that it should be resolved relative to its enclosing folder.
*/
bindingset[path]
pragma[inline_late]
predicate isRelativePath(string path) { path.regexpMatch("\\.\\.?(?:[/\\\\].*)?") }
/**
* Gets the NPM package name from the beginning of the given import path, e.g.
* gets `foo` from `foo/bar`, and `@example/foo` from `@example/foo/bar`.
*/
pragma[nomagic]
private string getPackagePrefixFromPathExpr(RelevantPathExpr expr) {
result = expr.getValue().regexpFind("^(@[^/\\\\]+[/\\\\])?[^@./\\\\][^/\\\\]*", _, _)
}
/**
* Gets a folder name that is a common source folder name.
*/
string getASrcFolderName() { result = ["ts", "js", "src", "lib"] }
/**
* Gets a folder name that is a common build output folder name.
*/
string getABuildOutputFolderName() { result = ["dist", "build", "out", "lib"] }
module JSPaths {
/**
* Gets an additional child of `base` to include when resolving JS paths.
*
* This accounts for two things:
* - automatic file extensions (e.g `./foo` may resolve to `./foo.js`)
* - mapping compiled-generated files back to their original source files
*/
Container getAnAdditionalChild(Container base, string name) {
// Automatically fill in file extensions
result = base.(Folder).getJavaScriptFileOrTypings(name)
or
// When importing a .js file, map to the original file that compiles to the .js file.
exists(string stem |
result = base.(Folder).getJavaScriptFileOrTypings(stem) and
name = stem + ".js"
)
or
// Redirect './bar' to 'foo' given a tsconfig like:
//
// { include: ["foo"], compilerOptions: { outDir: "./bar" }}
//
exists(TSConfig tsconfig, Container includeTarget |
name =
tsconfig.getCompilerOptions().getPropStringValue("outDir").regexpReplaceAll("^\\./", "") and
base = tsconfig.getFolder() and
includeTarget = tsconfig.getAnIncludePathTarget()
|
result = includeTarget.(Folder)
or
result = includeTarget.(File).getParentContainer()
)
or
// Heuristic version of the above based on commonly used source and build folder names
exists(Folder folder | base = folder |
folder = any(PackageJson pkg).getFolder() and
name = getABuildOutputFolderName() and
not exists(folder.getChildContainer(name)) and
(
result = folder.getChildContainer(getASrcFolderName())
or
result = folder
)
)
}
}
private Variable dirname() { result.getName() = "__dirname" }
/** Holds if `add` is a relevant path expression of form `__dirname + expr`. */
private predicate prefixedByDirname(PathExpr expr) {
expr = dirname().getAnAccess()
or
prefixedByDirname(expr.(AddExpr).getLeftOperand())
or
prefixedByDirname(expr.(CallExpr).getArgument(0))
}
/**
* Holds if `expr` matches a path mapping, and should thus be resolved as `newPath` relative to `base`.
*/
pragma[nomagic]
private predicate resolveViaPathMapping(RelevantPathExpr expr, Container base, string newPath) {
// Handle tsconfig mappings such as `{ "paths": { "@/*": "./src/*" }}`
exists(TSConfig config, string value |
config = getTSConfigFromPathExpr(expr).getExtendedTSConfig*() and
value = expr.getValue() and
base = config.getBaseUrlFolderOrOwnFolder()
|
config.hasExactPathMapping(value, newPath)
or
exists(string pattern, string suffix, string mappedPath |
config.hasPrefixPathMapping(pattern, 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 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 = getPackagePrefixFromPathExpr(expr) 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(RelevantPathExpr expr, Container base, string path) {
expr instanceof RelevantPathExpr and
path = expr.getValue() and
isRelativePath(path) and
exists(File file |
file = expr.getFile() and
base = file.getParentContainer()
)
}
/**
* Holds if `expr` should be resolved as `path` relative to `base`.
*/
pragma[nomagic]
private predicate shouldResolve(RelevantPathExpr expr, Container base, string path) {
// Relative paths are resolved from their enclosing folder
relativePathExpr(expr, base, path)
or
// Paths prefixed by __dirname should be resolved from the root dir, because __dirname
// currently has a getValue() that returns its absolute path.
prefixedByDirname(expr) and
not exists(base.getParentContainer()) and // get root dir
path = expr.getValue()
or
resolveViaPathMapping(expr, base, path)
or
// Resolve from baseUrl of relevant tsconfig.json file
path = expr.getValue() and
not isRelativePath(path) and
base = getTSConfigFromPathExpr(expr).getBaseUrlFolder()
or
// If the path starts with the name of a package, but did not match any path mapping,
// resolve relative to the enclosing 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 = getPackagePrefixFromPathExpr(expr) and
pkg.getDeclaredPackageName() = packageName and
path = expr.getValue().suffix(packageName.length()).regexpReplaceAll("^[/\\\\]", "") and
base = pkg.getFolder()
)
}
private module ResolverConfig implements PathResolverSig {
predicate shouldResolve(Container base, string path) { shouldResolve(_, base, path) }
predicate getAnAdditionalChild = JSPaths::getAnAdditionalChild/2;
}
private module Resolver = PathResolver<ResolverConfig>;
private Container resolvePathExpr1(RelevantPathExpr expr) {
exists(Container base, string path |
shouldResolve(expr, base, path) and
result = Resolver::resolve(base, path)
)
}
/**
* Removes the scope from a package name, e.g. `@foo/bar` -> `bar`.
*/
bindingset[name]
private string stripPackageScope(string name) { result = name.regexpReplaceAll("^@[^/]+/", "") }
private File guessPackageJsonMain1(PackageJsonEx pkg) {
not exists(pkg.getMainFile()) and
exists(Folder folder, Folder subfolder |
folder = pkg.getFolder() and
(
subfolder = folder or
subfolder = folder.getChildContainer(getASrcFolderName()) or
subfolder =
folder
.getChildContainer(getASrcFolderName())
.(Folder)
.getChildContainer(getASrcFolderName())
)
|
result = subfolder.getJavaScriptFileOrTypings("index")
or
result = subfolder.getJavaScriptFileOrTypings(stripPackageScope(pkg.getDeclaredPackageName()))
)
}
private File guessPackageJsonMain2(PackageJsonEx pkg) {
not exists(pkg.getMainFile()) and
not exists(guessPackageJsonMain1(pkg)) and
result = pkg.getAFileInFilesArray()
}
private 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 |
result = pkg.getMainFile()
or
result = guessPackageJsonMain1(pkg)
or
result = guessPackageJsonMain2(pkg)
)
}
File resolvePathExpr(PathExpr expr) {
result = resolvePathExpr1(expr)
or
result = getFileFromFolderImport(resolvePathExpr1(expr))
}
module Debug {
class PathExprToDebug extends RelevantPathExpr {
PathExprToDebug() { this.getValue() = "vs/nls" }
}
query PathExprToDebug pathExprs() { any() }
query TSConfig getTSConfigFromPathExpr_(PathExprToDebug expr) {
result = getTSConfigFromPathExpr(expr)
}
query string getPackagePrefixFromPathExpr_(PathExprToDebug expr) {
result = getPackagePrefixFromPathExpr(expr)
}
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 resolvePathExpr_(PathExprToDebug expr) { result = resolvePathExpr(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;
}

View File

@@ -0,0 +1,121 @@
private import javascript
/**
* Signature for modules to pass to `PathResolver`.
*/
signature module PathResolverSig {
/**
* Holds if `path` should be resolved to a file or folder, relative to `base`.
*/
predicate shouldResolve(Container base, string path);
/**
* Gets an additional file or folder to consider a child of `base`.
*/
default Container getAnAdditionalChild(Container base, string name) { none() }
/**
* Holds if `component` may be treated as `.` if it does not match a child.
*/
default predicate isOptionalPathComponent(string component) { none() }
/**
* Holds if globs should be interpreted in the paths being resolved.
*
* The following types of globs are supported:
* - `*` (matches any child)
* - `**` (matches any child recursively)
* - Complex patterns like `foo-*.txt` are also supported
*/
default predicate allowGlobs() { none() }
}
/**
* Provides a mechanism for resolving file paths relative to a given directory.
*/
module PathResolver<PathResolverSig Config> {
private import Config
pragma[nomagic]
private string getPathSegment(string path, int n) {
shouldResolve(_, path) and
result = path.replaceAll("\\", "/").splitAt("/", n)
}
pragma[nomagic]
private string getPathSegmentAsGlobRegexp(string segment) {
allowGlobs() and
segment = getPathSegment(_, _) and
segment.matches("%*%") and
not segment = ["*", "**"] and // these are special-cased
result = segment.regexpReplaceAll("[^a-zA-Z0-9*]", "\\\\$0").replaceAll("*", ".*")
}
pragma[nomagic]
private int getNumPathSegment(string path) {
result = strictcount(int n | exists(getPathSegment(path, n)))
}
private Container getChild(Container base, string name) {
result = base.(Folder).getChildContainer(name)
or
result = getAnAdditionalChild(base, name)
}
pragma[nomagic]
private Container resolve(Container base, string path, int n) {
shouldResolve(base, path) and n = 0 and result = base
or
exists(Container current, string segment |
current = resolve(base, path, n - 1) and
segment = getPathSegment(path, n - 1)
|
result = getChild(current, segment)
or
segment = [".", ""] and
result = current
or
segment = ".." and
result = current.getParentContainer()
or
isOptionalPathComponent(segment) and
not exists(getChild(current, segment)) and
result = current
or
allowGlobs() and
(
segment = "*" and
result = getChild(current, _)
or
segment = "**" and // allow empty match
result = current
or
exists(string name |
result = getChild(current, name) and
name.regexpMatch(getPathSegmentAsGlobRegexp(segment))
)
)
)
or
exists(Container current, string segment |
current = resolve(base, path, n) and
segment = getPathSegment(path, n)
|
// Follow child without advancing 'n'
allowGlobs() and
segment = "**" and
result = getChild(current, _)
)
}
/**
* 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.
*/
pragma[nomagic]
Container resolve(Container base, string path) {
result = resolve(base, path, getNumPathSegment(path))
}
}

View File

@@ -0,0 +1,109 @@
private import javascript
private import semmle.javascript.internal.paths.PathExprResolver
private import semmle.javascript.internal.paths.PathResolver
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()
}
private JsonObject getPathMappings() { result = this.getCompilerOptions().getPropValue("paths") }
predicate hasPathMapping(string pattern, string newPath) {
this.getPathMappings().getPropStringValue(pattern) = newPath
or
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 + "*")
}
}
/** For resolving paths in a tsconfig file, except `paths` mappings. */
private module ResolverConfig implements PathResolverSig {
predicate shouldResolve(Container base, string path) {
exists(TSConfig cfg |
base = cfg.getFolder() and
path = [cfg.getExtendsPath(), cfg.getBaseUrlPath(), cfg.getAnIncludePath()]
)
}
predicate allowGlobs() { any() } // "include" can use globs
}
private module Resolver = PathResolver<ResolverConfig>;

View File

@@ -9,6 +9,7 @@
*/
import javascript
private import semmle.javascript.internal.TypeResolution
/**
* Holds if `call` is a call to an `async` function.
@@ -27,7 +28,7 @@ predicate isPromise(DataFlow::SourceNode node, boolean nullable) {
isAsyncCall(node, nullable)
or
not isAsyncCall(node, _) and
node.asExpr().getType() instanceof PromiseType and
TypeResolution::valueHasPromiseType(node.asExpr()) and
nullable = true
}

View File

@@ -12,7 +12,10 @@ import javascript
from DataFlow::Node invoke, Function f, string kind
where
invoke.(DataFlow::InvokeNode).getACallee() = f and kind = "Call"
or
invoke.(DataFlow::PropRef).getAnAccessorCallee().getFunction() = f and kind = "Accessor call"
(
invoke.(DataFlow::InvokeNode).getACallee() = f and kind = "Call"
or
invoke.(DataFlow::PropRef).getAnAccessorCallee().getFunction() = f and kind = "Accessor call"
) and
not f.getTopLevel().isExterns()
select invoke, kind + " to $@", f, f.describe()

View File

@@ -0,0 +1,15 @@
/**
* @name Nodes with underlying type
* @description Nodes that has an underlying type coming from a package.
* @kind problem
* @problem.severity recommendation
* @id js/meta/alerts/nodes-with-underlying-type
* @tags meta-disabled
* @precision very-low
*/
import javascript
from DataFlow::SourceNode sn, string mod, string name
where sn.hasUnderlyingType(mod, name)
select sn, "'" + mod + "'." + name

View File

@@ -1,10 +1,10 @@
| bar.js:5:14:5:14 | x | x |
| bar.js:5:14:5:14 | x | ns.very.long.namespace |
| bar.js:5:14:5:18 | x.Foo | ns.very.long.namespace.Foo |
| bar.js:12:14:12:17 | iife | iife |
| bar.js:12:14:12:17 | iife | IIFE |
| bar.js:12:14:12:21 | iife.Foo | IIFE.Foo |
| closure.js:8:12:8:15 | goog | goog |
| closure.js:8:12:8:19 | goog.net | goog.net |
| closure.js:8:12:8:28 | goog.net.SomeType | goog.net.SomeType |
| closure.js:9:12:9:14 | net | net |
| closure.js:9:12:9:14 | net | goog.net |
| closure.js:9:12:9:23 | net.SomeType | goog.net.SomeType |
| closure.js:10:12:10:19 | SomeType | goog.net.SomeType |

View File

@@ -1,3 +1,3 @@
import javascript
query string test_hasQualifiedName(JSDocNamedTypeExpr expr) { expr.hasQualifiedName(result) }
query string test_hasUnderlyingType(JSDocNamedTypeExpr expr) { expr.hasUnderlyingType(result) }

View File

@@ -0,0 +1 @@
export const x = "file.ts"

View File

@@ -0,0 +1 @@
export const x = "index.ts";

View File

@@ -0,0 +1 @@
export const x = "nostar.ts"

View File

@@ -0,0 +1 @@
export const x = 1;

View File

@@ -0,0 +1,47 @@
// Relative import
import "../base/lib/file"; // $ importTarget=BaseUrl/base/lib/file.ts
import "../base/lib/file.ts"; // $ importTarget=BaseUrl/base/lib/file.ts
import "../base/lib/file.js"; // $ importTarget=BaseUrl/base/lib/file.ts
import "../base/lib"; // $ importTarget=BaseUrl/base/lib/index.ts
import "../base/lib/index"; // $ importTarget=BaseUrl/base/lib/index.ts
import "../base/lib/index.ts"; // $ importTarget=BaseUrl/base/lib/index.ts
import "../base/lib/index.js"; // $ importTarget=BaseUrl/base/lib/index.ts
// Import relative to baseUrl
import "lib/file"; // $ importTarget=BaseUrl/base/lib/file.ts
import "lib/file.ts"; // $ importTarget=BaseUrl/base/lib/file.ts
import "lib/file.js"; // $ importTarget=BaseUrl/base/lib/file.ts
import "lib"; // $ importTarget=BaseUrl/base/lib/index.ts
import "lib/index"; // $ importTarget=BaseUrl/base/lib/index.ts
import "lib/index.ts"; // $ importTarget=BaseUrl/base/lib/index.ts
import "lib/index.js"; // $ importTarget=BaseUrl/base/lib/index.ts
// Import matching "@/*" path mapping
import "@/file"; // $ importTarget=BaseUrl/base/lib/file.ts
import "@/file.ts"; // $ importTarget=BaseUrl/base/lib/file.ts
import "@/file.js"; // $ importTarget=BaseUrl/base/lib/file.ts
import "@"; // $ importTarget=BaseUrl/base/lib/nostar.ts
import "@/index"; // $ importTarget=BaseUrl/base/lib/index.ts
import "@/index.ts"; // $ importTarget=BaseUrl/base/lib/index.ts
import "@/index.js"; // $ importTarget=BaseUrl/base/lib/index.ts
// Import matching "#/*" path mapping
import "#/file"; // $ importTarget=BaseUrl/base/lib/file.ts
import "#/file.ts"; // $ importTarget=BaseUrl/base/lib/file.ts
import "#/file.js"; // $ importTarget=BaseUrl/base/lib/file.ts
import "#/index"; // $ importTarget=BaseUrl/base/lib/index.ts
import "#/index.ts"; // $ importTarget=BaseUrl/base/lib/index.ts
import "#/index.js"; // $ importTarget=BaseUrl/base/lib/index.ts
// Import matching "^lib*" path mapping
import "^lib/file"; // $ importTarget=BaseUrl/base/lib/file.ts
import "^lib2/file"; // $ importTarget=BaseUrl/base/lib2/file.ts
// Import matching "@/*.xyz" path mapping. Note that this is not actually supported by TypeScript.
import "@/file.xyz";
import "@/file.ts.xyz";
import "@/file.js.xyz";
import "@.xyz";
import "@/index.xyz";
import "@/index.ts.xyz";
import "@/index.js.xyz";

View File

@@ -0,0 +1,14 @@
{
"include": ["."],
"compilerOptions": {
// Path mappings are resolved relative to baseUrl
"baseUrl": "./base",
"paths": {
"@/*": ["lib/*"],
"#/*": ["./lib/*"], // relative paths here are also resolved from the base url
"^lib*": ["./lib*"], // must end with "*" but not necessarily "/*"
"@/*.xyz": ["lib/*"],
"@": ["lib/nostar.ts"]
}
}
}

View File

@@ -0,0 +1,4 @@
import '..'; // $ importTarget=Basic/index.ts
import '../'; // $ importTarget=Basic/index.ts
import './..'; // $ importTarget=Basic/index.ts
import './../'; // $ importTarget=Basic/index.ts

View File

@@ -0,0 +1 @@
import './Subdir/sub'; // $ importTarget=Basic/Subdir/sub.ts

View File

@@ -0,0 +1,5 @@
// Type declaration for dual-module
export interface DualType {
prop: string;
}
export declare function helper(): DualType;

View File

@@ -0,0 +1,4 @@
// Implementation of dual-module
export function helper() {
return { prop: "implementation" };
}

View File

@@ -0,0 +1 @@
export const x: number;

View File

@@ -0,0 +1 @@
export const x = 1;

View File

@@ -0,0 +1 @@
export const x = 1;

View File

@@ -0,0 +1,7 @@
import "../lib/split"; // $ importTarget=DeclarationFiles/lib/split.d.ts importTarget=DeclarationFiles/lib/split.js
import "../lib/split.js"; // $ importTarget=DeclarationFiles/lib/split.js importTarget=DeclarationFiles/lib/split.d.ts
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

View File

@@ -0,0 +1,21 @@
// Test for imports using __dirname
const path = require('path');
// Using __dirname directly
const direct = require(__dirname + '/target.js'); // $ importTarget=DirnameImports/target.js
// Using __dirname with path.join
const withPathJoin = require(path.join(__dirname, 'target.js')); // $ importTarget=DirnameImports/target.js
// Using __dirname with nested path
const nested = require(__dirname + '/nested/target.js'); // $ importTarget=DirnameImports/nested/target.js
// Using __dirname with parent directory
const parent = require(__dirname + '/../import-packages.ts'); // $ importTarget=import-packages.ts
// Using __dirname with path concat and variable
const subdir = 'nested';
const dynamic = require(__dirname + '/' + subdir + '/target.js'); // $ importTarget=DirnameImports/nested/target.js
// Using __dirname in an AddExpr chain
const chainedAdd = require(__dirname + '/' + 'target.js'); // $ importTarget=DirnameImports/target.js

View File

@@ -0,0 +1,2 @@
// Nested target file for __dirname imports
module.exports = { name: 'nested-target' };

View File

@@ -0,0 +1,2 @@
// Target file for __dirname imports
module.exports = { name: 'target' };

View File

@@ -0,0 +1,3 @@
{
"main": "./dist/src/foo.js"
}

View File

@@ -0,0 +1 @@
export const x = 1;

View File

@@ -0,0 +1,11 @@
{
"compilerOptions": {
// The baseUrl is interpreted relative to this file, not the "root" tsconfig.json file extending it.
"baseUrl": "./../",
"paths": {
"@/*": ["lib/*"],
"@/*.xyz": ["lib/*"],
"@": ["lib/nostar.ts"]
}
}
}

View File

@@ -0,0 +1 @@
export const x = "file.ts"

View File

@@ -0,0 +1 @@
export const x = "index.ts";

View File

@@ -0,0 +1 @@
export const x = "nostar.ts"

View File

@@ -0,0 +1,35 @@
// Relative import
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 relative to baseUrl
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"; // $ importTarget=Extended/lib/file.ts
import "@/file.ts"; // $ importTarget=Extended/lib/file.ts
import "@/file.js"; // $ importTarget=Extended/lib/file.ts
import "@"; // $ importTarget=Extended/lib/nostar.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";
import "@/file.ts.xyz";
import "@/file.js.xyz";
import "@.xyz";
import "@/index.xyz";
import "@/index.ts.xyz";
import "@/index.js.xyz";

View File

@@ -0,0 +1,4 @@
{
"include": ["."],
"extends": "./base/tsconfig.base.json"
}

View File

@@ -0,0 +1 @@
export const x = 1;

View File

@@ -0,0 +1 @@
export const x = 1;

View File

@@ -0,0 +1 @@
export const x = 1;

View File

@@ -0,0 +1 @@
export const x = 1;

View File

@@ -0,0 +1 @@
export const x = 1;

View File

@@ -0,0 +1 @@
export const x = 1;

View File

@@ -0,0 +1 @@
export const x = 1;

View File

@@ -0,0 +1 @@
export const x = 1;

View File

@@ -0,0 +1 @@
export const x = 1;

View File

@@ -0,0 +1 @@
export const x = 1;

View File

@@ -0,0 +1 @@
export const x = 1;

View File

@@ -0,0 +1 @@
export const x = 1;

View File

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

View File

@@ -0,0 +1,8 @@
{
"include": ["."],
"compilerOptions": {
"paths": {
"@/*": ["./lib1/*", "./lib2/*"],
}
}
}

View File

@@ -0,0 +1,4 @@
{
"name": "@example/main-is-folder",
"main": "dist"
}

View File

@@ -0,0 +1 @@
export const x = 1;

View File

@@ -0,0 +1 @@
export const x = "file.ts"

View File

@@ -0,0 +1 @@
export const x = "index.ts";

View File

@@ -0,0 +1 @@
export const x = "nostar.ts"

View File

@@ -0,0 +1,35 @@
// Relative import
import "../lib/file"; // $ importTarget=NoBaseUrl/lib/file.ts
import "../lib/file.ts"; // $ importTarget=NoBaseUrl/lib/file.ts
import "../lib/file.js"; // $ importTarget=NoBaseUrl/lib/file.ts
import "../lib"; // $ importTarget=NoBaseUrl/lib/index.ts
import "../lib/index"; // $ importTarget=NoBaseUrl/lib/index.ts
import "../lib/index.ts"; // $ importTarget=NoBaseUrl/lib/index.ts
import "../lib/index.js"; // $ importTarget=NoBaseUrl/lib/index.ts
// Import unresolvable due to missing baseUrl
import "lib/file";
import "lib/file.ts";
import "lib/file.js";
import "lib";
import "lib/index";
import "lib/index.ts";
import "lib/index.js";
// Import matching "@/*" path mapping
import "@/file"; // $ importTarget=NoBaseUrl/lib/file.ts
import "@/file.ts"; // $ importTarget=NoBaseUrl/lib/file.ts
import "@/file.js"; // $ importTarget=NoBaseUrl/lib/file.ts
import "@"; // $ importTarget=NoBaseUrl/lib/nostar.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";
import "@/file.ts.xyz";
import "@/file.js.xyz";
import "@.xyz";
import "@/index.xyz";
import "@/index.ts.xyz";
import "@/index.js.xyz";

View File

@@ -0,0 +1,10 @@
{
"include": ["."],
"compilerOptions": {
"paths": {
"@/*": ["./lib/*"],
"@/*.xyz": ["./lib/*"],
"@": ["./lib/nostar.ts"]
}
}
}

View File

@@ -0,0 +1,3 @@
{
"name": "@example/package-guess1"
}

View File

@@ -0,0 +1 @@
export const x = 1;

View File

@@ -0,0 +1 @@
export const x = 1;

View File

@@ -0,0 +1,6 @@
{
"name": "@example/package-guess2",
"files": [
"./blah/stuff.js"
]
}

View File

@@ -0,0 +1 @@
export const x = 1;

View File

@@ -0,0 +1,3 @@
{
"name": "@example/package-with-index-file"
}

View File

@@ -0,0 +1 @@
export const x = 1;

View File

@@ -0,0 +1 @@
export const x = 1;

View File

@@ -0,0 +1,14 @@
{
"name": "@example/package-with-exports",
"exports": {
".": {
"default": "./main.js"
},
"./fake-file": {
"default": "./fake-file-impl.js"
},
"./star/*": {
"default": "./star-impl/*"
}
}
}

View File

@@ -0,0 +1 @@
export const x = 1;

View File

@@ -0,0 +1 @@
export const x = 1;

View File

@@ -0,0 +1,4 @@
{
"name": "@example/package-with-main",
"main": "./main.js"
}

View File

@@ -0,0 +1 @@
export const x = 1;

View File

@@ -0,0 +1,4 @@
{
"name": "@example/package-with-main-ext",
"main": "./lib/main.js"
}

View File

@@ -0,0 +1 @@
export const x = 1;

View File

@@ -0,0 +1,4 @@
{
"name": "@example/package-with-module-main",
"module": "./main.js"
}

View File

@@ -0,0 +1 @@
export const x = 1;

View File

@@ -0,0 +1,7 @@
{
"include": ["src"],
"compilerOptions": {
// Use unusual name to test independently of naming heuristics
"outDir": "./customOutDir"
}
}

View File

@@ -0,0 +1,31 @@
import './PackageWithMain/main'; // $ importTarget=PackageWithMain/main.js
import '@example/package-with-main'; // $ importTarget=PackageWithMain/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'; // $ 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'; // $ importTarget=PackageGuess1/src/index.ts
import '@example/package-guess1'; // $ importTarget=PackageGuess1/src/index.ts
import './PackageGuess2'; // $ importTarget=PackageGuess2/blah/stuff.ts
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
import './MainIsFolder'; // $ importTarget=MainIsFolder/src/index.ts
import '@example/main-is-folder'; // $ importTarget=MainIsFolder/src/index.ts
import './DistContainsSrc'; // $ importTarget=DistContainsSrc/src/foo.ts

View File

@@ -0,0 +1,102 @@
| BaseUrl/src/main.ts:2:1:2:26 | import ... /file"; | BaseUrl/base/lib/file.ts |
| BaseUrl/src/main.ts:3:1:3:29 | import ... le.ts"; | BaseUrl/base/lib/file.ts |
| BaseUrl/src/main.ts:4:1:4:29 | import ... le.js"; | BaseUrl/base/lib/file.ts |
| BaseUrl/src/main.ts:5:1:5:21 | import ... e/lib"; | BaseUrl/base/lib/index.ts |
| BaseUrl/src/main.ts:6:1:6:27 | import ... index"; | BaseUrl/base/lib/index.ts |
| BaseUrl/src/main.ts:7:1:7:30 | import ... ex.ts"; | BaseUrl/base/lib/index.ts |
| BaseUrl/src/main.ts:8:1:8:30 | import ... ex.js"; | BaseUrl/base/lib/index.ts |
| BaseUrl/src/main.ts:11:1:11:18 | import "lib/file"; | BaseUrl/base/lib/file.ts |
| BaseUrl/src/main.ts:12:1:12:21 | import ... le.ts"; | BaseUrl/base/lib/file.ts |
| BaseUrl/src/main.ts:13:1:13:21 | import ... le.js"; | BaseUrl/base/lib/file.ts |
| BaseUrl/src/main.ts:14:1:14:13 | import "lib"; | BaseUrl/base/lib/index.ts |
| BaseUrl/src/main.ts:15:1:15:19 | import "lib/index"; | BaseUrl/base/lib/index.ts |
| BaseUrl/src/main.ts:16:1:16:22 | import ... ex.ts"; | BaseUrl/base/lib/index.ts |
| BaseUrl/src/main.ts:17:1:17:22 | import ... ex.js"; | BaseUrl/base/lib/index.ts |
| BaseUrl/src/main.ts:20:1:20:16 | import "@/file"; | BaseUrl/base/lib/file.ts |
| BaseUrl/src/main.ts:21:1:21:19 | import "@/file.ts"; | BaseUrl/base/lib/file.ts |
| BaseUrl/src/main.ts:22:1:22:19 | import "@/file.js"; | BaseUrl/base/lib/file.ts |
| BaseUrl/src/main.ts:23:1:23:11 | import "@"; | BaseUrl/base/lib/nostar.ts |
| BaseUrl/src/main.ts:24:1:24:17 | import "@/index"; | BaseUrl/base/lib/index.ts |
| BaseUrl/src/main.ts:25:1:25:20 | import "@/index.ts"; | BaseUrl/base/lib/index.ts |
| BaseUrl/src/main.ts:26:1:26:20 | import "@/index.js"; | BaseUrl/base/lib/index.ts |
| BaseUrl/src/main.ts:29:1:29:16 | import "#/file"; | BaseUrl/base/lib/file.ts |
| BaseUrl/src/main.ts:30:1:30:19 | import "#/file.ts"; | BaseUrl/base/lib/file.ts |
| BaseUrl/src/main.ts:31:1:31:19 | import "#/file.js"; | BaseUrl/base/lib/file.ts |
| BaseUrl/src/main.ts:32:1:32:17 | import "#/index"; | BaseUrl/base/lib/index.ts |
| BaseUrl/src/main.ts:33:1:33:20 | import "#/index.ts"; | BaseUrl/base/lib/index.ts |
| BaseUrl/src/main.ts:34:1:34:20 | import "#/index.js"; | BaseUrl/base/lib/index.ts |
| BaseUrl/src/main.ts:37:1:37:19 | import "^lib/file"; | BaseUrl/base/lib/file.ts |
| BaseUrl/src/main.ts:38:1:38:20 | import "^lib2/file"; | BaseUrl/base/lib2/file.ts |
| Basic/Subdir/sub.ts:1:1:1:12 | import '..'; | Basic/index.ts |
| Basic/Subdir/sub.ts:2:1:2:13 | import '../'; | Basic/index.ts |
| Basic/Subdir/sub.ts:3:1:3:14 | import './..'; | Basic/index.ts |
| Basic/Subdir/sub.ts:4:1:4:15 | import './../'; | Basic/index.ts |
| Basic/index.ts:1:1:1:22 | import ... r/sub'; | Basic/Subdir/sub.ts |
| DeclarationFiles/src/main.ts:1:1:1:22 | import ... split"; | DeclarationFiles/lib/split.d.ts |
| DeclarationFiles/src/main.ts:1:1:1:22 | import ... split"; | DeclarationFiles/lib/split.js |
| DeclarationFiles/src/main.ts:2:1:2:25 | import ... it.js"; | DeclarationFiles/lib/split.d.ts |
| DeclarationFiles/src/main.ts:2:1:2:25 | import ... it.js"; | DeclarationFiles/lib/split.js |
| DeclarationFiles/src/main.ts:3:1:3:27 | import ... .d.ts"; | DeclarationFiles/lib/split.d.ts |
| 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 |
| DirnameImports/main.js:5:16:5:48 | require ... et.js') | DirnameImports/target.js |
| DirnameImports/main.js:8:22:8:63 | require ... t.js')) | DirnameImports/target.js |
| DirnameImports/main.js:11:16:11:55 | require ... et.js') | DirnameImports/nested/target.js |
| DirnameImports/main.js:14:16:14:60 | require ... es.ts') | import-packages.ts |
| DirnameImports/main.js:18:17:18:64 | require ... et.js') | DirnameImports/nested/target.js |
| DirnameImports/main.js:21:20:21:57 | require ... et.js') | DirnameImports/target.js |
| 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 |
| Extended/src/main.ts:5:1:5:16 | import "../lib"; | Extended/lib/index.ts |
| 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:23:1:23:11 | import "@"; | Extended/lib/nostar.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 |
| NoBaseUrl/src/main.ts:5:1:5:16 | import "../lib"; | NoBaseUrl/lib/index.ts |
| 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:23:1:23:11 | import "@"; | NoBaseUrl/lib/nostar.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 |
| 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 |

Some files were not shown because too many files have changed in this diff Show More