diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 5ee9fa4ab3..ea4c04e8b8 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -5302,7 +5302,7 @@ module ts { var templateExpression = tagExpression.template; var lastSpan = lastOrUndefined(templateExpression.templateSpans); Debug.assert(lastSpan !== undefined); // we should always have at least one span. - callIsIncomplete = lastSpan.literal.kind === SyntaxKind.Missing || isUnterminatedTemplateEnd(lastSpan.literal); + callIsIncomplete = lastSpan.literal.kind === SyntaxKind.Missing || !!lastSpan.literal.isUnterminated; } else { // If the template didn't end in a backtick, or its beginning occurred right prior to EOF, @@ -5310,7 +5310,7 @@ module ts { // so we consider the call to be incomplete. var templateLiteral = tagExpression.template; Debug.assert(templateLiteral.kind === SyntaxKind.NoSubstitutionTemplateLiteral); - callIsIncomplete = isUnterminatedTemplateEnd(templateLiteral); + callIsIncomplete = !!templateLiteral.isUnterminated; } } else { diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index f60fd051cb..c08ba6a7ab 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -809,26 +809,6 @@ module ts { return SyntaxKind.FirstTriviaToken <= token && token <= SyntaxKind.LastTriviaToken; } - export function isUnterminatedTemplateEnd(node: LiteralExpression) { - Debug.assert(isTemplateLiteralKind(node.kind)); - var sourceText = getSourceFileOfNode(node).text; - - // If we're not at the EOF, we know we must be terminated. - if (node.end !== sourceText.length) { - return false; - } - - // The literal can only be unterminated if it is a template tail or a no-sub template. - if (node.kind !== SyntaxKind.TemplateTail && node.kind !== SyntaxKind.NoSubstitutionTemplateLiteral) { - return false; - } - - // If we didn't end in a backtick, we must still be in the middle of a template literal, - // but if it's the *initial* backtick (whereby the token is 1 char long), then it's unclosed. - var width = node.end - getTokenPosOfNode(node); - return width < 2 || sourceText.charCodeAt(node.end - 1) !== CharacterCodes.backtick; - } - export function isModifier(token: SyntaxKind): boolean { switch (token) { case SyntaxKind.PublicKeyword: @@ -1545,6 +1525,10 @@ module ts { var text = scanner.getTokenValue(); node.text = internName ? internIdentifier(text) : text; + if (scanner.isUnterminated()) { + node.isUnterminated = true; + } + var tokenPos = scanner.getTokenPos(); nextToken(); finishNode(node); diff --git a/src/compiler/scanner.ts b/src/compiler/scanner.ts index 4858265932..f9d23166fe 100644 --- a/src/compiler/scanner.ts +++ b/src/compiler/scanner.ts @@ -22,6 +22,7 @@ module ts { hasPrecedingLineBreak(): boolean; isIdentifier(): boolean; isReservedWord(): boolean; + isUnterminated(): boolean; reScanGreaterToken(): SyntaxKind; reScanSlashToken(): SyntaxKind; reScanTemplateToken(): SyntaxKind; @@ -470,6 +471,7 @@ module ts { var token: SyntaxKind; var tokenValue: string; var precedingLineBreak: boolean; + var tokenUnterminated: boolean; function error(message: DiagnosticMessage): void { if (onError) { @@ -553,6 +555,7 @@ module ts { while (true) { if (pos >= len) { result += text.substring(start, pos); + tokenUnterminated = true; error(Diagnostics.Unterminated_string_literal); break; } @@ -570,6 +573,7 @@ module ts { } if (isLineBreak(ch)) { result += text.substring(start, pos); + tokenUnterminated = true; error(Diagnostics.Unterminated_string_literal); break; } @@ -593,6 +597,7 @@ module ts { while (true) { if (pos >= len) { contents += text.substring(start, pos); + tokenUnterminated = true; error(Diagnostics.Unterminated_template_literal); resultingToken = startedWithBacktick ? SyntaxKind.NoSubstitutionTemplateLiteral : SyntaxKind.TemplateTail; break; @@ -756,6 +761,7 @@ module ts { function scan(): SyntaxKind { startPos = pos; precedingLineBreak = false; + tokenUnterminated = false; while (true) { tokenPos = pos; if (pos >= len) { @@ -912,6 +918,7 @@ module ts { continue; } else { + tokenUnterminated = !commentClosed; return token = SyntaxKind.MultiLineCommentTrivia; } } @@ -1069,12 +1076,14 @@ module ts { // If we reach the end of a file, or hit a newline, then this is an unterminated // regex. Report error and return what we have so far. if (p >= len) { + tokenUnterminated = true; error(Diagnostics.Unterminated_regular_expression_literal) break; } var ch = text.charCodeAt(p); if (isLineBreak(ch)) { + tokenUnterminated = true; error(Diagnostics.Unterminated_regular_expression_literal) break; } @@ -1167,6 +1176,7 @@ module ts { hasPrecedingLineBreak: () => precedingLineBreak, isIdentifier: () => token === SyntaxKind.Identifier || token > SyntaxKind.LastReservedWord, isReservedWord: () => token >= SyntaxKind.FirstReservedWord && token <= SyntaxKind.LastReservedWord, + isUnterminated: () => tokenUnterminated, reScanGreaterToken, reScanSlashToken, reScanTemplateToken, diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 83937c24ed..3bfd8bc341 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -467,6 +467,7 @@ module ts { // For a NumericLiteral, the stored value is the toString() representation of the number. For example 1, 1.00, and 1e0 are all stored as just "1". export interface LiteralExpression extends Expression { text: string; + isUnterminated?: boolean; } export interface TemplateExpression extends Expression { diff --git a/src/services/services.ts b/src/services/services.ts index 67e8aba34d..8ff61b05a7 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -2497,10 +2497,11 @@ module ts { } function isInStringOrRegularExpressionOrTemplateLiteral(previousToken: Node): boolean { - if (previousToken.kind === SyntaxKind.StringLiteral || isTemplateLiteralKind(previousToken.kind)) { + if (previousToken.kind === SyntaxKind.StringLiteral + || previousToken.kind === SyntaxKind.RegularExpressionLiteral + || isTemplateLiteralKind(previousToken.kind)) { // The position has to be either: 1. entirely within the token text, or - // 2. at the end position, and the string literal is not terminated - + // 2. at the end position of an unterminated token. var start = previousToken.getStart(); var end = previousToken.getEnd(); @@ -2508,36 +2509,9 @@ module ts { return true; } else if (position === end) { - var width = end - start; - var text = previousToken.getSourceFile().text; - - // If the token is a single character, or its second-to-last charcter indicates an escape code, - // then we can immediately say that we are in the middle of an unclosed string. - if (width <= 1 || text.charCodeAt(end - 2) === CharacterCodes.backslash) { - return true; - } - - // Now check if the last character is a closing character for the token. - switch (previousToken.kind) { - case SyntaxKind.StringLiteral: - case SyntaxKind.NoSubstitutionTemplateLiteral: - return text.charCodeAt(start) !== text.charCodeAt(end - 1); - - case SyntaxKind.TemplateHead: - case SyntaxKind.TemplateMiddle: - return text.charCodeAt(end - 1) !== CharacterCodes.openBrace - || text.charCodeAt(end - 2) !== CharacterCodes.$; - - case SyntaxKind.TemplateTail: - return text.charCodeAt(end - 1) !== CharacterCodes.backtick; - } - - return false; + return !!(previousToken).isUnterminated; } } - else if (previousToken.kind === SyntaxKind.RegularExpressionLiteral) { - return previousToken.getStart() < position && position < previousToken.getEnd(); - } return false; } @@ -5579,7 +5553,7 @@ module ts { if (token === SyntaxKind.StringLiteral) { // Check to see if we finished up on a multiline string literal. var tokenText = scanner.getTokenText(); - if (tokenText.length > 0 && tokenText.charCodeAt(tokenText.length - 1) === CharacterCodes.backslash) { + if (scanner.isUnterminated()) { var quoteChar = tokenText.charCodeAt(0); result.finalLexState = quoteChar === CharacterCodes.doubleQuote ? EndOfLineState.InDoubleQuoteStringLiteral @@ -5588,10 +5562,7 @@ module ts { } else if (token === SyntaxKind.MultiLineCommentTrivia) { // Check to see if the multiline comment was unclosed. - var tokenText = scanner.getTokenText() - if (!(tokenText.length > 3 && // need to avoid catching '/*/' - tokenText.charCodeAt(tokenText.length - 2) === CharacterCodes.asterisk && - tokenText.charCodeAt(tokenText.length - 1) === CharacterCodes.slash)) { + if (scanner.isUnterminated()) { result.finalLexState = EndOfLineState.InMultiLineCommentTrivia; } } diff --git a/src/services/signatureHelp.ts b/src/services/signatureHelp.ts index aa2f5d32c5..e399cf25c7 100644 --- a/src/services/signatureHelp.ts +++ b/src/services/signatureHelp.ts @@ -295,8 +295,8 @@ module ts.SignatureHelp { var tagExpression = templateExpression.parent; Debug.assert(templateExpression.kind === SyntaxKind.TemplateExpression); - // If we're just after a template tail, don't show signature help. - if (node.kind === SyntaxKind.TemplateTail && !isInsideTemplateLiteral(node, position)) { + // If we're just after a template tail, don't show signature help. + if (node.kind === SyntaxKind.TemplateTail && !isInsideTemplateLiteral(node, position)) { return undefined; } diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 75bea36bf3..4a3301312f 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -322,7 +322,7 @@ module ts { } export function isInsideTemplateLiteral(node: LiteralExpression, position: number) { - return (node.getStart() < position && position < node.getEnd()) - || (isUnterminatedTemplateEnd(node) && position === node.getEnd()); + return isTemplateLiteralKind(node.kind) + && (node.getStart() < position && position < node.getEnd()) || (!!node.isUnterminated && position === node.getEnd()); } } \ No newline at end of file