mirror of
https://github.com/github/codeql.git
synced 2026-07-08 13:05:36 +02:00
Compare commits
85 Commits
codeql-cli
...
js/disable
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
21e3dc7aa5 | ||
|
|
ca75ee161a | ||
|
|
81c7b4a9f1 | ||
|
|
83b6ae4007 | ||
|
|
69893db0dc | ||
|
|
38742010a7 | ||
|
|
9998bbe6a8 | ||
|
|
63eb74e0e9 | ||
|
|
4b2e1d9c13 | ||
|
|
e774c8ed6b | ||
|
|
b552e92c38 | ||
|
|
2cb430e86c | ||
|
|
36b8a14e39 | ||
|
|
fce9749b97 | ||
|
|
bcd5c686eb | ||
|
|
15b9eee644 | ||
|
|
e5ea8de78b | ||
|
|
94c9d98412 | ||
|
|
bc6720e65a | ||
|
|
b7c5b5cf61 | ||
|
|
7c2bb6098f | ||
|
|
5e0091b8f1 | ||
|
|
39980e79c5 | ||
|
|
bdf2d66d04 | ||
|
|
2f257f1b86 | ||
|
|
3fe16f09f5 | ||
|
|
41aea8828d | ||
|
|
46f1af4ea4 | ||
|
|
7957070055 | ||
|
|
d3fd5b1673 | ||
|
|
e8dc128dab | ||
|
|
7a5a021a82 | ||
|
|
d48b5d9174 | ||
|
|
2b7f2488d4 | ||
|
|
1a41aa7296 | ||
|
|
8ffea7d1fe | ||
|
|
e0b241a206 | ||
|
|
4d6f53ed9a | ||
|
|
5b0d7f5990 | ||
|
|
0a15b1b0ba | ||
|
|
848ac641a5 | ||
|
|
8d658ea27a | ||
|
|
cd03ef9f02 | ||
|
|
76ad3e6705 | ||
|
|
1f0a43340f | ||
|
|
971193ae44 | ||
|
|
920f618101 | ||
|
|
f13e8c517f | ||
|
|
a835aec81b | ||
|
|
86a882303d | ||
|
|
26a8e0b572 | ||
|
|
95ea1b25f4 | ||
|
|
c807664578 | ||
|
|
92337d701e | ||
|
|
a849d3fb2d | ||
|
|
233ed4f113 | ||
|
|
55a91ebaef | ||
|
|
190dfce9c5 | ||
|
|
ec4b3ef202 | ||
|
|
a889e981ba | ||
|
|
aa0f7d8ec3 | ||
|
|
ffe9d542f8 | ||
|
|
abfbc2e294 | ||
|
|
cada794cf1 | ||
|
|
69077ff9b6 | ||
|
|
fe06115180 | ||
|
|
05212e6042 | ||
|
|
0109ab6a70 | ||
|
|
9978657a1c | ||
|
|
6ed900504c | ||
|
|
da7387ee63 | ||
|
|
406754c646 | ||
|
|
52c729b161 | ||
|
|
21f4349cc6 | ||
|
|
a374b04f0f | ||
|
|
45ed331115 | ||
|
|
7586631934 | ||
|
|
f8be64b313 | ||
|
|
0f981b4a1e | ||
|
|
bcf26ef537 | ||
|
|
372606a93d | ||
|
|
f18335da5b | ||
|
|
4443dec443 | ||
|
|
6d58293478 | ||
|
|
ae55f2c80f |
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) }
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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() }
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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, _)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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) }
|
||||
}
|
||||
|
||||
@@ -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" }
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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", "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
468
javascript/ql/lib/semmle/javascript/internal/NameResolution.qll
Normal file
468
javascript/ql/lib/semmle/javascript/internal/NameResolution.qll
Normal 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)
|
||||
)
|
||||
}
|
||||
}
|
||||
389
javascript/ql/lib/semmle/javascript/internal/TypeResolution.qll
Normal file
389
javascript/ql/lib/semmle/javascript/internal/TypeResolution.qll
Normal 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)
|
||||
)
|
||||
}
|
||||
}
|
||||
128
javascript/ql/lib/semmle/javascript/internal/UnderlyingTypes.qll
Normal file
128
javascript/ql/lib/semmle/javascript/internal/UnderlyingTypes.qll
Normal 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
|
||||
}
|
||||
}
|
||||
143
javascript/ql/lib/semmle/javascript/internal/VirtualPaths.qll
Normal file
143
javascript/ql/lib/semmle/javascript/internal/VirtualPaths.qll
Normal 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() }
|
||||
}
|
||||
}
|
||||
@@ -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>;
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
109
javascript/ql/lib/semmle/javascript/internal/paths/TSConfig.qll
Normal file
109
javascript/ql/lib/semmle/javascript/internal/paths/TSConfig.qll
Normal 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>;
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
15
javascript/ql/src/meta/alerts/NodesWithUnderlyingType.ql
Normal file
15
javascript/ql/src/meta/alerts/NodesWithUnderlyingType.ql
Normal 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
|
||||
@@ -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 |
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
import javascript
|
||||
|
||||
query string test_hasQualifiedName(JSDocNamedTypeExpr expr) { expr.hasQualifiedName(result) }
|
||||
query string test_hasUnderlyingType(JSDocNamedTypeExpr expr) { expr.hasUnderlyingType(result) }
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export const x = "file.ts"
|
||||
@@ -0,0 +1 @@
|
||||
export const x = "index.ts";
|
||||
@@ -0,0 +1 @@
|
||||
export const x = "nostar.ts"
|
||||
@@ -0,0 +1 @@
|
||||
export const x = 1;
|
||||
@@ -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";
|
||||
@@ -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"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import '..'; // $ importTarget=Basic/index.ts
|
||||
import '../'; // $ importTarget=Basic/index.ts
|
||||
import './..'; // $ importTarget=Basic/index.ts
|
||||
import './../'; // $ importTarget=Basic/index.ts
|
||||
@@ -0,0 +1 @@
|
||||
import './Subdir/sub'; // $ importTarget=Basic/Subdir/sub.ts
|
||||
5
javascript/ql/test/library-tests/PathResolution/DeclarationFiles/lib/split.d.ts
vendored
Normal file
5
javascript/ql/test/library-tests/PathResolution/DeclarationFiles/lib/split.d.ts
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
// Type declaration for dual-module
|
||||
export interface DualType {
|
||||
prop: string;
|
||||
}
|
||||
export declare function helper(): DualType;
|
||||
@@ -0,0 +1,4 @@
|
||||
// Implementation of dual-module
|
||||
export function helper() {
|
||||
return { prop: "implementation" };
|
||||
}
|
||||
1
javascript/ql/test/library-tests/PathResolution/DeclarationFiles/lib/typescript.d.ts
vendored
Normal file
1
javascript/ql/test/library-tests/PathResolution/DeclarationFiles/lib/typescript.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export const x: number;
|
||||
@@ -0,0 +1 @@
|
||||
export const x = 1;
|
||||
@@ -0,0 +1 @@
|
||||
export const x = 1;
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,2 @@
|
||||
// Nested target file for __dirname imports
|
||||
module.exports = { name: 'nested-target' };
|
||||
@@ -0,0 +1,2 @@
|
||||
// Target file for __dirname imports
|
||||
module.exports = { name: 'target' };
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"main": "./dist/src/foo.js"
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export const x = 1;
|
||||
@@ -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"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export const x = "file.ts"
|
||||
@@ -0,0 +1 @@
|
||||
export const x = "index.ts";
|
||||
@@ -0,0 +1 @@
|
||||
export const x = "nostar.ts"
|
||||
@@ -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";
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"include": ["."],
|
||||
"extends": "./base/tsconfig.base.json"
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export const x = 1;
|
||||
@@ -0,0 +1 @@
|
||||
export const x = 1;
|
||||
@@ -0,0 +1 @@
|
||||
export const x = 1;
|
||||
@@ -0,0 +1 @@
|
||||
export const x = 1;
|
||||
@@ -0,0 +1 @@
|
||||
export const x = 1;
|
||||
@@ -0,0 +1 @@
|
||||
export const x = 1;
|
||||
@@ -0,0 +1 @@
|
||||
export const x = 1;
|
||||
@@ -0,0 +1 @@
|
||||
export const x = 1;
|
||||
@@ -0,0 +1 @@
|
||||
export const x = 1;
|
||||
@@ -0,0 +1 @@
|
||||
export const x = 1;
|
||||
@@ -0,0 +1 @@
|
||||
export const x = 1;
|
||||
@@ -0,0 +1 @@
|
||||
export const x = 1;
|
||||
@@ -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
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"include": ["."],
|
||||
"compilerOptions": {
|
||||
"paths": {
|
||||
"@/*": ["./lib1/*", "./lib2/*"],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "@example/main-is-folder",
|
||||
"main": "dist"
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export const x = 1;
|
||||
@@ -0,0 +1 @@
|
||||
export const x = "file.ts"
|
||||
@@ -0,0 +1 @@
|
||||
export const x = "index.ts";
|
||||
@@ -0,0 +1 @@
|
||||
export const x = "nostar.ts"
|
||||
@@ -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";
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"include": ["."],
|
||||
"compilerOptions": {
|
||||
"paths": {
|
||||
"@/*": ["./lib/*"],
|
||||
"@/*.xyz": ["./lib/*"],
|
||||
"@": ["./lib/nostar.ts"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"name": "@example/package-guess1"
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export const x = 1;
|
||||
@@ -0,0 +1 @@
|
||||
export const x = 1;
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"name": "@example/package-guess2",
|
||||
"files": [
|
||||
"./blah/stuff.js"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export const x = 1;
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"name": "@example/package-with-index-file"
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export const x = 1;
|
||||
@@ -0,0 +1 @@
|
||||
export const x = 1;
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "@example/package-with-exports",
|
||||
"exports": {
|
||||
".": {
|
||||
"default": "./main.js"
|
||||
},
|
||||
"./fake-file": {
|
||||
"default": "./fake-file-impl.js"
|
||||
},
|
||||
"./star/*": {
|
||||
"default": "./star-impl/*"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export const x = 1;
|
||||
@@ -0,0 +1 @@
|
||||
export const x = 1;
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "@example/package-with-main",
|
||||
"main": "./main.js"
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export const x = 1;
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "@example/package-with-main-ext",
|
||||
"main": "./lib/main.js"
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export const x = 1;
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "@example/package-with-module-main",
|
||||
"module": "./main.js"
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export const x = 1;
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"include": ["src"],
|
||||
"compilerOptions": {
|
||||
// Use unusual name to test independently of naming heuristics
|
||||
"outDir": "./customOutDir"
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
102
javascript/ql/test/library-tests/PathResolution/test.expected
Normal file
102
javascript/ql/test/library-tests/PathResolution/test.expected
Normal 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
Reference in New Issue
Block a user