Skip to content

Commit 0f26940

Browse files
committed
JS: Update handling of JSDoc comments
1 parent 62a0641 commit 0f26940

File tree

4 files changed

+56
-15
lines changed

4 files changed

+56
-15
lines changed

javascript/ql/lib/semmle/javascript/JSDoc.qll

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ class JSDoc extends @jsdoc, Locatable {
3333
result.getTitle() = title
3434
}
3535

36+
/** Gets the element to which this JSDoc comment is attached */
37+
Documentable getDocumentedElement() { result.getDocumentation() = this }
38+
3639
override string toString() { result = this.getComment().toString() }
3740
}
3841

@@ -299,6 +302,41 @@ class JSDocIdentifierTypeExpr extends @jsdoc_identifier_type_expr, JSDocTypeExpr
299302
override predicate isRawFunction() { this.getName() = "Function" }
300303
}
301304

305+
private AstNode getAncestorInScope(Documentable doc) {
306+
any(JSDocLocalTypeAccess t).getJSDocComment() = doc.getDocumentation() and // restrict to cases where we need this
307+
result = doc.getParent()
308+
or
309+
exists(AstNode mid |
310+
mid = getAncestorInScope(doc) and
311+
not mid = any(Scope s).getScopeElement() and
312+
result = mid.getParent()
313+
)
314+
}
315+
316+
private Scope getScope(Documentable doc) { result.getScopeElement() = getAncestorInScope(doc) }
317+
318+
pragma[nomagic]
319+
private predicate shouldResolveName(TopLevel top, string name) {
320+
exists(JSDocLocalTypeAccess access |
321+
access.getName() = name and
322+
access.getTopLevel() = top
323+
)
324+
}
325+
326+
private LexicalName getOwnLocal(Scope scope, string name, DeclarationSpace space) {
327+
scope = result.getScope() and
328+
name = result.getName() and
329+
space = result.getDeclarationSpace() and
330+
shouldResolveName(scope.getScopeElement().getTopLevel(), name) // restrict size of predicate
331+
}
332+
333+
private LexicalName resolveLocal(Scope scope, string name, DeclarationSpace space) {
334+
result = getOwnLocal(scope, name, space)
335+
or
336+
result = resolveLocal(scope.getOuterScope(), name, space) and
337+
not exists(getOwnLocal(scope, name, space))
338+
}
339+
302340
/**
303341
* An unqualified identifier in a JSDoc type expression.
304342
*
@@ -311,6 +349,12 @@ class JSDocIdentifierTypeExpr extends @jsdoc_identifier_type_expr, JSDocTypeExpr
311349
*/
312350
class JSDocLocalTypeAccess extends JSDocIdentifierTypeExpr {
313351
JSDocLocalTypeAccess() { not this = any(JSDocQualifiedTypeAccess a).getNameNode() }
352+
353+
/** Gets a variable, type-name, or namespace that this expression may resolve to. */
354+
LexicalName getALexicalName() {
355+
result =
356+
resolveLocal(getScope(this.getJSDocComment().getDocumentedElement()), this.getName(), _)
357+
}
314358
}
315359

316360
/**

javascript/ql/lib/semmle/javascript/internal/NameResolution.qll

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -137,12 +137,6 @@ module NameResolution {
137137
node1 = type.getExpressionName() and
138138
node2 = type
139139
)
140-
or
141-
exists(JSDocNamedTypeExpr type, string name |
142-
type.hasNameParts(name, "") and
143-
node1 = type.getTopLevel().getScope().getLocalTypeName(name) and
144-
node2 = type
145-
)
146140
}
147141

148142
/**
@@ -186,11 +180,9 @@ module NameResolution {
186180
node2 = spec.getLocal()
187181
)
188182
or
189-
// Support JSDoc expressions of the form 'foo.bar' where 'foo' is an import
190-
// at the top-level.
191-
exists(JSDocNamedTypeExpr expr, string prefix |
192-
expr.hasNameParts(prefix, "." + name) and
193-
node1 = expr.getTopLevel().getScope().getVariable(prefix) and
183+
exists(JSDocQualifiedTypeAccess expr |
184+
node1 = expr.getBase() and
185+
name = expr.getName() and
194186
node2 = expr
195187
)
196188
}
@@ -277,6 +269,9 @@ module NameResolution {
277269
or
278270
node1 = var and
279271
node2.(LexicalAccess).getALexicalName() = var
272+
or
273+
node1 = var and
274+
node2.(JSDocLocalTypeAccess).getALexicalName() = var
280275
)
281276
or
282277
exists(Node base, string name, ModuleLike mod |

javascript/ql/lib/semmle/javascript/internal/UnderlyingTypes.qll

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,11 @@ module UnderlyingTypes {
7474
predicate nodeHasUnderlyingType(Node node, string mod, string name) {
7575
nodeRefersToModule(node, mod, name)
7676
or
77-
exists(JSDocNamedTypeExpr type |
77+
exists(JSDocLocalTypeAccess type |
7878
node = type and
79-
type.hasQualifiedName(name) and
79+
not exists(type.getALexicalName()) and
80+
not type = any(JSDocQualifiedTypeAccess t).getBase() and
81+
name = type.getName() and
8082
mod = "global"
8183
)
8284
or

javascript/ql/test/library-tests/UnderlyingTypes/jsdoc.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ import { Response } from 'express';
44
/**
55
* @param {e.Request} req
66
*/
7-
function t1(req) { // $ hasUnderlyingType='express'.Request SPURIOUS: hasUnderlyingType=e.Request
7+
function t1(req) { // $ hasUnderlyingType='express'.Request
88
}
99

1010
/**
1111
* @param {Response} res
1212
*/
13-
function t2(res) { // $ hasUnderlyingType='express'.Response SPURIOUS: hasUnderlyingType=Response
13+
function t2(res) { // $ hasUnderlyingType='express'.Response
1414
}

0 commit comments

Comments
 (0)