Skip to content

Commit 48e4079

Browse files
committed
JS: Refactor definitions query, add queries for ide search
This enables jump-to-definition and find-references in the VS Code extension, for javascript source archives.
1 parent 29a5ea1 commit 48e4079

File tree

5 files changed

+229
-165
lines changed

5 files changed

+229
-165
lines changed

javascript/ql/src/codeql-suites/javascript-lgtm-full.qls

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,8 @@
22
- qlpack: codeql-javascript
33
- apply: lgtm-selectors.yml
44
from: codeql-suite-helpers
5+
# These are only for IDE use.
6+
- exclude:
7+
tags contain:
8+
- ide-contextual-queries/local-definitions
9+
- ide-contextual-queries/local-references

javascript/ql/src/definitions.ql

Lines changed: 4 additions & 165 deletions
Original file line numberDiff line numberDiff line change
@@ -6,169 +6,8 @@
66
* @id js/jump-to-definition
77
*/
88

9-
import javascript
10-
private import Declarations.Declarations
9+
import definitions
1110

12-
/**
13-
* Gets the kind of reference that `r` represents.
14-
*
15-
* References in callee position have kind `"M"` (for "method"), all
16-
* others have kind `"V"` (for "variable").
17-
*
18-
* For example, in the expression `f(x)`, `f` has kind `"M"` while
19-
* `x` has kind `"V"`.
20-
*/
21-
string refKind(RefExpr r) {
22-
if exists(InvokeExpr invk | r = invk.getCallee().getUnderlyingReference())
23-
then result = "M"
24-
else result = "V"
25-
}
26-
27-
/**
28-
* Gets a class, function or object literal `va` may refer to.
29-
*/
30-
ASTNode lookupDef(VarAccess va) {
31-
exists(AbstractValue av | av = va.analyze().getAValue() |
32-
result = av.(AbstractClass).getClass() or
33-
result = av.(AbstractFunction).getFunction() or
34-
result = av.(AbstractObjectLiteral).getObjectExpr()
35-
)
36-
}
37-
38-
/**
39-
* Holds if `va` is of kind `kind` and `def` is the unique class,
40-
* function or object literal it refers to.
41-
*/
42-
predicate variableDefLookup(VarAccess va, ASTNode def, string kind) {
43-
count(lookupDef(va)) = 1 and
44-
def = lookupDef(va) and
45-
kind = refKind(va)
46-
}
47-
48-
/**
49-
* Holds if variable access `va` is of kind `kind` and refers to the
50-
* variable declaration.
51-
*
52-
* For example, in the statement `var x = 42, y = x;`, the initializing
53-
* expression of `y` is a variable access `x` of kind `"V"` that refers to
54-
* the declaration `x = 42`.
55-
*/
56-
predicate variableDeclLookup(VarAccess va, VarDecl decl, string kind) {
57-
// restrict to declarations in same file to avoid accidentally picking up
58-
// unrelated global definitions
59-
decl = firstRefInTopLevel(va.getVariable(), Decl(), va.getTopLevel()) and
60-
kind = refKind(va)
61-
}
62-
63-
/**
64-
* Holds if path expression `path`, which appears in a CommonJS `require`
65-
* call or an ES 2015 import statement, imports module `target`; `kind`
66-
* is always "I" (for "import").
67-
*
68-
* For example, in the statement `var a = require("./a")`, the path expression
69-
* `"./a"` imports a module `a` in the same folder.
70-
*/
71-
predicate importLookup(ASTNode path, Module target, string kind) {
72-
kind = "I" and
73-
(
74-
exists(Import i |
75-
path = i.getImportedPath() and
76-
target = i.getImportedModule()
77-
)
78-
or
79-
exists(ReExportDeclaration red |
80-
path = red.getImportedPath() and
81-
target = red.getReExportedModule()
82-
)
83-
)
84-
}
85-
86-
/**
87-
* Gets a node that may write the property read by `prn`.
88-
*/
89-
ASTNode getAWrite(DataFlow::PropRead prn) {
90-
exists(DataFlow::AnalyzedNode base, DefiniteAbstractValue baseVal, string propName |
91-
base = prn.getBase() and
92-
propName = prn.getPropertyName() and
93-
baseVal = base.getAValue().getAPrototype*()
94-
|
95-
// write to a property on baseVal
96-
exists(AnalyzedPropertyWrite apw |
97-
result = apw.getAstNode() and
98-
apw.writes(baseVal, propName, _)
99-
)
100-
or
101-
// non-static class members aren't covered by `AnalyzedPropWrite`, so have to be handled
102-
// separately
103-
exists(ClassDefinition c, MemberDefinition m |
104-
m = c.getMember(propName) and
105-
baseVal.(AbstractInstance).getConstructor().(AbstractClass).getClass() = c and
106-
result = m.getNameExpr()
107-
)
108-
)
109-
}
110-
111-
/**
112-
* Holds if `prop` is the property name expression of a property read that
113-
* may read the property written by `write`. Furthermore, `write` must be the
114-
* only such property write. Parameter `kind` is always bound to `"M"`
115-
* at the moment.
116-
*/
117-
predicate propertyLookup(Expr prop, ASTNode write, string kind) {
118-
exists(DataFlow::PropRead prn | prop = prn.getPropertyNameExpr() |
119-
count(getAWrite(prn)) = 1 and
120-
write = getAWrite(prn) and
121-
kind = "M"
122-
)
123-
}
124-
125-
/**
126-
* Holds if `ref` is an identifier that refers to a type declared at `decl`.
127-
*/
128-
predicate typeLookup(ASTNode ref, ASTNode decl, string kind) {
129-
exists(TypeAccess typeAccess |
130-
ref = typeAccess.getIdentifier() and
131-
decl = typeAccess.getTypeName().getADefinition() and
132-
kind = "T"
133-
)
134-
}
135-
136-
/**
137-
* Holds if `ref` is the callee name of an invocation of `decl`.
138-
*/
139-
predicate typedInvokeLookup(ASTNode ref, ASTNode decl, string kind) {
140-
not variableDefLookup(ref, decl, _) and
141-
not propertyLookup(ref, decl, _) and
142-
exists(InvokeExpr invoke, Expr callee |
143-
callee = invoke.getCallee().getUnderlyingReference() and
144-
(ref = callee.(Identifier) or ref = callee.(DotExpr).getPropertyNameExpr()) and
145-
decl = invoke.getResolvedCallee() and
146-
kind = "M"
147-
)
148-
}
149-
150-
/**
151-
* Holds if `ref` is a JSDoc type annotation referring to a class defined at `decl`.
152-
*/
153-
predicate jsdocTypeLookup(JSDocNamedTypeExpr ref, ASTNode decl, string kind) {
154-
decl = ref.getClass().getAstNode() and
155-
kind = "T"
156-
}
157-
158-
from Locatable ref, ASTNode decl, string kind
159-
where
160-
variableDefLookup(ref, decl, kind)
161-
or
162-
// prefer definitions over declarations
163-
not variableDefLookup(ref, _, _) and variableDeclLookup(ref, decl, kind)
164-
or
165-
importLookup(ref, decl, kind)
166-
or
167-
propertyLookup(ref, decl, kind)
168-
or
169-
typeLookup(ref, decl, kind)
170-
or
171-
typedInvokeLookup(ref, decl, kind)
172-
or
173-
jsdocTypeLookup(ref, decl, kind)
174-
select ref, decl, kind
11+
from Locatable e, ASTNode def, string kind
12+
where def = definitionOf(e, kind)
13+
select e, def, kind

javascript/ql/src/definitions.qll

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
/**
2+
* Provides classes and predicates related to jump-to-definition links
3+
* in the code viewer.
4+
*/
5+
6+
import javascript
7+
private import Declarations.Declarations
8+
9+
/**
10+
* Gets the kind of reference that `r` represents.
11+
*
12+
* References in callee position have kind `"M"` (for "method"), all
13+
* others have kind `"V"` (for "variable").
14+
*
15+
* For example, in the expression `f(x)`, `f` has kind `"M"` while
16+
* `x` has kind `"V"`.
17+
*/
18+
string refKind(RefExpr r) {
19+
if exists(InvokeExpr invk | r = invk.getCallee().getUnderlyingReference())
20+
then result = "M"
21+
else result = "V"
22+
}
23+
24+
/**
25+
* Gets a class, function or object literal `va` may refer to.
26+
*/
27+
ASTNode lookupDef(VarAccess va) {
28+
exists(AbstractValue av | av = va.analyze().getAValue() |
29+
result = av.(AbstractClass).getClass() or
30+
result = av.(AbstractFunction).getFunction() or
31+
result = av.(AbstractObjectLiteral).getObjectExpr()
32+
)
33+
}
34+
35+
/**
36+
* Holds if `va` is of kind `kind` and `def` is the unique class,
37+
* function or object literal it refers to.
38+
*/
39+
predicate variableDefLookup(VarAccess va, ASTNode def, string kind) {
40+
count(lookupDef(va)) = 1 and
41+
def = lookupDef(va) and
42+
kind = refKind(va)
43+
}
44+
45+
/**
46+
* Holds if variable access `va` is of kind `kind` and refers to the
47+
* variable declaration.
48+
*
49+
* For example, in the statement `var x = 42, y = x;`, the initializing
50+
* expression of `y` is a variable access `x` of kind `"V"` that refers to
51+
* the declaration `x = 42`.
52+
*/
53+
predicate variableDeclLookup(VarAccess va, VarDecl decl, string kind) {
54+
// restrict to declarations in same file to avoid accidentally picking up
55+
// unrelated global definitions
56+
decl = firstRefInTopLevel(va.getVariable(), Decl(), va.getTopLevel()) and
57+
kind = refKind(va)
58+
}
59+
60+
/**
61+
* Holds if path expression `path`, which appears in a CommonJS `require`
62+
* call or an ES 2015 import statement, imports module `target`; `kind`
63+
* is always "I" (for "import").
64+
*
65+
* For example, in the statement `var a = require("./a")`, the path expression
66+
* `"./a"` imports a module `a` in the same folder.
67+
*/
68+
predicate importLookup(ASTNode path, Module target, string kind) {
69+
kind = "I" and
70+
(
71+
exists(Import i |
72+
path = i.getImportedPath() and
73+
target = i.getImportedModule()
74+
)
75+
or
76+
exists(ReExportDeclaration red |
77+
path = red.getImportedPath() and
78+
target = red.getReExportedModule()
79+
)
80+
)
81+
}
82+
83+
/**
84+
* Gets a node that may write the property read by `prn`.
85+
*/
86+
ASTNode getAWrite(DataFlow::PropRead prn) {
87+
exists(DataFlow::AnalyzedNode base, DefiniteAbstractValue baseVal, string propName |
88+
base = prn.getBase() and
89+
propName = prn.getPropertyName() and
90+
baseVal = base.getAValue().getAPrototype*()
91+
|
92+
// write to a property on baseVal
93+
exists(AnalyzedPropertyWrite apw |
94+
result = apw.getAstNode() and
95+
apw.writes(baseVal, propName, _)
96+
)
97+
or
98+
// non-static class members aren't covered by `AnalyzedPropWrite`, so have to be handled
99+
// separately
100+
exists(ClassDefinition c, MemberDefinition m |
101+
m = c.getMember(propName) and
102+
baseVal.(AbstractInstance).getConstructor().(AbstractClass).getClass() = c and
103+
result = m.getNameExpr()
104+
)
105+
)
106+
}
107+
108+
/**
109+
* Holds if `prop` is the property name expression of a property read that
110+
* may read the property written by `write`. Furthermore, `write` must be the
111+
* only such property write. Parameter `kind` is always bound to `"M"`
112+
* at the moment.
113+
*/
114+
predicate propertyLookup(Expr prop, ASTNode write, string kind) {
115+
exists(DataFlow::PropRead prn | prop = prn.getPropertyNameExpr() |
116+
count(getAWrite(prn)) = 1 and
117+
write = getAWrite(prn) and
118+
kind = "M"
119+
)
120+
}
121+
122+
/**
123+
* Holds if `ref` is an identifier that refers to a type declared at `decl`.
124+
*/
125+
predicate typeLookup(ASTNode ref, ASTNode decl, string kind) {
126+
exists(TypeAccess typeAccess |
127+
ref = typeAccess.getIdentifier() and
128+
decl = typeAccess.getTypeName().getADefinition() and
129+
kind = "T"
130+
)
131+
}
132+
133+
/**
134+
* Holds if `ref` is the callee name of an invocation of `decl`.
135+
*/
136+
predicate typedInvokeLookup(ASTNode ref, ASTNode decl, string kind) {
137+
not variableDefLookup(ref, decl, _) and
138+
not propertyLookup(ref, decl, _) and
139+
exists(InvokeExpr invoke, Expr callee |
140+
callee = invoke.getCallee().getUnderlyingReference() and
141+
(ref = callee.(Identifier) or ref = callee.(DotExpr).getPropertyNameExpr()) and
142+
decl = invoke.getResolvedCallee() and
143+
kind = "M"
144+
)
145+
}
146+
147+
/**
148+
* Holds if `ref` is a JSDoc type annotation referring to a class defined at `decl`.
149+
*/
150+
predicate jsdocTypeLookup(JSDocNamedTypeExpr ref, ASTNode decl, string kind) {
151+
decl = ref.getClass().getAstNode() and
152+
kind = "T"
153+
}
154+
155+
/**
156+
* Gets an element, of kind `kind`, that element `e` uses, if any.
157+
*
158+
* The `kind` is a string representing what kind of use it is:
159+
* - `"M"` for function and method calls
160+
* - `"T"` for uses of types
161+
* - `"V"` for variable accesses
162+
* - `"I"` for imports
163+
*/
164+
cached
165+
ASTNode definitionOf(Locatable e, string kind) {
166+
variableDefLookup(e, result, kind)
167+
or
168+
// prefer definitions over declarations
169+
not variableDefLookup(e, _, _) and variableDeclLookup(e, result, kind)
170+
or
171+
importLookup(e, result, kind)
172+
or
173+
propertyLookup(e, result, kind)
174+
or
175+
typeLookup(e, result, kind)
176+
or
177+
typedInvokeLookup(e, result, kind)
178+
or
179+
jsdocTypeLookup(e, result, kind)
180+
}
181+
182+
/**
183+
* Returns an appropriately encoded version of a filename `name`
184+
* passed by the VS Code extension in order to coincide with the
185+
* output of `.getFile()` on locatable entities.
186+
*/
187+
cached
188+
File getEncodedFile(string name) { result.getAbsolutePath().replaceAll(":", "_") = name }
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/**
2+
* @name Jump-to-definition links
3+
* @description Generates use-definition pairs that provide the data
4+
* for jump-to-definition in the code viewer.
5+
* @kind definitions
6+
* @id js/ide-jump-to-definition
7+
* @tags ide-contextual-queries/local-definitions
8+
*/
9+
10+
import definitions
11+
12+
external string selectedSourceFile();
13+
14+
from Locatable e, ASTNode def, string kind
15+
where def = definitionOf(e, kind) and e.getFile() = getEncodedFile(selectedSourceFile())
16+
select e, def, kind

0 commit comments

Comments
 (0)