Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# This file is part of the lib3to6 project
2# https://github.com/mbarkhau/lib3to6
3#
4# Copyright (c) 2019-2021 Manuel Barkhau (mbarkhau@gmail.com) - MIT License
5# SPDX-License-Identifier: MIT
7import ast
8import typing as typ
9import builtins
11PackageName = str
12PackageDirectory = str
13PackageDir = typ.Dict[PackageName, PackageDirectory]
15InstallRequires = typ.Optional[typ.Set[str]]
18ConstantNodeTypes: typ.Tuple[typ.Type[ast.Constant], ...] = (ast.Constant,)
20# Deprecated since version 3.8: Class ast.Constant is now used for all
21# constants. Old classes ast.Num, ast.Str, ast.Bytes, ast.NameConstant and
22# ast.Ellipsis are still available, but they will be removed in future Python
23# releases.
25if hasattr(ast, 'Num'):
26 ConstantNodeTypes += (ast.Num, ast.Str, ast.Bytes, ast.NameConstant, ast.Ellipsis)
29LeafNodeTypes = ConstantNodeTypes + (
30 ast.Name,
31 ast.cmpop,
32 ast.boolop,
33 ast.operator,
34 ast.unaryop,
35 ast.expr_context,
36)
39ContainerNodes = (ast.List, ast.Set, ast.Tuple)
42class BuildConfig(typ.NamedTuple):
44 target_version : str # e.g. "2.7"
45 cache_enabled : bool
46 default_mode : str
47 fixers : str
48 checkers : str
49 install_requires: InstallRequires
52class BuildContext(typ.NamedTuple):
54 cfg : BuildConfig
55 filepath: str
58def init_build_context(
59 target_version : str = "2.7",
60 cache_enabled : bool = True,
61 default_mode : str = 'enabled',
62 fixers : str = "",
63 checkers : str = "",
64 install_requires: InstallRequires = None,
65 filepath : str = "<filepath>",
66) -> BuildContext:
67 cfg = BuildConfig(
68 target_version=target_version,
69 cache_enabled=cache_enabled,
70 default_mode=default_mode,
71 fixers=fixers,
72 checkers=checkers,
73 install_requires=install_requires,
74 )
75 return BuildContext(cfg=cfg, filepath=filepath)
78# Additional items:
79# 'filepath': "/path/to/inputfile.py"
82class InvalidPackage(Exception):
83 pass
86def get_node_lineno(
87 node: typ.Optional[ast.AST] = None, parent: typ.Optional[ast.AST] = None
88) -> int:
89 if isinstance(node, (ast.stmt, ast.expr)):
90 return node.lineno
91 elif isinstance(parent, (ast.stmt, ast.expr)):
92 return parent.lineno
93 else:
94 return -1
97class CheckError(Exception):
99 lineno: int
101 def __init__(self, msg: str, node: ast.AST = None, parent: ast.AST = None) -> None:
102 self.lineno = get_node_lineno(node, parent)
103 super().__init__(msg)
106class FixerError(Exception):
108 msg : str
109 node : ast.AST
110 parent : typ.Optional[ast.AST]
111 filepath: typ.Optional[str]
113 def __init__(
114 self,
115 msg : str,
116 node : ast.AST,
117 parent : typ.Optional[ast.AST] = None,
118 filepath: typ.Optional[str ] = None,
119 ) -> None:
120 self.msg = msg
121 self.node = node
122 self.parent = parent
123 self.filepath = filepath
124 super().__init__(msg)
126 def __str__(self) -> str:
127 msg = self.msg
128 if self.filepath:
129 lineno = get_node_lineno(self.node, self.parent)
130 msg += f" File: {self.filepath}@{lineno}"
131 return msg
134class VersionInfo:
135 """Compatibility info for Fixer and Checker classes.
137 Used as a class attribute (not an instance attribute) of a fixer or checker.
138 The compatability info relates to the fixer/checker rather than the feature
139 it deals with. The question that is being ansered is: "should this
140 fixer/checker be exectued for this (src_version, tgt_version) pair"
141 """
143 # since/until is inclusive
144 apply_since: typ.List[int]
145 apply_until: typ.Optional[typ.List[int]]
146 works_since: typ.List[int]
147 works_until: typ.Optional[typ.List[int]]
149 def __init__(
150 self,
151 apply_since: str = "1.0",
152 apply_until: typ.Optional[str] = None,
153 works_since: typ.Optional[str] = None,
154 works_until: typ.Optional[str] = None,
155 ) -> None:
157 self.apply_since = [int(part) for part in apply_since.split(".")]
158 if apply_until is None:
159 self.apply_until = None
160 else:
161 self.apply_until = [int(part) for part in apply_until.split(".")]
163 if works_since is None:
164 # Implicitly, if it's applied since a version, it
165 # also works since then.
166 self.works_since = self.apply_since
167 else:
168 self.works_since = [int(part) for part in works_since.split(".")]
170 if works_until is None:
171 self.works_until = None
172 else:
173 self.works_until = [int(part) for part in works_until.split(".")]
175 def is_required_for(self, version: str) -> bool:
176 version_num = [int(part) for part in version.split(".")]
177 apply_until = self.apply_until
178 if apply_until and apply_until < version_num:
179 return False
180 else:
181 return self.apply_since <= version_num
183 def is_compatible_with(self, version: str) -> bool:
184 version_num = [int(part) for part in version.split(".")]
185 works_since = self.works_since
186 works_until = self.works_until
187 if works_since and version_num < works_since:
188 return False
189 elif works_until and works_until < version_num:
190 return False
191 else:
192 return True
194 def is_applicable_to(self, source_version: str, target_version: str) -> bool:
195 return self.is_required_for(target_version) and self.is_compatible_with(source_version)
198# NOTE (mb 2018-06-29): None of the fixers use asname. If a
199# module already has an import using asname, it won't be
200# detected as already imported, and another import (without the
201# asname) will be added to the module.
202class ImportDecl(typ.NamedTuple):
203 module_name : str
204 import_name : typ.Optional[str]
205 py2_module_name: typ.Optional[str]
208# NOTE (mb 2018-06-24): This also includes builtins from py27
209# because a fixer might add a reference to it when building for
210# py27, in which case, we don't want other code to have
211# replaced it with something other than the old builtin.
212# For example replace range -> xrange, so it would be nice if
213# xrange isn't a string or something.
214BUILTIN_NAMES = {
215 "ArithmeticError",
216 "AssertionError",
217 "AttributeError",
218 "BaseException",
219 "BlockingIOError",
220 "BrokenPipeError",
221 "BufferError",
222 "BytesWarning",
223 "ChildProcessError",
224 "ConnectionAbortedError",
225 "ConnectionError",
226 "ConnectionRefusedError",
227 "ConnectionResetError",
228 "DeprecationWarning",
229 "EOFError",
230 "Ellipsis",
231 "EnvironmentError",
232 "Exception",
233 "False",
234 "FileExistsError",
235 "FileNotFoundError",
236 "FloatingPointError",
237 "FutureWarning",
238 "GeneratorExit",
239 "IOError",
240 "ImportError",
241 "ImportWarning",
242 "IndentationError",
243 "IndexError",
244 "InterruptedError",
245 "IsADirectoryError",
246 "KeyError",
247 "KeyboardInterrupt",
248 "LookupError",
249 "MemoryError",
250 "ModuleNotFoundError",
251 "NameError",
252 "None",
253 "NotADirectoryError",
254 "NotImplemented",
255 "NotImplementedError",
256 "OSError",
257 "OverflowError",
258 "PendingDeprecationWarning",
259 "PermissionError",
260 "ProcessLookupError",
261 "RecursionError",
262 "ReferenceError",
263 "ResourceWarning",
264 "RuntimeError",
265 "RuntimeWarning",
266 "StopAsyncIteration",
267 "StopIteration",
268 "SyntaxError",
269 "SyntaxWarning",
270 "SystemError",
271 "SystemExit",
272 "TabError",
273 "TimeoutError",
274 "True",
275 "TypeError",
276 "UnboundLocalError",
277 "UnicodeDecodeError",
278 "UnicodeEncodeError",
279 "UnicodeError",
280 "UnicodeTranslateError",
281 "UnicodeWarning",
282 "UserWarning",
283 "ValueError",
284 "Warning",
285 "ZeroDivisionError",
286 "abs",
287 "all",
288 "any",
289 "ascii",
290 "bin",
291 "bool",
292 "bytearray",
293 "bytes",
294 "callable",
295 "chr",
296 "classmethod",
297 "compile",
298 "complex",
299 "copyright",
300 "credits",
301 "delattr",
302 "dict",
303 "dir",
304 "display",
305 "divmod",
306 "enumerate",
307 "eval",
308 "exec",
309 "filter",
310 "float",
311 "format",
312 "frozenset",
313 "get_ipython",
314 "getattr",
315 "globals",
316 "hasattr",
317 "hash",
318 "help",
319 "hex",
320 "id",
321 "input",
322 "int",
323 "isinstance",
324 "issubclass",
325 "iter",
326 "len",
327 "license",
328 "list",
329 "locals",
330 "map",
331 "max",
332 "memoryview",
333 "min",
334 "next",
335 "object",
336 "oct",
337 "open",
338 "ord",
339 "pow",
340 "print",
341 "property",
342 "range",
343 "repr",
344 "reversed",
345 "round",
346 "set",
347 "setattr",
348 "slice",
349 "sorted",
350 "staticmethod",
351 "str",
352 "sum",
353 "super",
354 "tuple",
355 "type",
356 "vars",
357 "zip",
358 "StandardError",
359 "apply",
360 "basestring",
361 "buffer",
362 "cmp",
363 "coerce",
364 "dreload",
365 "execfile",
366 "file",
367 "intern",
368 "long",
369 "raw_input",
370 "reduce",
371 "reload",
372 "unichr",
373 "unicode",
374 "xrange",
375}
377# In case somebody is working on py4k or something
379BUILTIN_NAMES.update([name for name in dir(builtins) if not name.startswith("__")])