Skip to content

Commit f5eb61a

Browse files
Add support for parsing JSDoc types.
1 parent c53b0a5 commit f5eb61a

File tree

2 files changed

+417
-8
lines changed

2 files changed

+417
-8
lines changed

src/compiler/parser.ts

Lines changed: 341 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1432,14 +1432,7 @@ module ts {
14321432

14331433
function createNode(kind: SyntaxKind, pos?: number): Node {
14341434
nodeCount++;
1435-
let node = new (nodeConstructors[kind] || (nodeConstructors[kind] = objectAllocator.getNodeConstructor(kind)))();
1436-
if (!(pos >= 0)) {
1437-
pos = scanner.getStartPos();
1438-
}
1439-
1440-
node.pos = pos;
1441-
node.end = pos;
1442-
return node;
1435+
return createNodeAtPosition(scanner, kind, pos);
14431436
}
14441437

14451438
function finishNode<T extends Node>(node: T): T {
@@ -5390,4 +5383,344 @@ module ts {
53905383
export function isAssignmentOperator(token: SyntaxKind): boolean {
53915384
return token >= SyntaxKind.FirstAssignment && token <= SyntaxKind.LastAssignment;
53925385
}
5386+
5387+
// Parses out a JSDoc type expression. The starting position should be right at the open
5388+
// curly in the type expression. Returns 'undefined' if it encounters any errors while parsing.
5389+
function parseJSDocTypeExpression(content: string, start: number): JSDocType {
5390+
let scanner = createScanner(ScriptTarget.Latest, /*skipTrivia:*/ true, content);
5391+
scanner.setTextPos(start);
5392+
5393+
// Prime the first token for us to start processing.
5394+
let token = nextToken();
5395+
let error = false;
5396+
5397+
parseExpected(SyntaxKind.OpenBraceToken);
5398+
let type = parseJSDocType();
5399+
parseExpected(SyntaxKind.CloseBraceToken);
5400+
5401+
return error ? undefined : type;
5402+
5403+
function nextToken(): SyntaxKind {
5404+
return token = scanner.scan();
5405+
}
5406+
5407+
function createNode(kind: SyntaxKind, pos?: number): Node {
5408+
return createNodeAtPosition(scanner, kind, pos);
5409+
}
5410+
5411+
function parseExpected(kind: SyntaxKind): void {
5412+
if (token === kind) {
5413+
nextToken();
5414+
}
5415+
else {
5416+
error = true;
5417+
}
5418+
}
5419+
5420+
function isIdentifier() {
5421+
if (token === SyntaxKind.Identifier) {
5422+
return true;
5423+
}
5424+
5425+
return token > SyntaxKind.LastReservedWord && token <= SyntaxKind.LastKeyword;
5426+
}
5427+
5428+
function isIdentifierOrKeyword() {
5429+
if (token === SyntaxKind.Identifier) {
5430+
return true;
5431+
}
5432+
5433+
return token >= SyntaxKind.FirstKeyword && token <= SyntaxKind.LastKeyword;
5434+
}
5435+
5436+
function parseJSDocType(): JSDocType {
5437+
let type = parseJSDocType();
5438+
if (type && token === SyntaxKind.EqualsToken) {
5439+
return parseJSDocOptionalType(type);
5440+
}
5441+
5442+
return type;
5443+
}
5444+
5445+
function parseJSDocTypeCore(): JSDocType {
5446+
switch (token) {
5447+
case SyntaxKind.AsteriskToken:
5448+
return parseJSDocAllType();
5449+
case SyntaxKind.QuestionToken:
5450+
return parseJSDocUnknownOrNullableType();
5451+
case SyntaxKind.OpenParenToken:
5452+
return parseJSDocUnionType();
5453+
case SyntaxKind.ExclamationToken:
5454+
return parseJSDocNonNullableType();
5455+
case SyntaxKind.OpenBraceToken:
5456+
return parseJSDocRecordType();
5457+
case SyntaxKind.FunctionKeyword:
5458+
return parseJSDocFunctionType();
5459+
case SyntaxKind.DotDotDotToken:
5460+
return parseJSDocVariadicType();
5461+
case SyntaxKind.NewKeyword:
5462+
return parseJSDocConstructorType();
5463+
case SyntaxKind.ThisKeyword:
5464+
return parseJSDocThisType();
5465+
}
5466+
5467+
if (isIdentifier()) {
5468+
return parseJSDocTypeReference();
5469+
}
5470+
5471+
error = true;
5472+
return undefined;
5473+
}
5474+
5475+
function parseJSDocThisType(): JSDocThisType {
5476+
let result = <JSDocThisType>createNode(SyntaxKind.JSDocThisType);
5477+
nextToken();
5478+
parseExpected(SyntaxKind.ColonToken);
5479+
result.type = parseJSDocType();
5480+
return finishNode(result);
5481+
}
5482+
5483+
function parseJSDocConstructorType(): JSDocConstructorType {
5484+
let result = <JSDocConstructorType>createNode(SyntaxKind.JSDocConstructorType);
5485+
nextToken();
5486+
parseExpected(SyntaxKind.ColonToken);
5487+
result.type = parseJSDocType();
5488+
return finishNode(result);
5489+
}
5490+
5491+
function parseJSDocVariadicType(): JSDocVariadicType {
5492+
let result = <JSDocVariadicType>createNode(SyntaxKind.JSDocVariadicType);
5493+
nextToken();
5494+
result.type = parseJSDocType();
5495+
return finishNode(result);
5496+
}
5497+
5498+
function parseJSDocFunctionType(): JSDocFunctionType {
5499+
let result = <JSDocFunctionType>createNode(SyntaxKind.JSDocFunctionType);
5500+
nextToken();
5501+
parseExpected(SyntaxKind.OpenParenToken);
5502+
5503+
let parameters = <NodeArray<JSDocType>>[];
5504+
parameters.pos = scanner.getStartPos();
5505+
5506+
while (!error && token !== SyntaxKind.CloseParenToken && token !== SyntaxKind.EndOfFileToken) {
5507+
if (parameters.length > 0) {
5508+
parseExpected(SyntaxKind.CommaToken);
5509+
}
5510+
5511+
parameters.push(parseJSDocType());
5512+
}
5513+
5514+
parameters.end = scanner.getStartPos();
5515+
parseExpected(SyntaxKind.CloseParenToken);
5516+
5517+
if (token === SyntaxKind.ColonToken) {
5518+
nextToken();
5519+
result.type = parseJSDocType();
5520+
}
5521+
5522+
return finishNode(result);
5523+
}
5524+
5525+
function parseJSDocOptionalType(type: JSDocType): JSDocOptionalType {
5526+
let result = <JSDocOptionalType>createNode(SyntaxKind.JSDocOptionalType, type.pos);
5527+
nextToken();
5528+
return finishNode(result);
5529+
}
5530+
5531+
function parseJSDocTypeReference(): JSDocTypeReference {
5532+
let result = <JSDocTypeReference>createNode(SyntaxKind.JSDocTypeReference);
5533+
result.name = parseIdentifier();
5534+
5535+
while (!error && result.name && token === SyntaxKind.DotToken) {
5536+
if (isIdentifierOrKeyword()) {
5537+
result.name = parseQualifiedName(result.name);
5538+
}
5539+
else if (token === SyntaxKind.LessThanToken) {
5540+
result.typeArguments = parseTypeArguments();
5541+
break;
5542+
}
5543+
else {
5544+
error = true;
5545+
return undefined;
5546+
}
5547+
}
5548+
5549+
return finishNode(result);
5550+
}
5551+
5552+
function parseTypeArguments() {
5553+
// Move past the <
5554+
nextToken();
5555+
5556+
let typeArguments = <NodeArray<JSDocType>>[];
5557+
typeArguments.pos = scanner.getStartPos();
5558+
5559+
typeArguments.push(parseJSDocType());
5560+
while (!error && token === SyntaxKind.CommaToken) {
5561+
nextToken();
5562+
typeArguments.push(parseJSDocType());
5563+
}
5564+
5565+
typeArguments.end = scanner.getStartPos();
5566+
5567+
parseExpected(SyntaxKind.GreaterThanToken);
5568+
5569+
return typeArguments;
5570+
}
5571+
5572+
function parseQualifiedName(left: EntityName): QualifiedName {
5573+
// Move past the .
5574+
nextToken();
5575+
5576+
let result = <QualifiedName>createNode(SyntaxKind.QualifiedName, left.pos);
5577+
result.left = left;
5578+
result.right = parseIdentifierOrKeyword();
5579+
5580+
return finishNode(result);
5581+
}
5582+
5583+
function parseIdentifierOrKeyword(): Identifier {
5584+
return parseIdentifierHelper(isIdentifierOrKeyword());
5585+
}
5586+
5587+
function parseIdentifier(): Identifier {
5588+
return parseIdentifierHelper(isIdentifier());
5589+
}
5590+
5591+
function parseIdentifierHelper(isIdentifier: boolean): Identifier {
5592+
if (isIdentifier) {
5593+
let result = <Identifier>createNode(SyntaxKind.Identifier);
5594+
result.text = scanner.getTokenValue();
5595+
nextToken();
5596+
return finishNode(result);
5597+
}
5598+
else {
5599+
error = true;
5600+
return undefined;
5601+
}
5602+
}
5603+
5604+
function parseJSDocRecordType(): JSDocRecordType {
5605+
let result = <JSDocRecordType>createNode(SyntaxKind.JSDocRecordType);
5606+
nextToken();
5607+
5608+
let members = <NodeArray<JSDocMember>>[];
5609+
members.pos = scanner.getStartPos();
5610+
5611+
while (token !== SyntaxKind.CloseBraceToken && token !== SyntaxKind.EndOfFileToken && !error) {
5612+
members.push(parseJSDocMember());
5613+
}
5614+
5615+
members.end = scanner.getStartPos();
5616+
5617+
parseExpected(SyntaxKind.CloseBraceToken);
5618+
return finishNode(result);
5619+
}
5620+
5621+
function parseJSDocMember(): JSDocMember {
5622+
let result = <JSDocMember>createNode(SyntaxKind.JSDocMember);
5623+
result.name = parsePropertyName();
5624+
5625+
if (token === SyntaxKind.ColonToken) {
5626+
nextToken();
5627+
result.type = parseJSDocType();
5628+
}
5629+
5630+
return finishNode(result);
5631+
}
5632+
5633+
function parsePropertyName(): Identifier | LiteralExpression {
5634+
if (token === SyntaxKind.Identifier || token === SyntaxKind.StringLiteral || token === SyntaxKind.NumericLiteral) {
5635+
let result = <Identifier | LiteralExpression>createNode(token);
5636+
nextToken();
5637+
return finishNode(result);
5638+
}
5639+
else {
5640+
error = true;
5641+
return undefined;
5642+
}
5643+
}
5644+
5645+
function parseJSDocNonNullableType(): JSDocNonNullableType {
5646+
let result = <JSDocNonNullableType>createNode(SyntaxKind.JSDocNonNullableType);
5647+
nextToken();
5648+
result.type = parseJSDocType();
5649+
return finishNode(result);
5650+
}
5651+
5652+
function parseJSDocUnionType(): JSDocUnionType {
5653+
let result = <JSDocUnionType>createNode(SyntaxKind.JSDocUnionType);
5654+
nextToken();
5655+
5656+
let types = <NodeArray<JSDocType>>[];
5657+
types.pos = scanner.getStartPos();
5658+
5659+
types.push(parseJSDocType());
5660+
while (token === SyntaxKind.BarToken) {
5661+
nextToken();
5662+
types.push(parseJSDocType());
5663+
}
5664+
5665+
types.end = scanner.getStartPos();
5666+
5667+
parseExpected(SyntaxKind.CloseParenToken);
5668+
5669+
return finishNode(result);
5670+
}
5671+
5672+
function parseJSDocAllType(): JSDocAllType {
5673+
let result = <JSDocAllType>createNode(SyntaxKind.JSDocAllType);
5674+
nextToken();
5675+
return finishNode(result);
5676+
}
5677+
5678+
function parseJSDocUnknownOrNullableType(): JSDocUnknownType | JSDocNullableType {
5679+
let pos = scanner.getStartPos();
5680+
// skip the ?
5681+
nextToken();
5682+
5683+
// Need to lookahead to decide if this is a nullable or unknown type.
5684+
5685+
// Here are cases where we'll pick the unknown type:
5686+
//
5687+
// Foo(?,
5688+
// { a: ? }
5689+
// Foo(?)
5690+
// Foo<?>
5691+
// Foo(?=
5692+
// (?|
5693+
if (token === SyntaxKind.CommaToken ||
5694+
token === SyntaxKind.CloseBraceToken ||
5695+
token === SyntaxKind.CloseParenToken ||
5696+
token === SyntaxKind.GreaterThanToken ||
5697+
token === SyntaxKind.EqualsToken ||
5698+
token === SyntaxKind.BarToken) {
5699+
5700+
let result = <JSDocUnknownType>createNode(SyntaxKind.JSDocUnknownType, pos);
5701+
return finishNode(result);
5702+
}
5703+
else {
5704+
let result = <JSDocNullableType>createNode(SyntaxKind.JSDocNullableType, pos);
5705+
result.type = parseJSDocType();
5706+
return finishNode(result);
5707+
}
5708+
}
5709+
5710+
function finishNode<T extends Node>(node: T): T {
5711+
node.end = scanner.getStartPos();
5712+
return node;
5713+
}
5714+
}
5715+
5716+
function createNodeAtPosition(scanner: Scanner, kind: SyntaxKind, pos?: number): Node {
5717+
let node = new (nodeConstructors[kind] || (nodeConstructors[kind] = objectAllocator.getNodeConstructor(kind)))();
5718+
if (!(pos >= 0)) {
5719+
pos = scanner.getStartPos();
5720+
}
5721+
5722+
node.pos = pos;
5723+
node.end = pos;
5724+
return node;
5725+
}
53935726
}

0 commit comments

Comments
 (0)