mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-05-19 10:41:56 -05:00
Merge pull request #1275 from Microsoft/unterminator
Keep track of unterminated literal expressions.
This commit is contained in:
@@ -5335,7 +5335,7 @@ module ts {
|
||||
var templateExpression = <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,
|
||||
@@ -5343,7 +5343,7 @@ module ts {
|
||||
// so we consider the call to be incomplete.
|
||||
var templateLiteral = <LiteralExpression>tagExpression.template;
|
||||
Debug.assert(templateLiteral.kind === SyntaxKind.NoSubstitutionTemplateLiteral);
|
||||
callIsIncomplete = isUnterminatedTemplateEnd(templateLiteral);
|
||||
callIsIncomplete = !!templateLiteral.isUnterminated;
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -855,25 +855,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.
|
||||
// If we did, make sure that it's not the *initial* backtick.
|
||||
return sourceText.charCodeAt(node.end - 1) !== CharacterCodes.backtick || node.text.length === 0;
|
||||
}
|
||||
|
||||
export function isModifier(token: SyntaxKind): boolean {
|
||||
switch (token) {
|
||||
case SyntaxKind.PublicKeyword:
|
||||
@@ -1691,6 +1672,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);
|
||||
|
||||
@@ -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 tokenIsUnterminated: boolean;
|
||||
|
||||
function error(message: DiagnosticMessage): void {
|
||||
if (onError) {
|
||||
@@ -553,6 +555,7 @@ module ts {
|
||||
while (true) {
|
||||
if (pos >= len) {
|
||||
result += text.substring(start, pos);
|
||||
tokenIsUnterminated = true;
|
||||
error(Diagnostics.Unterminated_string_literal);
|
||||
break;
|
||||
}
|
||||
@@ -570,6 +573,7 @@ module ts {
|
||||
}
|
||||
if (isLineBreak(ch)) {
|
||||
result += text.substring(start, pos);
|
||||
tokenIsUnterminated = true;
|
||||
error(Diagnostics.Unterminated_string_literal);
|
||||
break;
|
||||
}
|
||||
@@ -593,6 +597,7 @@ module ts {
|
||||
while (true) {
|
||||
if (pos >= len) {
|
||||
contents += text.substring(start, pos);
|
||||
tokenIsUnterminated = true;
|
||||
error(Diagnostics.Unterminated_template_literal);
|
||||
resultingToken = startedWithBacktick ? SyntaxKind.NoSubstitutionTemplateLiteral : SyntaxKind.TemplateTail;
|
||||
break;
|
||||
@@ -780,6 +785,7 @@ module ts {
|
||||
function scan(): SyntaxKind {
|
||||
startPos = pos;
|
||||
precedingLineBreak = false;
|
||||
tokenIsUnterminated = false;
|
||||
while (true) {
|
||||
tokenPos = pos;
|
||||
if (pos >= len) {
|
||||
@@ -936,6 +942,7 @@ module ts {
|
||||
continue;
|
||||
}
|
||||
else {
|
||||
tokenIsUnterminated = !commentClosed;
|
||||
return token = SyntaxKind.MultiLineCommentTrivia;
|
||||
}
|
||||
}
|
||||
@@ -1113,12 +1120,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) {
|
||||
tokenIsUnterminated = true;
|
||||
error(Diagnostics.Unterminated_regular_expression_literal)
|
||||
break;
|
||||
}
|
||||
|
||||
var ch = text.charCodeAt(p);
|
||||
if (isLineBreak(ch)) {
|
||||
tokenIsUnterminated = true;
|
||||
error(Diagnostics.Unterminated_regular_expression_literal)
|
||||
break;
|
||||
}
|
||||
@@ -1211,6 +1220,7 @@ module ts {
|
||||
hasPrecedingLineBreak: () => precedingLineBreak,
|
||||
isIdentifier: () => token === SyntaxKind.Identifier || token > SyntaxKind.LastReservedWord,
|
||||
isReservedWord: () => token >= SyntaxKind.FirstReservedWord && token <= SyntaxKind.LastReservedWord,
|
||||
isUnterminated: () => tokenIsUnterminated,
|
||||
reScanGreaterToken,
|
||||
reScanSlashToken,
|
||||
reScanTemplateToken,
|
||||
|
||||
@@ -527,6 +527,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 PrimaryExpression {
|
||||
text: string;
|
||||
isUnterminated?: boolean;
|
||||
}
|
||||
|
||||
export interface TemplateExpression extends PrimaryExpression {
|
||||
|
||||
@@ -2501,10 +2501,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();
|
||||
|
||||
@@ -2512,36 +2513,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 !!(<LiteralExpression>previousToken).isUnterminated;
|
||||
}
|
||||
}
|
||||
else if (previousToken.kind === SyntaxKind.RegularExpressionLiteral) {
|
||||
return previousToken.getStart() < position && position < previousToken.getEnd();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -5665,23 +5639,29 @@ module ts {
|
||||
addResult(end - start, classFromKind(token));
|
||||
|
||||
if (end >= text.length) {
|
||||
// We're at the end.
|
||||
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) {
|
||||
var quoteChar = tokenText.charCodeAt(0);
|
||||
result.finalLexState = quoteChar === CharacterCodes.doubleQuote
|
||||
? EndOfLineState.InDoubleQuoteStringLiteral
|
||||
: EndOfLineState.InSingleQuoteStringLiteral;
|
||||
if (scanner.isUnterminated()) {
|
||||
var lastCharIndex = tokenText.length - 1;
|
||||
|
||||
var numBackslashes = 0;
|
||||
while (tokenText.charCodeAt(lastCharIndex - numBackslashes) === CharacterCodes.backslash) {
|
||||
numBackslashes++;
|
||||
}
|
||||
|
||||
// If we have an odd number of backslashes, then the multiline string is unclosed
|
||||
if (numBackslashes & 1) {
|
||||
var quoteChar = tokenText.charCodeAt(0);
|
||||
result.finalLexState = quoteChar === CharacterCodes.doubleQuote
|
||||
? EndOfLineState.InDoubleQuoteStringLiteral
|
||||
: EndOfLineState.InSingleQuoteStringLiteral;
|
||||
}
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -296,7 +296,7 @@ module ts.SignatureHelp {
|
||||
Debug.assert(templateExpression.kind === SyntaxKind.TemplateExpression);
|
||||
|
||||
// If we're just after a template tail, don't show signature help.
|
||||
if (node.kind === SyntaxKind.TemplateTail && position >= node.getEnd() && !isUnterminatedTemplateEnd(<LiteralExpression>node)) {
|
||||
if (node.kind === SyntaxKind.TemplateTail && position >= node.getEnd() && !(<LiteralExpression>node).isUnterminated) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user