diff --git a/packages/pyright-internal/src/parser/tokenizer.ts b/packages/pyright-internal/src/parser/tokenizer.ts index b09f36f2a..e8775c6e4 100644 --- a/packages/pyright-internal/src/parser/tokenizer.ts +++ b/packages/pyright-internal/src/parser/tokenizer.ts @@ -1564,17 +1564,17 @@ export class Tokenizer { ) { isInNamedUnicodeEscape = true; } else { - // If this is an f-string, the only escape that is allowed is for - // a single or double quote symbol. - if ( - !isFString || + // If this is an f-string, the only escapes that are allowed is for + // a single or double quote symbol or a newline/carriage return. + const isEscapedQuote = this._cs.getCurrentChar() === Char.SingleQuote || - this._cs.getCurrentChar() === Char.DoubleQuote - ) { - if ( - this._cs.getCurrentChar() === Char.CarriageReturn || - this._cs.getCurrentChar() === Char.LineFeed - ) { + this._cs.getCurrentChar() === Char.DoubleQuote; + const isEscapedNewLine = + this._cs.getCurrentChar() === Char.CarriageReturn || + this._cs.getCurrentChar() === Char.LineFeed; + + if (!isFString || isEscapedQuote || isEscapedNewLine) { + if (isEscapedNewLine) { if ( this._cs.getCurrentChar() === Char.CarriageReturn && this._cs.nextChar === Char.LineFeed diff --git a/packages/pyright-internal/src/tests/samples/fstring1.py b/packages/pyright-internal/src/tests/samples/fstring1.py index 275b632ec..96065e22a 100644 --- a/packages/pyright-internal/src/tests/samples/fstring1.py +++ b/packages/pyright-internal/src/tests/samples/fstring1.py @@ -140,3 +140,9 @@ u1 = f"'{{\"{0}\": {0}}}'" def func1(x): f"x:{yield (lambda i: x * i)}" + +v1 = f"x \ +y" + +v2 = f'x \ +y' diff --git a/packages/pyright-internal/src/tests/tokenizer.test.ts b/packages/pyright-internal/src/tests/tokenizer.test.ts index ec4e5a58a..17208894f 100644 --- a/packages/pyright-internal/src/tests/tokenizer.test.ts +++ b/packages/pyright-internal/src/tests/tokenizer.test.ts @@ -652,6 +652,16 @@ test('Strings: f-string with single right brace', () => { assert.equal(fStringEndToken.length, 1); }); +test('Strings: f-string with new line escape', () => { + const t = new Tokenizer(); + const results = t.tokenize(`f'x \\\ny'`); + assert.equal(results.tokens.count, 3 + _implicitTokenCount); + + assert.equal(results.tokens.getItemAt(0).type, TokenType.FStringStart); + assert.equal(results.tokens.getItemAt(1).type, TokenType.FStringMiddle); + assert.equal(results.tokens.getItemAt(2).type, TokenType.FStringEnd); +}); + test('Strings: f-string with escape in expression', () => { const t = new Tokenizer(); const results = t.tokenize(`f'hello { "\\t" }'`);