mirror of
https://github.com/github/codeql.git
synced 2025-12-20 10:46:30 +01:00
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.
This commit is contained in:
@@ -2,3 +2,8 @@
|
||||
- qlpack: codeql-javascript
|
||||
- apply: lgtm-selectors.yml
|
||||
from: codeql-suite-helpers
|
||||
# These are only for IDE use.
|
||||
- exclude:
|
||||
tags contain:
|
||||
- ide-contextual-queries/local-definitions
|
||||
- ide-contextual-queries/local-references
|
||||
|
||||
@@ -6,169 +6,8 @@
|
||||
* @id js/jump-to-definition
|
||||
*/
|
||||
|
||||
import javascript
|
||||
private import Declarations.Declarations
|
||||
import definitions
|
||||
|
||||
/**
|
||||
* Gets the kind of reference that `r` represents.
|
||||
*
|
||||
* References in callee position have kind `"M"` (for "method"), all
|
||||
* others have kind `"V"` (for "variable").
|
||||
*
|
||||
* For example, in the expression `f(x)`, `f` has kind `"M"` while
|
||||
* `x` has kind `"V"`.
|
||||
*/
|
||||
string refKind(RefExpr r) {
|
||||
if exists(InvokeExpr invk | r = invk.getCallee().getUnderlyingReference())
|
||||
then result = "M"
|
||||
else result = "V"
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a class, function or object literal `va` may refer to.
|
||||
*/
|
||||
ASTNode lookupDef(VarAccess va) {
|
||||
exists(AbstractValue av | av = va.analyze().getAValue() |
|
||||
result = av.(AbstractClass).getClass() or
|
||||
result = av.(AbstractFunction).getFunction() or
|
||||
result = av.(AbstractObjectLiteral).getObjectExpr()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `va` is of kind `kind` and `def` is the unique class,
|
||||
* function or object literal it refers to.
|
||||
*/
|
||||
predicate variableDefLookup(VarAccess va, ASTNode def, string kind) {
|
||||
count(lookupDef(va)) = 1 and
|
||||
def = lookupDef(va) and
|
||||
kind = refKind(va)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if variable access `va` is of kind `kind` and refers to the
|
||||
* variable declaration.
|
||||
*
|
||||
* For example, in the statement `var x = 42, y = x;`, the initializing
|
||||
* expression of `y` is a variable access `x` of kind `"V"` that refers to
|
||||
* the declaration `x = 42`.
|
||||
*/
|
||||
predicate variableDeclLookup(VarAccess va, VarDecl decl, string kind) {
|
||||
// restrict to declarations in same file to avoid accidentally picking up
|
||||
// unrelated global definitions
|
||||
decl = firstRefInTopLevel(va.getVariable(), Decl(), va.getTopLevel()) and
|
||||
kind = refKind(va)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if path expression `path`, which appears in a CommonJS `require`
|
||||
* call or an ES 2015 import statement, imports module `target`; `kind`
|
||||
* is always "I" (for "import").
|
||||
*
|
||||
* For example, in the statement `var a = require("./a")`, the path expression
|
||||
* `"./a"` imports a module `a` in the same folder.
|
||||
*/
|
||||
predicate importLookup(ASTNode path, Module target, string kind) {
|
||||
kind = "I" and
|
||||
(
|
||||
exists(Import i |
|
||||
path = i.getImportedPath() and
|
||||
target = i.getImportedModule()
|
||||
)
|
||||
or
|
||||
exists(ReExportDeclaration red |
|
||||
path = red.getImportedPath() and
|
||||
target = red.getReExportedModule()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a node that may write the property read by `prn`.
|
||||
*/
|
||||
ASTNode getAWrite(DataFlow::PropRead prn) {
|
||||
exists(DataFlow::AnalyzedNode base, DefiniteAbstractValue baseVal, string propName |
|
||||
base = prn.getBase() and
|
||||
propName = prn.getPropertyName() and
|
||||
baseVal = base.getAValue().getAPrototype*()
|
||||
|
|
||||
// write to a property on baseVal
|
||||
exists(AnalyzedPropertyWrite apw |
|
||||
result = apw.getAstNode() and
|
||||
apw.writes(baseVal, propName, _)
|
||||
)
|
||||
or
|
||||
// non-static class members aren't covered by `AnalyzedPropWrite`, so have to be handled
|
||||
// separately
|
||||
exists(ClassDefinition c, MemberDefinition m |
|
||||
m = c.getMember(propName) and
|
||||
baseVal.(AbstractInstance).getConstructor().(AbstractClass).getClass() = c and
|
||||
result = m.getNameExpr()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `prop` is the property name expression of a property read that
|
||||
* may read the property written by `write`. Furthermore, `write` must be the
|
||||
* only such property write. Parameter `kind` is always bound to `"M"`
|
||||
* at the moment.
|
||||
*/
|
||||
predicate propertyLookup(Expr prop, ASTNode write, string kind) {
|
||||
exists(DataFlow::PropRead prn | prop = prn.getPropertyNameExpr() |
|
||||
count(getAWrite(prn)) = 1 and
|
||||
write = getAWrite(prn) and
|
||||
kind = "M"
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `ref` is an identifier that refers to a type declared at `decl`.
|
||||
*/
|
||||
predicate typeLookup(ASTNode ref, ASTNode decl, string kind) {
|
||||
exists(TypeAccess typeAccess |
|
||||
ref = typeAccess.getIdentifier() and
|
||||
decl = typeAccess.getTypeName().getADefinition() and
|
||||
kind = "T"
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `ref` is the callee name of an invocation of `decl`.
|
||||
*/
|
||||
predicate typedInvokeLookup(ASTNode ref, ASTNode decl, string kind) {
|
||||
not variableDefLookup(ref, decl, _) and
|
||||
not propertyLookup(ref, decl, _) and
|
||||
exists(InvokeExpr invoke, Expr callee |
|
||||
callee = invoke.getCallee().getUnderlyingReference() and
|
||||
(ref = callee.(Identifier) or ref = callee.(DotExpr).getPropertyNameExpr()) and
|
||||
decl = invoke.getResolvedCallee() and
|
||||
kind = "M"
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `ref` is a JSDoc type annotation referring to a class defined at `decl`.
|
||||
*/
|
||||
predicate jsdocTypeLookup(JSDocNamedTypeExpr ref, ASTNode decl, string kind) {
|
||||
decl = ref.getClass().getAstNode() and
|
||||
kind = "T"
|
||||
}
|
||||
|
||||
from Locatable ref, ASTNode decl, string kind
|
||||
where
|
||||
variableDefLookup(ref, decl, kind)
|
||||
or
|
||||
// prefer definitions over declarations
|
||||
not variableDefLookup(ref, _, _) and variableDeclLookup(ref, decl, kind)
|
||||
or
|
||||
importLookup(ref, decl, kind)
|
||||
or
|
||||
propertyLookup(ref, decl, kind)
|
||||
or
|
||||
typeLookup(ref, decl, kind)
|
||||
or
|
||||
typedInvokeLookup(ref, decl, kind)
|
||||
or
|
||||
jsdocTypeLookup(ref, decl, kind)
|
||||
select ref, decl, kind
|
||||
from Locatable e, ASTNode def, string kind
|
||||
where def = definitionOf(e, kind)
|
||||
select e, def, kind
|
||||
|
||||
188
javascript/ql/src/definitions.qll
Normal file
188
javascript/ql/src/definitions.qll
Normal file
@@ -0,0 +1,188 @@
|
||||
/**
|
||||
* Provides classes and predicates related to jump-to-definition links
|
||||
* in the code viewer.
|
||||
*/
|
||||
|
||||
import javascript
|
||||
private import Declarations.Declarations
|
||||
|
||||
/**
|
||||
* Gets the kind of reference that `r` represents.
|
||||
*
|
||||
* References in callee position have kind `"M"` (for "method"), all
|
||||
* others have kind `"V"` (for "variable").
|
||||
*
|
||||
* For example, in the expression `f(x)`, `f` has kind `"M"` while
|
||||
* `x` has kind `"V"`.
|
||||
*/
|
||||
string refKind(RefExpr r) {
|
||||
if exists(InvokeExpr invk | r = invk.getCallee().getUnderlyingReference())
|
||||
then result = "M"
|
||||
else result = "V"
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a class, function or object literal `va` may refer to.
|
||||
*/
|
||||
ASTNode lookupDef(VarAccess va) {
|
||||
exists(AbstractValue av | av = va.analyze().getAValue() |
|
||||
result = av.(AbstractClass).getClass() or
|
||||
result = av.(AbstractFunction).getFunction() or
|
||||
result = av.(AbstractObjectLiteral).getObjectExpr()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `va` is of kind `kind` and `def` is the unique class,
|
||||
* function or object literal it refers to.
|
||||
*/
|
||||
predicate variableDefLookup(VarAccess va, ASTNode def, string kind) {
|
||||
count(lookupDef(va)) = 1 and
|
||||
def = lookupDef(va) and
|
||||
kind = refKind(va)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if variable access `va` is of kind `kind` and refers to the
|
||||
* variable declaration.
|
||||
*
|
||||
* For example, in the statement `var x = 42, y = x;`, the initializing
|
||||
* expression of `y` is a variable access `x` of kind `"V"` that refers to
|
||||
* the declaration `x = 42`.
|
||||
*/
|
||||
predicate variableDeclLookup(VarAccess va, VarDecl decl, string kind) {
|
||||
// restrict to declarations in same file to avoid accidentally picking up
|
||||
// unrelated global definitions
|
||||
decl = firstRefInTopLevel(va.getVariable(), Decl(), va.getTopLevel()) and
|
||||
kind = refKind(va)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if path expression `path`, which appears in a CommonJS `require`
|
||||
* call or an ES 2015 import statement, imports module `target`; `kind`
|
||||
* is always "I" (for "import").
|
||||
*
|
||||
* For example, in the statement `var a = require("./a")`, the path expression
|
||||
* `"./a"` imports a module `a` in the same folder.
|
||||
*/
|
||||
predicate importLookup(ASTNode path, Module target, string kind) {
|
||||
kind = "I" and
|
||||
(
|
||||
exists(Import i |
|
||||
path = i.getImportedPath() and
|
||||
target = i.getImportedModule()
|
||||
)
|
||||
or
|
||||
exists(ReExportDeclaration red |
|
||||
path = red.getImportedPath() and
|
||||
target = red.getReExportedModule()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a node that may write the property read by `prn`.
|
||||
*/
|
||||
ASTNode getAWrite(DataFlow::PropRead prn) {
|
||||
exists(DataFlow::AnalyzedNode base, DefiniteAbstractValue baseVal, string propName |
|
||||
base = prn.getBase() and
|
||||
propName = prn.getPropertyName() and
|
||||
baseVal = base.getAValue().getAPrototype*()
|
||||
|
|
||||
// write to a property on baseVal
|
||||
exists(AnalyzedPropertyWrite apw |
|
||||
result = apw.getAstNode() and
|
||||
apw.writes(baseVal, propName, _)
|
||||
)
|
||||
or
|
||||
// non-static class members aren't covered by `AnalyzedPropWrite`, so have to be handled
|
||||
// separately
|
||||
exists(ClassDefinition c, MemberDefinition m |
|
||||
m = c.getMember(propName) and
|
||||
baseVal.(AbstractInstance).getConstructor().(AbstractClass).getClass() = c and
|
||||
result = m.getNameExpr()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `prop` is the property name expression of a property read that
|
||||
* may read the property written by `write`. Furthermore, `write` must be the
|
||||
* only such property write. Parameter `kind` is always bound to `"M"`
|
||||
* at the moment.
|
||||
*/
|
||||
predicate propertyLookup(Expr prop, ASTNode write, string kind) {
|
||||
exists(DataFlow::PropRead prn | prop = prn.getPropertyNameExpr() |
|
||||
count(getAWrite(prn)) = 1 and
|
||||
write = getAWrite(prn) and
|
||||
kind = "M"
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `ref` is an identifier that refers to a type declared at `decl`.
|
||||
*/
|
||||
predicate typeLookup(ASTNode ref, ASTNode decl, string kind) {
|
||||
exists(TypeAccess typeAccess |
|
||||
ref = typeAccess.getIdentifier() and
|
||||
decl = typeAccess.getTypeName().getADefinition() and
|
||||
kind = "T"
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `ref` is the callee name of an invocation of `decl`.
|
||||
*/
|
||||
predicate typedInvokeLookup(ASTNode ref, ASTNode decl, string kind) {
|
||||
not variableDefLookup(ref, decl, _) and
|
||||
not propertyLookup(ref, decl, _) and
|
||||
exists(InvokeExpr invoke, Expr callee |
|
||||
callee = invoke.getCallee().getUnderlyingReference() and
|
||||
(ref = callee.(Identifier) or ref = callee.(DotExpr).getPropertyNameExpr()) and
|
||||
decl = invoke.getResolvedCallee() and
|
||||
kind = "M"
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `ref` is a JSDoc type annotation referring to a class defined at `decl`.
|
||||
*/
|
||||
predicate jsdocTypeLookup(JSDocNamedTypeExpr ref, ASTNode decl, string kind) {
|
||||
decl = ref.getClass().getAstNode() and
|
||||
kind = "T"
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an element, of kind `kind`, that element `e` uses, if any.
|
||||
*
|
||||
* The `kind` is a string representing what kind of use it is:
|
||||
* - `"M"` for function and method calls
|
||||
* - `"T"` for uses of types
|
||||
* - `"V"` for variable accesses
|
||||
* - `"I"` for imports
|
||||
*/
|
||||
cached
|
||||
ASTNode definitionOf(Locatable e, string kind) {
|
||||
variableDefLookup(e, result, kind)
|
||||
or
|
||||
// prefer definitions over declarations
|
||||
not variableDefLookup(e, _, _) and variableDeclLookup(e, result, kind)
|
||||
or
|
||||
importLookup(e, result, kind)
|
||||
or
|
||||
propertyLookup(e, result, kind)
|
||||
or
|
||||
typeLookup(e, result, kind)
|
||||
or
|
||||
typedInvokeLookup(e, result, kind)
|
||||
or
|
||||
jsdocTypeLookup(e, result, kind)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an appropriately encoded version of a filename `name`
|
||||
* passed by the VS Code extension in order to coincide with the
|
||||
* output of `.getFile()` on locatable entities.
|
||||
*/
|
||||
cached
|
||||
File getEncodedFile(string name) { result.getAbsolutePath().replaceAll(":", "_") = name }
|
||||
16
javascript/ql/src/localDefinitions.ql
Normal file
16
javascript/ql/src/localDefinitions.ql
Normal file
@@ -0,0 +1,16 @@
|
||||
/**
|
||||
* @name Jump-to-definition links
|
||||
* @description Generates use-definition pairs that provide the data
|
||||
* for jump-to-definition in the code viewer.
|
||||
* @kind definitions
|
||||
* @id js/ide-jump-to-definition
|
||||
* @tags ide-contextual-queries/local-definitions
|
||||
*/
|
||||
|
||||
import definitions
|
||||
|
||||
external string selectedSourceFile();
|
||||
|
||||
from Locatable e, ASTNode def, string kind
|
||||
where def = definitionOf(e, kind) and e.getFile() = getEncodedFile(selectedSourceFile())
|
||||
select e, def, kind
|
||||
16
javascript/ql/src/localReferences.ql
Normal file
16
javascript/ql/src/localReferences.ql
Normal file
@@ -0,0 +1,16 @@
|
||||
/**
|
||||
* @name Find-references links
|
||||
* @description Generates use-definition pairs that provide the data
|
||||
* for find-references in the code viewer.
|
||||
* @kind definitions
|
||||
* @id js/ide-find-references
|
||||
* @tags ide-contextual-queries/local-references
|
||||
*/
|
||||
|
||||
import definitions
|
||||
|
||||
external string selectedSourceFile();
|
||||
|
||||
from Locatable e, ASTNode def, string kind
|
||||
where def = definitionOf(e, kind) and def.getFile() = getEncodedFile(selectedSourceFile())
|
||||
select e, def, kind
|
||||
Reference in New Issue
Block a user