1
1
from typing import Any
2
2
3
3
from .EscapeLeadingWhitespace import EscapeLeadingWhitespace
4
- from .Literals import Language , LiteralFormat , LiteralValue
4
+ from .Literals import Language , LiteralFormat , LiteralRepr , LiteralValue
5
5
from .Slice import Slice
6
6
7
7
@@ -78,21 +78,25 @@ def _get_arg(self):
78
78
79
79
def set_literal_and_get_newline_delta (self , literal_value : LiteralValue ) -> int :
80
80
encoded = literal_value .format .encode (
81
- literal_value .actual , self .__language , self .__escape_leading_whitespace
81
+ literal_value .actual ,
82
+ self .__language ,
83
+ self .__escape_leading_whitespace ,
82
84
)
83
- round_tripped = literal_value .format .parse (encoded , self .__language )
84
- if round_tripped != literal_value .actual :
85
- raise ValueError (
86
- f"There is an error in { literal_value .format .__class__ .__name__ } , "
87
- "the following value isn't round tripping.\n "
88
- f"Please report this error and the data below at "
89
- "https://github.com/diffplug/selfie/issues/new\n "
90
- f"```\n "
91
- f"ORIGINAL\n { literal_value .actual } \n "
92
- f"ROUNDTRIPPED\n { round_tripped } \n "
93
- f"ENCODED ORIGINAL\n { encoded } \n "
94
- f"```\n "
95
- )
85
+ if not isinstance (literal_value .format , LiteralRepr ):
86
+ # we don't roundtrip LiteralRepr because `eval` is dangerous
87
+ round_tripped = literal_value .format .parse (encoded , self .__language )
88
+ if round_tripped != literal_value .actual :
89
+ raise ValueError (
90
+ f"There is an error in { literal_value .format .__class__ .__name__ } , "
91
+ "the following value isn't round tripping.\n "
92
+ f"Please report this error and the data below at "
93
+ "https://github.com/diffplug/selfie/issues/new\n "
94
+ f"```\n "
95
+ f"ORIGINAL\n { literal_value .actual } \n "
96
+ f"ROUNDTRIPPED\n { round_tripped } \n "
97
+ f"ENCODED ORIGINAL\n { encoded } \n "
98
+ f"```\n "
99
+ )
96
100
existing_newlines = self .__function_call_plus_arg .count ("\n " )
97
101
new_newlines = encoded .count ("\n " )
98
102
self .__parent ._content_slice = Slice ( # noqa: SLF001
@@ -153,48 +157,98 @@ def parse_to_be_like(self, line_one_indexed: int) -> ToBeLiteral:
153
157
f"on line { line_one_indexed } "
154
158
)
155
159
156
- end_arg = - 1
157
- end_paren = 0
158
160
if self ._content_slice [arg_start ] == '"' :
159
- if self ._content_slice .subSequence (
160
- arg_start , len (self ._content_slice )
161
- ).starts_with (self .TRIPLE_QUOTE ):
162
- end_arg = self ._content_slice .indexOf (
163
- self .TRIPLE_QUOTE , arg_start + len (self .TRIPLE_QUOTE )
161
+ (end_paren , end_arg ) = self ._parse_string (
162
+ line_one_indexed , arg_start , dot_fun_open_paren
163
+ )
164
+ else :
165
+ (end_paren , end_arg ) = self ._parse_code (
166
+ line_one_indexed , arg_start , dot_fun_open_paren
167
+ )
168
+ return self .ToBeLiteral (
169
+ self ,
170
+ dot_fun_open_paren .replace ("_TODO" , "" ),
171
+ self ._content_slice .subSequence (dot_function_call , end_paren + 1 ),
172
+ self ._content_slice .subSequence (arg_start , end_arg ),
173
+ self .__language ,
174
+ self .__escape_leading_whitespace ,
175
+ )
176
+
177
+ def _parse_code (
178
+ self ,
179
+ line_one_indexed : int ,
180
+ arg_start : int ,
181
+ dot_fun_open_paren : str ,
182
+ ):
183
+ # Initialize variables
184
+ parenthesis_count = 1
185
+ string_delimiter = None
186
+
187
+ # Iterate through the characters starting from the given index
188
+ for i in range (arg_start , len (self ._content_slice )):
189
+ char = self ._content_slice [i ]
190
+
191
+ # Check if we are entering or leaving a string
192
+ if char in ["'" , '"' ] and self ._content_slice [i - 1 ] != "\\ " :
193
+ if not string_delimiter :
194
+ string_delimiter = char
195
+ elif char == string_delimiter :
196
+ string_delimiter = None
197
+
198
+ # Skip characters inside strings
199
+ if string_delimiter :
200
+ continue
201
+
202
+ # Count parentheses
203
+ if char == "(" :
204
+ parenthesis_count += 1
205
+ elif char == ")" :
206
+ parenthesis_count -= 1
207
+
208
+ # If all parentheses are closed, return the current index
209
+ if parenthesis_count == 0 :
210
+ end_paren = i
211
+ end_arg = i - 1
212
+ return (end_paren , end_arg )
213
+ # else ...
214
+ raise AssertionError (
215
+ f"Appears to be an unclosed function call `{ dot_fun_open_paren } ` "
216
+ f"starting at line { line_one_indexed } "
217
+ )
218
+
219
+ def _parse_string (
220
+ self ,
221
+ line_one_indexed : int ,
222
+ arg_start : int ,
223
+ dot_fun_open_paren : str ,
224
+ ):
225
+ if self ._content_slice .subSequence (
226
+ arg_start , len (self ._content_slice )
227
+ ).starts_with (self .TRIPLE_QUOTE ):
228
+ end_arg = self ._content_slice .indexOf (
229
+ self .TRIPLE_QUOTE , arg_start + len (self .TRIPLE_QUOTE )
230
+ )
231
+ if end_arg == - 1 :
232
+ raise AssertionError (
233
+ f"Appears to be an unclosed multiline string literal `{ self .TRIPLE_QUOTE } ` "
234
+ f"on line { line_one_indexed } "
164
235
)
165
- if end_arg == - 1 :
166
- raise AssertionError (
167
- f"Appears to be an unclosed multiline string literal `{ self .TRIPLE_QUOTE } ` "
168
- f"on line { line_one_indexed } "
169
- )
170
- else :
171
- end_arg += len (self .TRIPLE_QUOTE )
172
- end_paren = end_arg
173
236
else :
174
- end_arg = arg_start + 1
175
- while (
176
- self ._content_slice [end_arg ] != '"'
177
- or self ._content_slice [end_arg - 1 ] == "\\ "
178
- ):
179
- end_arg += 1
180
- if end_arg == self ._content_slice .__len__ ():
181
- raise AssertionError (
182
- f'Appears to be an unclosed string literal `"` '
183
- f"on line { line_one_indexed } "
184
- )
185
- end_arg += 1
237
+ end_arg += len (self .TRIPLE_QUOTE )
186
238
end_paren = end_arg
187
239
else :
188
- end_arg = arg_start
189
- while not self ._content_slice [end_arg ].isspace ():
190
- if self ._content_slice [end_arg ] == ")" :
191
- break
240
+ end_arg = arg_start + 1
241
+ while (
242
+ self ._content_slice [end_arg ] != '"'
243
+ or self ._content_slice [end_arg - 1 ] == "\\ "
244
+ ):
192
245
end_arg += 1
193
246
if end_arg == self ._content_slice .__len__ ():
194
247
raise AssertionError (
195
- f" Appears to be an unclosed numeric literal "
248
+ f' Appears to be an unclosed string literal `"` '
196
249
f"on line { line_one_indexed } "
197
250
)
251
+ end_arg += 1
198
252
end_paren = end_arg
199
253
while self ._content_slice [end_paren ] != ")" :
200
254
if not self ._content_slice [end_paren ].isspace ():
@@ -210,14 +264,7 @@ def parse_to_be_like(self, line_one_indexed: int) -> ToBeLiteral:
210
264
f"Appears to be an unclosed function call `{ dot_fun_open_paren } ` "
211
265
f"starting at line { line_one_indexed } "
212
266
)
213
- return self .ToBeLiteral (
214
- self ,
215
- dot_fun_open_paren .replace ("_TODO" , "" ),
216
- self ._content_slice .subSequence (dot_function_call , end_paren + 1 ),
217
- self ._content_slice .subSequence (arg_start , end_arg ),
218
- self .__language ,
219
- self .__escape_leading_whitespace ,
220
- )
267
+ return (end_paren , end_arg )
221
268
222
269
223
270
TO_BE_LIKES = [
0 commit comments