diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index f63094ae16d..19fd1071efb 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -4,19 +4,29 @@ /* @internal */ namespace ts { let NodeConstructor: new (kind: SyntaxKind, pos: number, end: number) => Node; + let TokenConstructor: new (kind: SyntaxKind, pos: number, end: number) => Node; + let IdentifierConstructor: new (kind: SyntaxKind, pos: number, end: number) => Node; let SourceFileConstructor: new (kind: SyntaxKind, pos: number, end: number) => Node; - function createNode(kind: SyntaxKind, location?: TextRange, flags?: NodeFlags): Node { - const ConstructorForKind = kind === SyntaxKind.SourceFile - ? (SourceFileConstructor || (SourceFileConstructor = objectAllocator.getSourceFileConstructor())) - : (NodeConstructor || (NodeConstructor = objectAllocator.getNodeConstructor())); + function getConstructorForKind(kind: SyntaxKind): new (kind: SyntaxKind, pos: number, end: number) => Node { + if (kind === SyntaxKind.SourceFile) { + return SourceFileConstructor || (SourceFileConstructor = objectAllocator.getSourceFileConstructor()); + } + if (kind === SyntaxKind.Identifier) { + return IdentifierConstructor || (IdentifierConstructor = objectAllocator.getIdentifierConstructor()); + } + if (kind < SyntaxKind.FirstNode) { + return TokenConstructor || (TokenConstructor = objectAllocator.getTokenConstructor()); + } + return NodeConstructor || (NodeConstructor = objectAllocator.getNodeConstructor()); + } + function createNode(kind: SyntaxKind, location?: TextRange, flags?: NodeFlags): Node { + const ConstructorForKind = getConstructorForKind(kind); const node = location ? new ConstructorForKind(kind, location.pos, location.end) : new ConstructorForKind(kind, /*pos*/ -1, /*end*/ -1); - node.flags = flags | NodeFlags.Synthesized; - return node; } @@ -211,6 +221,13 @@ namespace ts { // Names + export function createQualifiedName(left: EntityName, right: Identifier, location?: TextRange) { + const node = createNode(SyntaxKind.QualifiedName, location); + node.left = left; + node.right = right; + return node; + } + export function createComputedPropertyName(expression: Expression, location?: TextRange) { const node = createNode(SyntaxKind.ComputedPropertyName, location); node.expression = expression; @@ -225,6 +242,24 @@ namespace ts { } // Signature elements + export function createTypeParameter(name: Identifier, constraintOrExpression: TypeNode | Expression) { + let constraint: TypeNode; + let expression: Expression; + if (constraint) { + if (isTypeNode(constraintOrExpression)) { + constraint = constraintOrExpression; + } + else { + expression = constraintOrExpression; + } + } + + const node = createNode(SyntaxKind.TypeParameter); + node.name = name; + node.constraint = constraint; + node.expression = expression; + return node; + } export function createParameter(decorators: Decorator[], modifiers: Modifier[], dotDotDotToken: DotDotDotToken, name: string | Identifier | BindingPattern, questionToken?: QuestionToken, type?: TypeNode, initializer?: Expression, location?: TextRange, flags?: NodeFlags) { const node = createNode(SyntaxKind.Parameter, location, flags); @@ -341,6 +376,28 @@ namespace ts { return node; } + // Types + + export function createTypePredicate(parameterName: Identifier | ThisTypeNode, type: TypeNode) { + const node = createNode(SyntaxKind.TypePredicate, /*location*/ undefined); + node.parameterName = parameterName; + node.type = type; + return node; + } + + export function createTypeReference(typeName: EntityName, typeArguments: TypeNode[]) { + const node = createNode(SyntaxKind.TypeReference, /*location*/ undefined); + node.typeName = typeName; + node.typeArguments = typeArguments ? createNodeArray(typeArguments) : undefined; + return node; + } + + export function createTypeQuery(exprName: EntityName) { + const node = createNode(SyntaxKind.TypeQuery, /*location*/ undefined); + node.exprName = exprName; + return node; + } + // Binding Patterns export function createObjectBindingPattern(elements: BindingElement[], location?: TextRange) { diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index c795cab14a3..f94316c8354 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -1119,6 +1119,46 @@ namespace ts { return node; } + function makeParseNode(node: T, pos?: number, end?: number): T { + nodeCount++; + node.pos = pos === undefined ? scanner.getStartPos() : pos; + node.end = end === undefined ? scanner.getStartPos() : end; + node.flags |= contextFlags; + + // Keep track on the node if we encountered an error while parsing it. If we did, then + // we cannot reuse the node incrementally. Once we've marked this node, clear out the + // flag so that we don't mark any subsequent nodes. + if (parseErrorBeforeNextFinishedNode) { + parseErrorBeforeNextFinishedNode = false; + node.flags |= NodeFlags.ThisNodeHasError; + } + + node.flags &= ~NodeFlags.Synthesized; + return node; + } + + function makeParseArray(elements: T[], pos?: number, end?: number): NodeArray { + if (elements) { + const array = >elements; + array.pos = pos === undefined ? getNodePos() : pos; + array.end = end === undefined ? getNodeEnd() : end; + return array; + } + return undefined; + } + + function parseUntil(parseNode: () => T, condition: (node: T) => boolean): NodeArray { + const fullStart = getNodePos(); + const elements: T[] = []; + while (true) { + const node = parseNode(); + elements.push(node); + if (condition(node)) { + return makeParseArray(elements, fullStart) + } + } + } + function createMissingNode(kind: SyntaxKind, reportAtCurrentPosition: boolean, diagnosticMessage: DiagnosticMessage, arg0?: any): Node { if (reportAtCurrentPosition) { parseErrorAtPosition(scanner.getStartPos(), 0, diagnosticMessage, arg0); @@ -1198,19 +1238,21 @@ namespace ts { } function parseComputedPropertyName(): ComputedPropertyName { + const fullStart = scanner.getStartPos(); + // PropertyName [Yield]: // LiteralPropertyName // ComputedPropertyName[?Yield] - const node = createNode(SyntaxKind.ComputedPropertyName); parseExpected(SyntaxKind.OpenBracketToken); // We parse any expression (including a comma expression). But the grammar // says that only an assignment expression is allowed, so the grammar checker // will error if it sees a comma expression. - node.expression = allowInAnd(parseExpression); + const expression = allowInAnd(parseExpression); parseExpected(SyntaxKind.CloseBracketToken); - return finishNode(node); + + return makeParseNode(createComputedPropertyName(expression), fullStart); } function parseContextualModifier(t: SyntaxKind): boolean { @@ -1919,10 +1961,7 @@ namespace ts { function parseEntityName(allowReservedWords: boolean, diagnosticMessage?: DiagnosticMessage): EntityName { let entity: EntityName = parseIdentifier(diagnosticMessage); while (parseOptional(SyntaxKind.DotToken)) { - const node: QualifiedName = createNode(SyntaxKind.QualifiedName, entity.pos); // !!! - node.left = entity; - node.right = parseRightSideOfDot(allowReservedWords); - entity = finishNode(node); + entity = makeParseNode(createQualifiedName(entity, parseRightSideOfDot(allowReservedWords)), entity.pos); } return entity; } @@ -1962,39 +2001,22 @@ namespace ts { } function parseTemplateExpression(): TemplateExpression { - const template = createNode(SyntaxKind.TemplateExpression); - - template.head = parseTemplateHead(); - Debug.assert(template.head.kind === SyntaxKind.TemplateHead, "Template head has wrong token kind"); - - const templateSpans = createNodeArray(); - - do { - templateSpans.push(parseTemplateSpan()); - } - while (lastOrUndefined(templateSpans).literal.kind === SyntaxKind.TemplateMiddle); - - templateSpans.end = getNodeEnd(); - template.templateSpans = templateSpans; - - return finishNode(template); + const fullStart = getNodePos(); + return makeParseNode( + createTemplateExpression( + parseTemplateHead(), + parseUntil(parseTemplateSpan, span => span.literal.kind !== SyntaxKind.TemplateMiddle) + ), fullStart); } function parseTemplateSpan(): TemplateSpan { - const span = createNode(SyntaxKind.TemplateSpan); - span.expression = allowInAnd(parseExpression); - - let literal: TemplateMiddle | TemplateTail; - if (token() === SyntaxKind.CloseBraceToken) { - reScanTemplateToken(); - literal = parseTemplateMiddleOrTemplateTail(); - } - else { - literal = parseExpectedToken(SyntaxKind.TemplateTail, /*reportAtCurrentPosition*/ false, Diagnostics._0_expected, tokenToString(SyntaxKind.CloseBraceToken)); - } - - span.literal = literal; - return finishNode(span); + const fullStart = getNodePos(); + return makeParseNode( + createTemplateSpan( + allowInAnd(parseExpression), + parseTemplateMiddleOrTemplateTail() + ), + fullStart); } function parseLiteralNode(internName?: boolean): LiteralExpression { @@ -2008,9 +2030,13 @@ namespace ts { } function parseTemplateMiddleOrTemplateTail(): TemplateMiddle | TemplateTail { - const fragment = parseLiteralLikeNode(token(), /*internName*/ false); - Debug.assert(fragment.kind === SyntaxKind.TemplateMiddle || fragment.kind === SyntaxKind.TemplateTail, "Template fragment has wrong token kind"); - return fragment; + if (token() === SyntaxKind.CloseBraceToken) { + reScanTemplateToken(); + const fragment = parseLiteralLikeNode(token(), /*internName*/ false); + Debug.assert(fragment.kind === SyntaxKind.TemplateMiddle || fragment.kind === SyntaxKind.TemplateTail, "Template fragment has wrong token kind"); + return fragment; + } + return parseExpectedToken(SyntaxKind.TemplateTail, /*reportAtCurrentPosition*/ false, Diagnostics._0_expected, tokenToString(SyntaxKind.CloseBraceToken)); } function parseLiteralLikeNode(kind: SyntaxKind, internName: boolean): LiteralLikeNode { @@ -2049,21 +2075,28 @@ namespace ts { // TYPES function parseTypeReference(): TypeReferenceNode { - const typeName = parseEntityName(/*allowReservedWords*/ false, Diagnostics.Type_expected); - const node = createNode(SyntaxKind.TypeReference, typeName.pos); - node.typeName = typeName; - if (!scanner.hasPrecedingLineBreak() && token() === SyntaxKind.LessThanToken) { - node.typeArguments = parseBracketedList(ParsingContext.TypeArguments, parseType, SyntaxKind.LessThanToken, SyntaxKind.GreaterThanToken); - } - return finishNode(node); + const fullStart = getNodePos(); + return makeParseNode( + createTypeReference( + parseEntityName(/*allowReservedWords*/ false, Diagnostics.Type_expected), + tryParseTypeArguments() + ), fullStart); + } + + function tryParseTypeArguments() { + return !scanner.hasPrecedingLineBreak() && token() === SyntaxKind.LessThanToken + ? parseBracketedList(ParsingContext.TypeArguments, parseType, SyntaxKind.LessThanToken, SyntaxKind.GreaterThanToken) + : undefined; } function parseThisTypePredicate(lhs: ThisTypeNode): TypePredicateNode { nextToken(); - const node = createNode(SyntaxKind.TypePredicate, lhs.pos) as TypePredicateNode; - node.parameterName = lhs; - node.type = parseType(); - return finishNode(node); + return makeParseNode( + createTypePredicate( + lhs, + parseType() + ), + lhs.pos); } function parseThisTypeNode(): ThisTypeNode { @@ -2073,36 +2106,43 @@ namespace ts { } function parseTypeQuery(): TypeQueryNode { - const node = createNode(SyntaxKind.TypeQuery); + const fullStart = getNodePos(); parseExpected(SyntaxKind.TypeOfKeyword); - node.exprName = parseEntityName(/*allowReservedWords*/ true); - return finishNode(node); + return makeParseNode( + createTypeQuery( + parseEntityName(/*allowReservedWords*/ true) + ), + fullStart); } function parseTypeParameter(): TypeParameterDeclaration { - const node = createNode(SyntaxKind.TypeParameter); - node.name = parseIdentifier(); + const fullStart = getNodePos(); + return makeParseNode( + createTypeParameter( + parseIdentifier(), + parseTypeParameterConstraint() + ), + fullStart); + } + + function parseTypeParameterConstraint() { if (parseOptional(SyntaxKind.ExtendsKeyword)) { // It's not uncommon for people to write improper constraints to a generic. If the // user writes a constraint that is an expression and not an actual type, then parse // it out as an expression (so we can recover well), but report that a type is needed // instead. if (isStartOfType() || !isStartOfExpression()) { - node.constraint = parseType(); - } - else { - // It was not a type, and it looked like an expression. Parse out an expression - // here so we recover well. Note: it is important that we call parseUnaryExpression - // and not parseExpression here. If the user has: - // - // - // - // We do *not* want to consume the > as we're consuming the expression for "". - node.expression = parseUnaryExpressionOrHigher(); + return parseType(); } + // It was not a type, and it looked like an expression. Parse out an expression + // here so we recover well. Note: it is important that we call parseUnaryExpression + // and not parseExpression here. If the user has: + // + // + // + // We do *not* want to consume the > as we're consuming the expression for "". + return parseUnaryExpressionOrHigher(); } - - return finishNode(node); } function parseTypeParameters(): NodeArray {