diff --git a/src/services/services.ts b/src/services/services.ts index 8ff61b05a7a..68177066b86 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -5549,15 +5549,24 @@ 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 (scanner.isUnterminated()) { - var quoteChar = tokenText.charCodeAt(0); - result.finalLexState = quoteChar === CharacterCodes.doubleQuote - ? EndOfLineState.InDoubleQuoteStringLiteral - : EndOfLineState.InSingleQuoteStringLiteral; + 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) { diff --git a/tests/cases/fourslash/completionListInStringLiterals1.ts b/tests/cases/fourslash/completionListInStringLiterals1.ts new file mode 100644 index 00000000000..e394e8dfe7c --- /dev/null +++ b/tests/cases/fourslash/completionListInStringLiterals1.ts @@ -0,0 +1,10 @@ +/// + +////"/*1*/ /*2*/\/*3*/ +//// /*4*/ \\/*5*/ + +test.markers().forEach(marker => { + goTo.position(marker.position); + + verify.completionListIsEmpty() +}); \ No newline at end of file diff --git a/tests/cases/fourslash/completionListInStringLiterals2.ts b/tests/cases/fourslash/completionListInStringLiterals2.ts new file mode 100644 index 00000000000..10cb05a4f91 --- /dev/null +++ b/tests/cases/fourslash/completionListInStringLiterals2.ts @@ -0,0 +1,11 @@ +/// + +////"/*1*/ /*2*/\/*3*/ +//// /*4*/ \\\/*5*/ +//// /*6*/ + +test.markers().forEach(marker => { + goTo.position(marker.position); + + verify.completionListIsEmpty() +}); \ No newline at end of file diff --git a/tests/cases/fourslash/signatureHelpTaggedTemplatesIncomplete7.ts b/tests/cases/fourslash/signatureHelpTaggedTemplatesIncomplete7.ts new file mode 100644 index 00000000000..ae322acf0b8 --- /dev/null +++ b/tests/cases/fourslash/signatureHelpTaggedTemplatesIncomplete7.ts @@ -0,0 +1,21 @@ +/// + +//// function f(templateStrings, x, y, z) { return 10; } +//// function g(templateStrings, x, y, z) { return ""; } +//// +//// f ` ${ 123 } /*1*/${ } /*2*/\/*3*/ +//// /*4*/\\/*5*/ +//// /*6*/\\\/*7*/ +//// /*8*/ + +test.markers().forEach(m => { + goTo.position(m.position); + + verify.signatureHelpCountIs(1); + verify.signatureHelpArgumentCountIs(1); + + verify.currentSignatureParameterCountIs(4); + verify.currentSignatureHelpIs('f(templateStrings: any, x: any, y: any, z: any): number'); + verify.currentParameterHelpArgumentNameIs("templateStrings"); + verify.currentParameterSpanIs("templateStrings: any"); +}); \ No newline at end of file diff --git a/tests/cases/fourslash/signatureHelpTaggedTemplatesIncomplete8.ts b/tests/cases/fourslash/signatureHelpTaggedTemplatesIncomplete8.ts new file mode 100644 index 00000000000..86455a65ccd --- /dev/null +++ b/tests/cases/fourslash/signatureHelpTaggedTemplatesIncomplete8.ts @@ -0,0 +1,18 @@ +/// + +//// function f(templateStrings, x, y, z) { return 10; } +//// function g(templateStrings, x, y, z) { return ""; } +//// +//// f `/*1*/\/*2*/`/*3*/ /*4*/ + +test.markers().forEach(m => { + goTo.position(m.position); + + verify.signatureHelpCountIs(1); + verify.signatureHelpArgumentCountIs(1); + + verify.currentSignatureParameterCountIs(4); + verify.currentSignatureHelpIs('f(templateStrings: any, x: any, y: any, z: any): number'); + verify.currentParameterHelpArgumentNameIs("templateStrings"); + verify.currentParameterSpanIs("templateStrings: any"); +}); \ No newline at end of file diff --git a/tests/cases/fourslash/signatureHelpTaggedTemplatesIncomplete9.ts b/tests/cases/fourslash/signatureHelpTaggedTemplatesIncomplete9.ts new file mode 100644 index 00000000000..de42a33b397 --- /dev/null +++ b/tests/cases/fourslash/signatureHelpTaggedTemplatesIncomplete9.ts @@ -0,0 +1,18 @@ +/// + +//// function f(templateStrings, x, y, z) { return 10; } +//// function g(templateStrings, x, y, z) { return ""; } +//// +//// f `/*1*/ \\\/*2*/`/*3*/ /*4*/ + +test.markers().forEach(m => { + goTo.position(m.position); + + verify.signatureHelpCountIs(1); + verify.signatureHelpArgumentCountIs(1); + + verify.currentSignatureParameterCountIs(4); + verify.currentSignatureHelpIs('f(templateStrings: any, x: any, y: any, z: any): number'); + verify.currentParameterHelpArgumentNameIs("templateStrings"); + verify.currentParameterSpanIs("templateStrings: any"); +}); \ No newline at end of file diff --git a/tests/cases/unittests/services/colorization.ts b/tests/cases/unittests/services/colorization.ts index 3031cefb56c..b29f119f944 100644 --- a/tests/cases/unittests/services/colorization.ts +++ b/tests/cases/unittests/services/colorization.ts @@ -130,20 +130,76 @@ describe('Colorization', function () { operator(",")); }); - it("correctly classifies an unterminated multi-line string", function () { + it("correctly classifies a multi-line string with one backslash", function () { test("'line1\\", ts.EndOfLineState.Start, stringLiteral("'line1\\"), finalEndOfLineState(ts.EndOfLineState.InSingleQuoteStringLiteral)); }); - it("correctly classifies the second line of an unterminated multi-line string", function () { + it("correctly classifies a multi-line string with three backslashes", function () { + test("'line1\\\\\\", + ts.EndOfLineState.Start, + stringLiteral("'line1\\\\\\"), + finalEndOfLineState(ts.EndOfLineState.InSingleQuoteStringLiteral)); + }); + + it("correctly classifies an unterminated single-line string with no backslashes", function () { + test("'line1", + ts.EndOfLineState.Start, + stringLiteral("'line1"), + finalEndOfLineState(ts.EndOfLineState.Start)); + }); + + it("correctly classifies an unterminated single-line string with two backslashes", function () { + test("'line1\\\\", + ts.EndOfLineState.Start, + stringLiteral("'line1\\\\"), + finalEndOfLineState(ts.EndOfLineState.Start)); + }); + + it("correctly classifies an unterminated single-line string with four backslashes", function () { + test("'line1\\\\\\\\", + ts.EndOfLineState.Start, + stringLiteral("'line1\\\\\\\\"), + finalEndOfLineState(ts.EndOfLineState.Start)); + }); + + it("correctly classifies the continuing line of a multi-line string ending in one backslash", function () { test("\\", ts.EndOfLineState.InDoubleQuoteStringLiteral, stringLiteral("\\"), finalEndOfLineState(ts.EndOfLineState.InDoubleQuoteStringLiteral)); }); + it("correctly classifies the continuing line of a multi-line string ending in three backslashes", function () { + test("\\", + ts.EndOfLineState.InDoubleQuoteStringLiteral, + stringLiteral("\\"), + finalEndOfLineState(ts.EndOfLineState.InDoubleQuoteStringLiteral)); + }); + + it("correctly classifies the last line of an unterminated multi-line string ending in no backslashes", function () { + test(" ", + ts.EndOfLineState.InDoubleQuoteStringLiteral, + stringLiteral(" "), + finalEndOfLineState(ts.EndOfLineState.Start)); + }); + + it("correctly classifies the last line of an unterminated multi-line string ending in two backslashes", function () { + test("\\\\", + ts.EndOfLineState.InDoubleQuoteStringLiteral, + stringLiteral("\\\\"), + finalEndOfLineState(ts.EndOfLineState.Start)); + }); + + it("correctly classifies the last line of an unterminated multi-line string ending in four backslashes", function () { + test("\\\\\\\\", + ts.EndOfLineState.InDoubleQuoteStringLiteral, + stringLiteral("\\\\\\\\"), + finalEndOfLineState(ts.EndOfLineState.Start)); + }); + it("correctly classifies the last line of a multi-line string", function () { test("'", ts.EndOfLineState.InSingleQuoteStringLiteral,