JS: Resolve qualified name of JSDoc types

This commit is contained in:
Asger F
2019-07-05 15:15:20 +01:00
parent 13da242576
commit 225e96ddc3

View File

@@ -132,7 +132,13 @@ class JSDocTypeExpr extends @jsdoc_type_expr, JSDocTypeExprParent, TypeAnnotatio
}
override Stmt getEnclosingStmt() {
result.getDocumentation() = getJSDocComment()
exists(Documentable astNode | astNode.getDocumentation() = getJSDocComment() |
result = astNode
or
result = astNode.(ExprOrType).getEnclosingStmt()
or
result = astNode.(Property).getObjectExpr().getEnclosingStmt()
)
}
override StmtContainer getContainer() { result = getEnclosingStmt().getContainer() }
@@ -205,7 +211,44 @@ class JSDocNamedTypeExpr extends @jsdoc_named_type_expr, JSDocTypeExpr {
override predicate isRawFunction() { getName() = "Function" }
/**
* Holds if this name consists of the unqualified name `prefix`
* followed by a (possibly empty) `suffix`.
*
* For example:
* - `foo.bar.Baz` has prefix `foo` and suffix `.bar.Baz`.
* - `Baz` has prefix `Baz` and an empty suffix.
*/
predicate hasNameParts(string prefix, string suffix) {
exists(string regex, string name | regex = "([^.]+)(.*)" |
name = getName() and
prefix = name.regexpCapture(regex, 1) and
suffix = name.regexpCapture(regex, 2)
)
}
pragma[noinline]
pragma[nomagic]
private predicate hasNamePartsAndEnv(string prefix, string suffix, JSDoc::Environment env) {
// Force join ordering
hasNameParts(prefix, suffix) and
env.isContainerInScope(getContainer())
}
/**
* Gets the qualified name of this name by resolving its prefix, if any.
*/
private string resolvedName() {
exists(string prefix, string suffix, JSDoc::Environment env |
hasNamePartsAndEnv(prefix, suffix, env) and
result = env.resolveAlias(prefix) + suffix
)
}
override predicate hasQualifiedName(string globalName) {
globalName = resolvedName()
or
not exists(resolvedName()) and
globalName = getName()
}
}
@@ -353,3 +396,65 @@ class JSDocError extends @jsdoc_error {
/** Gets a textual representation of this element. */
string toString() { jsdoc_errors(this, _, _, result) }
}
module JSDoc {
/**
* A statement container which may declare JSDoc name aliases.
*/
class Environment extends StmtContainer {
/**
* Gets the fully qualified name aliased by the given unqualified name
* within this container.
*/
string resolveAlias(string alias) {
result = GlobalAccessPath::getAccessPath(getNodeFromAlias(alias))
}
/**
* Gets a data flow node from which the type name `alias` should
* be resolved.
*/
DataFlow::Node getNodeFromAlias(string alias) {
isPrefixUsedInFile(alias, getFile()) and // Restrict size of predicate
exists(VarDef def, VarRef ref |
def.getContainer() = this and
ref = def.getTarget().(BindingPattern).getABindingVarRef() and
ref.getName() = alias
|
exists(PropertyPattern p |
ref = p.getValuePattern() and
result.getAstNode() = p
)
or
result = DataFlow::valueNode(def.(Stmt))
or
ref = def.getTarget() and
result = def.getSource().flow()
)
}
/**
* Holds if the alises declared in this environment should be in scope
* within the given container.
*
* Specifically, this holds if this environment declares at least one
* alias and is an ancestor of `container`.
*/
final predicate isContainerInScope(StmtContainer container) {
exists(resolveAlias(_)) and // restrict size of predicate
container = this
or
isContainerInScope(container.getEnclosingContainer())
}
}
/**
* Holds if a JSDoc type in `f` referenced the given local name.
*/
private predicate isPrefixUsedInFile(string name, File f) {
exists(JSDocNamedTypeExpr t |
t.hasNameParts(name, _) and
t.getFile() = f
)
}
}