@@ -100,7 +100,57 @@ def _unasync_file(self, filepath):
100
100
def _unasync_tokens (self , tokens ):
101
101
# TODO __await__, ...?
102
102
used_space = None
103
+ context = None # Can be `None`, `"func_decl"`, `"func_name"`, `"arg_list"`, `"arg_list_end"`, `"return_type"`
104
+ brace_depth = 0
105
+ typing_ctx = False
106
+
103
107
for space , toknum , tokval in tokens :
108
+ # Update context state tracker
109
+ if context is None and toknum == std_tokenize .NAME and tokval == "def" :
110
+ context = "func_decl"
111
+ elif context == "func_decl" and toknum == std_tokenize .NAME :
112
+ context = "func_name"
113
+ elif context == "func_name" and toknum == std_tokenize .OP and tokval == "(" :
114
+ context = "arg_list"
115
+ elif context == "arg_list" :
116
+ if toknum == std_tokenize .OP and tokval in ("(" , "[" ):
117
+ brace_depth += 1
118
+ elif (
119
+ toknum == std_tokenize .OP
120
+ and tokval in (")" , "]" )
121
+ and brace_depth >= 1
122
+ ):
123
+ brace_depth -= 1
124
+ elif toknum == std_tokenize .OP and tokval == ")" :
125
+ context = "arg_list_end"
126
+ elif toknum == std_tokenize .OP and tokval == ":" and brace_depth < 1 :
127
+ typing_ctx = True
128
+ elif toknum == std_tokenize .OP and tokval == "," and brace_depth < 1 :
129
+ typing_ctx = False
130
+ elif (
131
+ context == "arg_list_end"
132
+ and toknum == std_tokenize .OP
133
+ and tokval == "->"
134
+ ):
135
+ context = "return_type"
136
+ typing_ctx = True
137
+ elif context == "return_type" :
138
+ if toknum == std_tokenize .OP and tokval in ("(" , "[" ):
139
+ brace_depth += 1
140
+ elif (
141
+ toknum == std_tokenize .OP
142
+ and tokval in (")" , "]" )
143
+ and brace_depth >= 1
144
+ ):
145
+ brace_depth -= 1
146
+ elif toknum == std_tokenize .OP and tokval == ":" :
147
+ context = None
148
+ typing_ctx = False
149
+ else : # Something unexpected happend - reset state
150
+ context = None
151
+ brace_depth = 0
152
+ typing_ctx = False
153
+
104
154
if tokval in ["async" , "await" ]:
105
155
# When removing async or await, we want to use the whitespace that
106
156
# was before async/await before the next token so that
@@ -111,8 +161,34 @@ def _unasync_tokens(self, tokens):
111
161
if toknum == std_tokenize .NAME :
112
162
tokval = self ._unasync_name (tokval )
113
163
elif toknum == std_tokenize .STRING :
114
- left_quote , name , right_quote = tokval [0 ], tokval [1 :- 1 ], tokval [- 1 ]
115
- tokval = left_quote + self ._unasync_name (name ) + right_quote
164
+ # Strings in typing context are forward-references and should be unasyncified
165
+ quote = ""
166
+ prefix = ""
167
+ while ord (tokval [0 ]) in range (ord ("a" ), ord ("z" ) + 1 ):
168
+ prefix += tokval [0 ]
169
+ tokval = tokval [1 :]
170
+
171
+ if tokval .startswith ('"""' ) and tokval .endswith ('"""' ):
172
+ quote = '"""' # Broken syntax highlighters workaround: """
173
+ elif tokval .startswith ("'''" ) and tokval .endswith ("'''" ):
174
+ quote = "'''" # Broken syntax highlighters wokraround: '''
175
+ elif tokval .startswith ('"' ) and tokval .endswith ('"' ):
176
+ quote = '"'
177
+ elif tokval .startswith ("'" ) and tokval .endswith (
178
+ "'"
179
+ ): # pragma: no branch
180
+ quote = "'"
181
+ assert (
182
+ len (quote ) > 0
183
+ ), "Quoting style of string {0!r} unknown" .format (tokval )
184
+ stringval = tokval [len (quote ) : - len (quote )]
185
+ if typing_ctx :
186
+ stringval = _untokenize (
187
+ self ._unasync_tokens (_tokenize (io .StringIO (stringval )))
188
+ )
189
+ else :
190
+ stringval = self ._unasync_name (stringval )
191
+ tokval = prefix + quote + stringval + quote
116
192
elif toknum == std_tokenize .COMMENT and tokval .startswith (
117
193
_TYPE_COMMENT_PREFIX
118
194
):
@@ -190,7 +266,7 @@ def _tokenize(f):
190
266
last_end = (1 , 0 )
191
267
for tok in _get_tokens (f ):
192
268
if last_end [0 ] < tok .start [0 ]:
193
- yield ("" , std_tokenize .STRING , " \\ \n " )
269
+ yield (" " , std_tokenize .NEWLINE , "\\ \n " )
194
270
last_end = (tok .start [0 ], 0 )
195
271
196
272
space = ""
0 commit comments