Replace services' jsdoc parser with parsers'

Also modify scanner's definition of leading vs trailing comments, with
associated changes to emitter and emit baselines (the last is in a
separate commit).
This commit is contained in:
Nathan Shively-Sanders
2016-09-01 09:15:59 -07:00
parent 38de65a0d5
commit 65d1079f8f
10 changed files with 1053 additions and 832 deletions
+2 -4
View File
@@ -262,7 +262,7 @@ namespace ts {
Debug.assert(node.parent.kind === SyntaxKind.JSDocFunctionType);
let functionType = <JSDocFunctionType>node.parent;
let index = indexOf(functionType.parameters, node);
return "p" + index;
return "arg" + index;
case SyntaxKind.JSDocTypedefTag:
const parentNode = node.parent && node.parent.parent;
let nameFromParentNode: string;
@@ -517,9 +517,7 @@ namespace ts {
// because the scope of JsDocComment should not be affected by whether the current node is a
// container or not.
if (isInJavaScriptFile(node) && node.jsDocComments) {
for (const jsDocComment of node.jsDocComments) {
bind(jsDocComment);
}
forEach(node.jsDocComments, bind);
}
if (checkUnreachable(node)) {
forEachChild(node, bind);
+2 -1
View File
@@ -5629,12 +5629,13 @@ namespace ts {
case SyntaxKind.JSDocThisType:
case SyntaxKind.JSDocOptionalType:
return getTypeFromTypeNode((<ParenthesizedTypeNode | JSDocTypeReferencingNode>node).type);
case SyntaxKind.JSDocRecordType:
return getTypeFromTypeNode((node as JSDocRecordType).literal);
case SyntaxKind.FunctionType:
case SyntaxKind.ConstructorType:
case SyntaxKind.TypeLiteral:
case SyntaxKind.JSDocTypeLiteral:
case SyntaxKind.JSDocFunctionType:
case SyntaxKind.JSDocRecordType:
return getTypeFromTypeLiteralOrFunctionOrConstructorTypeNode(node, aliasSymbol, aliasTypeArguments);
// This function assumes that an identifier or qualified name is a type expression
// Callers should first ensure this by calling isTypeNode
+55 -27
View File
@@ -851,7 +851,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
}
}
function emitLinePreservingList(parent: Node, nodes: NodeArray<Node>, allowTrailingComma: boolean, spacesBetweenBraces: boolean) {
function emitLinePreservingList(parent: Node, nodes: NodeArray<Node>, allowTrailingComma: boolean, spacesBetweenBraces: boolean, commentsBeforePunctuation: boolean) {
Debug.assert(nodes.length > 0);
increaseIndent();
@@ -877,6 +877,9 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
}
emit(nodes[i]);
if (commentsBeforePunctuation) {
emitLeadingCommentsAtEnd(nodes[i]);
}
}
if (nodes.hasTrailingComma && allowTrailingComma) {
@@ -895,7 +898,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
}
}
function emitList<TNode extends Node>(nodes: TNode[], start: number, count: number, multiLine: boolean, trailingComma: boolean, leadingComma?: boolean, noTrailingNewLine?: boolean, emitNode?: (node: TNode) => void): number {
function emitList<TNode extends Node>(nodes: TNode[], start: number, count: number, multiLine: boolean, trailingComma: boolean, leadingComma?: boolean, noTrailingNewLine?: boolean, commentsBeforePunctuation?: boolean, emitNode?: (node: TNode) => void): number {
if (!emitNode) {
emitNode = emit;
}
@@ -913,13 +916,10 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
}
}
const node = nodes[start + i];
// This emitting is to make sure we emit following comment properly
// ...(x, /*comment1*/ y)...
// ^ => node.pos
// "comment1" is not considered leading comment for "y" but rather
// considered as trailing comment of the previous node.
emitTrailingCommentsOfPosition(node.pos);
emitNode(node);
if (commentsBeforePunctuation) {
emitLeadingCommentsAtEnd(nodes[i]);
}
leadingComma = true;
}
if (trailingComma) {
@@ -1897,7 +1897,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
}
else if (languageVersion >= ScriptTarget.ES6 || !forEach(elements, isSpreadElementExpression)) {
write("[");
emitLinePreservingList(node, node.elements, elements.hasTrailingComma, /*spacesBetweenBraces*/ false);
emitLinePreservingList(node, node.elements, elements.hasTrailingComma, /*spacesBetweenBraces*/ false, /*commentsBeforePunctuation*/ true);
write("]");
}
else {
@@ -1921,7 +1921,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
// then try to preserve the original shape of the object literal.
// Otherwise just try to preserve the formatting.
if (numElements === properties.length) {
emitLinePreservingList(node, properties, /*allowTrailingComma*/ languageVersion >= ScriptTarget.ES5, /*spacesBetweenBraces*/ true);
emitLinePreservingList(node, properties, /*allowTrailingComma*/ languageVersion >= ScriptTarget.ES5, /*spacesBetweenBraces*/ true, /*commentsBeforePunctuation*/ true);
}
else {
const multiLine = node.multiLine;
@@ -2166,14 +2166,6 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
function emitPropertyAssignment(node: PropertyDeclaration) {
emit(node.name);
write(": ");
// This is to ensure that we emit comment in the following case:
// For example:
// obj = {
// id: /*comment1*/ ()=>void
// }
// "comment1" is not considered to be leading comment for node.initializer
// but rather a trailing comment on the previous node.
emitTrailingCommentsOfPosition(node.initializer.pos);
emit(node.initializer);
}
@@ -2285,6 +2277,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
}
emit(node.expression);
emitLeadingCommentsAtEnd(node.expression);
const dotRangeStart = nodeIsSynthesized(node.expression) ? -1 : node.expression.end;
const dotRangeEnd = nodeIsSynthesized(node.expression) ? -1 : skipTrivia(currentText, node.expression.end) + 1;
const dotToken = <TextRange>{ pos: dotRangeStart, end: dotRangeEnd };
@@ -2501,13 +2494,17 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
emitThis(expression);
if (node.arguments.length) {
write(", ");
emitTrailingCommentsOfPosition(node.arguments.pos);
emitCommaList(node.arguments);
emitLeadingCommentsAtEnd(node.arguments);
}
write(")");
}
else {
write("(");
emitTrailingCommentsOfPosition(node.arguments.pos);
emitCommaList(node.arguments);
emitLeadingCommentsAtEnd(node.arguments);
write(")");
}
}
@@ -2608,6 +2605,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
write("(");
emit(node.expression);
emitLeadingCommentsAtEnd(node.expression);
write(")");
}
@@ -2886,6 +2884,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
}
else {
emit(node.left);
emitLeadingCommentsAtEnd(node.left);
// Add indentation before emit the operator if the operator is on different line
// For example:
// 3
@@ -3898,6 +3897,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
}
emitNodeWithCommentsAndWithoutSourcemap(node.name);
emitEnd(node.name);
emitLeadingCommentsAtEnd(node.name);
}
function createVoidZero(): Expression {
@@ -4040,6 +4040,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
emit(name);
}
emitLeadingCommentsAtEnd(name);
write(" = ");
emit(value);
});
@@ -4591,6 +4592,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
emitStart(restParam);
write("var ");
emitNodeWithCommentsAndWithoutSourcemap(restParam.name);
emitLeadingCommentsAtEnd(restParam.name);
write(" = [];");
emitEnd(restParam);
emitTrailingComments(restParam);
@@ -4612,6 +4614,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
writeLine();
emitStart(restParam);
emitNodeWithCommentsAndWithoutSourcemap(restParam.name);
emitLeadingCommentsAtEnd(restParam.name);
write("[" + tempName + " - " + restIndex + "] = arguments[" + tempName + "];");
emitEnd(restParam);
decreaseIndent();
@@ -4633,6 +4636,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
function emitDeclarationName(node: Declaration) {
if (node.name) {
emitNodeWithCommentsAndWithoutSourcemap(node.name);
emitLeadingCommentsAtEnd(node.name);
}
else {
write(getGeneratedNameForNode(node));
@@ -4660,6 +4664,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
const { kind, parent } = node;
if (kind !== SyntaxKind.MethodDeclaration &&
kind !== SyntaxKind.MethodSignature &&
kind !== SyntaxKind.ArrowFunction &&
parent &&
parent.kind !== SyntaxKind.PropertyAssignment &&
parent.kind !== SyntaxKind.CallExpression &&
@@ -4734,7 +4739,11 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
const parameters = node.parameters;
const skipCount = node.parameters.length && (<Identifier>node.parameters[0].name).originalKeywordKind === SyntaxKind.ThisKeyword ? 1 : 0;
const omitCount = languageVersion < ScriptTarget.ES6 && hasDeclaredRestParameter(node) ? 1 : 0;
emitList(parameters, skipCount, parameters.length - omitCount - skipCount, /*multiLine*/ false, /*trailingComma*/ false);
emitTrailingCommentsOfPosition(node.parameters.pos);
emitList(parameters, skipCount, parameters.length - omitCount - skipCount, /*multiLine*/ false, /*trailingComma*/ false, /*leadingComma*/ false, /*noTrailingNewLine*/ false, /*commentsBeforePunctuation*/ true);
if ((parameters.length - omitCount - skipCount) <= 0) {
emitLeadingCommentsAtEnd(node.parameters);
}
}
write(")");
decreaseIndent();
@@ -5791,7 +5800,7 @@ const _super = (function (geti, seti) {
writeLine();
const decoratorCount = decorators ? decorators.length : 0;
let argumentsWritten = emitList(decorators, 0, decoratorCount, /*multiLine*/ true, /*trailingComma*/ false, /*leadingComma*/ false, /*noTrailingNewLine*/ true,
let argumentsWritten = emitList(decorators, 0, decoratorCount, /*multiLine*/ true, /*trailingComma*/ false, /*leadingComma*/ false, /*noTrailingNewLine*/ true, /*commentsBeforePunctuation*/ false,
decorator => emit(decorator.expression));
if (firstParameterDecorator) {
argumentsWritten += emitDecoratorsOfParameters(constructor, /*leadingComma*/ argumentsWritten > 0);
@@ -5891,7 +5900,7 @@ const _super = (function (geti, seti) {
writeLine();
const decoratorCount = decorators ? decorators.length : 0;
let argumentsWritten = emitList(decorators, 0, decoratorCount, /*multiLine*/ true, /*trailingComma*/ false, /*leadingComma*/ false, /*noTrailingNewLine*/ true,
let argumentsWritten = emitList(decorators, 0, decoratorCount, /*multiLine*/ true, /*trailingComma*/ false, /*leadingComma*/ false, /*noTrailingNewLine*/ true, /*commentsBeforePunctuation*/ false,
decorator => emit(decorator.expression));
if (firstParameterDecorator) {
@@ -5933,7 +5942,7 @@ const _super = (function (geti, seti) {
for (const parameter of node.parameters) {
if (nodeIsDecorated(parameter)) {
const decorators = parameter.decorators;
argumentsWritten += emitList(decorators, 0, decorators.length, /*multiLine*/ true, /*trailingComma*/ false, /*leadingComma*/ leadingComma, /*noTrailingNewLine*/ true, decorator => {
argumentsWritten += emitList(decorators, 0, decorators.length, /*multiLine*/ true, /*trailingComma*/ false, /*leadingComma*/ leadingComma, /*noTrailingNewLine*/ true, /*commentsBeforePunctuation*/ false, decorator => {
write(`__param(${parameterIndex}, `);
emit(decorator.expression);
write(")");
@@ -6347,6 +6356,7 @@ const _super = (function (geti, seti) {
emitExpressionForPropertyName(node.name);
emitEnd(node);
write(";");
emitLeadingCommentsAtEnd(node.name);
}
function writeEnumMemberDeclarationValue(member: EnumMember) {
@@ -8288,7 +8298,11 @@ const _super = (function (geti, seti) {
emitNewLineBeforeLeadingComments(currentLineMap, writer, node, leadingComments);
// Leading comments are emitted at /*leading comment1 */space/*leading comment*/space
emitComments(currentText, currentLineMap, writer, leadingComments, /*trailingSeparator*/ true, newLine, writeComment);
// However, a leading mid-line comment of the end of file token is emitted with spaces before,
// as if it were a trailing comment of the previous token
const commentStartsInMidLine = leadingComments && leadingComments.length && leadingComments[0].pos - 1 > 0 && currentText.charCodeAt(leadingComments[0].pos - 1) !== 10;
const isEndOfFile = node.kind === SyntaxKind.EndOfFileToken && commentStartsInMidLine;
emitComments(currentText, currentLineMap, writer, leadingComments, /*trailingSeparator*/ !isEndOfFile, newLine, writeComment);
}
function emitTrailingComments(node: Node) {
@@ -8305,8 +8319,11 @@ const _super = (function (geti, seti) {
/**
* Emit trailing comments at the position. The term trailing comment is used here to describe following comment:
* x, /comment1/ y
* ^ => pos; the function will emit "comment1" in the emitJS
* x, /*comment1* / // comment2
* ^ => pos; the function will emit "comment1" and "comment2" in the emitJS
* However,
* x /*comment1* /, y
* 'comment1' is actually a leading comment of ',' and needs to be emitted using emitLeadingCommentsAtEnd
*/
function emitTrailingCommentsOfPosition(pos: number) {
if (compilerOptions.removeComments) {
@@ -8319,7 +8336,18 @@ const _super = (function (geti, seti) {
emitComments(currentText, currentLineMap, writer, trailingComments, /*trailingSeparator*/ true, newLine, writeComment);
}
function emitLeadingCommentsOfPositionWorker(pos: number) {
/**
* Emit leading comments for the token after the current token.
* This is only needed for punctuation tokens. For example, in
* x /*comment1* /, y
* 'comment1' is a leading comment of ',' but needs to be emitted
* from x because there is no token for ','.
*/
function emitLeadingCommentsAtEnd(node: Node | NodeArray<any>) {
emitLeadingCommentsOfPositionWorker(node.end, /*trailingSeparator*/ false);
}
function emitLeadingCommentsOfPositionWorker(pos: number, trailingSeparator = true) {
if (compilerOptions.removeComments) {
return;
}
@@ -8337,7 +8365,7 @@ const _super = (function (geti, seti) {
emitNewLineBeforeLeadingComments(currentLineMap, writer, { pos: pos, end: pos }, leadingComments);
// Leading comments are emitted at /*leading comment1 */space/*leading comment*/space
emitComments(currentText, currentLineMap, writer, leadingComments, /*trailingSeparator*/ true, newLine, writeComment);
emitComments(currentText, currentLineMap, writer, leadingComments, trailingSeparator, newLine, writeComment);
}
function emitDetachedCommentsAndUpdateCommentsInfo(node: TextRange) {
+275 -136
View File
@@ -378,7 +378,7 @@ namespace ts {
case SyntaxKind.JSDocNullableType:
return visitNode(cbNode, (<JSDocNullableType>node).type);
case SyntaxKind.JSDocRecordType:
return visitNodes(cbNodes, (<JSDocRecordType>node).members);
return visitNode(cbNode, (<JSDocRecordType>node).literal);
case SyntaxKind.JSDocTypeReference:
return visitNode(cbNode, (<JSDocTypeReference>node).name) ||
visitNodes(cbNodes, (<JSDocTypeReference>node).typeArguments);
@@ -397,7 +397,7 @@ namespace ts {
return visitNode(cbNode, (<JSDocRecordMember>node).name) ||
visitNode(cbNode, (<JSDocRecordMember>node).type);
case SyntaxKind.JSDocComment:
return visitNodes(cbNodes, (<JSDocComment>node).tags);
return visitNodes(cbNodes, (<JSDoc>node).tags);
case SyntaxKind.JSDocParameterTag:
return visitNode(cbNode, (<JSDocParameterTag>node).preParameterName) ||
visitNode(cbNode, (<JSDocParameterTag>node).typeExpression) ||
@@ -450,10 +450,10 @@ namespace ts {
/* @internal */
export function parseIsolatedJSDocComment(content: string, start?: number, length?: number) {
const result = Parser.JSDocParser.parseIsolatedJSDocComment(content, start, length);
if (result && result.jsDocComment) {
if (result && result.jsDoc) {
// because the jsDocComment was parsed out of the source file, it might
// not be covered by the fixupParentReferences.
Parser.fixupParentReferences(result.jsDocComment);
Parser.fixupParentReferences(result.jsDoc);
}
return result;
@@ -652,20 +652,18 @@ namespace ts {
function addJSDocComment<T extends Node>(node: T): T {
if (contextFlags & NodeFlags.JavaScriptFile) {
const comments = getLeadingCommentRangesOfNode(node, sourceFile);
if (comments) {
for (const comment of comments) {
const jsDocComment = JSDocParser.parseJSDocComment(node, comment.pos, comment.end - comment.pos);
if (!jsDocComment) {
continue;
}
if (!node.jsDocComments) {
node.jsDocComments = [];
}
node.jsDocComments.push(jsDocComment);
const comments = getLeadingCommentRangesOfNode(node, sourceFile);
if (comments) {
for (const comment of comments) {
const jsDoc = JSDocParser.parseJSDocComment(node, comment.pos, comment.end - comment.pos);
if (!jsDoc) {
continue;
}
if (!node.jsDocComments) {
node.jsDocComments = [];
}
node.jsDocComments.push(jsDoc);
}
}
@@ -2201,7 +2199,7 @@ namespace ts {
}
fillSignature(SyntaxKind.ColonToken, /*yieldContext*/ false, /*awaitContext*/ false, /*requireCompleteParameterList*/ false, node);
parseTypeMemberSemicolon();
return finishNode(node);
return addJSDocComment(finishNode(node));
}
function isIndexSignature(): boolean {
@@ -2291,7 +2289,7 @@ namespace ts {
// [Yield] nor [Await]
fillSignature(SyntaxKind.ColonToken, /*yieldContext*/ false, /*awaitContext*/ false, /*requireCompleteParameterList*/ false, method);
parseTypeMemberSemicolon();
return finishNode(method);
return addJSDocComment(finishNode(method));
}
else {
const property = <PropertySignature>createNode(SyntaxKind.PropertySignature, fullStart);
@@ -2308,7 +2306,7 @@ namespace ts {
}
parseTypeMemberSemicolon();
return finishNode(property);
return addJSDocComment(finishNode(property));
}
}
@@ -2897,7 +2895,7 @@ namespace ts {
node.equalsGreaterThanToken = parseExpectedToken(SyntaxKind.EqualsGreaterThanToken, /*reportAtCurrentPosition*/ false, Diagnostics._0_expected, "=>");
node.body = parseArrowFunctionExpressionBody(/*isAsync*/ !!asyncModifier);
return finishNode(node);
return addJSDocComment(finishNode(node));
}
function tryParseParenthesizedArrowFunctionExpression(): Expression {
@@ -2930,7 +2928,7 @@ namespace ts {
? parseArrowFunctionExpressionBody(isAsync)
: parseIdentifier();
return finishNode(arrowFunction);
return addJSDocComment(finishNode(arrowFunction));
}
// True -> We definitely expect a parenthesized arrow function here.
@@ -4114,7 +4112,7 @@ namespace ts {
function tryParseAccessorDeclaration(fullStart: number, decorators: NodeArray<Decorator>, modifiers: ModifiersArray): AccessorDeclaration {
if (parseContextualModifier(SyntaxKind.GetKeyword)) {
return addJSDocComment(parseAccessorDeclaration(SyntaxKind.GetAccessor, fullStart, decorators, modifiers));
return parseAccessorDeclaration(SyntaxKind.GetAccessor, fullStart, decorators, modifiers);
}
else if (parseContextualModifier(SyntaxKind.SetKeyword)) {
return parseAccessorDeclaration(SyntaxKind.SetAccessor, fullStart, decorators, modifiers);
@@ -4993,7 +4991,7 @@ namespace ts {
: doOutsideOfContext(NodeFlags.YieldContext | NodeFlags.DisallowInContext, parseNonParameterInitializer);
parseSemicolon();
return finishNode(property);
return addJSDocComment(finishNode(property));
}
function parsePropertyOrMethodDeclaration(fullStart: number, decorators: NodeArray<Decorator>, modifiers: ModifiersArray): ClassElement {
@@ -5022,7 +5020,7 @@ namespace ts {
node.name = parsePropertyName();
fillSignature(SyntaxKind.ColonToken, /*yieldContext*/ false, /*awaitContext*/ false, /*requireCompleteParameterList*/ false, node);
node.body = parseFunctionBlockOrSemicolon(/*isGenerator*/ false, /*isAsync*/ false);
return finishNode(node);
return addJSDocComment(finishNode(node));
}
function isClassMemberModifier(idToken: SyntaxKind) {
@@ -5265,7 +5263,7 @@ namespace ts {
node.members = createMissingList<ClassElement>();
}
return finishNode(node);
return addJSDocComment(finishNode(node));
}
function parseNameOfClassDeclarationOrExpression(): Identifier {
@@ -5333,7 +5331,7 @@ namespace ts {
node.typeParameters = parseTypeParameters();
node.heritageClauses = parseHeritageClauses(/*isClassHeritageClause*/ false);
node.members = parseObjectTypeMembers();
return finishNode(node);
return addJSDocComment(finishNode(node));
}
function parseTypeAliasDeclaration(fullStart: number, decorators: NodeArray<Decorator>, modifiers: ModifiersArray): TypeAliasDeclaration {
@@ -5357,7 +5355,7 @@ namespace ts {
const node = <EnumMember>createNode(SyntaxKind.EnumMember, scanner.getStartPos());
node.name = parsePropertyName();
node.initializer = allowInAnd(parseNonParameterInitializer);
return finishNode(node);
return addJSDocComment(finishNode(node));
}
function parseEnumDeclaration(fullStart: number, decorators: NodeArray<Decorator>, modifiers: ModifiersArray): EnumDeclaration {
@@ -5373,7 +5371,7 @@ namespace ts {
else {
node.members = createMissingList<EnumMember>();
}
return finishNode(node);
return addJSDocComment(finishNode(node));
}
function parseModuleBlock(): ModuleBlock {
@@ -5400,7 +5398,7 @@ namespace ts {
node.body = parseOptional(SyntaxKind.DotToken)
? parseModuleOrNamespaceDeclaration(getNodePos(), /*decorators*/ undefined, /*modifiers*/ undefined, NodeFlags.Export | namespaceFlag)
: parseModuleBlock();
return finishNode(node);
return addJSDocComment(finishNode(node));
}
function parseAmbientExternalModuleDeclaration(fullStart: number, decorators: NodeArray<Decorator>, modifiers: ModifiersArray): ModuleDeclaration {
@@ -5489,7 +5487,7 @@ namespace ts {
parseExpected(SyntaxKind.EqualsToken);
importEqualsDeclaration.moduleReference = parseModuleReference();
parseSemicolon();
return finishNode(importEqualsDeclaration);
return addJSDocComment(finishNode(importEqualsDeclaration));
}
}
@@ -5810,6 +5808,7 @@ namespace ts {
export function parseJSDocTypeExpressionForTests(content: string, start: number, length: number) {
initializeState("file.js", content, ScriptTarget.Latest, /*_syntaxCursor:*/ undefined, ScriptKind.JS);
sourceFile = createSourceFile("file.js", ScriptTarget.Latest, ScriptKind.JS);
scanner.setText(content, start, length);
currentToken = scanner.scan();
const jsDocTypeExpression = parseJSDocTypeExpression();
@@ -6028,22 +6027,7 @@ namespace ts {
function parseJSDocRecordType(): JSDocRecordType {
const result = <JSDocRecordType>createNode(SyntaxKind.JSDocRecordType);
nextToken();
result.members = parseDelimitedList(ParsingContext.JSDocRecordMembers, parseJSDocRecordMember);
checkForTrailingComma(result.members);
parseExpected(SyntaxKind.CloseBraceToken);
return finishNode(result);
}
function parseJSDocRecordMember(): JSDocRecordMember {
const result = <JSDocRecordMember>createNode(SyntaxKind.JSDocRecordMember);
result.name = parseSimplePropertyName();
if (token() === SyntaxKind.ColonToken) {
nextToken();
result.type = parseJSDocType();
}
result.literal = parseTypeLiteral();
return finishNode(result);
}
@@ -6143,14 +6127,14 @@ namespace ts {
export function parseIsolatedJSDocComment(content: string, start: number, length: number) {
initializeState("file.js", content, ScriptTarget.Latest, /*_syntaxCursor:*/ undefined, ScriptKind.JS);
sourceFile = <SourceFile>{ languageVariant: LanguageVariant.Standard, text: content };
const jsDocComment = parseJSDocCommentWorker(start, length);
const jsDoc = parseJSDocCommentWorker(start, length);
const diagnostics = parseDiagnostics;
clearState();
return jsDocComment ? { jsDocComment, diagnostics } : undefined;
return jsDoc ? { jsDoc, diagnostics } : undefined;
}
export function parseJSDocComment(parent: Node, start: number, length: number): JSDocComment {
export function parseJSDocComment(parent: Node, start: number, length: number): JSDoc {
const saveToken = currentToken;
const saveParseDiagnosticsLength = parseDiagnostics.length;
const saveParseErrorBeforeNextFinishedNode = parseErrorBeforeNextFinishedNode;
@@ -6167,7 +6151,7 @@ namespace ts {
return comment;
}
export function parseJSDocCommentWorker(start: number, length: number): JSDocComment {
export function parseJSDocCommentWorker(start: number, length: number): JSDoc {
const content = sourceText;
start = start || 0;
const end = length === undefined ? content.length : start + length;
@@ -6178,76 +6162,145 @@ namespace ts {
Debug.assert(end <= content.length);
let tags: NodeArray<JSDocTag>;
let result: JSDocComment;
const comments: string[] = [];
let result: JSDoc;
// Check for /** (JSDoc opening part)
if (content.charCodeAt(start) === CharacterCodes.slash &&
content.charCodeAt(start + 1) === CharacterCodes.asterisk &&
content.charCodeAt(start + 2) === CharacterCodes.asterisk &&
content.charCodeAt(start + 3) !== CharacterCodes.asterisk) {
if (!isJsDocStart(content, start)) {
return result;
}
// + 3 for leading /**, - 5 in total for /** */
scanner.scanRange(start + 3, length - 5, () => {
// 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;
let advanceToken = true;
let margin: number | undefined = undefined;
let indent = start - Math.max(content.lastIndexOf("\n", start), 0) + 4;
let text: string;
// + 3 for leading /**, - 5 in total for /** */
scanner.scanRange(start + 3, length - 5, () => {
// 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;
nextJSDocToken();
while (token() === SyntaxKind.WhitespaceTrivia) {
nextJSDocToken();
while (token() !== SyntaxKind.EndOfFileToken) {
switch (token()) {
case SyntaxKind.AtToken:
if (canParseTag) {
parseTag();
}
// This will take us to the end of the line, so it's OK to parse a tag on the next pass through the loop
}
if (token() === SyntaxKind.NewLineTrivia) {
canParseTag = true;
seenAsterisk = false;
nextJSDocToken();
}
while (token() !== SyntaxKind.EndOfFileToken) {
switch (token()) {
case SyntaxKind.AtToken:
if (canParseTag) {
removeTrailingNewlines(comments);
parseTag(indent);
// This will take us past the end of the line, so it's OK to parse a tag on the next pass through the loop
// NOTE: According to usejsdoc.org, a tag goes to end of line, except the last tag. But real-world comments may break this rule.
seenAsterisk = false;
break;
advanceToken = false;
margin = undefined;
}
else {
comments.push(scanner.getTokenText());
}
indent++;
break;
case SyntaxKind.NewLineTrivia:
// After a line break, we can parse a tag, and we haven't seen an asterisk on the next line yet
canParseTag = true;
seenAsterisk = false;
break;
case SyntaxKind.NewLineTrivia:
// After a line break, we can parse a tag, and we haven't seen an asterisk on the next line yet
comments.push(scanner.getTokenText());
canParseTag = true;
seenAsterisk = false;
indent = 0;
break;
case SyntaxKind.AsteriskToken:
if (seenAsterisk) {
// If we've already seen an asterisk, then we can no longer parse a tag on this line
canParseTag = false;
}
// Ignore the first asterisk on a line
seenAsterisk = true;
break;
case SyntaxKind.Identifier:
// 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.
case SyntaxKind.AsteriskToken:
text = scanner.getTokenText();
if (seenAsterisk) {
// If we've already seen an asterisk, then we can no longer parse a tag on this line
canParseTag = false;
break;
comments.push(text);
if (!margin) {
margin = indent;
}
}
// Ignore the first asterisk on a line
seenAsterisk = true;
indent += text.length;
break;
case SyntaxKind.EndOfFileToken:
break;
}
case SyntaxKind.Identifier:
// Anything else is doc comment text. We just save it. Because it
// wasn't a tag, we can no longer parse a tag on this line until we hit the next
// line break.
text = scanner.getTokenText();
comments.push(text);
if (!margin) {
margin = indent;
}
canParseTag = false;
indent += text.length;
break;
case SyntaxKind.WhitespaceTrivia:
// only collect whitespace if we *know* that we're just looking at comments, not a possible jsdoc tag
text = scanner.getTokenText();
if (!canParseTag || margin !== undefined && indent + text.length > margin) {
comments.push(text.slice(margin - indent - 1));
}
indent += text.length;
break;
case SyntaxKind.EndOfFileToken:
break;
default:
text = scanner.getTokenText();
comments.push(text);
indent += text.length;
break;
}
if (advanceToken) {
nextJSDocToken();
}
else {
advanceToken = true;
}
}
removeLeadingNewlines(comments);
removeTrailingNewlines(comments);
result = createJSDocComment();
result = createJSDocComment();
});
}
});
return result;
function createJSDocComment(): JSDocComment {
if (!tags) {
return undefined;
function removeLeadingNewlines(comments: string[]) {
while (comments.length && (comments[0] === "\n" || comments[0] === "\r")) {
comments.shift();
}
}
const result = <JSDocComment>createNode(SyntaxKind.JSDocComment, start);
function removeTrailingNewlines(comments: string[]) {
while (comments.length && (comments[comments.length - 1] === "\n" || comments[comments.length - 1] === "\r")) {
comments.pop();
}
}
function isJsDocStart(content: string, start: number) {
return content.charCodeAt(start) === CharacterCodes.slash &&
content.charCodeAt(start + 1) === CharacterCodes.asterisk &&
content.charCodeAt(start + 2) === CharacterCodes.asterisk &&
content.charCodeAt(start + 3) !== CharacterCodes.asterisk;
}
function createJSDocComment(): JSDoc {
const result = <JSDoc>createNode(SyntaxKind.JSDocComment, start);
result.tags = tags;
result.comment = comments.length ? comments.join("") : undefined;
return finishNode(result, end);
}
@@ -6257,78 +6310,158 @@ namespace ts {
}
}
function parseTag(): void {
function parseTag(indent: number) {
Debug.assert(token() === SyntaxKind.AtToken);
const atToken = createNode(SyntaxKind.AtToken, scanner.getTokenPos());
atToken.end = scanner.getTextPos();
nextJSDocToken();
const tagName = parseJSDocIdentifierName();
skipWhitespace();
if (!tagName) {
return;
}
const tag = handleTag(atToken, tagName) || handleUnknownTag(atToken, tagName);
addTag(tag);
}
function handleTag(atToken: Node, tagName: Identifier): JSDocTag {
let tag: JSDocTag;
if (tagName) {
switch (tagName.text) {
case "param":
return handleParamTag(atToken, tagName);
tag = parseParamTag(atToken, tagName);
break;
case "return":
case "returns":
return handleReturnTag(atToken, tagName);
tag = parseReturnTag(atToken, tagName);
break;
case "template":
return handleTemplateTag(atToken, tagName);
tag = parseTemplateTag(atToken, tagName);
break;
case "type":
return handleTypeTag(atToken, tagName);
tag = parseTypeTag(atToken, tagName);
break;
case "typedef":
return handleTypedefTag(atToken, tagName);
tag = parseTypedefTag(atToken, tagName);
break;
default:
tag = parseUnknownTag(atToken, tagName);
break;
}
}
else {
tag = parseUnknownTag(atToken, tagName);
}
if (!tag) {
// a badly malformed tag should not be added to the list of tags
return;
}
addTag(tag, parseTagComments(indent + tag.end - tag.pos));
}
function parseTagComments(indent: number) {
const comments: string[] = [];
let savingComments = false;
let seenAsterisk = true;
let done = false;
let margin: number | undefined;
let text: string;
function pushComment(text: string) {
if (!margin) {
margin = indent;
}
comments.push(text);
indent += text.length;
}
while (!done && token() !== SyntaxKind.EndOfFileToken) {
text = scanner.getTokenText();
switch (token()) {
case SyntaxKind.NewLineTrivia:
if (seenAsterisk) {
savingComments = false;
seenAsterisk = false;
comments.push(text);
}
indent = 0;
break;
case SyntaxKind.AtToken:
done = true;
break;
case SyntaxKind.WhitespaceTrivia:
if (savingComments && seenAsterisk) {
pushComment(text);
}
else {
// if the whitespace crosses the margin, take only the whitespace that passes the margin
if (margin !== undefined && indent + text.length > margin) {
comments.push(text.slice(margin - indent - 1));
}
indent += text.length;
}
break;
case SyntaxKind.AsteriskToken:
if (!seenAsterisk) {
// leading asterisks start recording on the *next* (non-whitespace) token
savingComments = false;
indent += text.length;
}
// FALLTHROUGH to gather comments
default:
if (seenAsterisk) {
savingComments = true; // leading identifiers start recording as well
pushComment(text);
}
seenAsterisk = true;
break;
}
if (!done) {
nextJSDocToken();
}
}
return undefined;
removeLeadingNewlines(comments);
removeTrailingNewlines(comments);
return comments;
}
function handleUnknownTag(atToken: Node, tagName: Identifier) {
function parseUnknownTag(atToken: Node, tagName: Identifier) {
const result = <JSDocTag>createNode(SyntaxKind.JSDocTag, atToken.pos);
result.atToken = atToken;
result.tagName = tagName;
return finishNode(result);
}
function addTag(tag: JSDocTag): void {
if (tag) {
if (!tags) {
tags = <NodeArray<JSDocTag>>[];
tags.pos = tag.pos;
}
function addTag(tag: JSDocTag, comments: string[]): void {
tag.comment = comments.join("");
tags.push(tag);
tags.end = tag.end;
if (!tags) {
tags = <NodeArray<JSDocTag>>[];
tags.pos = tag.pos;
}
tags.push(tag);
tags.end = tag.end;
}
function tryParseTypeExpression(): JSDocTypeExpression {
if (token() !== SyntaxKind.OpenBraceToken) {
return undefined;
}
return tryParse(() => {
skipWhitespace();
if (token() !== SyntaxKind.OpenBraceToken) {
return undefined;
}
const typeExpression = parseJSDocTypeExpression();
return typeExpression;
return parseJSDocTypeExpression();
});
}
function handleParamTag(atToken: Node, tagName: Identifier) {
function parseParamTag(atToken: Node, tagName: Identifier) {
let typeExpression = tryParseTypeExpression();
skipWhitespace();
let name: Identifier;
let isBracketed: boolean;
// Looking for something like '[foo]' or 'foo'
if (parseOptionalToken(SyntaxKind.OpenBracketToken)) {
name = parseJSDocIdentifierName();
skipWhitespace();
isBracketed = true;
// May have an optional default, e.g. '[foo = 42]'
@@ -6365,11 +6498,12 @@ namespace ts {
result.preParameterName = preName;
result.typeExpression = typeExpression;
result.postParameterName = postName;
result.parameterName = postName || preName;
result.isBracketed = isBracketed;
return finishNode(result);
}
function handleReturnTag(atToken: Node, tagName: Identifier): JSDocReturnTag {
function parseReturnTag(atToken: Node, tagName: Identifier): JSDocReturnTag {
if (forEach(tags, t => t.kind === SyntaxKind.JSDocReturnTag)) {
parseErrorAtPosition(tagName.pos, scanner.getTokenPos() - tagName.pos, Diagnostics._0_tag_already_specified, tagName.text);
}
@@ -6381,7 +6515,7 @@ namespace ts {
return finishNode(result);
}
function handleTypeTag(atToken: Node, tagName: Identifier): JSDocTypeTag {
function parseTypeTag(atToken: Node, tagName: Identifier): JSDocTypeTag {
if (forEach(tags, t => t.kind === SyntaxKind.JSDocTypeTag)) {
parseErrorAtPosition(tagName.pos, scanner.getTokenPos() - tagName.pos, Diagnostics._0_tag_already_specified, tagName.text);
}
@@ -6393,10 +6527,11 @@ namespace ts {
return finishNode(result);
}
function handlePropertyTag(atToken: Node, tagName: Identifier): JSDocPropertyTag {
function parsePropertyTag(atToken: Node, tagName: Identifier): JSDocPropertyTag {
const typeExpression = tryParseTypeExpression();
skipWhitespace();
const name = parseJSDocIdentifierName();
skipWhitespace();
if (!name) {
parseErrorAtPosition(scanner.getStartPos(), /*length*/ 0, Diagnostics.Identifier_expected);
return undefined;
@@ -6410,7 +6545,7 @@ namespace ts {
return finishNode(result);
}
function handleTypedefTag(atToken: Node, tagName: Identifier): JSDocTypedefTag {
function parseTypedefTag(atToken: Node, tagName: Identifier): JSDocTypedefTag {
const typeExpression = tryParseTypeExpression();
skipWhitespace();
@@ -6419,6 +6554,7 @@ namespace ts {
typedefTag.tagName = tagName;
typedefTag.name = parseJSDocIdentifierName();
typedefTag.typeExpression = typeExpression;
skipWhitespace();
if (typeExpression) {
if (typeExpression.type.kind === SyntaxKind.JSDocTypeReference) {
@@ -6488,6 +6624,7 @@ namespace ts {
nextJSDocToken();
const tagName = parseJSDocIdentifierName();
skipWhitespace();
if (!tagName) {
return false;
}
@@ -6498,21 +6635,21 @@ namespace ts {
// already has a @type tag, terminate the parent tag now.
return false;
}
parentTag.jsDocTypeTag = handleTypeTag(atToken, tagName);
parentTag.jsDocTypeTag = parseTypeTag(atToken, tagName);
return true;
case "prop":
case "property":
if (!parentTag.jsDocPropertyTags) {
parentTag.jsDocPropertyTags = <NodeArray<JSDocPropertyTag>>[];
}
const propertyTag = handlePropertyTag(atToken, tagName);
const propertyTag = parsePropertyTag(atToken, tagName);
parentTag.jsDocPropertyTags.push(propertyTag);
return true;
}
return false;
}
function handleTemplateTag(atToken: Node, tagName: Identifier): JSDocTemplateTag {
function parseTemplateTag(atToken: Node, tagName: Identifier): JSDocTemplateTag {
if (forEach(tags, t => t.kind === SyntaxKind.JSDocTemplateTag)) {
parseErrorAtPosition(tagName.pos, scanner.getTokenPos() - tagName.pos, Diagnostics._0_tag_already_specified, tagName.text);
}
@@ -6523,6 +6660,7 @@ namespace ts {
while (true) {
const name = parseJSDocIdentifierName();
skipWhitespace();
if (!name) {
parseErrorAtPosition(scanner.getStartPos(), 0, Diagnostics.Identifier_expected);
return undefined;
@@ -6536,6 +6674,7 @@ namespace ts {
if (token() === SyntaxKind.CommaToken) {
nextJSDocToken();
skipWhitespace();
}
else {
break;
+40 -27
View File
@@ -599,10 +599,11 @@ namespace ts {
* If false, whitespace is skipped until the first line break and comments between that location
* and the next token are returned.
* If true, comments occurring between the given position and the next line break are returned.
* If there is no line break, then no comments are returned.
*/
function getCommentRanges(text: string, pos: number, trailing: boolean): CommentRange[] {
let result: CommentRange[];
let collecting = trailing || pos === 0;
let needToSkipTrailingComment = pos > 0;
while (pos < text.length) {
const ch = text.charCodeAt(pos);
switch (ch) {
@@ -615,7 +616,11 @@ namespace ts {
if (trailing) {
return result;
}
collecting = true;
else if (needToSkipTrailingComment) {
// skip the first line if not trailing (it'll be part of the trailing comment).
needToSkipTrailingComment = false;
result = undefined;
}
if (result && result.length) {
lastOrUndefined(result).hasTrailingNewLine = true;
}
@@ -651,11 +656,13 @@ namespace ts {
pos++;
}
}
if (collecting) {
// if we are at the end of the file, don't add trailing comments.
// end-of-file comments are added as leading comment of the end-of-file token.
if (pos < text.length || !trailing) {
if (!result) {
result = [];
}
result.push({ pos: startPos, end: pos, hasTrailingNewLine, kind });
}
continue;
@@ -671,10 +678,10 @@ namespace ts {
}
break;
}
return result;
return !trailing ? result : undefined;
}
return result;
return !trailing ? result : undefined;
}
export function getLeadingCommentRanges(text: string, pos: number): CommentRange[] {
@@ -1689,40 +1696,46 @@ namespace ts {
}
startPos = pos;
// Eat leading whitespace
let ch = text.charCodeAt(pos);
while (pos < end) {
ch = text.charCodeAt(pos);
if (isWhiteSpaceSingleLine(ch)) {
pos++;
}
else {
break;
}
}
tokenPos = pos;
const ch = text.charCodeAt(pos);
switch (ch) {
case CharacterCodes.tab:
case CharacterCodes.verticalTab:
case CharacterCodes.formFeed:
case CharacterCodes.space:
while (pos < end && isWhiteSpaceSingleLine(text.charCodeAt(pos))) {
pos++;
}
return token = SyntaxKind.WhitespaceTrivia;
case CharacterCodes.at:
return pos += 1, token = SyntaxKind.AtToken;
pos++;
return token = SyntaxKind.AtToken;
case CharacterCodes.lineFeed:
case CharacterCodes.carriageReturn:
return pos += 1, token = SyntaxKind.NewLineTrivia;
pos++;
return token = SyntaxKind.NewLineTrivia;
case CharacterCodes.asterisk:
return pos += 1, token = SyntaxKind.AsteriskToken;
pos++;
return token = SyntaxKind.AsteriskToken;
case CharacterCodes.openBrace:
return pos += 1, token = SyntaxKind.OpenBraceToken;
pos++;
return token = SyntaxKind.OpenBraceToken;
case CharacterCodes.closeBrace:
return pos += 1, token = SyntaxKind.CloseBraceToken;
pos++;
return token = SyntaxKind.CloseBraceToken;
case CharacterCodes.openBracket:
return pos += 1, token = SyntaxKind.OpenBracketToken;
pos++;
return token = SyntaxKind.OpenBracketToken;
case CharacterCodes.closeBracket:
return pos += 1, token = SyntaxKind.CloseBracketToken;
pos++;
return token = SyntaxKind.CloseBracketToken;
case CharacterCodes.equals:
return pos += 1, token = SyntaxKind.EqualsToken;
pos++;
return token = SyntaxKind.EqualsToken;
case CharacterCodes.comma:
return pos += 1, token = SyntaxKind.CommaToken;
pos++;
return token = SyntaxKind.CommaToken;
}
if (isIdentifierStart(ch, ScriptTarget.Latest)) {
+9 -3
View File
@@ -464,7 +464,7 @@ namespace ts {
modifiers?: ModifiersArray; // Array of modifiers
/* @internal */ id?: number; // Unique id (used to look up NodeLinks)
parent?: Node; // Parent node (initialized by binding
/* @internal */ jsDocComments?: JSDocComment[]; // JSDoc for the node, if it has any. Only for .js files.
/* @internal */ jsDocComments?: JSDoc[]; // 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)
@@ -1471,7 +1471,7 @@ namespace ts {
// @kind(SyntaxKind.JSDocRecordType)
export interface JSDocRecordType extends JSDocType, TypeLiteralNode {
members: NodeArray<JSDocRecordMember>;
literal: TypeLiteralNode;
}
// @kind(SyntaxKind.JSDocTypeReference)
@@ -1519,14 +1519,16 @@ namespace ts {
}
// @kind(SyntaxKind.JSDocComment)
export interface JSDocComment extends Node {
export interface JSDoc extends Node {
tags: NodeArray<JSDocTag>;
comment: string | undefined;
}
// @kind(SyntaxKind.JSDocTag)
export interface JSDocTag extends Node {
atToken: Node;
tagName: Identifier;
comment: string | undefined;
}
// @kind(SyntaxKind.JSDocTemplateTag)
@@ -1565,9 +1567,13 @@ namespace ts {
// @kind(SyntaxKind.JSDocParameterTag)
export interface JSDocParameterTag extends JSDocTag {
/** the parameter name, if provided *before* the type (TypeScript-style) */
preParameterName?: Identifier;
typeExpression?: JSDocTypeExpression;
/** the parameter name, if provided *after* the type (JSDoc-standard) */
postParameterName?: Identifier;
/** the parameter name, regardless of the location it was provided */
parameterName: Identifier;
isBracketed: boolean;
}
+118 -44
View File
@@ -596,13 +596,7 @@ namespace ts {
}
export function getJsDocCommentsFromText(node: Node, text: string) {
const commentRanges = (node.kind === SyntaxKind.Parameter ||
node.kind === SyntaxKind.TypeParameter ||
node.kind === SyntaxKind.FunctionExpression ||
node.kind === SyntaxKind.ArrowFunction) ?
concatenate(getTrailingCommentRanges(text, node.pos), getLeadingCommentRanges(text, node.pos)) :
getLeadingCommentRangesOfNodeFromText(node, text);
return filter(commentRanges, isJsDocComment);
return filter(getLeadingCommentRanges(text, node.pos), isJsDocComment);
function isJsDocComment(comment: CommentRange) {
// True if the comment starts with '/**' but not if it is '/**/'
@@ -1356,39 +1350,75 @@ namespace ts {
return undefined;
}
const jsDocComments = getJSDocComments(node, checkParentVariableStatement);
if (!jsDocComments) {
const jsDocTags = getJSDocTags(node, checkParentVariableStatement);
if (!jsDocTags) {
return undefined;
}
for (const jsDocComment of jsDocComments) {
for (const tag of jsDocComment.tags) {
if (tag.kind === kind) {
return tag;
}
for (const tag of jsDocTags) {
if (tag.kind === kind) {
return tag;
}
}
}
function getJSDocComments(node: Node, checkParentVariableStatement: boolean): JSDocComment[] {
if (node.jsDocComments) {
return node.jsDocComments;
function append<T>(previous: T[] | undefined, additional: T[] | undefined): T[] | undefined {
if (additional) {
if (!previous) {
previous = [];
}
for (const x of additional) {
previous.push(x);
}
}
// Try to recognize this pattern when node is initializer of variable declaration and JSDoc comments are on containing variable statement.
// /**
// * @param {number} name
// * @returns {number}
// */
// var x = function(name) { return name.length; }
if (checkParentVariableStatement) {
const isInitializerOfVariableDeclarationInStatement =
node.parent.kind === SyntaxKind.VariableDeclaration &&
(<VariableDeclaration>node.parent).initializer === node &&
node.parent.parent.parent.kind === SyntaxKind.VariableStatement;
return previous;
}
const variableStatementNode = isInitializerOfVariableDeclarationInStatement ? node.parent.parent.parent : undefined;
export function getJSDocComments(node: Node, checkParentVariableStatement: boolean): string[] {
return getJSDocs(node, checkParentVariableStatement, docs => map(docs, doc => doc.comment), tags => map(tags, tag => tag.comment));
}
function getJSDocTags(node: Node, checkParentVariableStatement: boolean): JSDocTag[] {
return getJSDocs(node, checkParentVariableStatement, docs => {
let result: JSDocTag[] = [];
for (const doc of docs) {
if (doc.tags) {
result.push(...doc.tags);
}
}
return result;
}, tags => tags);
}
function getJSDocs<T>(node: Node, checkParentVariableStatement: boolean, getDocs: (docs: JSDoc[]) => T[], getTags: (tags: JSDocTag[]) => T[]): T[] {
// TODO: Get rid of getJsDocComments and friends (note the lowercase 's' in Js)
// TODO: A lot of this work should be cached, maybe. I guess it's only used in services right now...
let result: T[] = undefined;
// prepend documentation from parent sources
if (checkParentVariableStatement) {
// Try to recognize this pattern when node is initializer of variable declaration and JSDoc comments are on containing variable statement.
// /**
// * @param {number} name
// * @returns {number}
// */
// var x = function(name) { return name.length; }
const isInitializerOfVariableDeclarationInStatement =
isVariableLike(node.parent) &&
(node.parent).initializer === node &&
node.parent.parent.parent.kind === SyntaxKind.VariableStatement;
const isVariableOfVariableDeclarationStatement = isVariableLike(node) &&
node.parent.parent.kind === SyntaxKind.VariableStatement;
const variableStatementNode =
isInitializerOfVariableDeclarationInStatement ? node.parent.parent.parent :
isVariableOfVariableDeclarationStatement ? node.parent.parent :
undefined;
if (variableStatementNode) {
return variableStatementNode.jsDocComments;
result = append(result, getJSDocs(variableStatementNode, checkParentVariableStatement, getDocs, getTags));
}
if (node.kind === SyntaxKind.ModuleDeclaration &&
node.parent && node.parent.kind === SyntaxKind.ModuleDeclaration) {
result = append(result, getJSDocs(node.parent, checkParentVariableStatement, getDocs, getTags));
}
// Also recognize when the node is the RHS of an assignment expression
@@ -1399,16 +1429,62 @@ namespace ts {
(parent as BinaryExpression).operatorToken.kind === SyntaxKind.EqualsToken &&
parent.parent.kind === SyntaxKind.ExpressionStatement;
if (isSourceOfAssignmentExpressionStatement) {
return parent.parent.jsDocComments;
result = append(result, getJSDocs(parent.parent, checkParentVariableStatement, getDocs, getTags));
}
const isPropertyAssignmentExpression = parent && parent.kind === SyntaxKind.PropertyAssignment;
if (isPropertyAssignmentExpression) {
return parent.jsDocComments;
result = append(result, getJSDocs(parent, checkParentVariableStatement, getDocs, getTags));
}
// Pull parameter comments from declaring function as well
if (node.kind === SyntaxKind.Parameter) {
const paramTags = getJSDocParameterTag(node as ParameterDeclaration, checkParentVariableStatement);
if (paramTags) {
result = append(result, getTags(paramTags));
}
}
}
return undefined;
if (isVariableLike(node) && node.initializer) {
result = append(result, getJSDocs(node.initializer, /*checkParentVariableStatement*/ false, getDocs, getTags));
}
if (node.jsDocComments) {
if (result) {
result = append(result, getDocs(node.jsDocComments));
}
else {
return getDocs(node.jsDocComments);
}
}
return result;
}
function getJSDocParameterTag(param: ParameterDeclaration, checkParentVariableStatement: boolean): JSDocTag[] {
const func = param.parent as FunctionLikeDeclaration;
const tags = getJSDocTags(func, checkParentVariableStatement);
if (!param.name) {
// this is an anonymous jsdoc param from a `function(type1, type2): type3` specification
const i = func.parameters.indexOf(param);
const paramTags = filter(tags, tag => tag.kind === SyntaxKind.JSDocParameterTag);
if (paramTags && 0 <= i && i < paramTags.length) {
return [paramTags[i]];
}
}
else if (param.name.kind === SyntaxKind.Identifier) {
const name = (param.name as Identifier).text;
const paramTags = filter(tags, tag => tag.kind === SyntaxKind.JSDocParameterTag && (tag as JSDocParameterTag).parameterName.text === name);
if (paramTags) {
return paramTags;
}
}
else {
// TODO: it's a destructured parameter, so it should look up an "object type" series of multiple lines
// But multi-line object types aren't supported yet either
return undefined;
}
}
export function getJSDocTypeTag(node: Node): JSDocTypeTag {
@@ -1429,17 +1505,15 @@ namespace ts {
// annotation.
const parameterName = (<Identifier>parameter.name).text;
const jsDocComments = getJSDocComments(parameter.parent, /*checkParentVariableStatement*/ true);
if (jsDocComments) {
for (const jsDocComment of jsDocComments) {
for (const tag of jsDocComment.tags) {
if (tag.kind === SyntaxKind.JSDocParameterTag) {
const parameterTag = <JSDocParameterTag>tag;
const name = parameterTag.preParameterName || parameterTag.postParameterName;
if (name.text === parameterName) {
return parameterTag;
}
}
const jsDocTags = getJSDocTags(parameter.parent, /*checkParentVariableStatement*/ true);
if (!jsDocTags) {
return undefined;
}
for (const tag of jsDocTags) {
if (tag.kind === SyntaxKind.JSDocParameterTag) {
const parameterTag = <JSDocParameterTag>tag;
if (parameterTag.parameterName.text === parameterName) {
return parameterTag;
}
}
}
File diff suppressed because it is too large Load Diff
+52 -352
View File
@@ -194,7 +194,7 @@ namespace ts {
public end: number;
public flags: NodeFlags;
public parent: Node;
public jsDocComments: JSDocComment[];
public jsDocComments: JSDoc[];
private _children: Node[];
constructor(kind: SyntaxKind, pos: number, end: number) {
@@ -355,7 +355,7 @@ namespace ts {
public end: number;
public flags: NodeFlags;
public parent: Node;
public jsDocComments: JSDocComment[];
public jsDocComments: JSDoc[];
public __tokenTag: any;
constructor(pos: number, end: number) {
@@ -474,349 +474,48 @@ namespace ts {
}
function getJsDocCommentsFromDeclarations(declarations: Declaration[], name: string, canUseParsedParamTagComments: boolean) {
// Only collect doc comments from duplicate declarations once:
// In case of a union property there might be same declaration multiple times
// which only varies in type parameter
// Eg. const a: Array<string> | Array<number>; a.length
// The property length will have two declarations of property length coming
// from Array<T> - Array<string> and Array<number>
const documentationComment = <SymbolDisplayPart[]>[];
const docComments = getJsDocCommentsSeparatedByNewLines();
ts.forEach(docComments, docComment => {
if (documentationComment.length) {
documentationComment.push(lineBreakPart());
forEachUnique(declarations, declaration => {
const comments = getJSDocComments(declaration, /*checkParentVariableStatement*/ true);
if (!comments) {
return;
}
for (const comment of comments) {
if (comment) {
if (documentationComment.length) {
documentationComment.push(lineBreakPart());
}
documentationComment.push(textPart(comment));
}
}
documentationComment.push(docComment);
});
return documentationComment;
}
function getJsDocCommentsSeparatedByNewLines() {
const paramTag = "@param";
const jsDocCommentParts: SymbolDisplayPart[] = [];
ts.forEach(declarations, (declaration, indexOfDeclaration) => {
// Make sure we are collecting doc comment from declaration once,
// In case of union property there might be same declaration multiple times
// which only varies in type parameter
// Eg. const a: Array<string> | Array<number>; a.length
// The property length will have two declarations of property length coming
// from Array<T> - Array<string> and Array<number>
if (indexOf(declarations, declaration) === indexOfDeclaration) {
const sourceFileOfDeclaration = getSourceFileOfNode(declaration);
// If it is parameter - try and get the jsDoc comment with @param tag from function declaration's jsDoc comments
if (canUseParsedParamTagComments && declaration.kind === SyntaxKind.Parameter) {
if ((declaration.parent.kind === SyntaxKind.FunctionExpression || declaration.parent.kind === SyntaxKind.ArrowFunction) &&
declaration.parent.parent.kind === SyntaxKind.VariableDeclaration) {
addCommentParts(declaration.parent.parent.parent, sourceFileOfDeclaration, getCleanedParamJsDocComment);
}
addCommentParts(declaration.parent, sourceFileOfDeclaration, getCleanedParamJsDocComment);
}
// If this is left side of dotted module declaration, there is no doc comments associated with this node
if (declaration.kind === SyntaxKind.ModuleDeclaration && (<ModuleDeclaration>declaration).body && (<ModuleDeclaration>declaration).body.kind === SyntaxKind.ModuleDeclaration) {
return;
}
if ((declaration.kind === SyntaxKind.FunctionExpression || declaration.kind === SyntaxKind.ArrowFunction) &&
declaration.parent.kind === SyntaxKind.VariableDeclaration) {
addCommentParts(declaration.parent.parent, sourceFileOfDeclaration, getCleanedJsDocComment);
}
// If this is dotted module name, get the doc comments from the parent
while (declaration.kind === SyntaxKind.ModuleDeclaration && declaration.parent.kind === SyntaxKind.ModuleDeclaration) {
declaration = <ModuleDeclaration>declaration.parent;
}
addCommentParts(declaration.kind === SyntaxKind.VariableDeclaration ? declaration.parent.parent : declaration,
sourceFileOfDeclaration,
getCleanedJsDocComment);
if (declaration.kind === SyntaxKind.VariableDeclaration) {
const init = (declaration as VariableDeclaration).initializer;
if (init && (init.kind === SyntaxKind.FunctionExpression || init.kind === SyntaxKind.ArrowFunction)) {
// Get the cleaned js doc comment text from the initializer
addCommentParts(init, sourceFileOfDeclaration, getCleanedJsDocComment);
}
}
}
});
return jsDocCommentParts;
function addCommentParts(commented: Node,
sourceFileOfDeclaration: SourceFile,
getCommentPart: (pos: number, end: number, file: SourceFile) => SymbolDisplayPart[]): void {
const ranges = getJsDocCommentTextRange(commented, sourceFileOfDeclaration);
// Get the cleaned js doc comment text from the declaration
ts.forEach(ranges, jsDocCommentTextRange => {
const cleanedComment = getCommentPart(jsDocCommentTextRange.pos, jsDocCommentTextRange.end, sourceFileOfDeclaration);
if (cleanedComment) {
addRange(jsDocCommentParts, cleanedComment);
}
});
}
function getJsDocCommentTextRange(node: Node, sourceFile: SourceFile): TextRange[] {
return ts.map(getJsDocComments(node, sourceFile),
jsDocComment => {
return {
pos: jsDocComment.pos + "/*".length, // Consume /* from the comment
end: jsDocComment.end - "*/".length // Trim off comment end indicator
};
});
}
function consumeWhiteSpacesOnTheLine(pos: number, end: number, sourceFile: SourceFile, maxSpacesToRemove?: number) {
if (maxSpacesToRemove !== undefined) {
end = Math.min(end, pos + maxSpacesToRemove);
}
for (; pos < end; pos++) {
const ch = sourceFile.text.charCodeAt(pos);
if (!isWhiteSpaceSingleLine(ch)) {
return pos;
}
}
return end;
}
function consumeLineBreaks(pos: number, end: number, sourceFile: SourceFile) {
while (pos < end && isLineBreak(sourceFile.text.charCodeAt(pos))) {
pos++;
}
return pos;
}
function isName(pos: number, end: number, sourceFile: SourceFile, name: string) {
return pos + name.length < end &&
sourceFile.text.substr(pos, name.length) === name &&
isWhiteSpace(sourceFile.text.charCodeAt(pos + name.length));
}
function isParamTag(pos: number, end: number, sourceFile: SourceFile) {
// If it is @param tag
return isName(pos, end, sourceFile, paramTag);
}
function pushDocCommentLineText(docComments: SymbolDisplayPart[], text: string, blankLineCount: number) {
// Add the empty lines in between texts
while (blankLineCount) {
blankLineCount--;
docComments.push(textPart(""));
}
docComments.push(textPart(text));
}
function getCleanedJsDocComment(pos: number, end: number, sourceFile: SourceFile) {
let spacesToRemoveAfterAsterisk: number;
const docComments: SymbolDisplayPart[] = [];
let blankLineCount = 0;
let isInParamTag = false;
while (pos < end) {
let docCommentTextOfLine = "";
// First consume leading white space
pos = consumeWhiteSpacesOnTheLine(pos, end, sourceFile);
// If the comment starts with '*' consume the spaces on this line
if (pos < end && sourceFile.text.charCodeAt(pos) === CharacterCodes.asterisk) {
const lineStartPos = pos + 1;
pos = consumeWhiteSpacesOnTheLine(pos + 1, end, sourceFile, spacesToRemoveAfterAsterisk);
// Set the spaces to remove after asterisk as margin if not already set
if (spacesToRemoveAfterAsterisk === undefined && pos < end && !isLineBreak(sourceFile.text.charCodeAt(pos))) {
spacesToRemoveAfterAsterisk = pos - lineStartPos;
}
}
else if (spacesToRemoveAfterAsterisk === undefined) {
spacesToRemoveAfterAsterisk = 0;
}
// Analyze text on this line
while (pos < end && !isLineBreak(sourceFile.text.charCodeAt(pos))) {
const ch = sourceFile.text.charAt(pos);
if (ch === "@") {
// If it is @param tag
if (isParamTag(pos, end, sourceFile)) {
isInParamTag = true;
pos += paramTag.length;
continue;
}
else {
isInParamTag = false;
}
}
// Add the ch to doc text if we arent in param tag
if (!isInParamTag) {
docCommentTextOfLine += ch;
}
// Scan next character
pos++;
}
// Continue with next line
pos = consumeLineBreaks(pos, end, sourceFile);
if (docCommentTextOfLine) {
pushDocCommentLineText(docComments, docCommentTextOfLine, blankLineCount);
blankLineCount = 0;
}
else if (!isInParamTag && docComments.length) {
// This is blank line when there is text already parsed
blankLineCount++;
}
}
return docComments;
}
function getCleanedParamJsDocComment(pos: number, end: number, sourceFile: SourceFile) {
let paramHelpStringMargin: number;
const paramDocComments: SymbolDisplayPart[] = [];
while (pos < end) {
if (isParamTag(pos, end, sourceFile)) {
let blankLineCount = 0;
let recordedParamTag = false;
// Consume leading spaces
pos = consumeWhiteSpaces(pos + paramTag.length);
if (pos >= end) {
break;
}
// Ignore type expression
if (sourceFile.text.charCodeAt(pos) === CharacterCodes.openBrace) {
pos++;
for (let curlies = 1; pos < end; pos++) {
const charCode = sourceFile.text.charCodeAt(pos);
// { character means we need to find another } to match the found one
if (charCode === CharacterCodes.openBrace) {
curlies++;
continue;
}
// } char
if (charCode === CharacterCodes.closeBrace) {
curlies--;
if (curlies === 0) {
// We do not have any more } to match the type expression is ignored completely
pos++;
break;
}
else {
// there are more { to be matched with }
continue;
}
}
// Found start of another tag
if (charCode === CharacterCodes.at) {
break;
}
}
// Consume white spaces
pos = consumeWhiteSpaces(pos);
if (pos >= end) {
break;
}
}
// Parameter name
if (isName(pos, end, sourceFile, name)) {
// Found the parameter we are looking for consume white spaces
pos = consumeWhiteSpaces(pos + name.length);
if (pos >= end) {
break;
}
let paramHelpString = "";
const firstLineParamHelpStringPos = pos;
while (pos < end) {
const ch = sourceFile.text.charCodeAt(pos);
// at line break, set this comment line text and go to next line
if (isLineBreak(ch)) {
if (paramHelpString) {
pushDocCommentLineText(paramDocComments, paramHelpString, blankLineCount);
paramHelpString = "";
blankLineCount = 0;
recordedParamTag = true;
}
else if (recordedParamTag) {
blankLineCount++;
}
// Get the pos after cleaning start of the line
setPosForParamHelpStringOnNextLine(firstLineParamHelpStringPos);
continue;
}
// Done scanning param help string - next tag found
if (ch === CharacterCodes.at) {
break;
}
paramHelpString += sourceFile.text.charAt(pos);
// Go to next character
pos++;
}
// If there is param help text, add it top the doc comments
if (paramHelpString) {
pushDocCommentLineText(paramDocComments, paramHelpString, blankLineCount);
}
paramHelpStringMargin = undefined;
}
// If this is the start of another tag, continue with the loop in search of param tag with symbol name
if (sourceFile.text.charCodeAt(pos) === CharacterCodes.at) {
continue;
}
}
// Next character
pos++;
}
return paramDocComments;
function consumeWhiteSpaces(pos: number) {
while (pos < end && isWhiteSpaceSingleLine(sourceFile.text.charCodeAt(pos))) {
pos++;
}
return pos;
}
function setPosForParamHelpStringOnNextLine(firstLineParamHelpStringPos: number) {
// Get the pos after consuming line breaks
pos = consumeLineBreaks(pos, end, sourceFile);
if (pos >= end) {
return;
}
if (paramHelpStringMargin === undefined) {
paramHelpStringMargin = sourceFile.getLineAndCharacterOfPosition(firstLineParamHelpStringPos).character;
}
// Now consume white spaces max
const startOfLinePos = pos;
pos = consumeWhiteSpacesOnTheLine(pos, end, sourceFile, paramHelpStringMargin);
if (pos >= end) {
return;
}
const consumedSpaces = pos - startOfLinePos;
if (consumedSpaces < paramHelpStringMargin) {
const ch = sourceFile.text.charCodeAt(pos);
if (ch === CharacterCodes.asterisk) {
// Consume more spaces after asterisk
pos = consumeWhiteSpacesOnTheLine(pos + 1, end, sourceFile, paramHelpStringMargin - consumedSpaces - 1);
}
/**
* Iterates through 'array' by index and performs the callback on each element of array until the callback
* returns a truthy value, then returns that value.
* If no such value is found, the callback is applied to each element of array and undefined is returned.
*/
export function forEachUnique<T, U>(array: T[], callback: (element: T, index: number) => U): U {
if (array) {
for (let i = 0, len = array.length; i < len; i++) {
if (indexOf(array, array[i]) === i) {
const result = callback(array[i], i);
if (result) {
return result;
}
}
}
}
return undefined;
}
class TypeObject implements Type {
@@ -7498,9 +7197,9 @@ namespace ts {
// See if this is a doc comment. If so, we'll classify certain portions of it
// specially.
const docCommentAndDiagnostics = parseIsolatedJSDocComment(sourceFile.text, start, width);
if (docCommentAndDiagnostics && docCommentAndDiagnostics.jsDocComment) {
docCommentAndDiagnostics.jsDocComment.parent = token;
classifyJSDocComment(docCommentAndDiagnostics.jsDocComment);
if (docCommentAndDiagnostics && docCommentAndDiagnostics.jsDoc) {
docCommentAndDiagnostics.jsDoc.parent = token;
classifyJSDocComment(docCommentAndDiagnostics.jsDoc);
return;
}
}
@@ -7513,22 +7212,22 @@ namespace ts {
pushClassification(start, width, ClassificationType.comment);
}
function classifyJSDocComment(docComment: JSDocComment) {
function classifyJSDocComment(docComment: JSDoc) {
let pos = docComment.pos;
if (docComment.tags) {
for (const 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);
}
for (const 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);
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;
pos = tag.tagName.end;
switch (tag.kind) {
switch (tag.kind) {
case SyntaxKind.JSDocParameterTag:
processJSDocParameterTag(<JSDocParameterTag>tag);
break;
@@ -7541,9 +7240,10 @@ namespace ts {
case SyntaxKind.JSDocReturnTag:
processElement((<JSDocReturnTag>tag).typeExpression);
break;
}
}
pos = tag.end;
pos = tag.end;
}
}
if (pos !== docComment.end) {
+6 -4
View File
@@ -569,9 +569,11 @@ namespace ts {
if (node) {
if (node.jsDocComments) {
for (const jsDocComment of node.jsDocComments) {
for (const tag of jsDocComment.tags) {
if (tag.pos <= position && position <= tag.end) {
return tag;
if (jsDocComment.tags) {
for (const tag of jsDocComment.tags) {
if (tag.pos <= position && position <= tag.end) {
return tag;
}
}
}
}
@@ -946,4 +948,4 @@ namespace ts {
}
return { configJsonObject, diagnostics };
}
}
}