diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts
index 4a46e375f87..80c70a250fb 100644
--- a/src/compiler/parser.ts
+++ b/src/compiler/parser.ts
@@ -2,8 +2,6 @@
///
module ts {
- export var throwOnJSDocErrors = false;
-
let nodeConstructors = new Array Node>(SyntaxKind.Count);
/* @internal */ export let parseTime = 0;
@@ -4993,13 +4991,6 @@ module ts {
return finishNode(result);
}
- function setError(message: DiagnosticMessage) {
- parseErrorAtCurrentToken(message);
- if (throwOnJSDocErrors) {
- throw new Error(message.key);
- }
- }
-
function parseJSDocTopLevelType(): JSDocType {
var type = parseJSDocType();
if (token === SyntaxKind.BarToken) {
diff --git a/src/services/services.ts b/src/services/services.ts
index 980c22f529d..400bf754a00 100644
--- a/src/services/services.ts
+++ b/src/services/services.ts
@@ -1510,6 +1510,7 @@ module ts {
public static typeParameterName = "type parameter name";
public static typeAliasName = "type alias name";
public static parameterName = "parameter name";
+ public static docCommentTagName = "doc comment tag name";
}
export const enum ClassificationType {
@@ -1529,7 +1530,8 @@ module ts {
moduleName = 14,
typeParameterName = 15,
typeAliasName = 16,
- parameterName = 17
+ parameterName = 17,
+ docCommentTagName = 18,
}
/// Language Service
@@ -1813,7 +1815,8 @@ module ts {
}
export function createLanguageServiceSourceFile(fileName: string, scriptSnapshot: IScriptSnapshot, scriptTarget: ScriptTarget, version: string, setNodeParents: boolean): SourceFile {
- let sourceFile = createSourceFile(fileName, scriptSnapshot.getText(0, scriptSnapshot.getLength()), scriptTarget, setNodeParents);
+ let text = scriptSnapshot.getText(0, scriptSnapshot.getLength());
+ let sourceFile = createSourceFile(fileName, text, scriptTarget, setNodeParents);
setSourceFileFields(sourceFile, scriptSnapshot, version);
// after full parsing we can use table with interned strings as name table
sourceFile.nameTable = sourceFile.identifiers;
@@ -2837,6 +2840,7 @@ module ts {
let typeChecker = program.getTypeChecker();
let syntacticStart = new Date().getTime();
let sourceFile = getValidSourceFile(fileName);
+ let isJavaScriptFile = isJavaScript(fileName);
let start = new Date().getTime();
let currentToken = getTokenAtPosition(sourceFile, position);
@@ -2937,13 +2941,29 @@ module ts {
}
let type = typeChecker.getTypeAtLocation(node);
+ addTypeProperties(type);
+ }
+
+ function addTypeProperties(type: Type) {
if (type) {
// Filter private properties
- forEach(type.getApparentProperties(), symbol => {
+ for (let symbol of type.getApparentProperties()) {
if (typeChecker.isValidPropertyAccess((node.parent), symbol.name)) {
symbols.push(symbol);
}
- });
+ }
+
+ if (isJavaScriptFile && type.flags & TypeFlags.Union) {
+ // In javascript files, for union types, we don't just get the members that
+ // the individual types have in common, we also include all the members that
+ // each individual type has. This is because we're going to add all identifiers
+ // anyways. So we might as well elevate the members that were at least part
+ // of the individual types to a higher status since we know what they are.
+ let unionType = type;
+ for (let elementType of unionType.types) {
+ addTypeProperties(elementType);
+ }
+ }
}
}
@@ -6040,6 +6060,7 @@ module ts {
case ClassificationType.typeParameterName: return ClassificationTypeNames.typeParameterName;
case ClassificationType.typeAliasName: return ClassificationTypeNames.typeAliasName;
case ClassificationType.parameterName: return ClassificationTypeNames.parameterName;
+ case ClassificationType.docCommentTagName: return ClassificationTypeNames.docCommentTagName;
}
}
@@ -6102,8 +6123,7 @@ module ts {
// Only bother with the trivia if it at least intersects the span of interest.
if (textSpanIntersectsWith(span, start, width)) {
if (isComment(kind)) {
- // Simple comment. Just add as is.
- pushClassification(start, width, ClassificationType.comment);
+ classifyComment(token, kind, start, width);
continue;
}
@@ -6127,6 +6147,92 @@ module ts {
}
}
+ function classifyComment(token: Node, kind: SyntaxKind, start: number, width: number) {
+ if (kind === SyntaxKind.MultiLineCommentTrivia) {
+ // See if this is a doc comment. If so, we'll classify certain portions of it
+ // specially.
+ let docCommentAndDiagnostics = parseIsolatedJSDocComment(sourceFile.text, start, width);
+ if (docCommentAndDiagnostics && docCommentAndDiagnostics.jsDocComment) {
+ docCommentAndDiagnostics.jsDocComment.parent = token;
+ classifyJSDocComment(docCommentAndDiagnostics.jsDocComment);
+ return;
+ }
+ }
+
+ // Simple comment. Just add as is.
+ pushCommentRange(start, width);
+ }
+
+ function pushCommentRange(start: number, width: number) {
+ pushClassification(start, width, ClassificationType.comment);
+ }
+
+ function classifyJSDocComment(docComment: JSDocComment) {
+ let pos = docComment.pos;
+
+ for (let tag of docComment.tags) {
+ // As we walk through each tag, classify the portion of text from the end of
+ // the last tag (or the start of the entire doc comment) as 'comment'.
+ if (tag.pos !== pos) {
+ pushCommentRange(pos, tag.pos - pos);
+ }
+
+ pushClassification(tag.atToken.pos, tag.atToken.end - tag.atToken.pos, ClassificationType.punctuation);
+ pushClassification(tag.tagName.pos, tag.tagName.end - tag.tagName.pos, ClassificationType.docCommentTagName);
+
+ pos = tag.tagName.end;
+
+ switch (tag.kind) {
+ case SyntaxKind.JSDocParameterTag:
+ processJSDocParameterTag(tag);
+ break;
+ case SyntaxKind.JSDocTemplateTag:
+ processJSDocTemplateTag(tag);
+ break;
+ case SyntaxKind.JSDocTypeTag:
+ processElement((tag).typeExpression);
+ break;
+ case SyntaxKind.JSDocReturnTag:
+ processElement((tag).typeExpression);
+ break;
+ }
+
+ pos = tag.end;
+ }
+
+ if (pos !== docComment.end) {
+ pushCommentRange(pos, docComment.end - pos);
+ }
+
+ return;
+
+ function processJSDocParameterTag(tag: JSDocParameterTag) {
+ if (tag.preParameterName) {
+ pushCommentRange(pos, tag.preParameterName.pos - pos);
+ pushClassification(tag.preParameterName.pos, tag.preParameterName.end - tag.preParameterName.pos, ClassificationType.parameterName);
+ pos = tag.preParameterName.end;
+ }
+
+ if (tag.typeExpression) {
+ pushCommentRange(pos, tag.typeExpression.pos - pos);
+ processElement(tag.typeExpression);
+ pos = tag.typeExpression.end;
+ }
+
+ if (tag.postParameterName) {
+ pushCommentRange(pos, tag.postParameterName.pos - pos);
+ pushClassification(tag.postParameterName.pos, tag.postParameterName.end - tag.postParameterName.pos, ClassificationType.parameterName);
+ pos = tag.postParameterName.end;
+ }
+ }
+ }
+
+ function processJSDocTemplateTag(tag: JSDocTemplateTag) {
+ for (let child of tag.getChildren()) {
+ processElement(child);
+ }
+ }
+
function classifyDisabledMergeCode(text: string, start: number, end: number) {
// Classify the line that the ======= marker is on as a comment. Then just lex
// all further tokens and add them to the result.
@@ -6260,9 +6366,13 @@ module ts {
}
function processElement(element: Node) {
+ if (!element) {
+ return;
+ }
+
// Ignore nodes that don't intersect the original span to classify.
if (textSpanIntersectsWith(span, element.getFullStart(), element.getFullWidth())) {
- let children = element.getChildren();
+ let children = element.getChildren(sourceFile);
for (let child of children) {
if (isToken(child)) {
classifyToken(child);
diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts
index 21c65c699ab..4a97a140ba1 100644
--- a/tests/cases/fourslash/fourslash.ts
+++ b/tests/cases/fourslash/fourslash.ts
@@ -643,6 +643,10 @@ module FourSlashInterface {
return getClassification("punctuation", text, position);
}
+ export function docCommentTagName(text: string, position?: number): { classificationType: string; text: string; textSpan?: TextSpan } {
+ return getClassification("docCommentTagName", text, position);
+ }
+
export function className(text: string, position?: number): { classificationType: string; text: string; textSpan?: TextSpan } {
return getClassification("className", text, position);
}
diff --git a/tests/cases/fourslash/syntacticClassificationsDocComment1.ts b/tests/cases/fourslash/syntacticClassificationsDocComment1.ts
new file mode 100644
index 00000000000..fe812b4c780
--- /dev/null
+++ b/tests/cases/fourslash/syntacticClassificationsDocComment1.ts
@@ -0,0 +1,17 @@
+///
+
+//// /** @type {number} */
+//// var v;
+
+var c = classification;
+verify.syntacticClassificationsAre(
+ c.comment("/** "),
+ c.punctuation("@"),
+ c.docCommentTagName("type"),
+ c.punctuation("{"),
+ c.keyword("number"),
+ c.punctuation("}"),
+ c.comment(" */"),
+ c.keyword("var"),
+ c.text("v"),
+ c.punctuation(";"));
diff --git a/tests/cases/fourslash/syntacticClassificationsDocComment2.ts b/tests/cases/fourslash/syntacticClassificationsDocComment2.ts
new file mode 100644
index 00000000000..201251dde6d
--- /dev/null
+++ b/tests/cases/fourslash/syntacticClassificationsDocComment2.ts
@@ -0,0 +1,26 @@
+///
+
+//// /** @param foo { function(x): string } */
+//// var v;
+
+
+var c = classification;
+verify.syntacticClassificationsAre(
+ c.comment("/** "),
+ c.punctuation("@"),
+ c.docCommentTagName("param"),
+ c.comment(" "),
+ c.parameterName("foo"),
+ c.comment(" "),
+ c.punctuation("{"),
+ c.keyword("function"),
+ c.punctuation("("),
+ c.text("x"),
+ c.punctuation(")"),
+ c.punctuation(":"),
+ c.keyword("string"),
+ c.punctuation("}"),
+ c.comment(" */"),
+ c.keyword("var"),
+ c.text("v"),
+ c.punctuation(";"));
diff --git a/tests/cases/fourslash/syntacticClassificationsDocComment3.ts b/tests/cases/fourslash/syntacticClassificationsDocComment3.ts
new file mode 100644
index 00000000000..bdc7faa470e
--- /dev/null
+++ b/tests/cases/fourslash/syntacticClassificationsDocComment3.ts
@@ -0,0 +1,20 @@
+///
+
+//// /** @param foo { number /* } */
+//// var v;
+
+var c = classification;
+verify.syntacticClassificationsAre(
+ c.comment("/** "),
+ c.punctuation("@"),
+ c.docCommentTagName("param"),
+ c.comment(" "),
+ c.parameterName("foo"),
+ c.comment(" "),
+ c.punctuation("{"),
+ c.keyword("number"),
+ c.comment(" /* } */"),
+ c.comment("/* } */"),
+ c.keyword("var"),
+ c.text("v"),
+ c.punctuation(";"));