From 018cc73875ce265ddb1355b4a1aeb3cbaf6b65d9 Mon Sep 17 00:00:00 2001 From: blouirts123 <201396345+blouirts123@users.noreply.github.com> Date: Fri, 16 Oct 2026 06:10:53 +0530 Subject: [PATCH] Decode escaped backslashes in raw string literals --- CHANGELOG.rst | 2 +- jmespath/lexer.py | 14 ++++++++++++-- tests/compliance/literal.json | 4 ++-- tests/test_raw_string_escapes.py | 23 +++++++++++++++++++++++ 4 files changed, 38 insertions(+), 5 deletions(-) create mode 100644 tests/test_raw_string_escapes.py diff --git a/CHANGELOG.rst b/CHANGELOG.rst index dc673df4..e84bbe2a 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,7 +1,7 @@ Next Release (TBD) ================== -* No changes yet. +* Decode escaped backslashes in raw string literals according to the language specification. 1.1.0 diff --git a/jmespath/lexer.py b/jmespath/lexer.py index 8db05e37..13196237 100644 --- a/jmespath/lexer.py +++ b/jmespath/lexer.py @@ -191,9 +191,19 @@ def _consume_quoted_identifier(self): def _consume_raw_string_literal(self): start = self._position - lexeme = self._consume_until("'").replace("\\'", "'") + lexeme = self._consume_until("'") + raw_string = [] + i = 0 + while i < len(lexeme): + char = lexeme[i] + if (char == '\\' and i + 1 < len(lexeme) and + lexeme[i + 1] in ("\\", "'")): + i += 1 + char = lexeme[i] + raw_string.append(char) + i += 1 token_len = self._position - start - return {'type': 'literal', 'value': lexeme, + return {'type': 'literal', 'value': ''.join(raw_string), 'start': start, 'end': token_len} def _match_or_else(self, expected, match_type, else_type): diff --git a/tests/compliance/literal.json b/tests/compliance/literal.json index b5ddbeda..b50665aa 100644 --- a/tests/compliance/literal.json +++ b/tests/compliance/literal.json @@ -191,9 +191,9 @@ "result": "\\z" }, { - "comment": "Backslash not followed by single quote is treated as any other character", + "comment": "Backslash escapes a backslash in a raw string", "expression": "'\\\\'", - "result": "\\\\" + "result": "\\" } ] } diff --git a/tests/test_raw_string_escapes.py b/tests/test_raw_string_escapes.py new file mode 100644 index 00000000..becf7d58 --- /dev/null +++ b/tests/test_raw_string_escapes.py @@ -0,0 +1,23 @@ +from tests import unittest + +import jmespath +from jmespath.lexer import Lexer + + +class TestRawStringEscapes(unittest.TestCase): + + def test_escaped_backslash_is_unescaped(self): + tokens = list(Lexer().tokenize(r"'\\'")) + self.assertEqual(tokens[0]['value'], '\\') + self.assertEqual(jmespath.search(r"'\\'", {}), '\\') + + def test_backslash_before_other_character_is_preserved(self): + self.assertEqual(jmespath.search(r"'\z'", {}), r'\z') + + def test_escaped_quote_is_unescaped(self): + self.assertEqual(jmespath.search(r"'foo\'bar'", {}), "foo'bar") + + def test_windows_path_filter_with_trailing_backslash(self): + rows = [{'FullName': r'C:\x\msvc\file.txt'}] + expression = r"[? !contains(FullName, '\msvc\\')]" + self.assertEqual(jmespath.search(expression, rows), [])