1+ # -*- encoding: utf8 -*-
12"""Top-level package for unasync."""
23
34from __future__ import print_function
45
56import collections
67import errno
8+ import io
79import os
810import sys
911import tokenize as std_tokenize
3436 "StopAsyncIteration" : "StopIteration" ,
3537}
3638
39+ _TYPE_COMMENT_PREFIX = "# type: "
40+
41+
42+ if sys .version_info [0 ] == 2 : # PY2
43+
44+ def isidentifier (s ):
45+ return all ([c .isalnum () or c == "_" for c in s ])
46+
47+ StringIO = io .BytesIO
48+ else : # PY3
49+
50+ def isidentifier (s ):
51+ return s .isidentifier ()
52+
53+ StringIO = io .StringIO
54+
3755
3856class Rule :
3957 """A single set of rules for 'unasync'ing file(s)"""
@@ -95,6 +113,31 @@ def _unasync_tokens(self, tokens):
95113 elif toknum == std_tokenize .STRING :
96114 left_quote , name , right_quote = tokval [0 ], tokval [1 :- 1 ], tokval [- 1 ]
97115 tokval = left_quote + self ._unasync_name (name ) + right_quote
116+ elif toknum == std_tokenize .COMMENT and tokval .startswith (
117+ _TYPE_COMMENT_PREFIX
118+ ):
119+ type_decl , suffix = tokval [len (_TYPE_COMMENT_PREFIX ) :], ""
120+ if "#" in type_decl :
121+ type_decl , suffix = type_decl .split ("#" , 1 )
122+ suffix = "#" + suffix
123+ type_decl_stripped = type_decl .strip ()
124+
125+ # Do not process `type: ignore` or `type: ignore[…]` as these aren't actual identifiers
126+ is_type_ignore = type_decl_stripped == "ignore"
127+ is_type_ignore |= type_decl_stripped .startswith (
128+ "ignore"
129+ ) and not isidentifier (type_decl_stripped [0 :7 ])
130+ if not is_type_ignore :
131+ # Preserve trailing whitespace since the tokenizer won't
132+ trailing_space_len = len (type_decl ) - len (type_decl .rstrip ())
133+ if trailing_space_len > 0 :
134+ suffix = type_decl [- trailing_space_len :] + suffix
135+ type_decl = type_decl [:- trailing_space_len ]
136+ type_decl = _untokenize (
137+ self ._unasync_tokens (_tokenize (StringIO (type_decl )))
138+ )
139+
140+ tokval = _TYPE_COMMENT_PREFIX + type_decl + suffix
98141 if used_space is None :
99142 used_space = space
100143 yield (used_space , tokval )
@@ -133,7 +176,11 @@ def _get_tokens(f):
133176 type_ , string , start , end , line = tok
134177 yield Token (type_ , string , start , end , line )
135178 else : # PY3
136- for tok in std_tokenize .tokenize (f .readline ):
179+ if isinstance (f , io .TextIOBase ):
180+ gen = std_tokenize .generate_tokens (f .readline )
181+ else :
182+ gen = std_tokenize .tokenize (f .readline )
183+ for tok in gen :
137184 if tok .type == std_tokenize .ENCODING :
138185 continue
139186 yield tok
@@ -143,7 +190,10 @@ def _tokenize(f):
143190 last_end = (1 , 0 )
144191 for tok in _get_tokens (f ):
145192 if last_end [0 ] < tok .start [0 ]:
146- yield ("" , std_tokenize .STRING , " \\ \n " )
193+ # Somehow Python 3.5 and below produce the ENDMARKER in a way that
194+ # causes superfluous continuation lines to be generated
195+ if tok .type != std_tokenize .ENDMARKER :
196+ yield ("" , std_tokenize .STRING , " \\ \n " )
147197 last_end = (tok .start [0 ], 0 )
148198
149199 space = ""
0 commit comments