Skip to content

Commit f624c0f

Browse files
Merge pull request microsoft#3296 from Microsoft/jsDocClassification
Add syntactic classification for doc comments.
2 parents c25418a + 8fcd29f commit f624c0f

File tree

6 files changed

+184
-16
lines changed

6 files changed

+184
-16
lines changed

src/compiler/parser.ts

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22
/// <reference path="utilities.ts"/>
33

44
module ts {
5-
export var throwOnJSDocErrors = false;
6-
75
let nodeConstructors = new Array<new () => Node>(SyntaxKind.Count);
86
/* @internal */ export let parseTime = 0;
97

@@ -4993,13 +4991,6 @@ module ts {
49934991
return finishNode(result);
49944992
}
49954993

4996-
function setError(message: DiagnosticMessage) {
4997-
parseErrorAtCurrentToken(message);
4998-
if (throwOnJSDocErrors) {
4999-
throw new Error(message.key);
5000-
}
5001-
}
5002-
50034994
function parseJSDocTopLevelType(): JSDocType {
50044995
var type = parseJSDocType();
50054996
if (token === SyntaxKind.BarToken) {

src/services/services.ts

Lines changed: 117 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1510,6 +1510,7 @@ module ts {
15101510
public static typeParameterName = "type parameter name";
15111511
public static typeAliasName = "type alias name";
15121512
public static parameterName = "parameter name";
1513+
public static docCommentTagName = "doc comment tag name";
15131514
}
15141515

15151516
export const enum ClassificationType {
@@ -1529,7 +1530,8 @@ module ts {
15291530
moduleName = 14,
15301531
typeParameterName = 15,
15311532
typeAliasName = 16,
1532-
parameterName = 17
1533+
parameterName = 17,
1534+
docCommentTagName = 18,
15331535
}
15341536

15351537
/// Language Service
@@ -1813,7 +1815,8 @@ module ts {
18131815
}
18141816

18151817
export function createLanguageServiceSourceFile(fileName: string, scriptSnapshot: IScriptSnapshot, scriptTarget: ScriptTarget, version: string, setNodeParents: boolean): SourceFile {
1816-
let sourceFile = createSourceFile(fileName, scriptSnapshot.getText(0, scriptSnapshot.getLength()), scriptTarget, setNodeParents);
1818+
let text = scriptSnapshot.getText(0, scriptSnapshot.getLength());
1819+
let sourceFile = createSourceFile(fileName, text, scriptTarget, setNodeParents);
18171820
setSourceFileFields(sourceFile, scriptSnapshot, version);
18181821
// after full parsing we can use table with interned strings as name table
18191822
sourceFile.nameTable = sourceFile.identifiers;
@@ -2837,6 +2840,7 @@ module ts {
28372840
let typeChecker = program.getTypeChecker();
28382841
let syntacticStart = new Date().getTime();
28392842
let sourceFile = getValidSourceFile(fileName);
2843+
let isJavaScriptFile = isJavaScript(fileName);
28402844

28412845
let start = new Date().getTime();
28422846
let currentToken = getTokenAtPosition(sourceFile, position);
@@ -2937,13 +2941,29 @@ module ts {
29372941
}
29382942

29392943
let type = typeChecker.getTypeAtLocation(node);
2944+
addTypeProperties(type);
2945+
}
2946+
2947+
function addTypeProperties(type: Type) {
29402948
if (type) {
29412949
// Filter private properties
2942-
forEach(type.getApparentProperties(), symbol => {
2950+
for (let symbol of type.getApparentProperties()) {
29432951
if (typeChecker.isValidPropertyAccess(<PropertyAccessExpression>(node.parent), symbol.name)) {
29442952
symbols.push(symbol);
29452953
}
2946-
});
2954+
}
2955+
2956+
if (isJavaScriptFile && type.flags & TypeFlags.Union) {
2957+
// In javascript files, for union types, we don't just get the members that
2958+
// the individual types have in common, we also include all the members that
2959+
// each individual type has. This is because we're going to add all identifiers
2960+
// anyways. So we might as well elevate the members that were at least part
2961+
// of the individual types to a higher status since we know what they are.
2962+
let unionType = <UnionType>type;
2963+
for (let elementType of unionType.types) {
2964+
addTypeProperties(elementType);
2965+
}
2966+
}
29472967
}
29482968
}
29492969

@@ -6040,6 +6060,7 @@ module ts {
60406060
case ClassificationType.typeParameterName: return ClassificationTypeNames.typeParameterName;
60416061
case ClassificationType.typeAliasName: return ClassificationTypeNames.typeAliasName;
60426062
case ClassificationType.parameterName: return ClassificationTypeNames.parameterName;
6063+
case ClassificationType.docCommentTagName: return ClassificationTypeNames.docCommentTagName;
60436064
}
60446065
}
60456066

@@ -6102,8 +6123,7 @@ module ts {
61026123
// Only bother with the trivia if it at least intersects the span of interest.
61036124
if (textSpanIntersectsWith(span, start, width)) {
61046125
if (isComment(kind)) {
6105-
// Simple comment. Just add as is.
6106-
pushClassification(start, width, ClassificationType.comment);
6126+
classifyComment(token, kind, start, width);
61076127
continue;
61086128
}
61096129

@@ -6127,6 +6147,92 @@ module ts {
61276147
}
61286148
}
61296149

6150+
function classifyComment(token: Node, kind: SyntaxKind, start: number, width: number) {
6151+
if (kind === SyntaxKind.MultiLineCommentTrivia) {
6152+
// See if this is a doc comment. If so, we'll classify certain portions of it
6153+
// specially.
6154+
let docCommentAndDiagnostics = parseIsolatedJSDocComment(sourceFile.text, start, width);
6155+
if (docCommentAndDiagnostics && docCommentAndDiagnostics.jsDocComment) {
6156+
docCommentAndDiagnostics.jsDocComment.parent = token;
6157+
classifyJSDocComment(docCommentAndDiagnostics.jsDocComment);
6158+
return;
6159+
}
6160+
}
6161+
6162+
// Simple comment. Just add as is.
6163+
pushCommentRange(start, width);
6164+
}
6165+
6166+
function pushCommentRange(start: number, width: number) {
6167+
pushClassification(start, width, ClassificationType.comment);
6168+
}
6169+
6170+
function classifyJSDocComment(docComment: JSDocComment) {
6171+
let pos = docComment.pos;
6172+
6173+
for (let tag of docComment.tags) {
6174+
// As we walk through each tag, classify the portion of text from the end of
6175+
// the last tag (or the start of the entire doc comment) as 'comment'.
6176+
if (tag.pos !== pos) {
6177+
pushCommentRange(pos, tag.pos - pos);
6178+
}
6179+
6180+
pushClassification(tag.atToken.pos, tag.atToken.end - tag.atToken.pos, ClassificationType.punctuation);
6181+
pushClassification(tag.tagName.pos, tag.tagName.end - tag.tagName.pos, ClassificationType.docCommentTagName);
6182+
6183+
pos = tag.tagName.end;
6184+
6185+
switch (tag.kind) {
6186+
case SyntaxKind.JSDocParameterTag:
6187+
processJSDocParameterTag(<JSDocParameterTag>tag);
6188+
break;
6189+
case SyntaxKind.JSDocTemplateTag:
6190+
processJSDocTemplateTag(<JSDocTemplateTag>tag);
6191+
break;
6192+
case SyntaxKind.JSDocTypeTag:
6193+
processElement((<JSDocTypeTag>tag).typeExpression);
6194+
break;
6195+
case SyntaxKind.JSDocReturnTag:
6196+
processElement((<JSDocReturnTag>tag).typeExpression);
6197+
break;
6198+
}
6199+
6200+
pos = tag.end;
6201+
}
6202+
6203+
if (pos !== docComment.end) {
6204+
pushCommentRange(pos, docComment.end - pos);
6205+
}
6206+
6207+
return;
6208+
6209+
function processJSDocParameterTag(tag: JSDocParameterTag) {
6210+
if (tag.preParameterName) {
6211+
pushCommentRange(pos, tag.preParameterName.pos - pos);
6212+
pushClassification(tag.preParameterName.pos, tag.preParameterName.end - tag.preParameterName.pos, ClassificationType.parameterName);
6213+
pos = tag.preParameterName.end;
6214+
}
6215+
6216+
if (tag.typeExpression) {
6217+
pushCommentRange(pos, tag.typeExpression.pos - pos);
6218+
processElement(tag.typeExpression);
6219+
pos = tag.typeExpression.end;
6220+
}
6221+
6222+
if (tag.postParameterName) {
6223+
pushCommentRange(pos, tag.postParameterName.pos - pos);
6224+
pushClassification(tag.postParameterName.pos, tag.postParameterName.end - tag.postParameterName.pos, ClassificationType.parameterName);
6225+
pos = tag.postParameterName.end;
6226+
}
6227+
}
6228+
}
6229+
6230+
function processJSDocTemplateTag(tag: JSDocTemplateTag) {
6231+
for (let child of tag.getChildren()) {
6232+
processElement(child);
6233+
}
6234+
}
6235+
61306236
function classifyDisabledMergeCode(text: string, start: number, end: number) {
61316237
// Classify the line that the ======= marker is on as a comment. Then just lex
61326238
// all further tokens and add them to the result.
@@ -6260,9 +6366,13 @@ module ts {
62606366
}
62616367

62626368
function processElement(element: Node) {
6369+
if (!element) {
6370+
return;
6371+
}
6372+
62636373
// Ignore nodes that don't intersect the original span to classify.
62646374
if (textSpanIntersectsWith(span, element.getFullStart(), element.getFullWidth())) {
6265-
let children = element.getChildren();
6375+
let children = element.getChildren(sourceFile);
62666376
for (let child of children) {
62676377
if (isToken(child)) {
62686378
classifyToken(child);

tests/cases/fourslash/fourslash.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -643,6 +643,10 @@ module FourSlashInterface {
643643
return getClassification("punctuation", text, position);
644644
}
645645

646+
export function docCommentTagName(text: string, position?: number): { classificationType: string; text: string; textSpan?: TextSpan } {
647+
return getClassification("docCommentTagName", text, position);
648+
}
649+
646650
export function className(text: string, position?: number): { classificationType: string; text: string; textSpan?: TextSpan } {
647651
return getClassification("className", text, position);
648652
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/// <reference path="fourslash.ts"/>
2+
3+
//// /** @type {number} */
4+
//// var v;
5+
6+
var c = classification;
7+
verify.syntacticClassificationsAre(
8+
c.comment("/** "),
9+
c.punctuation("@"),
10+
c.docCommentTagName("type"),
11+
c.punctuation("{"),
12+
c.keyword("number"),
13+
c.punctuation("}"),
14+
c.comment(" */"),
15+
c.keyword("var"),
16+
c.text("v"),
17+
c.punctuation(";"));
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/// <reference path="fourslash.ts"/>
2+
3+
//// /** @param foo { function(x): string } */
4+
//// var v;
5+
6+
7+
var c = classification;
8+
verify.syntacticClassificationsAre(
9+
c.comment("/** "),
10+
c.punctuation("@"),
11+
c.docCommentTagName("param"),
12+
c.comment(" "),
13+
c.parameterName("foo"),
14+
c.comment(" "),
15+
c.punctuation("{"),
16+
c.keyword("function"),
17+
c.punctuation("("),
18+
c.text("x"),
19+
c.punctuation(")"),
20+
c.punctuation(":"),
21+
c.keyword("string"),
22+
c.punctuation("}"),
23+
c.comment(" */"),
24+
c.keyword("var"),
25+
c.text("v"),
26+
c.punctuation(";"));
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/// <reference path="fourslash.ts"/>
2+
3+
//// /** @param foo { number /* } */
4+
//// var v;
5+
6+
var c = classification;
7+
verify.syntacticClassificationsAre(
8+
c.comment("/** "),
9+
c.punctuation("@"),
10+
c.docCommentTagName("param"),
11+
c.comment(" "),
12+
c.parameterName("foo"),
13+
c.comment(" "),
14+
c.punctuation("{"),
15+
c.keyword("number"),
16+
c.comment(" /* } */"),
17+
c.comment("/* } */"),
18+
c.keyword("var"),
19+
c.text("v"),
20+
c.punctuation(";"));

0 commit comments

Comments
 (0)