Skip to content

Commit b82e849

Browse files
committed
JS: Add public API
1 parent 2a0c7c8 commit b82e849

File tree

4 files changed

+202
-0
lines changed

4 files changed

+202
-0
lines changed

javascript/ql/lib/semmle/javascript/AST.qll

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
import javascript
66
private import internal.StmtContainers
77
private import semmle.javascript.internal.CachedStages
8+
private import semmle.javascript.internal.TypeResolution
9+
private import semmle.javascript.internal.BindingInfo
810

911
/**
1012
* A program element corresponding to JavaScript code, such as an expression
@@ -472,5 +474,22 @@ module AST {
472474

473475
/** Gets the data flow node associated with this program element. */
474476
DataFlow::ValueNode flow() { result = DataFlow::valueNode(this) }
477+
478+
/**
479+
* Gets information about the results of name-resolution for this expression.
480+
*
481+
* This can be used to map an expression to the class it refers to, or
482+
* associate it with a named value coming from an dependency.
483+
*/
484+
ExprNameBindingNode getNameBinding() { result = this }
485+
486+
/**
487+
* Gets information about the type of this expression.
488+
*
489+
* This can be used to map an expression to the classes it may be an instance of
490+
* (according to the type system), or to associate it with a named type coming
491+
* from a dependency.
492+
*/
493+
TypeNameBindingNode getTypeBinding() { TypeResolution::valueHasType(this, result) }
475494
}
476495
}

javascript/ql/lib/semmle/javascript/TypeAnnotations.qll

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,20 @@ import javascript
66
private import internal.StmtContainers
77
private import internal.NameResolution
88
private import internal.UnderlyingTypes
9+
private import internal.BindingInfo
910

1011
/**
1112
* A type annotation, either in the form of a TypeScript type or a JSDoc comment.
1213
*/
1314
class TypeAnnotation extends @type_annotation, NodeInStmtContainer {
15+
/**
16+
* Gets information about the results of name-resolution for this type.
17+
*
18+
* This can be used to map a type name to the class/interface it refers to, or
19+
* associate it with a named type coming from an dependency.
20+
*/
21+
TypeNameBindingNode getTypeBinding() { result = this }
22+
1423
/** Holds if this is the `any` type. */
1524
predicate isAny() { none() }
1625

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
/**
2+
* Provides a limited public interface to name/type resolution information.
3+
*/
4+
5+
private import javascript
6+
private import semmle.javascript.internal.NameResolution
7+
private import semmle.javascript.internal.TypeResolution
8+
private import semmle.javascript.internal.UnderlyingTypes
9+
10+
/**
11+
* Interface for accessing name-resolution info about type names.
12+
*/
13+
class TypeNameBindingNode extends NameResolution::Node {
14+
/**
15+
* Holds if type refers to, or is an alias for, the given type name relative to the global scope.
16+
*
17+
* For example:
18+
* ```ts
19+
* var x: Document; // hasQualifiedName("Document")
20+
* var x: Electron; // hasQualifiedName("Electron")
21+
* var x: Electron.BrowserWindow; // hasQualifiedName("Electron.BrowserWindow")
22+
* ```
23+
*/
24+
predicate hasQualifiedName(string qualifiedName) {
25+
NameResolution::nodeRefersToModule(this, "global", qualifiedName)
26+
}
27+
28+
/**
29+
* Holds if this refers a value exported by the given module, with the given
30+
* qualified name. If the `qualifiedName` is empty, this refers to the module itself.
31+
*
32+
* For example, the type annotations below have the following name bindings:
33+
* ```ts
34+
* import { Request } from "express";
35+
*
36+
* var x: Request; // hasUnderlyingType("express", "Request")
37+
* var x: Request | null; // no result (see hasUnderlyingType)
38+
* var x: Request & { prop: string }; // no result (see hasUnderlyingType)
39+
*
40+
* interface CustomSubtype extends Request {}
41+
*
42+
* var x: CustomSubtype; // no result (see hasUnderlyingType)
43+
*
44+
* var x: typeof import("express"); // hasUnderlyingType("express", "")
45+
* ```
46+
*/
47+
predicate hasQualifiedName(string moduleName, string qualifiedName) {
48+
NameResolution::nodeRefersToModule(this, moduleName, qualifiedName)
49+
}
50+
51+
/**
52+
* Holds if this type refers to the given type exported from the given module, after
53+
* unfolding unions and intersections, and following subtype relations.
54+
*
55+
* For example:
56+
* ```ts
57+
* import { Request } from "express";
58+
*
59+
* var x: Request; // hasUnderlyingType("express", "Request")
60+
* var x: Request | null; // hasUnderlyingType("express", "Request")
61+
* var x: Request & { prop: string }; // hasUnderlyingType("express", "Request")
62+
*
63+
* interface CustomSubtype extends Request {}
64+
*
65+
* var x: CustomSubtype; // hasUnderlyingType("express", "Request")
66+
* ```
67+
*/
68+
predicate hasUnderlyingType(string moduleName, string qualifiedName) {
69+
UnderlyingTypes::nodeHasUnderlyingType(this, moduleName, qualifiedName)
70+
}
71+
72+
/**
73+
* Holds if this type refers to the given type from the global scope, after
74+
* unfolding unions and intersections, and following subtype relations.
75+
*
76+
* For example:
77+
* ```ts
78+
* var x: Document; // hasUnderlyingType("Document")
79+
* var x: Document | null; // hasUnderlyingType("Document")
80+
* var x: Document & { prop: string }; // hasUnderlyingType("Document")
81+
*
82+
* interface CustomSubtype extends Document {}
83+
*
84+
* var x: CustomSubtype; // hasUnderlyingType("Document")
85+
* ```
86+
*/
87+
predicate hasUnderlyingType(string qualifiedName) {
88+
UnderlyingTypes::nodeHasUnderlyingType(this, qualifiedName)
89+
}
90+
91+
/**
92+
* Gets the declaration of the type being referenced by this name.
93+
*
94+
* For example:
95+
* ```ts
96+
* class Foo {}
97+
*
98+
* type T = Foo;
99+
* var x: T; // getTypeDefinition() maps T to the class Foo above
100+
* ```
101+
*
102+
* Note that this has no result for function-style classes referenced from
103+
* a JSDoc comment.
104+
*/
105+
TypeDefinition getTypeDefinition() { TypeResolution::trackType(result) = this }
106+
107+
/**
108+
* Gets a class that this type refers to, after unfolding unions and intersections (but not subtyping).
109+
*
110+
* For example, the type of `x` maps to the class `C` in each example below:
111+
* ```ts
112+
* class C {}
113+
*
114+
* var x: C;
115+
* var x: C | null;
116+
* var x: C & { prop: string };
117+
* ```
118+
*/
119+
DataFlow::ClassNode getAnUnderlyingClass() {
120+
UnderlyingTypes::nodeHasUnderlyingClassType(this, result)
121+
}
122+
}
123+
124+
/**
125+
* Interface for accessing name-resolution info about expressions.
126+
*/
127+
class ExprNameBindingNode extends NameResolution::Node {
128+
/**
129+
* Holds if this refers a value exported by the given module, with the given
130+
* qualified name. If the `qualifiedName` is empty, this refers to the module itself.
131+
*
132+
* For example, the type annotations below have the following name bindings:
133+
* ```ts
134+
* import * as f from "foo";
135+
*
136+
* var x = f; // hasQualifiedName(f, "")
137+
* var x = f.x.y; // hasQualifiedName(f, "x.y")
138+
* ```
139+
*/
140+
predicate hasQualifiedName(string moduleName, string qualifiedName) {
141+
NameResolution::nodeRefersToModule(this, moduleName, qualifiedName)
142+
}
143+
144+
/**
145+
* Gets the class, or function acting as a class, referenced by this name.
146+
*
147+
* ```ts
148+
* class Foo {}
149+
* const T = Foo;
150+
* var x = T; // getClassNode() maps T to the class Foo above
151+
*
152+
* function Bar() {}
153+
* Bar.prototype.blah = function() {};
154+
* const S = Bar;
155+
* var x = S; // getClassNode() maps S to the function Bar above
156+
* ```
157+
*/
158+
DataFlow::ClassNode getClassNode() { NameResolution::nodeRefersToClass(this, result) }
159+
}

javascript/ql/lib/semmle/javascript/internal/NameResolution.qll

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -519,4 +519,19 @@ module NameResolution {
519519
cls.flowsTo(AccessPath::getAnAssignmentTo(name)) and
520520
not cls.getTopLevel().isExterns() // don't propagate externs classes
521521
}
522+
523+
/**
524+
* Holds if `node` refers to the given class.
525+
*/
526+
pragma[nomagic]
527+
predicate nodeRefersToClass(Node node, DataFlow::ClassNode cls) {
528+
exists(string name |
529+
classHasGlobalName(cls, name) and
530+
nodeRefersToModule(node, "global", name)
531+
)
532+
or
533+
trackClassValue(cls.getAstNode()) = node
534+
or
535+
trackFunctionValue(cls.getAstNode()) = node
536+
}
522537
}

0 commit comments

Comments
 (0)