Skip to content

Commit efcb9f1

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 efcb9f1

5 files changed

Lines changed: 224 additions & 165 deletions

File tree

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

0 commit comments

Comments
 (0)