diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 4c3aa8990bd19..ad6a93d2a9623 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -2211,7 +2211,7 @@ module ts { function getDoubleQuotedStringTextOfLiteral(node: LiteralExpression): string { var result = escapeString(node.text); - result = replaceNonAsciiCharacters(result); + result = escapeNonAsciiCharacters(result); return '"' + result + '"'; } diff --git a/src/compiler/scanner.ts b/src/compiler/scanner.ts index e3479e27152ea..1f050a0ec5be8 100644 --- a/src/compiler/scanner.ts +++ b/src/compiler/scanner.ts @@ -609,20 +609,26 @@ module ts { return +(text.substring(start, pos)); } + /** + * Scans the given number of hexadecimal digits in the text, + * returning -1 if the given number is unavailable. + */ function scanExactNumberOfHexDigits(count: number): number { - return scanHexDigits(/*minCount*/ count, /*maxCount*/ count); + return scanHexDigits(/*minCount*/ count, /*scanAsManyAsPossible*/ false); } + /** + * Scans as many hexadecimal digits as are available in the text, + * returning -1 if the given number of digits was unavailable. + */ function scanMinimumNumberOfHexDigits(count: number): number { - return scanHexDigits(/*minCount*/ count, /*maxCount*/ undefined); + return scanHexDigits(/*minCount*/ count, /*scanAsManyAsPossible*/ true); } - function scanHexDigits(minCount: number, maxCount?: number): number { - var maxCountSpecified = maxCount !== undefined; - + function scanHexDigits(minCount: number, scanAsManyAsPossible: boolean): number { var digits = 0; var value = 0; - while (!maxCountSpecified || digits < maxCount) { + while (digits < minCount || scanAsManyAsPossible) { var ch = text.charCodeAt(pos); if (ch >= CharacterCodes._0 && ch <= CharacterCodes._9) { value = value * 16 + ch - CharacterCodes._0; @@ -777,22 +783,19 @@ module ts { case CharacterCodes.doubleQuote: return "\""; case CharacterCodes.u: + // '\u{DDDDDDDD}' if (pos < len && text.charCodeAt(pos) === CharacterCodes.openBrace) { hasExtendedUnicodeEscape = true; pos++; return scanExtendedUnicodeEscape(); } - // fall through + // '\uDDDD' + return scanHexadecimalEscape(/*numDigits*/ 4) + case CharacterCodes.x: - var escapedValue = scanExactNumberOfHexDigits(ch === CharacterCodes.x ? 2 : 4); - if (escapedValue >= 0) { - return String.fromCharCode(escapedValue); - } - else { - error(Diagnostics.Hexadecimal_digit_expected); - return "" - } + // '\xDD' + return scanHexadecimalEscape(/*numDigits*/ 2) // when encountering a LineContinuation (i.e. a backslash and a line terminator sequence), // the line terminator is interpreted to be "the empty code unit sequence". @@ -810,6 +813,18 @@ module ts { } } + function scanHexadecimalEscape(numDigits: number): string { + var escapedValue = scanExactNumberOfHexDigits(numDigits); + + if (escapedValue >= 0) { + return String.fromCharCode(escapedValue); + } + else { + error(Diagnostics.Hexadecimal_digit_expected); + return "" + } + } + function scanExtendedUnicodeEscape(): string { var escapedValue = scanMinimumNumberOfHexDigits(1); var isInvalidExtendedEscape = false; diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 100c753d5f733..30b5a4a23a4db 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -1130,7 +1130,7 @@ module ts { newEndN = Math.max(newEnd2, newEnd2 + (newEnd1 - oldEnd2)); } - return createTextChangeRange(createTextSpanFromBounds(oldStartN, oldEndN), /*newLength: */ newEndN - oldStartN); + return createTextChangeRange(createTextSpanFromBounds(oldStartN, oldEndN), /*newLength*/ newEndN - oldStartN); } // @internal @@ -1213,8 +1213,12 @@ module ts { } } - var backslashOrDoubleQuote = /[\"\\]/g; - var escapedCharsRegExp = /[\u0000-\u001f\t\v\f\b\r\n\u2028\u2029\u0085]/g; + // This consists of the first 19 unprintable ASCII characters, canonical escapes, lineSeparator, + // paragraphSeparator, and nextLine. The latter three are just desirable to suppress new lines in + // the language service. These characters should be escaped when printing, and if any characters are added, + // the map below must be updated. Note that this regexp *does not* include the 'delete' character. + // There is no reason for this other than that JSON.stringify does not handle it either. + var escapedCharsRegExp = /[\\\"\u0000-\u001f\t\v\f\b\r\n\u2028\u2029\u0085]/g; var escapedCharsMap: Map = { "\0": "\\0", "\t": "\\t", @@ -1231,13 +1235,11 @@ module ts { }; /** - * Based heavily on the abstract 'Quote'/ 'QuoteJSONString' operation from ECMA-262 (24.3.2.2), - * but augmented for a few select characters. + * Based heavily on the abstract 'Quote'/'QuoteJSONString' operation from ECMA-262 (24.3.2.2), + * but augmented for a few select characters (e.g. lineSeparator, paragraphSeparator, nextLine) * Note that this doesn't actually wrap the input in double quotes. */ export function escapeString(s: string): string { - // Prioritize '"' and '\' - s = backslashOrDoubleQuote.test(s) ? s.replace(backslashOrDoubleQuote, getReplacement) : s; s = escapedCharsRegExp.test(s) ? s.replace(escapedCharsRegExp, getReplacement) : s; return s; @@ -1254,7 +1256,7 @@ module ts { } var nonAsciiCharacters = /[^\u0000-\u007F]/g; - export function replaceNonAsciiCharacters(s: string): string { + export function escapeNonAsciiCharacters(s: string): string { // Replace non-ASCII characters with '\uNNNN' escapes if any exist. // Otherwise just return the original string. return nonAsciiCharacters.test(s) ?