Merge pull request #9744 from github/aeisenberg/move-contextual-queries

This commit is contained in:
Andrew Eisenberg
2022-06-29 11:44:33 -07:00
committed by GitHub
42 changed files with 39 additions and 12 deletions

View File

@@ -0,0 +1,61 @@
/**
* Provides predicates for finding variable references and declarations
* in a given function or toplevel.
*/
import javascript
/**
* Classification of variable references; all references have kind `Ref`,
* but only declarations have kind `Decl`.
*
* Note that references that are not declarations are called accesses elsewhere,
* but they are not treated specially in this context.
*/
newtype RefKind =
Ref() or
Decl()
/**
* Gets a reference to `var` (if `kind` is `Ref()`) or declaration of
* `var` (if `kind` is `Decl()`) in `sc`.
*/
VarRef refInContainer(Variable var, RefKind kind, StmtContainer sc) {
result.getVariable() = var and
result.getContainer() = sc and
(kind = Decl() implies result instanceof VarDecl)
}
/**
* Gets the textually first reference to `var` (if `kind` is `Ref()`) or
* declaration of `var` (if `kind` is `Decl()`) in `sc`.
*/
VarRef firstRefInContainer(Variable var, RefKind kind, StmtContainer sc) {
result =
min(refInContainer(var, kind, sc) as ref
order by
ref.getLocation().getStartLine(), ref.getLocation().getStartColumn()
)
}
/**
* Gets a reference to `var` (if `kind` is `Ref()`) or declaration of
* `var` (if `kind` is `Decl()`) in `tl`.
*/
VarRef refInTopLevel(Variable var, RefKind kind, TopLevel tl) {
result.getVariable() = var and
result.getTopLevel() = tl and
(kind = Decl() implies result instanceof VarDecl)
}
/**
* Gets the textually first reference to `var` (if `kind` is `Ref()`) or
* declaration of `var` (if `kind` is `Decl()`) in `tl`.
*/
VarRef firstRefInTopLevel(Variable var, RefKind kind, TopLevel tl) {
result =
min(refInTopLevel(var, kind, tl) as ref
order by
ref.getLocation().getStartLine(), ref.getLocation().getStartColumn()
)
}

View File

@@ -0,0 +1,22 @@
/**
* Provides shared predicates related to contextual queries in the code viewer.
*/
import semmle.files.FileSystem
/**
* Returns the `File` matching the given source file name as encoded by the VS
* Code extension.
*/
cached
File getFileBySourceArchiveName(string name) {
// The name provided for a file in the source archive by the VS Code extension
// has some differences from the absolute path in the database:
// 1. colons are replaced by underscores
// 2. there's a leading slash, even for Windows paths: "C:/foo/bar" ->
// "/C_/foo/bar"
// 3. double slashes in UNC prefixes are replaced with a single slash
// We can handle 2 and 3 together by unconditionally adding a leading slash
// before replacing double slashes.
name = ("/" + result.getAbsolutePath().replaceAll(":", "_")).replaceAll("//", "/")
}

View File

@@ -0,0 +1,13 @@
/**
* @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/jump-to-definition
*/
import definitions
from Locatable e, AstNode def, string kind
where def = definitionOf(e, kind)
select e, def, kind

View File

@@ -0,0 +1,181 @@
/**
* Provides classes and predicates related to jump-to-definition links
* in the code viewer.
*/
import javascript
import IDEContextual
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"`.
*/
private 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.
*/
private 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.
*/
private 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`.
*/
private 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.
*/
private 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`.
*/
private 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.
*/
private 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`.
*/
private 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`.
*/
private 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`.
*/
private 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)
}

View 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() = getFileBySourceArchiveName(selectedSourceFile())
select e, def, kind

View File

@@ -0,0 +1,17 @@
/**
* @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() = getFileBySourceArchiveName(selectedSourceFile())
select e, def, kind

View File

@@ -0,0 +1,28 @@
/**
* @name Print AST
* @description Outputs a representation of a file's Abstract Syntax Tree. This
* query is used by the VS Code extension.
* @id js/print-ast
* @kind graph
* @tags ide-contextual-queries/print-ast
*/
import javascript
import semmle.javascript.PrintAst
import definitions
/**
* Gets the source file to generate an AST from.
*/
external string selectedSourceFile();
class PrintAstConfigurationOverride extends PrintAstConfiguration {
/**
* Holds if the location matches the selected file in the VS Code extension and
* the element is not a synthetic constructor.
*/
override predicate shouldPrint(Locatable e, Location l) {
super.shouldPrint(e, l) and
l.getFile() = getFileBySourceArchiveName(selectedSourceFile())
}
}