Skip to content

Commit 3a60897

Browse files
committed
JS: Separate JSDoc qualified names into individual identifiers
1 parent c61454b commit 3a60897

File tree

7 files changed

+146
-30
lines changed

7 files changed

+146
-30
lines changed

javascript/extractor/src/com/semmle/js/ast/jsdoc/NameExpression.java renamed to javascript/extractor/src/com/semmle/js/ast/jsdoc/Identifier.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@
22

33
import com.semmle.js.ast.SourceLocation;
44

5-
/** A named JSDoc type. */
6-
public class NameExpression extends JSDocTypeExpression {
5+
/** An identifier in a JSDoc type. */
6+
public class Identifier extends JSDocTypeExpression {
77
private final String name;
88

9-
public NameExpression(SourceLocation loc, String name) {
10-
super(loc, "NameExpression");
9+
public Identifier(SourceLocation loc, String name) {
10+
super(loc, "Identifier");
1111
this.name = name;
1212
}
1313

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package com.semmle.js.ast.jsdoc;
2+
3+
import com.semmle.js.ast.SourceLocation;
4+
5+
/** A qualified name in a JSDoc type. */
6+
public class QualifiedNameExpression extends JSDocTypeExpression {
7+
private final JSDocTypeExpression base;
8+
private final Identifier name;
9+
10+
public QualifiedNameExpression(SourceLocation loc, JSDocTypeExpression base, Identifier name) {
11+
super(loc, "QualifiedNameExpression");
12+
this.base = base;
13+
this.name = name;
14+
}
15+
16+
@Override
17+
public void accept(Visitor v) {
18+
v.visit(this);
19+
}
20+
21+
/** Returns the expression on the left side of the dot character. */
22+
public JSDocTypeExpression getBase() {
23+
return base;
24+
}
25+
26+
/** Returns the identifier on the right-hand side of the dot character. */
27+
public Identifier getNameNode() {
28+
return name;
29+
}
30+
31+
@Override
32+
public String pp() {
33+
return base.pp() + "." + name.pp();
34+
}
35+
}

javascript/extractor/src/com/semmle/js/ast/jsdoc/Visitor.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ public interface Visitor {
1010

1111
public void visit(JSDocTag nd);
1212

13-
public void visit(NameExpression nd);
13+
public void visit(Identifier nd);
14+
15+
public void visit(QualifiedNameExpression nd);
1416

1517
public void visit(NullableLiteral nd);
1618

javascript/extractor/src/com/semmle/js/extractor/JSDocExtractor.java

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,14 @@
99
import com.semmle.js.ast.jsdoc.JSDocElement;
1010
import com.semmle.js.ast.jsdoc.JSDocTag;
1111
import com.semmle.js.ast.jsdoc.JSDocTypeExpression;
12-
import com.semmle.js.ast.jsdoc.NameExpression;
12+
import com.semmle.js.ast.jsdoc.Identifier;
1313
import com.semmle.js.ast.jsdoc.NonNullableType;
1414
import com.semmle.js.ast.jsdoc.NullLiteral;
1515
import com.semmle.js.ast.jsdoc.NullableLiteral;
1616
import com.semmle.js.ast.jsdoc.NullableType;
1717
import com.semmle.js.ast.jsdoc.OptionalType;
1818
import com.semmle.js.ast.jsdoc.ParameterType;
19+
import com.semmle.js.ast.jsdoc.QualifiedNameExpression;
1920
import com.semmle.js.ast.jsdoc.RecordType;
2021
import com.semmle.js.ast.jsdoc.RestType;
2122
import com.semmle.js.ast.jsdoc.TypeApplication;
@@ -42,7 +43,7 @@ public class JSDocExtractor {
4243
jsdocTypeExprKinds.put("UndefinedLiteral", 2);
4344
jsdocTypeExprKinds.put("NullableLiteral", 3);
4445
jsdocTypeExprKinds.put("VoidLiteral", 4);
45-
jsdocTypeExprKinds.put("NameExpression", 5);
46+
jsdocTypeExprKinds.put("Identifier", 5);
4647
jsdocTypeExprKinds.put("TypeApplication", 6);
4748
jsdocTypeExprKinds.put("NullableType", 7);
4849
jsdocTypeExprKinds.put("NonNullableType", 8);
@@ -52,6 +53,7 @@ public class JSDocExtractor {
5253
jsdocTypeExprKinds.put("FunctionType", 12);
5354
jsdocTypeExprKinds.put("OptionalType", 13);
5455
jsdocTypeExprKinds.put("RestType", 14);
56+
jsdocTypeExprKinds.put("QualifiedNameExpression", 15);
5557
}
5658

5759
private final TrapWriter trapwriter;
@@ -122,10 +124,17 @@ public void visit(RecordType nd) {
122124
}
123125

124126
@Override
125-
public void visit(NameExpression nd) {
127+
public void visit(Identifier nd) {
126128
visit((JSDocTypeExpression) nd);
127129
}
128130

131+
@Override
132+
public void visit(QualifiedNameExpression nd) {
133+
Label label = visit((JSDocTypeExpression) nd);
134+
visit(nd.getBase(), label, 0);
135+
visit(nd.getNameNode(), label, 1);
136+
}
137+
129138
@Override
130139
public void visit(NullableLiteral nd) {
131140
visit((JSDocTypeExpression) nd);

javascript/extractor/src/com/semmle/js/parser/JSDocParser.java

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,14 @@
1010
import com.semmle.js.ast.jsdoc.JSDocComment;
1111
import com.semmle.js.ast.jsdoc.JSDocTag;
1212
import com.semmle.js.ast.jsdoc.JSDocTypeExpression;
13-
import com.semmle.js.ast.jsdoc.NameExpression;
13+
import com.semmle.js.ast.jsdoc.Identifier;
1414
import com.semmle.js.ast.jsdoc.NonNullableType;
1515
import com.semmle.js.ast.jsdoc.NullLiteral;
1616
import com.semmle.js.ast.jsdoc.NullableLiteral;
1717
import com.semmle.js.ast.jsdoc.NullableType;
1818
import com.semmle.js.ast.jsdoc.OptionalType;
1919
import com.semmle.js.ast.jsdoc.ParameterType;
20+
import com.semmle.js.ast.jsdoc.QualifiedNameExpression;
2021
import com.semmle.js.ast.jsdoc.RecordType;
2122
import com.semmle.js.ast.jsdoc.RestType;
2223
import com.semmle.js.ast.jsdoc.TypeApplication;
@@ -827,10 +828,16 @@ private JSDocTypeExpression parseRecordType() throws ParseError {
827828
}
828829

829830
private JSDocTypeExpression parseNameExpression() throws ParseError {
830-
Object name = value;
831831
SourceLocation loc = loc();
832832
expect(Token.NAME);
833-
return finishNode(new NameExpression(loc, name.toString()));
833+
// Hacky initial implementation with wrong locations
834+
String[] parts = value.toString().split("\\.");
835+
JSDocTypeExpression node = finishNode(new Identifier(loc, parts[0]));
836+
for (int i = 1; i < parts.length; i++) {
837+
Identifier memberName = finishNode(new Identifier(loc, parts[i]));
838+
node = finishNode(new QualifiedNameExpression(loc, node, memberName));
839+
}
840+
return node;
834841
}
835842

836843
// TypeExpressionList :=
@@ -923,14 +930,14 @@ private List<JSDocTypeExpression> parseParametersType() throws ParseError {
923930

924931
SourceLocation loc = loc();
925932
expr = parseTypeExpression();
926-
if (expr instanceof NameExpression && token == Token.COLON) {
933+
if (expr instanceof Identifier && token == Token.COLON) {
927934
// Identifier ':' TypeExpression
928935
consume(Token.COLON);
929936
expr =
930937
finishNode(
931938
new ParameterType(
932939
new SourceLocation(loc),
933-
((NameExpression) expr).getName(),
940+
((Identifier) expr).getName(),
934941
parseTypeExpression()));
935942
}
936943
if (token == Token.EQUAL) {
@@ -1106,7 +1113,7 @@ private JSDocTypeExpression parseTypeExpression() throws ParseError {
11061113
consume(Token.RBRACK, "expected an array-style type declaration (' + value + '[])");
11071114
List<JSDocTypeExpression> expressions = new ArrayList<>();
11081115
expressions.add(expr);
1109-
NameExpression nameExpr = finishNode(new NameExpression(new SourceLocation(loc), "Array"));
1116+
Identifier nameExpr = finishNode(new Identifier(new SourceLocation(loc), "Array"));
11101117
return finishNode(new TypeApplication(loc, nameExpr, expressions));
11111118
}
11121119

@@ -1527,9 +1534,9 @@ public boolean parseName() {
15271534
// fixed at the end
15281535
if (isParamTitle(this._title)
15291536
&& this._tag.type != null
1530-
&& this._tag.type instanceof NameExpression) {
1531-
this._extra_name = ((NameExpression) this._tag.type).getName();
1532-
this._tag.name = ((NameExpression) this._tag.type).getName();
1537+
&& this._tag.type instanceof Identifier) {
1538+
this._extra_name = ((Identifier) this._tag.type).getName();
1539+
this._tag.name = ((Identifier) this._tag.type).getName();
15331540
this._tag.type = null;
15341541
} else {
15351542
if (!this.addError("Missing or invalid tag name")) {
@@ -1645,7 +1652,7 @@ private boolean epilogue() {
16451652
Position start = new Position(_tag.startLine, _tag.startColumn, _tag.startColumn);
16461653
Position end = new Position(_tag.startLine, _tag.startColumn, _tag.startColumn);
16471654
SourceLocation loc = new SourceLocation(_extra_name, start, end);
1648-
this._tag.type = new NameExpression(loc, _extra_name);
1655+
this._tag.type = new Identifier(loc, _extra_name);
16491656
}
16501657
this._tag.name = null;
16511658

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

Lines changed: 73 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -261,17 +261,14 @@ class JSDocVoidTypeExpr extends @jsdoc_void_type_expr, JSDocTypeExpr {
261261
}
262262

263263
/**
264-
* A type expression referring to a named type.
265-
*
266-
* Example:
264+
* An identifier in a JSDoc type expression, such as `Object` or `string`.
267265
*
268-
* ```
269-
* string
270-
* Object
271-
* ```
266+
* Note that qualified names consist of multiple identifier nodes.
272267
*/
273-
class JSDocNamedTypeExpr extends @jsdoc_named_type_expr, JSDocTypeExpr {
274-
/** Gets the name of the type the expression refers to. */
268+
class JSDocIdentifierTypeExpr extends @jsdoc_identifier_type_expr, JSDocTypeExpr {
269+
/**
270+
* Gets the name of the identifier.
271+
*/
275272
string getName() { result = this.toString() }
276273

277274
override predicate isString() { this.getName() = "string" }
@@ -300,6 +297,71 @@ class JSDocNamedTypeExpr extends @jsdoc_named_type_expr, JSDocTypeExpr {
300297
}
301298

302299
override predicate isRawFunction() { this.getName() = "Function" }
300+
}
301+
302+
/**
303+
* An unqualified identifier in a JSDoc type expression.
304+
*
305+
* Example:
306+
*
307+
* ```
308+
* string
309+
* Object
310+
* ```
311+
*/
312+
class JSDocLocalTypeAccess extends JSDocIdentifierTypeExpr {
313+
JSDocLocalTypeAccess() { not this = any(JSDocQualifiedTypeAccess a).getNameNode() }
314+
}
315+
316+
/**
317+
* A qualified type name in a JSDoc type expression, such as `X.Y`.
318+
*/
319+
class JSDocQualifiedTypeAccess extends @jsdoc_qualified_type_expr, JSDocTypeExpr {
320+
/**
321+
* Gets the base of this access, such as the `X` in `X.Y`.
322+
*/
323+
JSDocTypeExpr getBase() { result = this.getChild(0) }
324+
325+
/**
326+
* Gets the node naming the member being accessed, such as the `Y` node in `X.Y`.
327+
*/
328+
JSDocIdentifierTypeExpr getNameNode() { result = this.getChild(1) }
329+
330+
/**
331+
* Gets the name being accessed, such as `Y` in `X.Y`.
332+
*/
333+
string getName() { result = this.getNameNode().getName() }
334+
}
335+
336+
/**
337+
* A type expression referring to a named type.
338+
*
339+
* Example:
340+
*
341+
* ```
342+
* string
343+
* Object
344+
* Namespace.Type
345+
* ```
346+
*/
347+
class JSDocNamedTypeExpr extends JSDocTypeExpr {
348+
JSDocNamedTypeExpr() {
349+
this instanceof JSDocLocalTypeAccess
350+
or
351+
this instanceof JSDocQualifiedTypeAccess
352+
}
353+
354+
/**
355+
* Gets the name directly as it appears in this type, including any qualifiers.
356+
*
357+
* For example, for `X.Y` this gets the string `"X.Y"`.
358+
*/
359+
string getRawName() { result = this.toString() }
360+
361+
/**
362+
* DEPRECATED. Use `getRawName()` instead.
363+
*/
364+
deprecated string getName() { result = this.toString() }
303365

304366
/**
305367
* Holds if this name consists of the unqualified name `prefix`
@@ -311,7 +373,7 @@ class JSDocNamedTypeExpr extends @jsdoc_named_type_expr, JSDocTypeExpr {
311373
*/
312374
predicate hasNameParts(string prefix, string suffix) {
313375
exists(string regex, string name | regex = "([^.]+)(.*)" |
314-
name = this.getName() and
376+
name = this.getRawName() and
315377
prefix = name.regexpCapture(regex, 1) and
316378
suffix = name.regexpCapture(regex, 2)
317379
)
@@ -340,7 +402,7 @@ class JSDocNamedTypeExpr extends @jsdoc_named_type_expr, JSDocTypeExpr {
340402
globalName = this.resolvedName()
341403
or
342404
not exists(this.resolvedName()) and
343-
globalName = this.getName()
405+
globalName = this.getRawName()
344406
}
345407

346408
override DataFlow::ClassNode getClass() {

javascript/ql/lib/semmlecode.javascript.dbscheme

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1001,7 +1001,7 @@ case @jsdoc_type_expr.kind of
10011001
| 2 = @jsdoc_undefined_type_expr
10021002
| 3 = @jsdoc_unknown_type_expr
10031003
| 4 = @jsdoc_void_type_expr
1004-
| 5 = @jsdoc_named_type_expr
1004+
| 5 = @jsdoc_identifier_type_expr
10051005
| 6 = @jsdoc_applied_type_expr
10061006
| 7 = @jsdoc_nullable_type_expr
10071007
| 8 = @jsdoc_non_nullable_type_expr
@@ -1011,6 +1011,7 @@ case @jsdoc_type_expr.kind of
10111011
| 12 = @jsdoc_function_type_expr
10121012
| 13 = @jsdoc_optional_type_expr
10131013
| 14 = @jsdoc_rest_type_expr
1014+
| 15 = @jsdoc_qualified_type_expr
10141015
;
10151016

10161017
#keyset[id, idx]

0 commit comments

Comments
 (0)