Merge pull request #19077 from asgerf/js/jsdoc-name-tokens

JS: Separate JSDoc qualified names into individual identifiers
This commit is contained in:
Asger F
2025-03-27 14:22:11 +01:00
committed by GitHub
22 changed files with 5131 additions and 180 deletions

View File

@@ -0,0 +1,37 @@
// Removes all nodes nested inside a qualified type access,
// and changes qualified type access nodes to "named type" nodes.
//
/*
* jsdoc_type_exprs (unique int id: @jsdoc_type_expr,
* int kind: int ref,
* int parent: @jsdoc_type_expr_parent ref,
* int idx: int ref,
* varchar(900) tostring: string ref);
*/
class JSDocTypeExprParent extends @jsdoc_type_expr_parent {
string toString() { none() }
}
class JSDocTypeExpr extends @jsdoc_type_expr {
string toString() { none() }
JSDocTypeExpr getChild(int n) { jsdoc_type_exprs(result, _, this, n, _) }
int getNewKind() { jsdoc_type_exprs(this, result, _, _, _) }
predicate shouldRemove() { this = any(JSDocQualifiedTypeAccess a).getChild(_) }
}
class JSDocQualifiedTypeAccess extends @jsdoc_qualified_type_expr, JSDocTypeExpr {
override int getNewKind() {
result = 5
/* 5 = @jsdoc_named_type_expr */
}
}
from JSDocTypeExpr node, JSDocTypeExprParent parent, int idx, string tostring
where
jsdoc_type_exprs(node, _, parent, idx, tostring) and
not node.shouldRemove()
select node, node.getNewKind(), parent, idx, tostring

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,4 @@
description: split up qualified names in jsdoc type exprs
compatibility: backwards
jsdoc_type_exprs.rel: run jsdoc_type_exprs.ql

View File

@@ -2,12 +2,12 @@ package com.semmle.js.ast.jsdoc;
import com.semmle.js.ast.SourceLocation;
/** A named JSDoc type. */
public class NameExpression extends JSDocTypeExpression {
/** An identifier in a JSDoc type. */
public class Identifier extends JSDocTypeExpression {
private final String name;
public NameExpression(SourceLocation loc, String name) {
super(loc, "NameExpression");
public Identifier(SourceLocation loc, String name) {
super(loc, "Identifier");
this.name = name;
}

View File

@@ -0,0 +1,35 @@
package com.semmle.js.ast.jsdoc;
import com.semmle.js.ast.SourceLocation;
/** A qualified name in a JSDoc type. */
public class QualifiedNameExpression extends JSDocTypeExpression {
private final JSDocTypeExpression base;
private final Identifier name;
public QualifiedNameExpression(SourceLocation loc, JSDocTypeExpression base, Identifier name) {
super(loc, "QualifiedNameExpression");
this.base = base;
this.name = name;
}
@Override
public void accept(Visitor v) {
v.visit(this);
}
/** Returns the expression on the left side of the dot character. */
public JSDocTypeExpression getBase() {
return base;
}
/** Returns the identifier on the right-hand side of the dot character. */
public Identifier getNameNode() {
return name;
}
@Override
public String pp() {
return base.pp() + "." + name.pp();
}
}

View File

@@ -10,7 +10,9 @@ public interface Visitor {
public void visit(JSDocTag nd);
public void visit(NameExpression nd);
public void visit(Identifier nd);
public void visit(QualifiedNameExpression nd);
public void visit(NullableLiteral nd);

View File

@@ -9,13 +9,14 @@ import com.semmle.js.ast.jsdoc.JSDocComment;
import com.semmle.js.ast.jsdoc.JSDocElement;
import com.semmle.js.ast.jsdoc.JSDocTag;
import com.semmle.js.ast.jsdoc.JSDocTypeExpression;
import com.semmle.js.ast.jsdoc.NameExpression;
import com.semmle.js.ast.jsdoc.Identifier;
import com.semmle.js.ast.jsdoc.NonNullableType;
import com.semmle.js.ast.jsdoc.NullLiteral;
import com.semmle.js.ast.jsdoc.NullableLiteral;
import com.semmle.js.ast.jsdoc.NullableType;
import com.semmle.js.ast.jsdoc.OptionalType;
import com.semmle.js.ast.jsdoc.ParameterType;
import com.semmle.js.ast.jsdoc.QualifiedNameExpression;
import com.semmle.js.ast.jsdoc.RecordType;
import com.semmle.js.ast.jsdoc.RestType;
import com.semmle.js.ast.jsdoc.TypeApplication;
@@ -42,7 +43,7 @@ public class JSDocExtractor {
jsdocTypeExprKinds.put("UndefinedLiteral", 2);
jsdocTypeExprKinds.put("NullableLiteral", 3);
jsdocTypeExprKinds.put("VoidLiteral", 4);
jsdocTypeExprKinds.put("NameExpression", 5);
jsdocTypeExprKinds.put("Identifier", 5);
jsdocTypeExprKinds.put("TypeApplication", 6);
jsdocTypeExprKinds.put("NullableType", 7);
jsdocTypeExprKinds.put("NonNullableType", 8);
@@ -52,6 +53,7 @@ public class JSDocExtractor {
jsdocTypeExprKinds.put("FunctionType", 12);
jsdocTypeExprKinds.put("OptionalType", 13);
jsdocTypeExprKinds.put("RestType", 14);
jsdocTypeExprKinds.put("QualifiedNameExpression", 15);
}
private final TrapWriter trapwriter;
@@ -122,10 +124,17 @@ public class JSDocExtractor {
}
@Override
public void visit(NameExpression nd) {
public void visit(Identifier nd) {
visit((JSDocTypeExpression) nd);
}
@Override
public void visit(QualifiedNameExpression nd) {
Label label = visit((JSDocTypeExpression) nd);
visit(nd.getBase(), label, 0);
visit(nd.getNameNode(), label, 1);
}
@Override
public void visit(NullableLiteral nd) {
visit((JSDocTypeExpression) nd);

View File

@@ -42,7 +42,7 @@ public class Main {
* A version identifier that should be updated every time the extractor changes in such a way that
* it may produce different tuples for the same file under the same {@link ExtractorConfig}.
*/
public static final String EXTRACTOR_VERSION = "2025-02-03";
public static final String EXTRACTOR_VERSION = "2025-03-20";
public static final Pattern NEWLINE = Pattern.compile("\n");

View File

@@ -10,13 +10,14 @@ import com.semmle.js.ast.jsdoc.FunctionType;
import com.semmle.js.ast.jsdoc.JSDocComment;
import com.semmle.js.ast.jsdoc.JSDocTag;
import com.semmle.js.ast.jsdoc.JSDocTypeExpression;
import com.semmle.js.ast.jsdoc.NameExpression;
import com.semmle.js.ast.jsdoc.Identifier;
import com.semmle.js.ast.jsdoc.NonNullableType;
import com.semmle.js.ast.jsdoc.NullLiteral;
import com.semmle.js.ast.jsdoc.NullableLiteral;
import com.semmle.js.ast.jsdoc.NullableType;
import com.semmle.js.ast.jsdoc.OptionalType;
import com.semmle.js.ast.jsdoc.ParameterType;
import com.semmle.js.ast.jsdoc.QualifiedNameExpression;
import com.semmle.js.ast.jsdoc.RecordType;
import com.semmle.js.ast.jsdoc.RestType;
import com.semmle.js.ast.jsdoc.TypeApplication;
@@ -70,30 +71,6 @@ public class JSDocParser {
return new JSDocComment(comment, r.fst(), tags);
}
/** Specification of Doctrine AST types for JSDoc type expressions. */
private static final Map<Class<? extends JSDocTypeExpression>, List<String>> spec =
new LinkedHashMap<Class<? extends JSDocTypeExpression>, List<String>>();
static {
spec.put(AllLiteral.class, Arrays.<String>asList());
spec.put(ArrayType.class, Arrays.asList("elements"));
spec.put(FieldType.class, Arrays.asList("key", "value"));
spec.put(FunctionType.class, Arrays.asList("this", "new", "params", "result"));
spec.put(NameExpression.class, Arrays.asList("name"));
spec.put(NonNullableType.class, Arrays.asList("expression", "prefix"));
spec.put(NullableLiteral.class, Arrays.<String>asList());
spec.put(NullLiteral.class, Arrays.<String>asList());
spec.put(NullableType.class, Arrays.asList("expression", "prefix"));
spec.put(OptionalType.class, Arrays.asList("expression"));
spec.put(ParameterType.class, Arrays.asList("name", "expression"));
spec.put(RecordType.class, Arrays.asList("fields"));
spec.put(RestType.class, Arrays.asList("expression"));
spec.put(TypeApplication.class, Arrays.asList("expression", "applications"));
spec.put(UndefinedLiteral.class, Arrays.<String>asList());
spec.put(UnionType.class, Arrays.asList("elements"));
spec.put(VoidLiteral.class, Arrays.<String>asList());
}
private static String sliceSource(String source, int index, int last) {
if (index >= source.length()) return "";
if (last > source.length()) last = source.length();
@@ -137,7 +114,7 @@ public class JSDocParser {
}
private static boolean isTypeName(char ch) {
return "><(){}[],:*|?!=".indexOf(ch) == -1 && !isWhiteSpace(ch) && !isLineTerminator(ch);
return "><(){}[],:*|?!=.".indexOf(ch) == -1 && !isWhiteSpace(ch) && !isLineTerminator(ch);
}
private static boolean isParamTitle(String title) {
@@ -559,20 +536,9 @@ public class JSDocParser {
}
private Token scanTypeName() {
char ch, ch2;
StringBuilder sb = new StringBuilder();
sb.append((char)advance());
while (index < endIndex && isTypeName(source.charAt(index))) {
ch = source.charAt(index);
if (ch == '.') {
if ((index + 1) < endIndex) {
ch2 = source.charAt(index + 1);
if (ch2 == '<') {
break;
}
}
}
sb.append((char)advance());
}
value = sb.toString();
@@ -850,11 +816,24 @@ public class JSDocParser {
return finishNode(new RecordType(loc, fields));
}
private JSDocTypeExpression parseNameExpression() throws ParseError {
Object name = value;
private Identifier parseIdentifier() throws ParseError {
SourceLocation loc = loc();
Object value = this.value; // save the value of the current token
expect(Token.NAME);
return finishNode(new NameExpression(loc, name.toString()));
return finishNode(new Identifier(loc, value.toString()));
}
private JSDocTypeExpression parseNameExpression() throws ParseError {
JSDocTypeExpression node = parseIdentifier();
while (token == Token.DOT) {
consume(Token.DOT);
Identifier memberName = parseIdentifier();
// Create a SourceLocation object with the correct start location.
// The call to finishNode() will set the end location.
SourceLocation loc = new SourceLocation(node.getLoc());
node = finishNode(new QualifiedNameExpression(loc, node, memberName));
}
return node;
}
// TypeExpressionList :=
@@ -947,14 +926,14 @@ public class JSDocParser {
SourceLocation loc = loc();
expr = parseTypeExpression();
if (expr instanceof NameExpression && token == Token.COLON) {
if (expr instanceof Identifier && token == Token.COLON) {
// Identifier ':' TypeExpression
consume(Token.COLON);
expr =
finishNode(
new ParameterType(
new SourceLocation(loc),
((NameExpression) expr).getName(),
((Identifier) expr).getName(),
parseTypeExpression()));
}
if (token == Token.EQUAL) {
@@ -1130,7 +1109,7 @@ public class JSDocParser {
consume(Token.RBRACK, "expected an array-style type declaration (' + value + '[])");
List<JSDocTypeExpression> expressions = new ArrayList<>();
expressions.add(expr);
NameExpression nameExpr = finishNode(new NameExpression(new SourceLocation(loc), "Array"));
Identifier nameExpr = finishNode(new Identifier(new SourceLocation(loc), "Array"));
return finishNode(new TypeApplication(loc, nameExpr, expressions));
}
@@ -1551,9 +1530,9 @@ public class JSDocParser {
// fixed at the end
if (isParamTitle(this._title)
&& this._tag.type != null
&& this._tag.type instanceof NameExpression) {
this._extra_name = ((NameExpression) this._tag.type).getName();
this._tag.name = ((NameExpression) this._tag.type).getName();
&& this._tag.type instanceof Identifier) {
this._extra_name = ((Identifier) this._tag.type).getName();
this._tag.name = ((Identifier) this._tag.type).getName();
this._tag.type = null;
} else {
if (!this.addError("Missing or invalid tag name")) {
@@ -1669,7 +1648,7 @@ public class JSDocParser {
Position start = new Position(_tag.startLine, _tag.startColumn, _tag.startColumn);
Position end = new Position(_tag.startLine, _tag.startColumn, _tag.startColumn);
SourceLocation loc = new SourceLocation(_extra_name, start, end);
this._tag.type = new NameExpression(loc, _extra_name);
this._tag.type = new Identifier(loc, _extra_name);
}
this._tag.name = null;

View File

@@ -517,150 +517,170 @@ jsdoc_type_exprs(#20157,4,#20145,-1,"void")
locations_default(#20158,#10000,11,60,11,63)
hasLocation(#20157,#20158)
#20159=*
jsdoc_type_exprs(#20159,5,#20145,-2,"goog.ui.Menu")
jsdoc_type_exprs(#20159,15,#20145,-2,"goog.ui.Menu")
#20160=@"loc,{#10000},11,26,11,37"
locations_default(#20160,#10000,11,26,11,37)
hasLocation(#20159,#20160)
jsdoc_has_new_parameter(#20145)
#20161=*
jsdoc_tags(#20161,"param",#20117,4,"@param")
#20162=@"loc,{#10000},12,5,12,10"
locations_default(#20162,#10000,12,5,12,10)
jsdoc_type_exprs(#20161,15,#20159,0,"goog.ui")
#20162=@"loc,{#10000},11,26,11,32"
locations_default(#20162,#10000,11,26,11,32)
hasLocation(#20161,#20162)
jsdoc_tag_names(#20161,"var_args")
#20163=*
jsdoc_type_exprs(#20163,14,#20161,0,"...number")
#20164=@"loc,{#10000},12,13,12,21"
locations_default(#20164,#10000,12,13,12,21)
jsdoc_type_exprs(#20163,5,#20161,0,"goog")
#20164=@"loc,{#10000},11,26,11,29"
locations_default(#20164,#10000,11,26,11,29)
hasLocation(#20163,#20164)
#20165=*
jsdoc_type_exprs(#20165,5,#20163,0,"number")
#20166=@"loc,{#10000},12,16,12,21"
locations_default(#20166,#10000,12,16,12,21)
jsdoc_type_exprs(#20165,5,#20161,1,"ui")
#20166=@"loc,{#10000},11,31,11,32"
locations_default(#20166,#10000,11,31,11,32)
hasLocation(#20165,#20166)
#20167=*
jsdoc(#20167,"",#20010)
hasLocation(#20167,#20011)
#20168=*
jsdoc_tags(#20168,"param",#20167,0,"@param")
#20169=@"loc,{#10000},15,4,15,9"
locations_default(#20169,#10000,15,4,15,9)
hasLocation(#20168,#20169)
#20170=*
jsdoc_errors(#20170,#20168,"Missing or invalid tag name","Missing ... ag name")
jsdoc_type_exprs(#20167,5,#20159,1,"Menu")
#20168=@"loc,{#10000},11,34,11,37"
locations_default(#20168,#10000,11,34,11,37)
hasLocation(#20167,#20168)
jsdoc_has_new_parameter(#20145)
#20169=*
jsdoc_tags(#20169,"param",#20117,4,"@param")
#20170=@"loc,{#10000},12,5,12,10"
locations_default(#20170,#10000,12,5,12,10)
hasLocation(#20169,#20170)
jsdoc_tag_names(#20169,"var_args")
#20171=*
jsdoc_tags(#20171,"param",#20167,1,"@param")
#20172=@"loc,{#10000},16,4,16,9"
locations_default(#20172,#10000,16,4,16,9)
jsdoc_type_exprs(#20171,14,#20169,0,"...number")
#20172=@"loc,{#10000},12,13,12,21"
locations_default(#20172,#10000,12,13,12,21)
hasLocation(#20171,#20172)
jsdoc_tag_names(#20171,"x")
#20173=*
jsdoc(#20173,"",#20012)
hasLocation(#20173,#20013)
#20174=*
jsdoc_tags(#20174,"",#20173,0,"@")
#20175=@"loc,{#10000},20,4,20,4"
locations_default(#20175,#10000,20,4,20,4)
hasLocation(#20174,#20175)
jsdoc_tag_descriptions(#20174,"{link a}")
jsdoc_type_exprs(#20173,5,#20171,0,"number")
#20174=@"loc,{#10000},12,16,12,21"
locations_default(#20174,#10000,12,16,12,21)
hasLocation(#20173,#20174)
#20175=*
jsdoc(#20175,"",#20010)
hasLocation(#20175,#20011)
#20176=*
jsdoc_errors(#20176,#20174,"Missing or invalid title","Missing ... d title")
#20177=*
jsdoc(#20177,"",#20014)
hasLocation(#20177,#20015)
jsdoc_tags(#20176,"param",#20175,0,"@param")
#20177=@"loc,{#10000},15,4,15,9"
locations_default(#20177,#10000,15,4,15,9)
hasLocation(#20176,#20177)
#20178=*
jsdoc_tags(#20178,"typedef",#20177,0,"@typedef")
#20179=@"loc,{#10000},24,4,24,11"
locations_default(#20179,#10000,24,4,24,11)
hasLocation(#20178,#20179)
jsdoc_tag_descriptions(#20178,"{a}")
#20180=*
jsdoc_errors(#20180,#20178,"Missing or invalid tag type","Missing ... ag type")
jsdoc_errors(#20178,#20176,"Missing or invalid tag name","Missing ... ag name")
#20179=*
jsdoc_tags(#20179,"param",#20175,1,"@param")
#20180=@"loc,{#10000},16,4,16,9"
locations_default(#20180,#10000,16,4,16,9)
hasLocation(#20179,#20180)
jsdoc_tag_names(#20179,"x")
#20181=*
jsdoc(#20181,"[resize description]",#20016)
hasLocation(#20181,#20017)
jsdoc(#20181,"",#20012)
hasLocation(#20181,#20013)
#20182=*
jsdoc_tags(#20182,"param",#20181,0,"@param")
#20183=@"loc,{#10000},30,4,30,9"
locations_default(#20183,#10000,30,4,30,9)
jsdoc_tags(#20182,"",#20181,0,"@")
#20183=@"loc,{#10000},20,4,20,4"
locations_default(#20183,#10000,20,4,20,4)
hasLocation(#20182,#20183)
jsdoc_tag_descriptions(#20182,"[description]
")
jsdoc_tag_names(#20182,"w")
jsdoc_tag_descriptions(#20182,"{link a}")
#20184=*
jsdoc_type_exprs(#20184,10,#20182,0,"[type]")
#20185=@"loc,{#10000},30,13,30,18"
locations_default(#20185,#10000,30,13,30,18)
hasLocation(#20184,#20185)
jsdoc_errors(#20184,#20182,"Missing or invalid title","Missing ... d title")
#20185=*
jsdoc(#20185,"",#20014)
hasLocation(#20185,#20015)
#20186=*
jsdoc_type_exprs(#20186,5,#20184,0,"type")
#20187=@"loc,{#10000},30,14,30,17"
locations_default(#20187,#10000,30,14,30,17)
jsdoc_tags(#20186,"typedef",#20185,0,"@typedef")
#20187=@"loc,{#10000},24,4,24,11"
locations_default(#20187,#10000,24,4,24,11)
hasLocation(#20186,#20187)
jsdoc_tag_descriptions(#20186,"{a}")
#20188=*
jsdoc_tags(#20188,"param",#20181,1,"@param")
#20189=@"loc,{#10000},31,4,31,9"
locations_default(#20189,#10000,31,4,31,9)
hasLocation(#20188,#20189)
jsdoc_tag_descriptions(#20188,"[description]
")
jsdoc_errors(#20188,#20186,"Missing or invalid tag type","Missing ... ag type")
#20189=*
jsdoc(#20189,"[resize description]",#20016)
hasLocation(#20189,#20017)
#20190=*
jsdoc_tags(#20190,"return",#20181,2,"@return")
#20191=@"loc,{#10000},32,4,32,10"
locations_default(#20191,#10000,32,4,32,10)
jsdoc_tags(#20190,"param",#20189,0,"@param")
#20191=@"loc,{#10000},30,4,30,9"
locations_default(#20191,#10000,30,4,30,9)
hasLocation(#20190,#20191)
jsdoc_tag_descriptions(#20190,"[description]")
jsdoc_tag_descriptions(#20190,"[description]
")
jsdoc_tag_names(#20190,"w")
#20192=*
jsdoc_type_exprs(#20192,10,#20190,0,"[type]")
#20193=@"loc,{#10000},32,13,32,18"
locations_default(#20193,#10000,32,13,32,18)
#20193=@"loc,{#10000},30,13,30,18"
locations_default(#20193,#10000,30,13,30,18)
hasLocation(#20192,#20193)
#20194=*
jsdoc_type_exprs(#20194,5,#20192,0,"type")
#20195=@"loc,{#10000},32,14,32,17"
locations_default(#20195,#10000,32,14,32,17)
#20195=@"loc,{#10000},30,14,30,17"
locations_default(#20195,#10000,30,14,30,17)
hasLocation(#20194,#20195)
#20196=*
jsdoc(#20196,"",#20018)
hasLocation(#20196,#20019)
#20197=*
jsdoc_tags(#20197,"exports",#20196,0,"@exports")
#20198=@"loc,{#10000},36,3,36,10"
locations_default(#20198,#10000,36,3,36,10)
hasLocation(#20197,#20198)
jsdoc_tag_descriptions(#20197,"R
jsdoc_tags(#20196,"param",#20189,1,"@param")
#20197=@"loc,{#10000},31,4,31,9"
locations_default(#20197,#10000,31,4,31,9)
hasLocation(#20196,#20197)
jsdoc_tag_descriptions(#20196,"[description]
")
#20199=*
jsdoc(#20199,"",#20020)
hasLocation(#20199,#20021)
#20198=*
jsdoc_tags(#20198,"return",#20189,2,"@return")
#20199=@"loc,{#10000},32,4,32,10"
locations_default(#20199,#10000,32,4,32,10)
hasLocation(#20198,#20199)
jsdoc_tag_descriptions(#20198,"[description]")
#20200=*
jsdoc_tags(#20200,"typedef",#20199,0,"@typedef")
#20201=@"loc,{#10000},41,4,41,11"
locations_default(#20201,#10000,41,4,41,11)
jsdoc_type_exprs(#20200,10,#20198,0,"[type]")
#20201=@"loc,{#10000},32,13,32,18"
locations_default(#20201,#10000,32,13,32,18)
hasLocation(#20200,#20201)
#20202=*
jsdoc_type_exprs(#20202,9,#20200,0,"{0: number}")
#20203=@"loc,{#10000},41,14,41,24"
locations_default(#20203,#10000,41,14,41,24)
jsdoc_type_exprs(#20202,5,#20200,0,"type")
#20203=@"loc,{#10000},32,14,32,17"
locations_default(#20203,#10000,32,14,32,17)
hasLocation(#20202,#20203)
jsdoc_record_field_name(#20202,0,"0")
#20204=*
jsdoc_type_exprs(#20204,5,#20202,0,"number")
#20205=@"loc,{#10000},41,18,41,23"
locations_default(#20205,#10000,41,18,41,23)
hasLocation(#20204,#20205)
toplevels(#20001,0)
#20206=@"loc,{#10000},1,1,43,0"
locations_default(#20206,#10000,1,1,43,0)
hasLocation(#20001,#20206)
jsdoc(#20204,"",#20018)
hasLocation(#20204,#20019)
#20205=*
jsdoc_tags(#20205,"exports",#20204,0,"@exports")
#20206=@"loc,{#10000},36,3,36,10"
locations_default(#20206,#10000,36,3,36,10)
hasLocation(#20205,#20206)
jsdoc_tag_descriptions(#20205,"R
")
#20207=*
entry_cfg_node(#20207,#20001)
#20208=@"loc,{#10000},1,1,1,0"
locations_default(#20208,#10000,1,1,1,0)
hasLocation(#20207,#20208)
#20209=*
exit_cfg_node(#20209,#20001)
hasLocation(#20209,#20105)
successor(#20207,#20209)
jsdoc(#20207,"",#20020)
hasLocation(#20207,#20021)
#20208=*
jsdoc_tags(#20208,"typedef",#20207,0,"@typedef")
#20209=@"loc,{#10000},41,4,41,11"
locations_default(#20209,#10000,41,4,41,11)
hasLocation(#20208,#20209)
#20210=*
jsdoc_type_exprs(#20210,9,#20208,0,"{0: number}")
#20211=@"loc,{#10000},41,14,41,24"
locations_default(#20211,#10000,41,14,41,24)
hasLocation(#20210,#20211)
jsdoc_record_field_name(#20210,0,"0")
#20212=*
jsdoc_type_exprs(#20212,5,#20210,0,"number")
#20213=@"loc,{#10000},41,18,41,23"
locations_default(#20213,#10000,41,18,41,23)
hasLocation(#20212,#20213)
toplevels(#20001,0)
#20214=@"loc,{#10000},1,1,43,0"
locations_default(#20214,#10000,1,1,43,0)
hasLocation(#20001,#20214)
#20215=*
entry_cfg_node(#20215,#20001)
#20216=@"loc,{#10000},1,1,1,0"
locations_default(#20216,#10000,1,1,1,0)
hasLocation(#20215,#20216)
#20217=*
exit_cfg_node(#20217,#20001)
hasLocation(#20217,#20105)
successor(#20215,#20217)
numlines(#10000,42,0,37)
filetype(#10000,"javascript")

View File

@@ -296,7 +296,7 @@ module DOM {
.getType()
.getAnUnderlyingType()
.(JSDocNamedTypeExpr)
.getName())
.getRawName())
)
}

View File

@@ -400,8 +400,8 @@ class ConstructorTag extends JSDocTag {
abstract private class NamedTypeReferent extends JSDocTag {
/** Gets the name of the type to which this tag refers. */
string getTarget() {
result = this.getType().(JSDocNamedTypeExpr).getName() or
result = this.getType().(JSDocAppliedTypeExpr).getHead().(JSDocNamedTypeExpr).getName()
result = this.getType().(JSDocNamedTypeExpr).getRawName() or
result = this.getType().(JSDocAppliedTypeExpr).getHead().(JSDocNamedTypeExpr).getRawName()
}
/**
@@ -423,7 +423,7 @@ abstract private class NamedTypeReferent extends JSDocTag {
* Gets the source declaration of the type to which `tp` refers, if any.
*/
private ExternalType sourceDecl(JSDocTypeExpr tp) {
result.getQualifiedName() = tp.(JSDocNamedTypeExpr).getName() or
result.getQualifiedName() = tp.(JSDocNamedTypeExpr).getRawName() or
result = sourceDecl(tp.(JSDocAppliedTypeExpr).getHead()) or
result = sourceDecl(tp.(JSDocNullableTypeExpr).getTypeExpr()) or
result = sourceDecl(tp.(JSDocNonNullableTypeExpr).getTypeExpr()) or

View File

@@ -261,17 +261,14 @@ class JSDocVoidTypeExpr extends @jsdoc_void_type_expr, JSDocTypeExpr {
}
/**
* A type expression referring to a named type.
* An identifier in a JSDoc type expression, such as `Object` or `string`.
*
* Example:
*
* ```
* string
* Object
* ```
* Note that qualified names consist of multiple identifier nodes.
*/
class JSDocNamedTypeExpr extends @jsdoc_named_type_expr, JSDocTypeExpr {
/** Gets the name of the type the expression refers to. */
class JSDocIdentifierTypeExpr extends @jsdoc_identifier_type_expr, JSDocTypeExpr {
/**
* Gets the name of the identifier.
*/
string getName() { result = this.toString() }
override predicate isString() { this.getName() = "string" }
@@ -300,6 +297,71 @@ class JSDocNamedTypeExpr extends @jsdoc_named_type_expr, JSDocTypeExpr {
}
override predicate isRawFunction() { this.getName() = "Function" }
}
/**
* An unqualified identifier in a JSDoc type expression.
*
* Example:
*
* ```
* string
* Object
* ```
*/
class JSDocLocalTypeAccess extends JSDocIdentifierTypeExpr {
JSDocLocalTypeAccess() { not this = any(JSDocQualifiedTypeAccess a).getNameNode() }
}
/**
* A qualified type name in a JSDoc type expression, such as `X.Y`.
*/
class JSDocQualifiedTypeAccess extends @jsdoc_qualified_type_expr, JSDocTypeExpr {
/**
* Gets the base of this access, such as the `X` in `X.Y`.
*/
JSDocTypeExpr getBase() { result = this.getChild(0) }
/**
* Gets the node naming the member being accessed, such as the `Y` node in `X.Y`.
*/
JSDocIdentifierTypeExpr getNameNode() { result = this.getChild(1) }
/**
* Gets the name being accessed, such as `Y` in `X.Y`.
*/
string getName() { result = this.getNameNode().getName() }
}
/**
* A type expression referring to a named type.
*
* Example:
*
* ```
* string
* Object
* Namespace.Type
* ```
*/
class JSDocNamedTypeExpr extends JSDocTypeExpr {
JSDocNamedTypeExpr() {
this instanceof JSDocLocalTypeAccess
or
this instanceof JSDocQualifiedTypeAccess
}
/**
* Gets the name directly as it appears in this type, including any qualifiers.
*
* For example, for `X.Y` this gets the string `"X.Y"`.
*/
string getRawName() { result = this.toString() }
/**
* DEPRECATED. Use `getRawName()` instead.
*/
deprecated string getName() { result = this.toString() }
/**
* Holds if this name consists of the unqualified name `prefix`
@@ -310,8 +372,9 @@ class JSDocNamedTypeExpr extends @jsdoc_named_type_expr, JSDocTypeExpr {
* - `Baz` has prefix `Baz` and an empty suffix.
*/
predicate hasNameParts(string prefix, string suffix) {
not this = any(JSDocQualifiedTypeAccess a).getBase() and // restrict size of predicate
exists(string regex, string name | regex = "([^.]+)(.*)" |
name = this.getName() and
name = this.getRawName() and
prefix = name.regexpCapture(regex, 1) and
suffix = name.regexpCapture(regex, 2)
)
@@ -340,7 +403,7 @@ class JSDocNamedTypeExpr extends @jsdoc_named_type_expr, JSDocTypeExpr {
globalName = this.resolvedName()
or
not exists(this.resolvedName()) and
globalName = this.getName()
globalName = this.getRawName()
}
override DataFlow::ClassNode getClass() {

View File

@@ -1001,7 +1001,7 @@ case @jsdoc_type_expr.kind of
| 2 = @jsdoc_undefined_type_expr
| 3 = @jsdoc_unknown_type_expr
| 4 = @jsdoc_void_type_expr
| 5 = @jsdoc_named_type_expr
| 5 = @jsdoc_identifier_type_expr
| 6 = @jsdoc_applied_type_expr
| 7 = @jsdoc_nullable_type_expr
| 8 = @jsdoc_non_nullable_type_expr
@@ -1011,6 +1011,7 @@ case @jsdoc_type_expr.kind of
| 12 = @jsdoc_function_type_expr
| 13 = @jsdoc_optional_type_expr
| 14 = @jsdoc_rest_type_expr
| 15 = @jsdoc_qualified_type_expr
;
#keyset[id, idx]

View File

@@ -1334,10 +1334,14 @@
<v>8</v>
</e>
<e>
<k>@jsdoc_named_type_expr</k>
<k>@jsdoc_identifier_type_expr</k>
<v>18639</v>
</e>
<e>
<k>@jsdoc_qualified_type_expr</k>
<v>1000</v>
</e>
<e>
<k>@jsdoc_applied_type_expr</k>
<v>303</v>
</e>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,2 @@
description: split up qualified names in jsdoc type exprs
compatibility: partial

View File

@@ -1,5 +1,10 @@
| bar.js:5:14:5:14 | x | x |
| bar.js:5:14:5:18 | x.Foo | ns.very.long.namespace.Foo |
| bar.js:12:14:12:17 | iife | iife |
| bar.js:12:14:12:21 | iife.Foo | IIFE.Foo |
| closure.js:8:12:8:15 | goog | goog |
| closure.js:8:12:8:19 | goog.net | goog.net |
| closure.js:8:12:8:28 | goog.net.SomeType | goog.net.SomeType |
| closure.js:9:12:9:14 | net | net |
| closure.js:9:12:9:23 | net.SomeType | goog.net.SomeType |
| closure.js:10:12:10:19 | SomeType | goog.net.SomeType |

View File

@@ -278,7 +278,11 @@ test_JSDocTypeExpr
| tst.js:26:14:26:20 | boolean | tst.js:26:5:26:11 | @define | 0 |
| tst.js:31:13:31:19 | boolean | tst.js:31:4:31:10 | @return | 0 |
| tst.js:53:11:53:16 | number | tst.js:53:4:53:8 | @enum | 0 |
| tst.js:68:14:68:17 | goog | tst.js:68:14:68:20 | goog.ds | 0 |
| tst.js:68:14:68:20 | goog.ds | tst.js:68:14:68:34 | goog.ds.BasicNodeList | 0 |
| tst.js:68:14:68:34 | goog.ds.BasicNodeList | tst.js:68:4:68:11 | @extends | 0 |
| tst.js:68:19:68:20 | ds | tst.js:68:14:68:20 | goog.ds | 1 |
| tst.js:68:22:68:34 | BasicNodeList | tst.js:68:14:68:34 | goog.ds.BasicNodeList | 1 |
| tst.js:95:17:95:21 | Shape | tst.js:95:4:95:14 | @implements | 0 |
| tst.js:110:14:110:18 | Shape | tst.js:110:4:110:11 | @extends | 0 |
| tst.js:134:13:134:18 | Object | tst.js:134:4:134:10 | @return | 0 |
@@ -298,7 +302,9 @@ test_JSDocTypeExpr
| tst.js:216:15:216:29 | (string\|number) | tst.js:216:5:216:12 | @typedef | 0 |
| tst.js:216:16:216:21 | string | tst.js:216:15:216:29 | (string\|number) | 0 |
| tst.js:216:23:216:28 | number | tst.js:216:15:216:29 | (string\|number) | 1 |
| tst.js:219:13:219:16 | goog | tst.js:219:13:219:27 | goog.NumberLike | 0 |
| tst.js:219:13:219:27 | goog.NumberLike | tst.js:219:5:219:10 | @param | 0 |
| tst.js:219:18:219:27 | NumberLike | tst.js:219:13:219:27 | goog.NumberLike | 1 |
| tst.js:223:12:223:36 | {myNum: number, myObject} | tst.js:223:5:223:9 | @type | 0 |
| tst.js:223:20:223:25 | number | tst.js:223:12:223:36 | {myNum: number, myObject} | 0 |
| tst.js:226:12:226:17 | number | tst.js:226:12:226:18 | number? | 0 |
@@ -311,10 +317,18 @@ test_JSDocTypeExpr
| tst.js:234:12:234:29 | function (): number | tst.js:234:4:234:9 | @param | 0 |
| tst.js:234:24:234:29 | number | tst.js:234:12:234:29 | function (): number | -1 |
| tst.js:235:12:235:46 | function (this: goog.ui.Menu, string) | tst.js:235:4:235:9 | @param | 0 |
| tst.js:235:26:235:29 | goog | tst.js:235:26:235:32 | goog.ui | 0 |
| tst.js:235:26:235:32 | goog.ui | tst.js:235:26:235:37 | goog.ui.Menu | 0 |
| tst.js:235:26:235:37 | goog.ui.Menu | tst.js:235:12:235:46 | function (this: goog.ui.Menu, string) | -2 |
| tst.js:235:31:235:32 | ui | tst.js:235:26:235:32 | goog.ui | 1 |
| tst.js:235:34:235:37 | Menu | tst.js:235:26:235:37 | goog.ui.Menu | 1 |
| tst.js:235:40:235:45 | string | tst.js:235:12:235:46 | function (this: goog.ui.Menu, string) | 0 |
| tst.js:236:12:236:45 | function (new: goog.ui.Menu, string) | tst.js:236:4:236:9 | @param | 0 |
| tst.js:236:25:236:28 | goog | tst.js:236:25:236:31 | goog.ui | 0 |
| tst.js:236:25:236:31 | goog.ui | tst.js:236:25:236:36 | goog.ui.Menu | 0 |
| tst.js:236:25:236:36 | goog.ui.Menu | tst.js:236:12:236:45 | function (new: goog.ui.Menu, string) | -2 |
| tst.js:236:30:236:31 | ui | tst.js:236:25:236:31 | goog.ui | 1 |
| tst.js:236:33:236:36 | Menu | tst.js:236:25:236:36 | goog.ui.Menu | 1 |
| tst.js:236:39:236:44 | string | tst.js:236:12:236:45 | function (new: goog.ui.Menu, string) | 0 |
| tst.js:237:12:237:48 | function (string, ...[number]): number | tst.js:237:4:237:9 | @param | 0 |
| tst.js:237:21:237:26 | string | tst.js:237:12:237:48 | function (string, ...[number]): number | 0 |

View File

@@ -5,6 +5,8 @@ test_isNumber
test_QualifiedName
| VarType | tst.js:9:13:9:19 | VarType |
| boolean | tst.js:5:14:5:20 | boolean |
| foo | tst.js:4:12:4:14 | foo |
| foo.bar | tst.js:4:12:4:18 | foo.bar |
| foo.bar.baz | tst.js:4:12:4:22 | foo.bar.baz |
| number | tst.js:3:12:3:17 | number |
| string | tst.js:2:12:2:17 | string |