diff --git a/Jakefile.js b/Jakefile.js
index cb53b479502..afe0ffd3ce5 100644
--- a/Jakefile.js
+++ b/Jakefile.js
@@ -123,6 +123,7 @@ var harnessSources = [
return path.join(harnessDirectory, f);
}).concat([
"incrementalParser.ts",
+ "jsDocParsing.ts",
"services/colorization.ts",
"services/documentRegistry.ts",
"services/preProcessFile.ts",
diff --git a/src/compiler/core.ts b/src/compiler/core.ts
index ef7c892be9d..f5719033a2a 100644
--- a/src/compiler/core.ts
+++ b/src/compiler/core.ts
@@ -312,8 +312,11 @@ module ts {
Debug.assert(start >= 0, "start must be non-negative, is " + start);
Debug.assert(length >= 0, "length must be non-negative, is " + length);
- Debug.assert(start <= file.text.length, `start must be within the bounds of the file. ${ start } > ${ file.text.length }`);
- Debug.assert(end <= file.text.length, `end must be the bounds of the file. ${ end } > ${ file.text.length }`);
+
+ if (file) {
+ Debug.assert(start <= file.text.length, `start must be within the bounds of the file. ${ start } > ${ file.text.length }`);
+ Debug.assert(end <= file.text.length, `end must be the bounds of the file. ${ end } > ${ file.text.length }`);
+ }
let text = getLocaleSpecificMessage(message.key);
diff --git a/src/compiler/diagnosticInformationMap.generated.ts b/src/compiler/diagnosticInformationMap.generated.ts
index 2a852716846..61a55dde55a 100644
--- a/src/compiler/diagnosticInformationMap.generated.ts
+++ b/src/compiler/diagnosticInformationMap.generated.ts
@@ -174,6 +174,7 @@ module ts {
Type_expected_0_is_a_reserved_word_in_strict_mode: { code: 1215, category: DiagnosticCategory.Error, key: "Type expected. '{0}' is a reserved word in strict mode" },
Type_expected_0_is_a_reserved_word_in_strict_mode_Class_definitions_are_automatically_in_strict_mode: { code: 1216, category: DiagnosticCategory.Error, key: "Type expected. '{0}' is a reserved word in strict mode. Class definitions are automatically in strict mode." },
Export_assignment_is_not_supported_when_module_flag_is_system: { code: 1218, category: DiagnosticCategory.Error, key: "Export assignment is not supported when '--module' flag is 'system'." },
+ _0_tag_already_specified: { code: 1219, category: DiagnosticCategory.Error, key: "'{0}' tag already specified." },
Duplicate_identifier_0: { code: 2300, category: DiagnosticCategory.Error, key: "Duplicate identifier '{0}'." },
Initializer_of_instance_member_variable_0_cannot_reference_identifier_1_declared_in_the_constructor: { code: 2301, category: DiagnosticCategory.Error, key: "Initializer of instance member variable '{0}' cannot reference identifier '{1}' declared in the constructor." },
Static_members_cannot_reference_class_type_parameters: { code: 2302, category: DiagnosticCategory.Error, key: "Static members cannot reference class type parameters." },
@@ -535,7 +536,6 @@ module ts {
types_can_only_be_used_in_a_ts_file: { code: 8010, category: DiagnosticCategory.Error, key: "'types' can only be used in a .ts file." },
type_arguments_can_only_be_used_in_a_ts_file: { code: 8011, category: DiagnosticCategory.Error, key: "'type arguments' can only be used in a .ts file." },
parameter_modifiers_can_only_be_used_in_a_ts_file: { code: 8012, category: DiagnosticCategory.Error, key: "'parameter modifiers' can only be used in a .ts file." },
- can_only_be_used_in_a_ts_file: { code: 8013, category: DiagnosticCategory.Error, key: "'?' can only be used in a .ts file." },
property_declarations_can_only_be_used_in_a_ts_file: { code: 8014, category: DiagnosticCategory.Error, key: "'property declarations' can only be used in a .ts file." },
enum_declarations_can_only_be_used_in_a_ts_file: { code: 8015, category: DiagnosticCategory.Error, key: "'enum declarations' can only be used in a .ts file." },
type_assertion_expressions_can_only_be_used_in_a_ts_file: { code: 8016, category: DiagnosticCategory.Error, key: "'type assertion expressions' can only be used in a .ts file." },
diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json
index cf1645b1407..a4d7e3168d7 100644
--- a/src/compiler/diagnosticMessages.json
+++ b/src/compiler/diagnosticMessages.json
@@ -683,7 +683,10 @@
"category": "Error",
"code": 1218
},
-
+ "'{0}' tag already specified.": {
+ "category": "Error",
+ "code": 1219
+ },
"Duplicate identifier '{0}'.": {
"category": "Error",
"code": 2300
@@ -2132,10 +2135,6 @@
"category": "Error",
"code": 8012
},
- "'?' can only be used in a .ts file.": {
- "category": "Error",
- "code": 8013
- },
"'property declarations' can only be used in a .ts file.": {
"category": "Error",
"code": 8014
diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts
index af9b486a1da..28fd8184d7e 100644
--- a/src/compiler/parser.ts
+++ b/src/compiler/parser.ts
@@ -2,6 +2,8 @@
///
module ts {
+ export var throwOnJSDocErrors = false;
+
let nodeConstructors = new Array Node>(SyntaxKind.Count);
/* @internal */ export let parseTime = 0;
@@ -315,6 +317,49 @@ module ts {
return visitNode(cbNode, (node).expression);
case SyntaxKind.MissingDeclaration:
return visitNodes(cbNodes, node.decorators);
+ case SyntaxKind.JSDocTypeExpression:
+ return visitNode(cbNode, (node).type);
+ case SyntaxKind.JSDocUnionType:
+ return visitNodes(cbNodes, (node).types);
+ case SyntaxKind.JSDocTupleType:
+ return visitNodes(cbNodes, (node).types);
+ case SyntaxKind.JSDocArrayType:
+ return visitNode(cbNode, (node).elementType);
+ case SyntaxKind.JSDocNonNullableType:
+ return visitNode(cbNode, (node).type);
+ case SyntaxKind.JSDocNullableType:
+ return visitNode(cbNode, (node).type);
+ case SyntaxKind.JSDocRecordType:
+ return visitNodes(cbNodes, (node).members);
+ case SyntaxKind.JSDocTypeReference:
+ return visitNode(cbNode, (node).name) ||
+ visitNodes(cbNodes, (node).typeArguments);
+ case SyntaxKind.JSDocOptionalType:
+ return visitNode(cbNode, (node).type);
+ case SyntaxKind.JSDocFunctionType:
+ return visitNodes(cbNodes, (node).parameters) ||
+ visitNode(cbNode, (node).type);
+ case SyntaxKind.JSDocVariadicType:
+ return visitNode(cbNode, (node).type);
+ case SyntaxKind.JSDocConstructorType:
+ return visitNode(cbNode, (node).type);
+ case SyntaxKind.JSDocThisType:
+ return visitNode(cbNode, (node).type);
+ case SyntaxKind.JSDocRecordMember:
+ return visitNode(cbNode, (node).name) ||
+ visitNode(cbNode, (node).type);
+ case SyntaxKind.JSDocComment:
+ return visitNodes(cbNodes, (node).tags);
+ case SyntaxKind.JSDocParameterTag:
+ return visitNode(cbNode, (node).preParameterName) ||
+ visitNode(cbNode, (node).typeExpression) ||
+ visitNode(cbNode, (node).postParameterName);
+ case SyntaxKind.JSDocReturnTag:
+ return visitNode(cbNode, (node).typeExpression);
+ case SyntaxKind.JSDocTypeTag:
+ return visitNode(cbNode, (node).typeExpression);
+ case SyntaxKind.JSDocTemplateTag:
+ return visitNodes(cbNodes, (node).typeParameters);
}
}
@@ -338,6 +383,17 @@ module ts {
export function updateSourceFile(sourceFile: SourceFile, newText: string, textChangeRange: TextChangeRange, aggressiveChecks?: boolean): SourceFile {
return IncrementalParser.updateSourceFile(sourceFile, newText, textChangeRange, aggressiveChecks);
}
+
+ /* @internal */
+ export function parseIsolatedJSDocComment(content: string, start?: number, length?: number) {
+ return Parser.JSDocParser.parseIsolatedJSDocComment(content, start, length);
+ }
+
+ /* @internal */
+ // Exposed only for testing.
+ export function parseJSDocTypeExpressionForTests(content: string, start?: number, length?: number) {
+ return Parser.JSDocParser.parseJSDocTypeExpressionForTests(content, start, length);
+ }
// Implement the parser as a singleton module. We do this for perf reasons because creating
// parser instances can actually be expensive enough to impact us on projects with many source
@@ -349,6 +405,7 @@ module ts {
const disallowInAndDecoratorContext = ParserContextFlags.DisallowIn | ParserContextFlags.Decorator;
let sourceFile: SourceFile;
+ let parseDiagnostics: Diagnostic[];
let syntaxCursor: IncrementalParser.SyntaxCursor;
let token: SyntaxKind;
@@ -405,7 +462,7 @@ module ts {
// Note: it should not be necessary to save/restore these flags during speculative/lookahead
// parsing. These context flags are naturally stored and restored through normal recursive
// descent parsing and unwinding.
- let contextFlags: ParserContextFlags = 0;
+ let contextFlags: ParserContextFlags;
// Whether or not we've had a parse error since creating the last AST node. If we have
// encountered an error, it will be stored on the next AST node we create. Parse errors
@@ -437,25 +494,52 @@ module ts {
let parseErrorBeforeNextFinishedNode: boolean = false;
export function parseSourceFile(fileName: string, _sourceText: string, languageVersion: ScriptTarget, _syntaxCursor: IncrementalParser.SyntaxCursor, setParentNodes?: boolean): SourceFile {
+ initializeState(fileName, _sourceText, languageVersion, _syntaxCursor);
+
+ let result = parseSourceFileWorker(fileName, languageVersion, setParentNodes);
+
+ clearState();
+
+ return result;
+ }
+
+ function initializeState(fileName: string, _sourceText: string, languageVersion: ScriptTarget, _syntaxCursor: IncrementalParser.SyntaxCursor) {
sourceText = _sourceText;
syntaxCursor = _syntaxCursor;
+ parseDiagnostics = [];
parsingContext = 0;
identifiers = {};
identifierCount = 0;
nodeCount = 0;
- contextFlags = 0;
+ contextFlags = isJavaScript(fileName) ? ParserContextFlags.JavaScriptFile : ParserContextFlags.None;
parseErrorBeforeNextFinishedNode = false;
- createSourceFile(fileName, languageVersion);
-
// Initialize and prime the scanner before parsing the source elements.
scanner.setText(sourceText);
scanner.setOnError(scanError);
scanner.setScriptTarget(languageVersion);
- token = nextToken();
+ }
+ function clearState() {
+ // Clear out the text the scanner is pointing at, so it doesn't keep anything alive unnecessarily.
+ scanner.setText("");
+ scanner.setOnError(undefined);
+
+ // Clear any data. We don't want to accidently hold onto it for too long.
+ parseDiagnostics = undefined;
+ sourceFile = undefined;
+ identifiers = undefined;
+ syntaxCursor = undefined;
+ sourceText = undefined;
+ }
+
+ function parseSourceFileWorker(fileName: string, languageVersion: ScriptTarget, setParentNodes: boolean): SourceFile {
+ sourceFile = createSourceFile(fileName, languageVersion);
+
+ // Prime the scanner.
+ token = nextToken();
processReferenceComments(sourceFile);
sourceFile.statements = parseList(ParsingContext.SourceElements, /*checkForStrictMode*/ true, parseSourceElement);
@@ -467,29 +551,53 @@ module ts {
sourceFile.nodeCount = nodeCount;
sourceFile.identifierCount = identifierCount;
sourceFile.identifiers = identifiers;
+ sourceFile.parseDiagnostics = parseDiagnostics;
if (setParentNodes) {
fixupParentReferences(sourceFile);
}
- syntaxCursor = undefined;
+ // If this is a javascript file, proactively see if we can get JSDoc comments for
+ // relevant nodes in the file. We'll use these to provide typing informaion if they're
+ // available.
+ if (isJavaScript(fileName)) {
+ addJSDocComments();
+ }
- // Clear out the text the scanner is pointing at, so it doesn't keep anything alive unnecessarily.
- scanner.setText("");
- scanner.setOnError(undefined);
-
- let result = sourceFile;
-
- // Clear any data. We don't want to accidently hold onto it for too long.
- sourceFile = undefined;
- identifiers = undefined;
- syntaxCursor = undefined;
- sourceText = undefined;
-
- return result;
+ return sourceFile;
}
- function fixupParentReferences(sourceFile: SourceFile) {
+ function addJSDocComments() {
+ forEachChild(sourceFile, visit);
+ return;
+
+ function visit(node: Node) {
+ // Add additional cases as necessary depending on how we see JSDoc comments used
+ // in the wild.
+ switch (node.kind) {
+ case SyntaxKind.VariableStatement:
+ case SyntaxKind.FunctionDeclaration:
+ case SyntaxKind.Parameter:
+ addJSDocComment(node);
+ }
+
+ forEachChild(node, visit);
+ }
+ }
+
+ function addJSDocComment(node: Node) {
+ let comments = getLeadingCommentRangesOfNode(node, sourceFile);
+ if (comments) {
+ for (let comment of comments) {
+ let jsDocComment = JSDocParser.parseJSDocComment(node, comment.pos, comment.end - comment.pos);
+ if (jsDocComment) {
+ node.jsDocComment = jsDocComment;
+ }
+ }
+ }
+ }
+
+ export function fixupParentReferences(sourceFile: Node) {
// normally parent references are set during binding. However, for clients that only need
// a syntax tree, and no semantic features, then the binding process is an unnecessary
// overhead. This functions allows us to set all the parents, without all the expense of
@@ -514,18 +622,18 @@ module ts {
}
}
- function createSourceFile(fileName: string, languageVersion: ScriptTarget) {
- sourceFile = createNode(SyntaxKind.SourceFile, /*pos*/ 0);
+ function createSourceFile(fileName: string, languageVersion: ScriptTarget): SourceFile {
+ let sourceFile = createNode(SyntaxKind.SourceFile, /*pos*/ 0);
sourceFile.pos = 0;
sourceFile.end = sourceText.length;
sourceFile.text = sourceText;
-
- sourceFile.parseDiagnostics = [];
sourceFile.bindDiagnostics = [];
sourceFile.languageVersion = languageVersion;
sourceFile.fileName = normalizePath(fileName);
sourceFile.flags = fileExtensionIs(sourceFile.fileName, ".d.ts") ? NodeFlags.DeclarationFile : 0;
+
+ return sourceFile;
}
function setContextFlag(val: Boolean, flag: ParserContextFlags) {
@@ -659,9 +767,9 @@ module ts {
function parseErrorAtPosition(start: number, length: number, message: DiagnosticMessage, arg0?: any): void {
// Don't report another error if it would just be at the same position as the last error.
- let lastError = lastOrUndefined(sourceFile.parseDiagnostics);
+ let lastError = lastOrUndefined(parseDiagnostics);
if (!lastError || start !== lastError.start) {
- sourceFile.parseDiagnostics.push(createFileDiagnostic(sourceFile, start, length, message, arg0));
+ parseDiagnostics.push(createFileDiagnostic(sourceFile, start, length, message, arg0));
}
// Mark that we've encountered an error. We'll set an appropriate bit on the next
@@ -706,7 +814,7 @@ module ts {
// Keep track of the state we'll need to rollback to if lookahead fails (or if the
// caller asked us to always reset our state).
let saveToken = token;
- let saveParseDiagnosticsLength = sourceFile.parseDiagnostics.length;
+ let saveParseDiagnosticsLength = parseDiagnostics.length;
let saveParseErrorBeforeNextFinishedNode = parseErrorBeforeNextFinishedNode;
// Note: it is not actually necessary to save/restore the context flags here. That's
@@ -728,7 +836,7 @@ module ts {
// then unconditionally restore us to where we were.
if (!result || isLookAhead) {
token = saveToken;
- sourceFile.parseDiagnostics.length = saveParseDiagnosticsLength;
+ parseDiagnostics.length = saveParseDiagnosticsLength;
parseErrorBeforeNextFinishedNode = saveParseErrorBeforeNextFinishedNode;
}
@@ -843,8 +951,8 @@ module ts {
return node;
}
- function finishNode(node: T): T {
- node.end = scanner.getStartPos();
+ function finishNode(node: T, end?: number): T {
+ node.end = end === undefined ? scanner.getStartPos() : end;
if (contextFlags) {
node.parserContextFlags = contextFlags;
@@ -913,16 +1021,28 @@ module ts {
token === SyntaxKind.NumericLiteral;
}
- function parsePropertyName(): DeclarationName {
+ function parsePropertyNameWorker(allowComputedPropertyNames: boolean): DeclarationName {
if (token === SyntaxKind.StringLiteral || token === SyntaxKind.NumericLiteral) {
return parseLiteralNode(/*internName:*/ true);
}
- if (token === SyntaxKind.OpenBracketToken) {
+ if (allowComputedPropertyNames && token === SyntaxKind.OpenBracketToken) {
return parseComputedPropertyName();
}
return parseIdentifierName();
}
+ function parsePropertyName(): DeclarationName {
+ return parsePropertyNameWorker(/*allowComputedPropertyNames:*/ true);
+ }
+
+ function parseSimplePropertyName(): Identifier | LiteralExpression {
+ return parsePropertyNameWorker(/*allowComputedPropertyNames:*/ false);
+ }
+
+ function isSimplePropertyName() {
+ return token === SyntaxKind.StringLiteral || token === SyntaxKind.NumericLiteral || isIdentifierOrKeyword();
+ }
+
function parseComputedPropertyName(): ComputedPropertyName {
// PropertyName[Yield,GeneratorParameter] :
// LiteralPropertyName
@@ -1057,6 +1177,12 @@ module ts {
return isHeritageClause();
case ParsingContext.ImportOrExportSpecifiers:
return isIdentifierOrKeyword();
+ case ParsingContext.JSDocFunctionParameters:
+ case ParsingContext.JSDocTypeArguments:
+ case ParsingContext.JSDocTupleTypes:
+ return JSDocParser.isJSDocType();
+ case ParsingContext.JSDocRecordMembers:
+ return isSimplePropertyName();
}
Debug.fail("Non-exhaustive case in 'isListElement'.");
@@ -1142,6 +1268,14 @@ module ts {
return token === SyntaxKind.GreaterThanToken || token === SyntaxKind.OpenParenToken;
case ParsingContext.HeritageClauses:
return token === SyntaxKind.OpenBraceToken || token === SyntaxKind.CloseBraceToken;
+ case ParsingContext.JSDocFunctionParameters:
+ return token === SyntaxKind.CloseParenToken || token === SyntaxKind.ColonToken || token === SyntaxKind.CloseBraceToken;
+ case ParsingContext.JSDocTypeArguments:
+ return token === SyntaxKind.GreaterThanToken || token === SyntaxKind.CloseBraceToken;
+ case ParsingContext.JSDocTupleTypes:
+ return token === SyntaxKind.CloseBracketToken || token === SyntaxKind.CloseBraceToken;
+ case ParsingContext.JSDocRecordMembers:
+ return token === SyntaxKind.CloseBraceToken;
}
}
@@ -1199,7 +1333,7 @@ module ts {
// test elements only if we are not already in strict mode
if (checkForStrictMode && !inStrictModeContext()) {
if (isPrologueDirective(element)) {
- if (isUseStrictPrologueDirective(sourceFile, element)) {
+ if (isUseStrictPrologueDirective(element)) {
setStrictModeContext(true);
checkForStrictMode = false;
}
@@ -1224,9 +1358,9 @@ module ts {
}
/// Should be called only on prologue directives (isPrologueDirective(node) should be true)
- function isUseStrictPrologueDirective(sourceFile: SourceFile, node: Node): boolean {
+ function isUseStrictPrologueDirective(node: Node): boolean {
Debug.assert(isPrologueDirective(node));
- let nodeText = getSourceTextOfNodeFromSourceFile(sourceFile, (node).expression);
+ let nodeText = getTextOfNodeFromSourceText(sourceText, (node).expression);
// Note: the node text must be exactly "use strict" or 'use strict'. It is not ok for the
// string to contain unicode escapes (as per ES5).
@@ -1547,6 +1681,10 @@ module ts {
case ParsingContext.TupleElementTypes: return Diagnostics.Type_expected;
case ParsingContext.HeritageClauses: return Diagnostics.Unexpected_token_expected;
case ParsingContext.ImportOrExportSpecifiers: return Diagnostics.Identifier_expected;
+ case ParsingContext.JSDocFunctionParameters: return Diagnostics.Parameter_declaration_expected;
+ case ParsingContext.JSDocTypeArguments: return Diagnostics.Type_argument_expected;
+ case ParsingContext.JSDocTupleTypes: return Diagnostics.Type_expected;
+ case ParsingContext.JSDocRecordMembers: return Diagnostics.Property_assignment_expected;
}
};
@@ -4785,7 +4923,7 @@ module ts {
referencedFiles.push(fileReference);
}
if (diagnosticMessage) {
- sourceFile.parseDiagnostics.push(createFileDiagnostic(sourceFile, range.pos, range.end - range.pos, diagnosticMessage));
+ parseDiagnostics.push(createFileDiagnostic(sourceFile, range.pos, range.end - range.pos, diagnosticMessage));
}
}
else {
@@ -4793,7 +4931,7 @@ module ts {
let amdModuleNameMatchResult = amdModuleNameRegEx.exec(comment);
if (amdModuleNameMatchResult) {
if (amdModuleName) {
- sourceFile.parseDiagnostics.push(createFileDiagnostic(sourceFile, range.pos, range.end - range.pos, Diagnostics.An_AMD_module_cannot_have_multiple_name_assignments));
+ parseDiagnostics.push(createFileDiagnostic(sourceFile, range.pos, range.end - range.pos, Diagnostics.An_AMD_module_cannot_have_multiple_name_assignments));
}
amdModuleName = amdModuleNameMatchResult[2];
}
@@ -4851,6 +4989,10 @@ module ts {
TupleElementTypes, // Element types in tuple element type list
HeritageClauses, // Heritage clauses for a class or interface declaration.
ImportOrExportSpecifiers, // Named import clause's import specifier list
+ JSDocFunctionParameters,
+ JSDocTypeArguments,
+ JSDocRecordMembers,
+ JSDocTupleTypes,
Count // Number of parsing contexts
}
@@ -4859,6 +5001,659 @@ module ts {
True,
Unknown
}
+
+ export module JSDocParser {
+ export function isJSDocType() {
+ switch (token) {
+ case SyntaxKind.AsteriskToken:
+ case SyntaxKind.QuestionToken:
+ case SyntaxKind.OpenParenToken:
+ case SyntaxKind.OpenBracketToken:
+ case SyntaxKind.ExclamationToken:
+ case SyntaxKind.OpenBraceToken:
+ case SyntaxKind.FunctionKeyword:
+ case SyntaxKind.DotDotDotToken:
+ case SyntaxKind.NewKeyword:
+ case SyntaxKind.ThisKeyword:
+ return true;
+ }
+
+ return isIdentifierOrKeyword();
+ }
+
+ export function parseJSDocTypeExpressionForTests(content: string, start: number, length: number) {
+ initializeState("file.js", content, ScriptTarget.Latest, /*_syntaxCursor:*/ undefined);
+ let jsDocTypeExpression = parseJSDocTypeExpression(start, length);
+ let diagnostics = parseDiagnostics;
+ clearState();
+
+ return jsDocTypeExpression ? { jsDocTypeExpression, diagnostics } : undefined;
+ }
+
+ // Parses out a JSDoc type expression. The starting position should be right at the open
+ // curly in the type expression. Returns 'undefined' if it encounters any errors while parsing.
+ /* @internal */
+ export function parseJSDocTypeExpression(start: number, length: number): JSDocTypeExpression {
+ scanner.setText(sourceText, start, length);
+
+ // Prime the first token for us to start processing.
+ token = nextToken();
+
+ let result = createNode(SyntaxKind.JSDocTypeExpression);
+
+ parseExpected(SyntaxKind.OpenBraceToken);
+ result.type = parseJSDocTopLevelType();
+ parseExpected(SyntaxKind.CloseBraceToken);
+
+ fixupParentReferences(result);
+ 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) {
+ var unionType = createNode(SyntaxKind.JSDocUnionType, type.pos);
+ unionType.types = parseJSDocTypeList(type);
+ type = finishNode(unionType);
+ }
+
+ if (token === SyntaxKind.EqualsToken) {
+ var optionalType = createNode(SyntaxKind.JSDocOptionalType, type.pos);
+ nextToken();
+ optionalType.type = type;
+ type = finishNode(optionalType);
+ }
+
+ return type;
+ }
+
+ function parseJSDocType(): JSDocType {
+ let type = parseBasicTypeExpression();
+
+ while (true) {
+ if (token === SyntaxKind.OpenBracketToken) {
+ let arrayType = createNode(SyntaxKind.JSDocArrayType, type.pos);
+ arrayType.elementType = type;
+
+ nextToken();
+ parseExpected(SyntaxKind.CloseBracketToken);
+
+ type = finishNode(arrayType);
+ }
+ else if (token === SyntaxKind.QuestionToken) {
+ let nullableType = createNode(SyntaxKind.JSDocNullableType, type.pos);
+ nullableType.type = type;
+
+ nextToken();
+ type = finishNode(nullableType);
+ }
+ else if (token === SyntaxKind.ExclamationToken) {
+ let nonNullableType = createNode(SyntaxKind.JSDocNonNullableType, type.pos);
+ nonNullableType.type = type;
+
+ nextToken();
+ type = finishNode(nonNullableType);
+ }
+ else {
+ break;
+ }
+ }
+
+ return type;
+ }
+
+ function parseBasicTypeExpression(): JSDocType {
+ switch (token) {
+ case SyntaxKind.AsteriskToken:
+ return parseJSDocAllType();
+ case SyntaxKind.QuestionToken:
+ return parseJSDocUnknownOrNullableType();
+ case SyntaxKind.OpenParenToken:
+ return parseJSDocUnionType();
+ case SyntaxKind.OpenBracketToken:
+ return parseJSDocTupleType();
+ case SyntaxKind.ExclamationToken:
+ return parseJSDocNonNullableType();
+ case SyntaxKind.OpenBraceToken:
+ return parseJSDocRecordType();
+ case SyntaxKind.FunctionKeyword:
+ return parseJSDocFunctionType();
+ case SyntaxKind.DotDotDotToken:
+ return parseJSDocVariadicType();
+ case SyntaxKind.NewKeyword:
+ return parseJSDocConstructorType();
+ case SyntaxKind.ThisKeyword:
+ return parseJSDocThisType();
+ case SyntaxKind.AnyKeyword:
+ case SyntaxKind.StringKeyword:
+ case SyntaxKind.NumberKeyword:
+ case SyntaxKind.BooleanKeyword:
+ case SyntaxKind.SymbolKeyword:
+ case SyntaxKind.VoidKeyword:
+ return parseTokenNode();
+ }
+
+ return parseJSDocTypeReference();
+ }
+
+ function parseJSDocThisType(): JSDocThisType {
+ let result = createNode(SyntaxKind.JSDocThisType);
+ nextToken();
+ parseExpected(SyntaxKind.ColonToken);
+ result.type = parseJSDocType();
+ return finishNode(result);
+ }
+
+ function parseJSDocConstructorType(): JSDocConstructorType {
+ let result = createNode(SyntaxKind.JSDocConstructorType);
+ nextToken();
+ parseExpected(SyntaxKind.ColonToken);
+ result.type = parseJSDocType();
+ return finishNode(result);
+ }
+
+ function parseJSDocVariadicType(): JSDocVariadicType {
+ let result = createNode(SyntaxKind.JSDocVariadicType);
+ nextToken();
+ result.type = parseJSDocType();
+ return finishNode(result);
+ }
+
+ function parseJSDocFunctionType(): JSDocFunctionType {
+ let result = createNode(SyntaxKind.JSDocFunctionType);
+ nextToken();
+
+ parseExpected(SyntaxKind.OpenParenToken);
+ result.parameters = parseDelimitedList(ParsingContext.JSDocFunctionParameters, parseJSDocParameter);
+ checkForTrailingComma(result.parameters);
+ parseExpected(SyntaxKind.CloseParenToken);
+
+ if (token === SyntaxKind.ColonToken) {
+ nextToken();
+ result.type = parseJSDocType();
+ }
+
+ return finishNode(result);
+ }
+
+ function parseJSDocParameter(): ParameterDeclaration {
+ let parameter = createNode(SyntaxKind.Parameter);
+ parameter.type = parseJSDocType();
+ return finishNode(parameter);
+ }
+
+ function parseJSDocOptionalType(type: JSDocType): JSDocOptionalType {
+ let result = createNode(SyntaxKind.JSDocOptionalType, type.pos);
+ nextToken();
+ result.type = type;
+ return finishNode(result);
+ }
+
+ function parseJSDocTypeReference(): JSDocTypeReference {
+ let result = createNode(SyntaxKind.JSDocTypeReference);
+ result.name = parseSimplePropertyName();
+
+ while (parseOptional(SyntaxKind.DotToken)) {
+ if (token === SyntaxKind.LessThanToken) {
+ result.typeArguments = parseTypeArguments();
+ break;
+ }
+ else {
+ result.name = parseQualifiedName(result.name);
+ }
+ }
+
+ return finishNode(result);
+ }
+
+ function parseTypeArguments() {
+ // Move past the <
+ nextToken();
+ let typeArguments = parseDelimitedList(ParsingContext.JSDocTypeArguments, parseJSDocType);
+ checkForTrailingComma(typeArguments);
+ checkForEmptyTypeArgumentList(typeArguments);
+ parseExpected(SyntaxKind.GreaterThanToken);
+
+ return typeArguments;
+ }
+
+ function checkForEmptyTypeArgumentList(typeArguments: NodeArray) {
+ if (parseDiagnostics.length === 0 && typeArguments && typeArguments.length === 0) {
+ let start = typeArguments.pos - "<".length;
+ let end = skipTrivia(sourceText, typeArguments.end) + ">".length;
+ return parseErrorAtPosition(start, end - start, Diagnostics.Type_argument_list_cannot_be_empty);
+ }
+ }
+
+ function parseQualifiedName(left: EntityName): QualifiedName {
+ let result = createNode(SyntaxKind.QualifiedName, left.pos);
+ result.left = left;
+ result.right = parseIdentifierName();
+
+ return finishNode(result);
+ }
+
+ function parseJSDocRecordType(): JSDocRecordType {
+ let result = createNode(SyntaxKind.JSDocRecordType);
+ nextToken();
+ result.members = parseDelimitedList(ParsingContext.JSDocRecordMembers, parseJSDocRecordMember);
+ checkForTrailingComma(result.members);
+ parseExpected(SyntaxKind.CloseBraceToken);
+ return finishNode(result);
+ }
+
+ function parseJSDocRecordMember(): JSDocRecordMember {
+ let result = createNode(SyntaxKind.JSDocRecordMember);
+ result.name = parseSimplePropertyName();
+
+ if (token === SyntaxKind.ColonToken) {
+ nextToken();
+ result.type = parseJSDocType();
+ }
+
+ return finishNode(result);
+ }
+
+ function parseJSDocNonNullableType(): JSDocNonNullableType {
+ let result = createNode(SyntaxKind.JSDocNonNullableType);
+ nextToken();
+ result.type = parseJSDocType();
+ return finishNode(result);
+ }
+
+ function parseJSDocTupleType(): JSDocTupleType {
+ let result = createNode(SyntaxKind.JSDocTupleType);
+ nextToken();
+ result.types = parseDelimitedList(ParsingContext.JSDocTupleTypes, parseJSDocType);
+ checkForTrailingComma(result.types);
+ parseExpected(SyntaxKind.CloseBracketToken);
+
+ return finishNode(result);
+ }
+
+ function checkForTrailingComma(list: NodeArray) {
+ if (parseDiagnostics.length === 0 && list.hasTrailingComma) {
+ let start = list.end - ",".length;
+ parseErrorAtPosition(start, ",".length, Diagnostics.Trailing_comma_not_allowed);
+ }
+ }
+
+ function parseJSDocUnionType(): JSDocUnionType {
+ let result = createNode(SyntaxKind.JSDocUnionType);
+ nextToken();
+ result.types = parseJSDocTypeList(parseJSDocType());
+
+ parseExpected(SyntaxKind.CloseParenToken);
+
+ return finishNode(result);
+ }
+
+ function parseJSDocTypeList(firstType: JSDocType) {
+ Debug.assert(!!firstType);
+
+ let types = >[];
+ types.pos = firstType.pos;
+
+ types.push(firstType);
+ while (parseOptional(SyntaxKind.BarToken)) {
+ types.push(parseJSDocType());
+ }
+
+ types.end = scanner.getStartPos();
+ return types;
+ }
+
+ function parseJSDocAllType(): JSDocAllType {
+ let result = createNode(SyntaxKind.JSDocAllType);
+ nextToken();
+ return finishNode(result);
+ }
+
+ function parseJSDocUnknownOrNullableType(): JSDocUnknownType | JSDocNullableType {
+ let pos = scanner.getStartPos();
+ // skip the ?
+ nextToken();
+
+ // Need to lookahead to decide if this is a nullable or unknown type.
+
+ // Here are cases where we'll pick the unknown type:
+ //
+ // Foo(?,
+ // { a: ? }
+ // Foo(?)
+ // Foo>
+ // Foo(?=
+ // (?|
+ if (token === SyntaxKind.CommaToken ||
+ token === SyntaxKind.CloseBraceToken ||
+ token === SyntaxKind.CloseParenToken ||
+ token === SyntaxKind.GreaterThanToken ||
+ token === SyntaxKind.EqualsToken ||
+ token === SyntaxKind.BarToken) {
+
+ let result = createNode(SyntaxKind.JSDocUnknownType, pos);
+ return finishNode(result);
+ }
+ else {
+ let result = createNode(SyntaxKind.JSDocNullableType, pos);
+ result.type = parseJSDocType();
+ return finishNode(result);
+ }
+ }
+
+ export function parseIsolatedJSDocComment(content: string, start: number, length: number) {
+ initializeState("file.js", content, ScriptTarget.Latest, /*_syntaxCursor:*/ undefined);
+ let jsDocComment = parseJSDocComment(/*parent:*/ undefined, start, length);
+ let diagnostics = parseDiagnostics;
+ clearState();
+
+ return jsDocComment ? { jsDocComment, diagnostics } : undefined;
+ }
+
+ export function parseJSDocComment(parent: Node, start: number, length: number): JSDocComment {
+ let comment = parseJSDocCommentWorker(start, length);
+ if (comment) {
+ fixupParentReferences(comment);
+ comment.parent = parent;
+ }
+
+ return comment;
+ }
+
+ export function parseJSDocCommentWorker(start: number, length: number): JSDocComment {
+ let content = sourceText;
+ start = start || 0;
+ let end = length === undefined ? content.length : start + length;
+ length = end - start;
+
+ Debug.assert(start >= 0);
+ Debug.assert(start <= end);
+ Debug.assert(end <= content.length);
+
+ let tags: NodeArray;
+ let pos: number;
+
+ // NOTE(cyrusn): This is essentially a handwritten scanner for JSDocComments. I
+ // considered using an actual Scanner, but this would complicate things. The
+ // scanner would need to know it was in a Doc Comment. Otherwise, it would then
+ // produce comments *inside* the doc comment. In the end it was just easier to
+ // write a simple scanner rather than go that route.
+ if (length >= "/** */".length) {
+ if (content.charCodeAt(start) === CharacterCodes.slash &&
+ content.charCodeAt(start + 1) === CharacterCodes.asterisk &&
+ content.charCodeAt(start + 2) === CharacterCodes.asterisk &&
+ content.charCodeAt(start + 3) !== CharacterCodes.asterisk) {
+
+ // Initially we can parse out a tag. We also have seen a starting asterisk.
+ // This is so that /** * @type */ doesn't parse.
+ let canParseTag = true;
+ let seenAsterisk = true;
+
+ for (pos = start + "/**".length; pos < end;) {
+ let ch = content.charCodeAt(pos);
+ pos++;
+
+ if (ch === CharacterCodes.at && canParseTag) {
+ parseTag();
+
+ // Once we parse out a tag, we cannot keep parsing out tags on this line.
+ canParseTag = false;
+ continue;
+ }
+
+ if (isLineBreak(ch)) {
+ // After a line break, we can parse a tag, and we haven't seen as asterisk
+ // on the next line yet.
+ canParseTag = true;
+ seenAsterisk = false;
+ continue;
+ }
+
+ if (isWhiteSpace(ch)) {
+ // Whitespace doesn't affect any of our parsing.
+ continue;
+ }
+
+ // Ignore the first asterisk on a line.
+ if (ch === CharacterCodes.asterisk) {
+ if (seenAsterisk) {
+ // If we've already seen an asterisk, then we can no longer parse a tag
+ // on this line.
+ canParseTag = false;
+ }
+ seenAsterisk = true;
+ continue;
+ }
+
+ // Anything else is doc comment text. We can't do anything with it. Because it
+ // wasn't a tag, we can no longer parse a tag on this line until we hit the next
+ // line break.
+ canParseTag = false;
+ }
+ }
+ }
+
+ return createJSDocComment();
+
+ function createJSDocComment(): JSDocComment {
+ if (!tags) {
+ return undefined;
+ }
+
+ let result = createNode(SyntaxKind.JSDocComment, start);
+ result.tags = tags;
+ return finishNode(result, end);
+ }
+
+ function skipWhitespace(): void {
+ while (pos < end && isWhiteSpace(content.charCodeAt(pos))) {
+ pos++;
+ }
+ }
+
+ function parseTag(): void {
+ Debug.assert(content.charCodeAt(pos - 1) === CharacterCodes.at);
+ let atToken = createNode(SyntaxKind.AtToken, pos - 1);
+ atToken.end = pos;
+
+ let startPos = pos;
+ let tagName = scanIdentifier();
+ if (!tagName) {
+ return;
+ }
+
+ let tag = handleTag(atToken, tagName) || handleUnknownTag(atToken, tagName);
+ addTag(tag);
+ }
+
+ function handleTag(atToken: Node, tagName: Identifier): JSDocTag {
+ if (tagName) {
+ switch (tagName.text) {
+ case "param":
+ return handleParamTag(atToken, tagName);
+ case "return":
+ case "returns":
+ return handleReturnTag(atToken, tagName);
+ case "template":
+ return handleTemplateTag(atToken, tagName);
+ case "type":
+ return handleTypeTag(atToken, tagName);
+ }
+ }
+
+ return undefined;
+ }
+
+ function handleUnknownTag(atToken: Node, tagName: Identifier) {
+ let result = createNode(SyntaxKind.JSDocTag, atToken.pos);
+ result.atToken = atToken;
+ result.tagName = tagName;
+ return finishNode(result, pos);
+ }
+
+ function addTag(tag: JSDocTag): void {
+ if (tag) {
+ if (!tags) {
+ tags = >[];
+ tags.pos = tag.pos;
+ }
+
+ tags.push(tag);
+ tags.end = tag.end;
+ }
+ }
+
+ function tryParseTypeExpression(): JSDocTypeExpression {
+ skipWhitespace();
+
+ if (content.charCodeAt(pos) !== CharacterCodes.openBrace) {
+ return undefined;
+ }
+
+ let typeExpression = parseJSDocTypeExpression(pos, end - pos);
+ pos = typeExpression.end;
+ return typeExpression;
+ }
+
+ function handleParamTag(atToken: Node, tagName: Identifier) {
+ let typeExpression = tryParseTypeExpression();
+
+ skipWhitespace();
+ let name: Identifier;
+ let isBracketed: boolean;
+ if (content.charCodeAt(pos) === CharacterCodes.openBracket) {
+ pos++;
+ skipWhitespace();
+ name = scanIdentifier();
+ isBracketed = true;
+ }
+ else {
+ name = scanIdentifier();
+ }
+
+ if (!name) {
+ parseErrorAtPosition(pos, 0, Diagnostics.Identifier_expected);
+ return undefined;
+ }
+
+ let preName: Identifier, postName: Identifier;
+ if (typeExpression) {
+ postName = name;
+ }
+ else {
+ preName = name;
+ }
+
+ if (!typeExpression) {
+ typeExpression = tryParseTypeExpression();
+ }
+
+ let result = createNode(SyntaxKind.JSDocParameterTag, atToken.pos);
+ result.atToken = atToken;
+ result.tagName = tagName;
+ result.preParameterName = preName;
+ result.typeExpression = typeExpression;
+ result.postParameterName = postName;
+ result.isBracketed = isBracketed;
+ return finishNode(result, pos);
+ }
+
+ function handleReturnTag(atToken: Node, tagName: Identifier): JSDocReturnTag {
+ if (forEach(tags, t => t.kind === SyntaxKind.JSDocReturnTag)) {
+ parseErrorAtPosition(tagName.pos, pos - tagName.pos, Diagnostics._0_tag_already_specified, tagName.text);
+ }
+
+ let result = createNode(SyntaxKind.JSDocReturnTag, atToken.pos);
+ result.atToken = atToken;
+ result.tagName = tagName;
+ result.typeExpression = tryParseTypeExpression();
+ return finishNode(result, pos);
+ }
+
+ function handleTypeTag(atToken: Node, tagName: Identifier): JSDocTypeTag {
+ if (forEach(tags, t => t.kind === SyntaxKind.JSDocTypeTag)) {
+ parseErrorAtPosition(tagName.pos, pos - tagName.pos, Diagnostics._0_tag_already_specified, tagName.text);
+ }
+
+ let result = createNode(SyntaxKind.JSDocTypeTag, atToken.pos);
+ result.atToken = atToken;
+ result.tagName = tagName;
+ result.typeExpression = tryParseTypeExpression();
+ return finishNode(result, pos);
+ }
+
+ function handleTemplateTag(atToken: Node, tagName: Identifier): JSDocTemplateTag {
+ if (forEach(tags, t => t.kind === SyntaxKind.JSDocTemplateTag)) {
+ parseErrorAtPosition(tagName.pos, pos - tagName.pos, Diagnostics._0_tag_already_specified, tagName.text);
+ }
+
+ let typeParameters = >[];
+ typeParameters.pos = pos;
+
+ while (true) {
+ skipWhitespace();
+
+ let startPos = pos;
+ let name = scanIdentifier();
+ if (!name) {
+ parseErrorAtPosition(startPos, 0, Diagnostics.Identifier_expected);
+ return undefined;
+ }
+
+ let typeParameter = createNode(SyntaxKind.TypeParameter, name.pos);
+ typeParameter.name = name;
+ finishNode(typeParameter, pos);
+
+ typeParameters.push(typeParameter);
+
+ skipWhitespace();
+ if (content.charCodeAt(pos) !== CharacterCodes.comma) {
+ break;
+ }
+
+ pos++;
+ }
+
+ typeParameters.end = pos;
+
+ let result = createNode(SyntaxKind.JSDocTemplateTag, atToken.pos);
+ result.atToken = atToken;
+ result.tagName = tagName;
+ result.typeParameters = typeParameters;
+ return finishNode(result, pos);
+ }
+
+ function scanIdentifier(): Identifier {
+ let startPos = pos;
+ for (; pos < end; pos++) {
+ let ch = content.charCodeAt(pos);
+ if (pos === startPos && isIdentifierStart(ch, ScriptTarget.Latest)) {
+ continue;
+ }
+ else if (pos > startPos && isIdentifierPart(ch, ScriptTarget.Latest)) {
+ continue;
+ }
+
+ break;
+ }
+
+ if (startPos === pos) {
+ return undefined;
+ }
+
+ let result = createNode(SyntaxKind.Identifier, startPos);
+ result.text = content.substring(startPos, pos);
+ return finishNode(result, pos);
+ }
+ }
+ }
}
module IncrementalParser {
@@ -4959,7 +5754,14 @@ module ts {
// Ditch any existing LS children we may have created. This way we can avoid
// moving them forward.
- node._children = undefined;
+ if (node._children) {
+ node._children = undefined;
+ }
+
+ if (node.jsDocComment) {
+ node.jsDocComment = undefined;
+ }
+
node.pos += delta;
node.end += delta;
diff --git a/src/compiler/scanner.ts b/src/compiler/scanner.ts
index 1fc8bff92e6..89ac837ab24 100644
--- a/src/compiler/scanner.ts
+++ b/src/compiler/scanner.ts
@@ -598,12 +598,26 @@ module ts {
ch > CharacterCodes.maxAsciiCharacter && isUnicodeIdentifierPart(ch, languageVersion);
}
- /** Creates a scanner over a (possibly unspecified) range of a piece of text. */
- export function createScanner(languageVersion: ScriptTarget, skipTrivia: boolean, text?: string, onError?: ErrorCallback, start?: number, length?: number): Scanner {
- let pos: number; // Current position (end position of text of current token)
- let end: number; // end of text
- let startPos: number; // Start position of whitespace before current token
- let tokenPos: number; // Start position of text of current token
+ /* @internal */
+ // Creates a scanner over a (possibly unspecified) range of a piece of text.
+ export function createScanner(languageVersion: ScriptTarget,
+ skipTrivia: boolean,
+ text?: string,
+ onError?: ErrorCallback,
+ start?: number,
+ length?: number): Scanner {
+ // Current position (end position of text of current token)
+ let pos: number;
+
+ // end of text
+ let end: number;
+
+ // Start position of whitespace before current token
+ let startPos: number;
+
+ // Start position of text of current token
+ let tokenPos: number;
+
let token: SyntaxKind;
let tokenValue: string;
let precedingLineBreak: boolean;
diff --git a/src/compiler/types.ts b/src/compiler/types.ts
index c8d22b26728..85fa461daab 100644
--- a/src/compiler/types.ts
+++ b/src/compiler/types.ts
@@ -270,6 +270,32 @@ module ts {
// Top-level nodes
SourceFile,
+ // JSDoc nodes.
+ JSDocTypeExpression,
+ // The * type.
+ JSDocAllType,
+ // The ? type.
+ JSDocUnknownType,
+ JSDocArrayType,
+ JSDocUnionType,
+ JSDocTupleType,
+ JSDocNullableType,
+ JSDocNonNullableType,
+ JSDocRecordType,
+ JSDocRecordMember,
+ JSDocTypeReference,
+ JSDocOptionalType,
+ JSDocFunctionType,
+ JSDocVariadicType,
+ JSDocConstructorType,
+ JSDocThisType,
+ JSDocComment,
+ JSDocTag,
+ JSDocParameterTag,
+ JSDocReturnTag,
+ JSDocTypeTag,
+ JSDocTemplateTag,
+
// Synthesized list
SyntaxList,
// Enum value count
@@ -324,6 +350,8 @@ module ts {
/* @internal */
export const enum ParserContextFlags {
+ None = 0,
+
// Set if this node was parsed in strict mode. Used for grammar error checks, as well as
// checking if the node can be reused in incremental settings.
StrictMode = 1 << 0,
@@ -345,6 +373,10 @@ module ts {
// error.
ThisNodeHasError = 1 << 5,
+ // This node was parsed in a JavaScript file and can be processed differently. For example
+ // its type can be specified usign a JSDoc comment.
+ JavaScriptFile = 1 << 6,
+
// Context flags set directly by the parser.
ParserGeneratedFlags = StrictMode | DisallowIn | Yield | GeneratorParameter | Decorator | ThisNodeHasError,
@@ -352,10 +384,10 @@ module ts {
// Used during incremental parsing to determine if this node or any of its children had an
// error. Computed only once and then cached.
- ThisNodeOrAnySubNodesHasError = 1 << 6,
+ ThisNodeOrAnySubNodesHasError = 1 << 7,
// Used to know if we've computed data from children and cached it in this node.
- HasAggregatedChildData = 1 << 7
+ HasAggregatedChildData = 1 << 8
}
/* @internal */
@@ -371,14 +403,15 @@ module ts {
// Specific context the parser was in when this node was created. Normally undefined.
// Only set when the parser was in some interesting context (like async/yield).
/* @internal */ parserContextFlags?: ParserContextFlags;
- decorators?: NodeArray; // Array of decorators (in document order)
- modifiers?: ModifiersArray; // Array of modifiers
- /* @internal */ id?: number; // Unique id (used to look up NodeLinks)
- parent?: Node; // Parent node (initialized by binding)
- /* @internal */ symbol?: Symbol; // Symbol declared by node (initialized by binding)
- /* @internal */ locals?: SymbolTable; // Locals associated with node (initialized by binding)
- /* @internal */ nextContainer?: Node; // Next container in declaration order (initialized by binding)
- /* @internal */ localSymbol?: Symbol; // Local symbol declared by node (initialized by binding only for exported nodes)
+ decorators?: NodeArray; // Array of decorators (in document order)
+ modifiers?: ModifiersArray; // Array of modifiers
+ /* @internal */ id?: number; // Unique id (used to look up NodeLinks)
+ parent?: Node; // Parent node (initialized by binding
+ /* @internal */ jsDocComment?: JSDocComment; // JSDoc for the node, if it has any. Only for .js files.
+ /* @internal */ symbol?: Symbol; // Symbol declared by node (initialized by binding)
+ /* @internal */ locals?: SymbolTable; // Locals associated with node (initialized by binding)
+ /* @internal */ nextContainer?: Node; // Next container in declaration order (initialized by binding)
+ /* @internal */ localSymbol?: Symbol; // Local symbol declared by node (initialized by binding only for exported nodes)
}
export interface NodeArray extends Array, TextRange {
@@ -992,6 +1025,106 @@ module ts {
kind: SyntaxKind;
}
+ // represents a top level: { type } expression in a JSDoc comment.
+ export interface JSDocTypeExpression extends Node {
+ type: JSDocType;
+ }
+
+ export interface JSDocType extends TypeNode {
+ _jsDocTypeBrand: any;
+ }
+
+ export interface JSDocAllType extends JSDocType {
+ _JSDocAllTypeBrand: any;
+ }
+
+ export interface JSDocUnknownType extends JSDocType {
+ _JSDocUnknownTypeBrand: any;
+ }
+
+ export interface JSDocArrayType extends JSDocType {
+ elementType: JSDocType;
+ }
+
+ export interface JSDocUnionType extends JSDocType {
+ types: NodeArray;
+ }
+
+ export interface JSDocTupleType extends JSDocType {
+ types: NodeArray;
+ }
+
+ export interface JSDocNonNullableType extends JSDocType {
+ type: JSDocType;
+ }
+
+ export interface JSDocNullableType extends JSDocType {
+ type: JSDocType;
+ }
+
+ export interface JSDocRecordType extends JSDocType, TypeLiteralNode {
+ members: NodeArray;
+ }
+
+ export interface JSDocTypeReference extends JSDocType {
+ name: EntityName;
+ typeArguments: NodeArray
+ }
+
+ export interface JSDocOptionalType extends JSDocType {
+ type: JSDocType;
+ }
+
+ export interface JSDocFunctionType extends JSDocType, SignatureDeclaration {
+ parameters: NodeArray;
+ type: JSDocType;
+ }
+
+ export interface JSDocVariadicType extends JSDocType {
+ type: JSDocType;
+ }
+
+ export interface JSDocConstructorType extends JSDocType {
+ type: JSDocType;
+ }
+
+ export interface JSDocThisType extends JSDocType {
+ type: JSDocType;
+ }
+
+ export interface JSDocRecordMember extends PropertyDeclaration {
+ name: Identifier | LiteralExpression,
+ type?: JSDocType
+ }
+
+ export interface JSDocComment extends Node {
+ tags: NodeArray;
+ }
+
+ export interface JSDocTag extends Node {
+ atToken: Node;
+ tagName: Identifier;
+ }
+
+ export interface JSDocTemplateTag extends JSDocTag {
+ typeParameters: NodeArray;
+ }
+
+ export interface JSDocReturnTag extends JSDocTag {
+ typeExpression: JSDocTypeExpression;
+ }
+
+ export interface JSDocTypeTag extends JSDocTag {
+ typeExpression: JSDocTypeExpression;
+ }
+
+ export interface JSDocParameterTag extends JSDocTag {
+ preParameterName?: Identifier;
+ typeExpression?: JSDocTypeExpression;
+ postParameterName?: Identifier;
+ isBracketed: boolean;
+ }
+
// Source files are declarations when they are external modules.
export interface SourceFile extends Declaration {
statements: NodeArray;
diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts
index 356337e1315..4aca0859cee 100644
--- a/src/compiler/utilities.ts
+++ b/src/compiler/utilities.ts
@@ -468,6 +468,12 @@ module ts {
return false;
}
+ export function isClassLike(node: Node): boolean {
+ if (node) {
+ return node.kind === SyntaxKind.ClassDeclaration || node.kind === SyntaxKind.ClassExpression;
+ }
+ }
+
export function isFunctionLike(node: Node): boolean {
if (node) {
switch (node.kind) {
@@ -830,7 +836,7 @@ module ts {
}
export function hasDotDotDotToken(node: Node) {
- return node && node.kind === SyntaxKind.Parameter && (node).dotDotDotToken !== undefined;
+ return node && node.kind === SyntaxKind.Parameter && (node).dotDotDotToken !== undefined;
}
export function hasQuestionToken(node: Node) {
@@ -856,6 +862,78 @@ module ts {
return s.parameters.length > 0 && lastOrUndefined(s.parameters).dotDotDotToken !== undefined;
}
+ export function isJSDocConstructSignature(node: Node) {
+ return node.kind === SyntaxKind.JSDocFunctionType &&
+ (node).parameters.length > 0 &&
+ (node).parameters[0].type.kind === SyntaxKind.JSDocConstructorType;
+ }
+
+ function getJSDocTag(node: Node, kind: SyntaxKind): JSDocTag {
+ if (node && node.jsDocComment) {
+ for (let tag of node.jsDocComment.tags) {
+ if (tag.kind === kind) {
+ return tag;
+ }
+ }
+ }
+ }
+
+ export function getJSDocTypeTag(node: Node): JSDocTypeTag {
+ return getJSDocTag(node, SyntaxKind.JSDocTypeTag);
+ }
+
+ export function getJSDocReturnTag(node: Node): JSDocReturnTag {
+ return getJSDocTag(node, SyntaxKind.JSDocReturnTag);
+ }
+
+ export function getJSDocTemplateTag(node: Node): JSDocTemplateTag {
+ return getJSDocTag(node, SyntaxKind.JSDocTemplateTag);
+ }
+
+ export function getCorrespondingJSDocParameterTag(parameter: ParameterDeclaration): JSDocParameterTag {
+ if (parameter.name && parameter.name.kind === SyntaxKind.Identifier) {
+ // If it's a parameter, see if the parent has a jsdoc comment with an @param
+ // annotation.
+ let parameterName = (parameter.name).text;
+
+ let docComment = parameter.parent.jsDocComment;
+ if (docComment) {
+ return forEach(docComment.tags, t => {
+ if (t.kind === SyntaxKind.JSDocParameterTag) {
+ let parameterTag = t;
+ let name = parameterTag.preParameterName || parameterTag.postParameterName;
+ if (name.text === parameterName) {
+ return t;
+ }
+ }
+ });
+ }
+ }
+ }
+
+ export function hasRestParameter(s: SignatureDeclaration): boolean {
+ return isRestParameter(lastOrUndefined(s.parameters));
+ }
+
+ export function isRestParameter(node: ParameterDeclaration) {
+ if (node) {
+ if (node.parserContextFlags & ParserContextFlags.JavaScriptFile) {
+ if (node.type && node.type.kind === SyntaxKind.JSDocVariadicType) {
+ return true;
+ }
+
+ let paramTag = getCorrespondingJSDocParameterTag(node);
+ if (paramTag && paramTag.typeExpression) {
+ return paramTag.typeExpression.type.kind === SyntaxKind.JSDocVariadicType;
+ }
+ }
+
+ return node.dotDotDotToken !== undefined;
+ }
+
+ return false;
+ }
+
export function isLiteralKind(kind: SyntaxKind): boolean {
return SyntaxKind.FirstLiteralToken <= kind && kind <= SyntaxKind.LastLiteralToken;
}
@@ -1707,6 +1785,10 @@ module ts {
return symbol && symbol.valueDeclaration && (symbol.valueDeclaration.flags & NodeFlags.Default) ? symbol.valueDeclaration.localSymbol : undefined;
}
+ export function isJavaScript(fileName: string) {
+ return fileExtensionIs(fileName, ".js");
+ }
+
/**
* Replace each instance of non-ascii characters by one, two, three, or four escape sequences
* representing the UTF-8 encoding of the character, and return the expanded char code list.
@@ -2000,4 +2082,14 @@ module ts {
return createTextChangeRange(createTextSpanFromBounds(oldStartN, oldEndN), /*newLength:*/ newEndN - oldStartN);
}
+
+ export function getTypeParameterOwner(d: Declaration): Declaration {
+ if (d && d.kind === SyntaxKind.TypeParameter) {
+ for (let current: Node = d; current; current = current.parent) {
+ if (isFunctionLike(current) || isClassLike(current) || current.kind === SyntaxKind.InterfaceDeclaration) {
+ return current;
+ }
+ }
+ }
+ }
}
diff --git a/src/harness/harness.ts b/src/harness/harness.ts
index 23cdfdfb7c5..90afab029eb 100644
--- a/src/harness/harness.ts
+++ b/src/harness/harness.ts
@@ -193,12 +193,16 @@ module Utils {
};
}
- export function sourceFileToJSON(file: ts.SourceFile): string {
+ export function sourceFileToJSON(file: ts.Node): string {
return JSON.stringify(file,(k, v) => {
return isNodeOrArray(v) ? serializeNode(v) : v;
}, " ");
- function getKindName(k: number): string {
+ function getKindName(k: number | string): string {
+ if (typeof k === "string") {
+ return k;
+ }
+
return (ts).SyntaxKind[k]
}
@@ -231,7 +235,9 @@ module Utils {
function serializeNode(n: ts.Node): any {
var o: any = { kind: getKindName(n.kind) };
- o.containsParseError = ts.containsParseError(n);
+ if (ts.containsParseError(n)) {
+ o.containsParseError = true;
+ }
ts.forEach(Object.getOwnPropertyNames(n), propertyName => {
switch (propertyName) {
@@ -249,6 +255,10 @@ module Utils {
// Blacklist of items we never put in the baseline file.
break;
+ case "originalKeywordKind":
+ o[propertyName] = getKindName((n)[propertyName]);
+ break;
+
case "flags":
// Print out flags with their enum names.
o[propertyName] = getNodeFlagName(n.flags);
diff --git a/src/services/services.ts b/src/services/services.ts
index a6a26f0b60b..1019c532192 100644
--- a/src/services/services.ts
+++ b/src/services/services.ts
@@ -2698,7 +2698,7 @@ module ts {
return true;
}
if (parameter.questionToken) {
- diagnostics.push(createDiagnosticForNode(parameter.questionToken, Diagnostics.can_only_be_used_in_a_ts_file));
+ diagnostics.push(createDiagnosticForNode(parameter.questionToken, Diagnostics._0_can_only_be_used_in_a_ts_file, '?'));
return true;
}
if (parameter.type) {
diff --git a/src/services/utilities.ts b/src/services/utilities.ts
index f2074d43ec9..da0a0aa96c1 100644
--- a/src/services/utilities.ts
+++ b/src/services/utilities.ts
@@ -652,8 +652,4 @@ module ts {
typechecker.getSymbolDisplayBuilder().buildSignatureDisplay(signature, writer, enclosingDeclaration, flags);
});
}
-
- export function isJavaScript(fileName: string) {
- return fileExtensionIs(fileName, ".js");
- }
}
\ No newline at end of file
diff --git a/tests/cases/fourslash/getJavaScriptSemanticDiagnostics16.ts b/tests/cases/fourslash/getJavaScriptSemanticDiagnostics16.ts
index 2ccac13dbf9..1e114532c79 100644
--- a/tests/cases/fourslash/getJavaScriptSemanticDiagnostics16.ts
+++ b/tests/cases/fourslash/getJavaScriptSemanticDiagnostics16.ts
@@ -10,6 +10,6 @@ verify.getSemanticDiagnostics(`[
"start": 12,
"length": 1,
"category": "error",
- "code": 8013
+ "code": 8009
}
]`);
\ No newline at end of file
diff --git a/tests/cases/unittests/jsDocParsing.ts b/tests/cases/unittests/jsDocParsing.ts
new file mode 100644
index 00000000000..ebba6e8a1a6
--- /dev/null
+++ b/tests/cases/unittests/jsDocParsing.ts
@@ -0,0 +1,2204 @@
+///
+///
+///
+
+module ts {
+ describe("JSDocParsing", () => {
+ describe("TypeExpressions", () => {
+ function parsesCorrectly(content: string, expected: string) {
+ let typeAndDiagnostics = ts.parseJSDocTypeExpressionForTests(content);
+ assert.isTrue(typeAndDiagnostics && typeAndDiagnostics.diagnostics.length === 0);
+
+ let result = Utils.sourceFileToJSON(typeAndDiagnostics.jsDocTypeExpression.type);
+ assert.equal(result, expected);
+ }
+
+ function parsesIncorrectly(content: string) {
+ let type = ts.parseJSDocTypeExpressionForTests(content);
+ assert.isTrue(!type || type.diagnostics.length > 0);
+ }
+
+ describe("parseCorrectly", () => {
+ it("unknownType", () => {
+ parsesCorrectly("{?}",
+ `{
+ "kind": "JSDocUnknownType",
+ "pos": 1,
+ "end": 2
+}`);
+ });
+
+ it("allType", () => {
+ parsesCorrectly("{*}",
+ `{
+ "kind": "JSDocAllType",
+ "pos": 1,
+ "end": 2
+}`);
+ });
+
+ it("nullableType", () => {
+ parsesCorrectly("{?number}",
+ `{
+ "kind": "JSDocNullableType",
+ "pos": 1,
+ "end": 8,
+ "type": {
+ "kind": "NumberKeyword",
+ "pos": 2,
+ "end": 8
+ }
+}`)
+ });
+
+ it("nullableType2", () => {
+ parsesCorrectly("{number?}",
+ `{
+ "kind": "JSDocNullableType",
+ "pos": 1,
+ "end": 8,
+ "type": {
+ "kind": "NumberKeyword",
+ "pos": 1,
+ "end": 7
+ }
+}`)
+ });
+
+ it("nonNullableType", () => {
+ parsesCorrectly("{!number}",
+ `{
+ "kind": "JSDocNonNullableType",
+ "pos": 1,
+ "end": 8,
+ "type": {
+ "kind": "NumberKeyword",
+ "pos": 2,
+ "end": 8
+ }
+}`)
+ });
+
+ it("nonNullableType2", () => {
+ parsesCorrectly("{number!}",
+ `{
+ "kind": "JSDocNonNullableType",
+ "pos": 1,
+ "end": 8,
+ "type": {
+ "kind": "NumberKeyword",
+ "pos": 1,
+ "end": 7
+ }
+}`)
+ });
+
+ it("recordType1", () => {
+ parsesCorrectly("{{}}",
+ `{
+ "kind": "JSDocRecordType",
+ "pos": 1,
+ "end": 3,
+ "members": {
+ "length": 0,
+ "pos": 2,
+ "end": 2
+ }
+}`)
+ });
+
+ it("recordType2", () => {
+ parsesCorrectly("{{foo}}",
+ `{
+ "kind": "JSDocRecordType",
+ "pos": 1,
+ "end": 6,
+ "members": {
+ "0": {
+ "kind": "JSDocRecordMember",
+ "pos": 2,
+ "end": 5,
+ "name": {
+ "kind": "Identifier",
+ "pos": 2,
+ "end": 5,
+ "text": "foo"
+ }
+ },
+ "length": 1,
+ "pos": 2,
+ "end": 5
+ }
+}`)
+ });
+
+ it("recordType3", () => {
+ parsesCorrectly("{{foo: number}}",
+ `{
+ "kind": "JSDocRecordType",
+ "pos": 1,
+ "end": 14,
+ "members": {
+ "0": {
+ "kind": "JSDocRecordMember",
+ "pos": 2,
+ "end": 13,
+ "name": {
+ "kind": "Identifier",
+ "pos": 2,
+ "end": 5,
+ "text": "foo"
+ },
+ "type": {
+ "kind": "NumberKeyword",
+ "pos": 6,
+ "end": 13
+ }
+ },
+ "length": 1,
+ "pos": 2,
+ "end": 13
+ }
+}`)
+ });
+
+ it("recordType4", () => {
+ parsesCorrectly("{{foo, bar}}",
+ `{
+ "kind": "JSDocRecordType",
+ "pos": 1,
+ "end": 11,
+ "members": {
+ "0": {
+ "kind": "JSDocRecordMember",
+ "pos": 2,
+ "end": 5,
+ "name": {
+ "kind": "Identifier",
+ "pos": 2,
+ "end": 5,
+ "text": "foo"
+ }
+ },
+ "1": {
+ "kind": "JSDocRecordMember",
+ "pos": 6,
+ "end": 10,
+ "name": {
+ "kind": "Identifier",
+ "pos": 6,
+ "end": 10,
+ "text": "bar"
+ }
+ },
+ "length": 2,
+ "pos": 2,
+ "end": 10
+ }
+}`)
+ });
+
+ it("recordType5", () => {
+ parsesCorrectly("{{foo: number, bar}}",
+ `{
+ "kind": "JSDocRecordType",
+ "pos": 1,
+ "end": 19,
+ "members": {
+ "0": {
+ "kind": "JSDocRecordMember",
+ "pos": 2,
+ "end": 13,
+ "name": {
+ "kind": "Identifier",
+ "pos": 2,
+ "end": 5,
+ "text": "foo"
+ },
+ "type": {
+ "kind": "NumberKeyword",
+ "pos": 6,
+ "end": 13
+ }
+ },
+ "1": {
+ "kind": "JSDocRecordMember",
+ "pos": 14,
+ "end": 18,
+ "name": {
+ "kind": "Identifier",
+ "pos": 14,
+ "end": 18,
+ "text": "bar"
+ }
+ },
+ "length": 2,
+ "pos": 2,
+ "end": 18
+ }
+}`)
+ });
+
+ it("recordType6", () => {
+ parsesCorrectly("{{foo, bar: number}}",
+ `{
+ "kind": "JSDocRecordType",
+ "pos": 1,
+ "end": 19,
+ "members": {
+ "0": {
+ "kind": "JSDocRecordMember",
+ "pos": 2,
+ "end": 5,
+ "name": {
+ "kind": "Identifier",
+ "pos": 2,
+ "end": 5,
+ "text": "foo"
+ }
+ },
+ "1": {
+ "kind": "JSDocRecordMember",
+ "pos": 6,
+ "end": 18,
+ "name": {
+ "kind": "Identifier",
+ "pos": 6,
+ "end": 10,
+ "text": "bar"
+ },
+ "type": {
+ "kind": "NumberKeyword",
+ "pos": 11,
+ "end": 18
+ }
+ },
+ "length": 2,
+ "pos": 2,
+ "end": 18
+ }
+}`)
+ });
+
+ it("recordType7", () => {
+ parsesCorrectly("{{foo: number, bar: number}}",
+ `{
+ "kind": "JSDocRecordType",
+ "pos": 1,
+ "end": 27,
+ "members": {
+ "0": {
+ "kind": "JSDocRecordMember",
+ "pos": 2,
+ "end": 13,
+ "name": {
+ "kind": "Identifier",
+ "pos": 2,
+ "end": 5,
+ "text": "foo"
+ },
+ "type": {
+ "kind": "NumberKeyword",
+ "pos": 6,
+ "end": 13
+ }
+ },
+ "1": {
+ "kind": "JSDocRecordMember",
+ "pos": 14,
+ "end": 26,
+ "name": {
+ "kind": "Identifier",
+ "pos": 14,
+ "end": 18,
+ "text": "bar"
+ },
+ "type": {
+ "kind": "NumberKeyword",
+ "pos": 19,
+ "end": 26
+ }
+ },
+ "length": 2,
+ "pos": 2,
+ "end": 26
+ }
+}`)
+ });
+
+ it("recordType8", () => {
+ parsesCorrectly("{{function}}",
+ `{
+ "kind": "JSDocRecordType",
+ "pos": 1,
+ "end": 11,
+ "members": {
+ "0": {
+ "kind": "JSDocRecordMember",
+ "pos": 2,
+ "end": 10,
+ "name": {
+ "kind": "Identifier",
+ "pos": 2,
+ "end": 10,
+ "originalKeywordKind": "FunctionKeyword",
+ "text": "function"
+ }
+ },
+ "length": 1,
+ "pos": 2,
+ "end": 10
+ }
+}`)
+ });
+
+ it("unionType", () => {
+ parsesCorrectly("{(number|string)}",
+ `{
+ "kind": "JSDocUnionType",
+ "pos": 1,
+ "end": 16,
+ "types": {
+ "0": {
+ "kind": "NumberKeyword",
+ "pos": 2,
+ "end": 8
+ },
+ "1": {
+ "kind": "StringKeyword",
+ "pos": 9,
+ "end": 15
+ },
+ "length": 2,
+ "pos": 2,
+ "end": 15
+ }
+}`);
+ });
+
+ it("topLevelNoParenUnionType", () => {
+ parsesCorrectly("{number|string}",
+ `{
+ "kind": "JSDocUnionType",
+ "pos": 1,
+ "end": 14,
+ "types": {
+ "0": {
+ "kind": "NumberKeyword",
+ "pos": 1,
+ "end": 7
+ },
+ "1": {
+ "kind": "StringKeyword",
+ "pos": 8,
+ "end": 14
+ },
+ "length": 2,
+ "pos": 1,
+ "end": 14
+ }
+}`);
+ });
+
+ it("functionType1", () => {
+ parsesCorrectly("{function()}",
+ `{
+ "kind": "JSDocFunctionType",
+ "pos": 1,
+ "end": 11,
+ "parameters": {
+ "length": 0,
+ "pos": 10,
+ "end": 10
+ }
+}`);
+ });
+
+ it("functionType2", () => {
+ parsesCorrectly("{function(string, boolean)}",
+ `{
+ "kind": "JSDocFunctionType",
+ "pos": 1,
+ "end": 26,
+ "parameters": {
+ "0": {
+ "kind": "Parameter",
+ "pos": 10,
+ "end": 16,
+ "type": {
+ "kind": "StringKeyword",
+ "pos": 10,
+ "end": 16
+ }
+ },
+ "1": {
+ "kind": "Parameter",
+ "pos": 17,
+ "end": 25,
+ "type": {
+ "kind": "BooleanKeyword",
+ "pos": 17,
+ "end": 25
+ }
+ },
+ "length": 2,
+ "pos": 10,
+ "end": 25
+ }
+}`);
+ });
+
+ it("functionReturnType1", () => {
+ parsesCorrectly("{function(string, boolean)}",
+ `{
+ "kind": "JSDocFunctionType",
+ "pos": 1,
+ "end": 26,
+ "parameters": {
+ "0": {
+ "kind": "Parameter",
+ "pos": 10,
+ "end": 16,
+ "type": {
+ "kind": "StringKeyword",
+ "pos": 10,
+ "end": 16
+ }
+ },
+ "1": {
+ "kind": "Parameter",
+ "pos": 17,
+ "end": 25,
+ "type": {
+ "kind": "BooleanKeyword",
+ "pos": 17,
+ "end": 25
+ }
+ },
+ "length": 2,
+ "pos": 10,
+ "end": 25
+ }
+}`);
+ });
+
+ it("thisType1", () => {
+ parsesCorrectly("{this:a.b}",
+ `{
+ "kind": "JSDocThisType",
+ "pos": 1,
+ "end": 9,
+ "type": {
+ "kind": "JSDocTypeReference",
+ "pos": 6,
+ "end": 9,
+ "name": {
+ "kind": "FirstNode",
+ "pos": 6,
+ "end": 9,
+ "left": {
+ "kind": "Identifier",
+ "pos": 6,
+ "end": 7,
+ "text": "a"
+ },
+ "right": {
+ "kind": "Identifier",
+ "pos": 8,
+ "end": 9,
+ "text": "b"
+ }
+ }
+ }
+}`);
+ });
+
+ it("newType1", () => {
+ parsesCorrectly("{new:a.b}",
+ `{
+ "kind": "JSDocConstructorType",
+ "pos": 1,
+ "end": 8,
+ "type": {
+ "kind": "JSDocTypeReference",
+ "pos": 5,
+ "end": 8,
+ "name": {
+ "kind": "FirstNode",
+ "pos": 5,
+ "end": 8,
+ "left": {
+ "kind": "Identifier",
+ "pos": 5,
+ "end": 6,
+ "text": "a"
+ },
+ "right": {
+ "kind": "Identifier",
+ "pos": 7,
+ "end": 8,
+ "text": "b"
+ }
+ }
+ }
+}`);
+ });
+
+ it("variadicType", () => {
+ parsesCorrectly("{...number}",
+ `{
+ "kind": "JSDocVariadicType",
+ "pos": 1,
+ "end": 10,
+ "type": {
+ "kind": "NumberKeyword",
+ "pos": 4,
+ "end": 10
+ }
+}`);
+ });
+
+ it("optionalType", () => {
+ parsesCorrectly("{number=}",
+ `{
+ "kind": "JSDocOptionalType",
+ "pos": 1,
+ "end": 8,
+ "type": {
+ "kind": "NumberKeyword",
+ "pos": 1,
+ "end": 7
+ }
+}`);
+ });
+
+ it("optionalNullable", () => {
+ parsesCorrectly("{?=}",
+ `{
+ "kind": "JSDocOptionalType",
+ "pos": 1,
+ "end": 3,
+ "type": {
+ "kind": "JSDocUnknownType",
+ "pos": 1,
+ "end": 2
+ }
+}`);
+ });
+
+ it("typeReference1", () => {
+ parsesCorrectly("{a.}",
+ `{
+ "kind": "JSDocTypeReference",
+ "pos": 1,
+ "end": 11,
+ "name": {
+ "kind": "Identifier",
+ "pos": 1,
+ "end": 2,
+ "text": "a"
+ },
+ "typeArguments": {
+ "0": {
+ "kind": "NumberKeyword",
+ "pos": 4,
+ "end": 10
+ },
+ "length": 1,
+ "pos": 4,
+ "end": 10
+ }
+}`);
+ });
+
+ it("typeReference2", () => {
+ parsesCorrectly("{a.}",
+ `{
+ "kind": "JSDocTypeReference",
+ "pos": 1,
+ "end": 18,
+ "name": {
+ "kind": "Identifier",
+ "pos": 1,
+ "end": 2,
+ "text": "a"
+ },
+ "typeArguments": {
+ "0": {
+ "kind": "NumberKeyword",
+ "pos": 4,
+ "end": 10
+ },
+ "1": {
+ "kind": "StringKeyword",
+ "pos": 11,
+ "end": 17
+ },
+ "length": 2,
+ "pos": 4,
+ "end": 17
+ }
+}`);
+ });
+
+ it("typeReference3", () => {
+ parsesCorrectly("{a.function}",
+ `{
+ "kind": "JSDocTypeReference",
+ "pos": 1,
+ "end": 11,
+ "name": {
+ "kind": "FirstNode",
+ "pos": 1,
+ "end": 11,
+ "left": {
+ "kind": "Identifier",
+ "pos": 1,
+ "end": 2,
+ "text": "a"
+ },
+ "right": {
+ "kind": "Identifier",
+ "pos": 3,
+ "end": 11,
+ "originalKeywordKind": "FunctionKeyword",
+ "text": "function"
+ }
+ }
+}`);
+ });
+
+ it("arrayType1", () => {
+ parsesCorrectly(
+ "{a[]}",
+ `{
+ "kind": "JSDocArrayType",
+ "pos": 1,
+ "end": 4,
+ "elementType": {
+ "kind": "JSDocTypeReference",
+ "pos": 1,
+ "end": 2,
+ "name": {
+ "kind": "Identifier",
+ "pos": 1,
+ "end": 2,
+ "text": "a"
+ }
+ }
+}`);
+ });
+
+ it("arrayType2", () => {
+ parsesCorrectly(
+ "{a[][]}",
+ `{
+ "kind": "JSDocArrayType",
+ "pos": 1,
+ "end": 6,
+ "elementType": {
+ "kind": "JSDocArrayType",
+ "pos": 1,
+ "end": 4,
+ "elementType": {
+ "kind": "JSDocTypeReference",
+ "pos": 1,
+ "end": 2,
+ "name": {
+ "kind": "Identifier",
+ "pos": 1,
+ "end": 2,
+ "text": "a"
+ }
+ }
+ }
+}`);
+ });
+
+ it("arrayType3", () => {
+ parsesCorrectly(
+ "{a[][]=}",
+ `{
+ "kind": "JSDocOptionalType",
+ "pos": 1,
+ "end": 7,
+ "type": {
+ "kind": "JSDocArrayType",
+ "pos": 1,
+ "end": 6,
+ "elementType": {
+ "kind": "JSDocArrayType",
+ "pos": 1,
+ "end": 4,
+ "elementType": {
+ "kind": "JSDocTypeReference",
+ "pos": 1,
+ "end": 2,
+ "name": {
+ "kind": "Identifier",
+ "pos": 1,
+ "end": 2,
+ "text": "a"
+ }
+ }
+ }
+ }
+}`);
+ });
+
+ it("keyword1", () => {
+ parsesCorrectly(
+ "{var}",
+ `{
+ "kind": "JSDocTypeReference",
+ "pos": 1,
+ "end": 4,
+ "name": {
+ "kind": "Identifier",
+ "pos": 1,
+ "end": 4,
+ "originalKeywordKind": "VarKeyword",
+ "text": "var"
+ }
+}`);
+ });
+
+ it("keyword2", () => {
+ parsesCorrectly(
+ "{null}",
+ `{
+ "kind": "JSDocTypeReference",
+ "pos": 1,
+ "end": 5,
+ "name": {
+ "kind": "Identifier",
+ "pos": 1,
+ "end": 5,
+ "originalKeywordKind": "NullKeyword",
+ "text": "null"
+ }
+}`);
+ });
+
+ it("keyword3", () => {
+ parsesCorrectly(
+ "{undefined}",
+ `{
+ "kind": "JSDocTypeReference",
+ "pos": 1,
+ "end": 10,
+ "name": {
+ "kind": "Identifier",
+ "pos": 1,
+ "end": 10,
+ "text": "undefined"
+ }
+}`);
+ });
+
+ it("tupleType0", () => {
+ parsesCorrectly(
+ "{[]}",
+ `{
+ "kind": "JSDocTupleType",
+ "pos": 1,
+ "end": 3,
+ "types": {
+ "length": 0,
+ "pos": 2,
+ "end": 2
+ }
+}`);
+ });
+
+ it("tupleType1", () => {
+ parsesCorrectly(
+ "{[number]}",
+ `{
+ "kind": "JSDocTupleType",
+ "pos": 1,
+ "end": 9,
+ "types": {
+ "0": {
+ "kind": "NumberKeyword",
+ "pos": 2,
+ "end": 8
+ },
+ "length": 1,
+ "pos": 2,
+ "end": 8
+ }
+}`);
+ });
+
+ it("tupleType2", () => {
+ parsesCorrectly(
+ "{[number,string]}",
+ `{
+ "kind": "JSDocTupleType",
+ "pos": 1,
+ "end": 16,
+ "types": {
+ "0": {
+ "kind": "NumberKeyword",
+ "pos": 2,
+ "end": 8
+ },
+ "1": {
+ "kind": "StringKeyword",
+ "pos": 9,
+ "end": 15
+ },
+ "length": 2,
+ "pos": 2,
+ "end": 15
+ }
+}`);
+ });
+
+ it("tupleType3", () => {
+ parsesCorrectly(
+ "{[number,string,boolean]}",
+ `{
+ "kind": "JSDocTupleType",
+ "pos": 1,
+ "end": 24,
+ "types": {
+ "0": {
+ "kind": "NumberKeyword",
+ "pos": 2,
+ "end": 8
+ },
+ "1": {
+ "kind": "StringKeyword",
+ "pos": 9,
+ "end": 15
+ },
+ "2": {
+ "kind": "BooleanKeyword",
+ "pos": 16,
+ "end": 23
+ },
+ "length": 3,
+ "pos": 2,
+ "end": 23
+ }
+}`);
+ });
+ });
+
+ describe("parsesIncorrectly", () => {
+ it("emptyType", () => {
+ parsesIncorrectly("{}");
+ });
+
+ it("trailingCommaInRecordType", () => {
+ parsesIncorrectly("{{a,}}");
+ });
+
+ it("unionTypeWithTrailingBar", () => {
+ parsesIncorrectly("{(a|)}");
+ });
+
+ it("unionTypeWithoutTypes", () => {
+ parsesIncorrectly("{()}");
+ });
+
+ it("nullableTypeWithoutType", () => {
+ parsesIncorrectly("{!}");
+ });
+
+ it("functionTypeWithTrailingComma", () => {
+ parsesIncorrectly("{function(a,)}");
+ });
+
+ it("thisWithoutType", () => {
+ parsesIncorrectly("{this:}");
+ });
+
+ it("newWithoutType", () => {
+ parsesIncorrectly("{new:}");
+ });
+
+ it("variadicWithoutType", () => {
+ parsesIncorrectly("{...}");
+ });
+
+ it("optionalWithoutType", () => {
+ parsesIncorrectly("{=}");
+ });
+
+ it("allWithType", () => {
+ parsesIncorrectly("{*foo}");
+ });
+
+ it("typeArgumentsNotFollowingDot", () => {
+ parsesIncorrectly("{a<>}");
+ });
+
+ it("emptyTypeArguments", () => {
+ parsesIncorrectly("{a.<>}");
+ });
+
+ it("typeArgumentsWithTrailingComma", () => {
+ parsesIncorrectly("{a.}");
+ });
+
+ it("tsFunctionType", () => {
+ parsesIncorrectly("{() => string}");
+ });
+
+ it("tsConstructoType", () => {
+ parsesIncorrectly("{new () => string}");
+ });
+
+ it("typeOfType", () => {
+ parsesIncorrectly("{typeof M}");
+ });
+
+ it("namedParameter", () => {
+ parsesIncorrectly("{function(a: number)}");
+ });
+
+ it("callSignatureInRecordType", () => {
+ parsesIncorrectly("{{(): number}}");
+ });
+
+ it("methodInRecordType", () => {
+ parsesIncorrectly("{{foo(): number}}");
+ });
+
+ it("tupleTypeWithComma", () => {
+ parsesIncorrectly( "{[,]}");
+ });
+
+ it("tupleTypeWithTrailingComma", () => {
+ parsesIncorrectly("{[number,]}");
+ });
+
+ it("tupleTypeWithLeadingComma", () => {
+ parsesIncorrectly("{[,number]}");
+ });
+ });
+ });
+
+ describe("DocComments", () => {
+ function parsesCorrectly(content: string, expected: string) {
+ let comment = parseIsolatedJSDocComment(content);
+ Debug.assert(comment && comment.diagnostics.length === 0);
+
+ let result = JSON.stringify(comment.jsDocComment, (k, v) => {
+ return v && v.pos !== undefined
+ ? JSON.parse(Utils.sourceFileToJSON(v))
+ : v;
+ }, " ");
+
+ assert.equal(result, expected);
+ }
+
+ function parsesIncorrectly(content: string) {
+ let type = parseIsolatedJSDocComment(content);
+ assert.isTrue(!type || type.diagnostics.length > 0);
+ }
+
+ describe("parsesIncorrectly", () => {
+ it("emptyComment", () => {
+ parsesIncorrectly("/***/");
+ });
+
+ it("threeAsterisks", () => {
+ parsesIncorrectly("/*** */");
+ });
+
+ it("asteriskAfterPreamble", () => {
+ parsesIncorrectly("/** * @type {number} */");
+ });
+
+ it("multipleTypes", () => {
+ parsesIncorrectly(
+`/**
+ * @type {number}
+ * @type {string}
+ */`);
+ });
+
+ it("multipleReturnTypes", () => {
+ parsesIncorrectly(
+`/**
+ * @return {number}
+ * @return {string}
+ */`);
+ });
+
+ it("noTypeParameters", () => {
+ parsesIncorrectly(
+`/**
+ * @template
+ */`);
+ });
+
+ it("trailingTypeParameterComma", () => {
+ parsesIncorrectly(
+`/**
+ * @template T,
+ */`);
+ });
+
+ it("paramWithoutName", () => {
+ parsesIncorrectly(
+`/**
+ * @param {number}
+ */`);
+ });
+
+ it("paramWithoutTypeOrName", () => {
+ parsesIncorrectly(
+`/**
+ * @param
+ */`);
+ });
+ });
+
+ describe("parsesCorrectly", () => {
+ it("noLeadingAsterisk", () => {
+ parsesCorrectly(
+`/**
+ @type {number}
+ */`,
+ `{
+ "kind": "JSDocComment",
+ "pos": 0,
+ "end": 27,
+ "tags": {
+ "0": {
+ "kind": "JSDocTypeTag",
+ "pos": 8,
+ "end": 22,
+ "atToken": {
+ "kind": "AtToken",
+ "pos": 8,
+ "end": 9
+ },
+ "tagName": {
+ "kind": "Identifier",
+ "pos": 9,
+ "end": 13,
+ "text": "type"
+ },
+ "typeExpression": {
+ "kind": "JSDocTypeExpression",
+ "pos": 14,
+ "end": 22,
+ "type": {
+ "kind": "NumberKeyword",
+ "pos": 15,
+ "end": 21
+ }
+ }
+ },
+ "length": 1,
+ "pos": 8,
+ "end": 22
+ }
+}`);
+ });
+
+ it("noType", () => {
+ parsesCorrectly(
+ `/**
+ * @type
+ */`,
+ `{
+ "kind": "JSDocComment",
+ "pos": 0,
+ "end": 18,
+ "tags": {
+ "0": {
+ "kind": "JSDocTypeTag",
+ "pos": 8,
+ "end": 13,
+ "atToken": {
+ "kind": "AtToken",
+ "pos": 8,
+ "end": 9
+ },
+ "tagName": {
+ "kind": "Identifier",
+ "pos": 9,
+ "end": 13,
+ "text": "type"
+ }
+ },
+ "length": 1,
+ "pos": 8,
+ "end": 13
+ }
+}`);
+ });
+
+ it("noReturnType", () => {
+ parsesCorrectly(
+ `/**
+ * @return
+ */`,
+ `{
+ "kind": "JSDocComment",
+ "pos": 0,
+ "end": 20,
+ "tags": {
+ "0": {
+ "kind": "JSDocReturnTag",
+ "pos": 8,
+ "end": 15,
+ "atToken": {
+ "kind": "AtToken",
+ "pos": 8,
+ "end": 9
+ },
+ "tagName": {
+ "kind": "Identifier",
+ "pos": 9,
+ "end": 15,
+ "text": "return"
+ }
+ },
+ "length": 1,
+ "pos": 8,
+ "end": 15
+ }
+}`);
+ });
+
+ it("leadingAsterisk", () => {
+ parsesCorrectly(
+`/**
+ * @type {number}
+ */`,
+ `{
+ "kind": "JSDocComment",
+ "pos": 0,
+ "end": 27,
+ "tags": {
+ "0": {
+ "kind": "JSDocTypeTag",
+ "pos": 8,
+ "end": 22,
+ "atToken": {
+ "kind": "AtToken",
+ "pos": 8,
+ "end": 9
+ },
+ "tagName": {
+ "kind": "Identifier",
+ "pos": 9,
+ "end": 13,
+ "text": "type"
+ },
+ "typeExpression": {
+ "kind": "JSDocTypeExpression",
+ "pos": 14,
+ "end": 22,
+ "type": {
+ "kind": "NumberKeyword",
+ "pos": 15,
+ "end": 21
+ }
+ }
+ },
+ "length": 1,
+ "pos": 8,
+ "end": 22
+ }
+}`);
+ });
+
+ it("typeTag", () => {
+ parsesCorrectly(
+`/**
+ * @type {number}
+ */`,
+ `{
+ "kind": "JSDocComment",
+ "pos": 0,
+ "end": 27,
+ "tags": {
+ "0": {
+ "kind": "JSDocTypeTag",
+ "pos": 8,
+ "end": 22,
+ "atToken": {
+ "kind": "AtToken",
+ "pos": 8,
+ "end": 9
+ },
+ "tagName": {
+ "kind": "Identifier",
+ "pos": 9,
+ "end": 13,
+ "text": "type"
+ },
+ "typeExpression": {
+ "kind": "JSDocTypeExpression",
+ "pos": 14,
+ "end": 22,
+ "type": {
+ "kind": "NumberKeyword",
+ "pos": 15,
+ "end": 21
+ }
+ }
+ },
+ "length": 1,
+ "pos": 8,
+ "end": 22
+ }
+}`);
+ });
+
+ it("returnTag1", () => {
+ parsesCorrectly(
+`/**
+ * @return {number}
+ */`,
+ `{
+ "kind": "JSDocComment",
+ "pos": 0,
+ "end": 29,
+ "tags": {
+ "0": {
+ "kind": "JSDocReturnTag",
+ "pos": 8,
+ "end": 24,
+ "atToken": {
+ "kind": "AtToken",
+ "pos": 8,
+ "end": 9
+ },
+ "tagName": {
+ "kind": "Identifier",
+ "pos": 9,
+ "end": 15,
+ "text": "return"
+ },
+ "typeExpression": {
+ "kind": "JSDocTypeExpression",
+ "pos": 16,
+ "end": 24,
+ "type": {
+ "kind": "NumberKeyword",
+ "pos": 17,
+ "end": 23
+ }
+ }
+ },
+ "length": 1,
+ "pos": 8,
+ "end": 24
+ }
+}`);
+ });
+
+ it("returnTag2", () => {
+ parsesCorrectly(
+ `/**
+ * @return {number} Description text follows
+ */`,
+ `{
+ "kind": "JSDocComment",
+ "pos": 0,
+ "end": 54,
+ "tags": {
+ "0": {
+ "kind": "JSDocReturnTag",
+ "pos": 8,
+ "end": 24,
+ "atToken": {
+ "kind": "AtToken",
+ "pos": 8,
+ "end": 9
+ },
+ "tagName": {
+ "kind": "Identifier",
+ "pos": 9,
+ "end": 15,
+ "text": "return"
+ },
+ "typeExpression": {
+ "kind": "JSDocTypeExpression",
+ "pos": 16,
+ "end": 24,
+ "type": {
+ "kind": "NumberKeyword",
+ "pos": 17,
+ "end": 23
+ }
+ }
+ },
+ "length": 1,
+ "pos": 8,
+ "end": 24
+ }
+}`);
+ });
+
+ it("returnsTag1", () => {
+ parsesCorrectly(
+ `/**
+ * @returns {number}
+ */`,
+ `{
+ "kind": "JSDocComment",
+ "pos": 0,
+ "end": 30,
+ "tags": {
+ "0": {
+ "kind": "JSDocReturnTag",
+ "pos": 8,
+ "end": 25,
+ "atToken": {
+ "kind": "AtToken",
+ "pos": 8,
+ "end": 9
+ },
+ "tagName": {
+ "kind": "Identifier",
+ "pos": 9,
+ "end": 16,
+ "text": "returns"
+ },
+ "typeExpression": {
+ "kind": "JSDocTypeExpression",
+ "pos": 17,
+ "end": 25,
+ "type": {
+ "kind": "NumberKeyword",
+ "pos": 18,
+ "end": 24
+ }
+ }
+ },
+ "length": 1,
+ "pos": 8,
+ "end": 25
+ }
+}`);
+ });
+
+ it("oneParamTag", () => {
+ parsesCorrectly(
+`/**
+ * @param {number} name1
+ */`,
+ `{
+ "kind": "JSDocComment",
+ "pos": 0,
+ "end": 34,
+ "tags": {
+ "0": {
+ "kind": "JSDocParameterTag",
+ "pos": 8,
+ "end": 29,
+ "atToken": {
+ "kind": "AtToken",
+ "pos": 8,
+ "end": 9
+ },
+ "tagName": {
+ "kind": "Identifier",
+ "pos": 9,
+ "end": 14,
+ "text": "param"
+ },
+ "typeExpression": {
+ "kind": "JSDocTypeExpression",
+ "pos": 15,
+ "end": 23,
+ "type": {
+ "kind": "NumberKeyword",
+ "pos": 16,
+ "end": 22
+ }
+ },
+ "postParameterName": {
+ "kind": "Identifier",
+ "pos": 24,
+ "end": 29,
+ "text": "name1"
+ }
+ },
+ "length": 1,
+ "pos": 8,
+ "end": 29
+ }
+}`);
+ });
+
+ it("twoParamTag2", () => {
+ parsesCorrectly(
+`/**
+ * @param {number} name1
+ * @param {number} name2
+ */`,
+ `{
+ "kind": "JSDocComment",
+ "pos": 0,
+ "end": 60,
+ "tags": {
+ "0": {
+ "kind": "JSDocParameterTag",
+ "pos": 8,
+ "end": 29,
+ "atToken": {
+ "kind": "AtToken",
+ "pos": 8,
+ "end": 9
+ },
+ "tagName": {
+ "kind": "Identifier",
+ "pos": 9,
+ "end": 14,
+ "text": "param"
+ },
+ "typeExpression": {
+ "kind": "JSDocTypeExpression",
+ "pos": 15,
+ "end": 23,
+ "type": {
+ "kind": "NumberKeyword",
+ "pos": 16,
+ "end": 22
+ }
+ },
+ "postParameterName": {
+ "kind": "Identifier",
+ "pos": 24,
+ "end": 29,
+ "text": "name1"
+ }
+ },
+ "1": {
+ "kind": "JSDocParameterTag",
+ "pos": 34,
+ "end": 55,
+ "atToken": {
+ "kind": "AtToken",
+ "pos": 34,
+ "end": 35
+ },
+ "tagName": {
+ "kind": "Identifier",
+ "pos": 35,
+ "end": 40,
+ "text": "param"
+ },
+ "typeExpression": {
+ "kind": "JSDocTypeExpression",
+ "pos": 41,
+ "end": 49,
+ "type": {
+ "kind": "NumberKeyword",
+ "pos": 42,
+ "end": 48
+ }
+ },
+ "postParameterName": {
+ "kind": "Identifier",
+ "pos": 50,
+ "end": 55,
+ "text": "name2"
+ }
+ },
+ "length": 2,
+ "pos": 8,
+ "end": 55
+ }
+}`);
+ });
+
+ it("paramTag1", () => {
+ parsesCorrectly(
+ `/**
+ * @param {number} name1 Description text follows
+ */`,
+ `{
+ "kind": "JSDocComment",
+ "pos": 0,
+ "end": 59,
+ "tags": {
+ "0": {
+ "kind": "JSDocParameterTag",
+ "pos": 8,
+ "end": 29,
+ "atToken": {
+ "kind": "AtToken",
+ "pos": 8,
+ "end": 9
+ },
+ "tagName": {
+ "kind": "Identifier",
+ "pos": 9,
+ "end": 14,
+ "text": "param"
+ },
+ "typeExpression": {
+ "kind": "JSDocTypeExpression",
+ "pos": 15,
+ "end": 23,
+ "type": {
+ "kind": "NumberKeyword",
+ "pos": 16,
+ "end": 22
+ }
+ },
+ "postParameterName": {
+ "kind": "Identifier",
+ "pos": 24,
+ "end": 29,
+ "text": "name1"
+ }
+ },
+ "length": 1,
+ "pos": 8,
+ "end": 29
+ }
+}`);
+ });
+
+ it("paramTagBracketedName1", () => {
+ parsesCorrectly(
+ `/**
+ * @param {number} [name1] Description text follows
+ */`,
+ `{
+ "kind": "JSDocComment",
+ "pos": 0,
+ "end": 61,
+ "tags": {
+ "0": {
+ "kind": "JSDocParameterTag",
+ "pos": 8,
+ "end": 30,
+ "atToken": {
+ "kind": "AtToken",
+ "pos": 8,
+ "end": 9
+ },
+ "tagName": {
+ "kind": "Identifier",
+ "pos": 9,
+ "end": 14,
+ "text": "param"
+ },
+ "typeExpression": {
+ "kind": "JSDocTypeExpression",
+ "pos": 15,
+ "end": 23,
+ "type": {
+ "kind": "NumberKeyword",
+ "pos": 16,
+ "end": 22
+ }
+ },
+ "postParameterName": {
+ "kind": "Identifier",
+ "pos": 25,
+ "end": 30,
+ "text": "name1"
+ },
+ "isBracketed": true
+ },
+ "length": 1,
+ "pos": 8,
+ "end": 30
+ }
+}`);
+ });
+
+ it("paramTagBracketedName2", () => {
+ parsesCorrectly(
+ `/**
+ * @param {number} [ name1 = 1] Description text follows
+ */`,
+ `{
+ "kind": "JSDocComment",
+ "pos": 0,
+ "end": 66,
+ "tags": {
+ "0": {
+ "kind": "JSDocParameterTag",
+ "pos": 8,
+ "end": 31,
+ "atToken": {
+ "kind": "AtToken",
+ "pos": 8,
+ "end": 9
+ },
+ "tagName": {
+ "kind": "Identifier",
+ "pos": 9,
+ "end": 14,
+ "text": "param"
+ },
+ "typeExpression": {
+ "kind": "JSDocTypeExpression",
+ "pos": 15,
+ "end": 23,
+ "type": {
+ "kind": "NumberKeyword",
+ "pos": 16,
+ "end": 22
+ }
+ },
+ "postParameterName": {
+ "kind": "Identifier",
+ "pos": 26,
+ "end": 31,
+ "text": "name1"
+ },
+ "isBracketed": true
+ },
+ "length": 1,
+ "pos": 8,
+ "end": 31
+ }
+}`);
+ });
+
+ it("twoParamTagOnSameLine", () => {
+ parsesCorrectly(
+`/**
+ * @param {number} name1 @param {number} name2
+ */`,
+ `{
+ "kind": "JSDocComment",
+ "pos": 0,
+ "end": 56,
+ "tags": {
+ "0": {
+ "kind": "JSDocParameterTag",
+ "pos": 8,
+ "end": 29,
+ "atToken": {
+ "kind": "AtToken",
+ "pos": 8,
+ "end": 9
+ },
+ "tagName": {
+ "kind": "Identifier",
+ "pos": 9,
+ "end": 14,
+ "text": "param"
+ },
+ "typeExpression": {
+ "kind": "JSDocTypeExpression",
+ "pos": 15,
+ "end": 23,
+ "type": {
+ "kind": "NumberKeyword",
+ "pos": 16,
+ "end": 22
+ }
+ },
+ "postParameterName": {
+ "kind": "Identifier",
+ "pos": 24,
+ "end": 29,
+ "text": "name1"
+ }
+ },
+ "length": 1,
+ "pos": 8,
+ "end": 29
+ }
+}`);
+ });
+
+ it("paramTagNameThenType1", () => {
+ parsesCorrectly(
+ `/**
+ * @param name1 {number}
+ */`,
+ `{
+ "kind": "JSDocComment",
+ "pos": 0,
+ "end": 34,
+ "tags": {
+ "0": {
+ "kind": "JSDocParameterTag",
+ "pos": 8,
+ "end": 29,
+ "atToken": {
+ "kind": "AtToken",
+ "pos": 8,
+ "end": 9
+ },
+ "tagName": {
+ "kind": "Identifier",
+ "pos": 9,
+ "end": 14,
+ "text": "param"
+ },
+ "preParameterName": {
+ "kind": "Identifier",
+ "pos": 15,
+ "end": 20,
+ "text": "name1"
+ },
+ "typeExpression": {
+ "kind": "JSDocTypeExpression",
+ "pos": 21,
+ "end": 29,
+ "type": {
+ "kind": "NumberKeyword",
+ "pos": 22,
+ "end": 28
+ }
+ }
+ },
+ "length": 1,
+ "pos": 8,
+ "end": 29
+ }
+}`);
+ });
+
+ it("paramTagNameThenType2", () => {
+ parsesCorrectly(
+ `/**
+ * @param name1 {number} Description
+ */`,
+ `{
+ "kind": "JSDocComment",
+ "pos": 0,
+ "end": 46,
+ "tags": {
+ "0": {
+ "kind": "JSDocParameterTag",
+ "pos": 8,
+ "end": 29,
+ "atToken": {
+ "kind": "AtToken",
+ "pos": 8,
+ "end": 9
+ },
+ "tagName": {
+ "kind": "Identifier",
+ "pos": 9,
+ "end": 14,
+ "text": "param"
+ },
+ "preParameterName": {
+ "kind": "Identifier",
+ "pos": 15,
+ "end": 20,
+ "text": "name1"
+ },
+ "typeExpression": {
+ "kind": "JSDocTypeExpression",
+ "pos": 21,
+ "end": 29,
+ "type": {
+ "kind": "NumberKeyword",
+ "pos": 22,
+ "end": 28
+ }
+ }
+ },
+ "length": 1,
+ "pos": 8,
+ "end": 29
+ }
+}`);
+ });
+
+ it("templateTag", () => {
+ parsesCorrectly(
+`/**
+ * @template T
+ */`,
+ `{
+ "kind": "JSDocComment",
+ "pos": 0,
+ "end": 24,
+ "tags": {
+ "0": {
+ "kind": "JSDocTemplateTag",
+ "pos": 8,
+ "end": 19,
+ "atToken": {
+ "kind": "AtToken",
+ "pos": 8,
+ "end": 9
+ },
+ "tagName": {
+ "kind": "Identifier",
+ "pos": 9,
+ "end": 17,
+ "text": "template"
+ },
+ "typeParameters": {
+ "0": {
+ "kind": "TypeParameter",
+ "pos": 18,
+ "end": 19,
+ "name": {
+ "kind": "Identifier",
+ "pos": 18,
+ "end": 19,
+ "text": "T"
+ }
+ },
+ "length": 1,
+ "pos": 17,
+ "end": 19
+ }
+ },
+ "length": 1,
+ "pos": 8,
+ "end": 19
+ }
+}`);
+ });
+
+ it("templateTag2", () => {
+ parsesCorrectly(
+ `/**
+ * @template K,V
+ */`,
+ `{
+ "kind": "JSDocComment",
+ "pos": 0,
+ "end": 26,
+ "tags": {
+ "0": {
+ "kind": "JSDocTemplateTag",
+ "pos": 8,
+ "end": 21,
+ "atToken": {
+ "kind": "AtToken",
+ "pos": 8,
+ "end": 9
+ },
+ "tagName": {
+ "kind": "Identifier",
+ "pos": 9,
+ "end": 17,
+ "text": "template"
+ },
+ "typeParameters": {
+ "0": {
+ "kind": "TypeParameter",
+ "pos": 18,
+ "end": 19,
+ "name": {
+ "kind": "Identifier",
+ "pos": 18,
+ "end": 19,
+ "text": "K"
+ }
+ },
+ "1": {
+ "kind": "TypeParameter",
+ "pos": 20,
+ "end": 21,
+ "name": {
+ "kind": "Identifier",
+ "pos": 20,
+ "end": 21,
+ "text": "V"
+ }
+ },
+ "length": 2,
+ "pos": 17,
+ "end": 21
+ }
+ },
+ "length": 1,
+ "pos": 8,
+ "end": 21
+ }
+}`);
+ });
+
+ it("templateTag3", () => {
+ parsesCorrectly(
+ `/**
+ * @template K ,V
+ */`,
+ `{
+ "kind": "JSDocComment",
+ "pos": 0,
+ "end": 27,
+ "tags": {
+ "0": {
+ "kind": "JSDocTemplateTag",
+ "pos": 8,
+ "end": 22,
+ "atToken": {
+ "kind": "AtToken",
+ "pos": 8,
+ "end": 9
+ },
+ "tagName": {
+ "kind": "Identifier",
+ "pos": 9,
+ "end": 17,
+ "text": "template"
+ },
+ "typeParameters": {
+ "0": {
+ "kind": "TypeParameter",
+ "pos": 18,
+ "end": 19,
+ "name": {
+ "kind": "Identifier",
+ "pos": 18,
+ "end": 19,
+ "text": "K"
+ }
+ },
+ "1": {
+ "kind": "TypeParameter",
+ "pos": 21,
+ "end": 22,
+ "name": {
+ "kind": "Identifier",
+ "pos": 21,
+ "end": 22,
+ "text": "V"
+ }
+ },
+ "length": 2,
+ "pos": 17,
+ "end": 22
+ }
+ },
+ "length": 1,
+ "pos": 8,
+ "end": 22
+ }
+}`);
+ });
+
+ it("templateTag4", () => {
+ parsesCorrectly(
+ `/**
+ * @template K, V
+ */`,
+ `{
+ "kind": "JSDocComment",
+ "pos": 0,
+ "end": 27,
+ "tags": {
+ "0": {
+ "kind": "JSDocTemplateTag",
+ "pos": 8,
+ "end": 22,
+ "atToken": {
+ "kind": "AtToken",
+ "pos": 8,
+ "end": 9
+ },
+ "tagName": {
+ "kind": "Identifier",
+ "pos": 9,
+ "end": 17,
+ "text": "template"
+ },
+ "typeParameters": {
+ "0": {
+ "kind": "TypeParameter",
+ "pos": 18,
+ "end": 19,
+ "name": {
+ "kind": "Identifier",
+ "pos": 18,
+ "end": 19,
+ "text": "K"
+ }
+ },
+ "1": {
+ "kind": "TypeParameter",
+ "pos": 21,
+ "end": 22,
+ "name": {
+ "kind": "Identifier",
+ "pos": 21,
+ "end": 22,
+ "text": "V"
+ }
+ },
+ "length": 2,
+ "pos": 17,
+ "end": 22
+ }
+ },
+ "length": 1,
+ "pos": 8,
+ "end": 22
+ }
+}`);
+ });
+
+ it("templateTag5", () => {
+ parsesCorrectly(
+ `/**
+ * @template K , V
+ */`,
+ `{
+ "kind": "JSDocComment",
+ "pos": 0,
+ "end": 28,
+ "tags": {
+ "0": {
+ "kind": "JSDocTemplateTag",
+ "pos": 8,
+ "end": 23,
+ "atToken": {
+ "kind": "AtToken",
+ "pos": 8,
+ "end": 9
+ },
+ "tagName": {
+ "kind": "Identifier",
+ "pos": 9,
+ "end": 17,
+ "text": "template"
+ },
+ "typeParameters": {
+ "0": {
+ "kind": "TypeParameter",
+ "pos": 18,
+ "end": 19,
+ "name": {
+ "kind": "Identifier",
+ "pos": 18,
+ "end": 19,
+ "text": "K"
+ }
+ },
+ "1": {
+ "kind": "TypeParameter",
+ "pos": 22,
+ "end": 23,
+ "name": {
+ "kind": "Identifier",
+ "pos": 22,
+ "end": 23,
+ "text": "V"
+ }
+ },
+ "length": 2,
+ "pos": 17,
+ "end": 23
+ }
+ },
+ "length": 1,
+ "pos": 8,
+ "end": 23
+ }
+}`);
+ });
+
+ it("templateTag6", () => {
+ parsesCorrectly(
+ `/**
+ * @template K , V Description of type parameters.
+ */`,
+ `{
+ "kind": "JSDocComment",
+ "pos": 0,
+ "end": 60,
+ "tags": {
+ "0": {
+ "kind": "JSDocTemplateTag",
+ "pos": 8,
+ "end": 24,
+ "atToken": {
+ "kind": "AtToken",
+ "pos": 8,
+ "end": 9
+ },
+ "tagName": {
+ "kind": "Identifier",
+ "pos": 9,
+ "end": 17,
+ "text": "template"
+ },
+ "typeParameters": {
+ "0": {
+ "kind": "TypeParameter",
+ "pos": 18,
+ "end": 19,
+ "name": {
+ "kind": "Identifier",
+ "pos": 18,
+ "end": 19,
+ "text": "K"
+ }
+ },
+ "1": {
+ "kind": "TypeParameter",
+ "pos": 22,
+ "end": 23,
+ "name": {
+ "kind": "Identifier",
+ "pos": 22,
+ "end": 23,
+ "text": "V"
+ }
+ },
+ "length": 2,
+ "pos": 17,
+ "end": 24
+ }
+ },
+ "length": 1,
+ "pos": 8,
+ "end": 24
+ }
+}`);
+ });
+
+ it("paramWithoutType", () => {
+ parsesCorrectly(
+ `/**
+ * @param foo
+ */`,
+ `{
+ "kind": "JSDocComment",
+ "pos": 0,
+ "end": 23,
+ "tags": {
+ "0": {
+ "kind": "JSDocParameterTag",
+ "pos": 8,
+ "end": 18,
+ "atToken": {
+ "kind": "AtToken",
+ "pos": 8,
+ "end": 9
+ },
+ "tagName": {
+ "kind": "Identifier",
+ "pos": 9,
+ "end": 14,
+ "text": "param"
+ },
+ "preParameterName": {
+ "kind": "Identifier",
+ "pos": 15,
+ "end": 18,
+ "text": "foo"
+ }
+ },
+ "length": 1,
+ "pos": 8,
+ "end": 18
+ }
+}`);
+ });
+ });
+ });
+ });
+}
\ No newline at end of file