Hide keyboard shortcuts

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 

6 

7import ast 

8import typing as typ 

9import builtins 

10 

11PackageName = str 

12PackageDirectory = str 

13PackageDir = typ.Dict[PackageName, PackageDirectory] 

14 

15InstallRequires = typ.Optional[typ.Set[str]] 

16 

17 

18ConstantNodeTypes: typ.Tuple[typ.Type[ast.Constant], ...] = (ast.Constant,) 

19 

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. 

24 

25if hasattr(ast, 'Num'): 

26 ConstantNodeTypes += (ast.Num, ast.Str, ast.Bytes, ast.NameConstant, ast.Ellipsis) 

27 

28 

29LeafNodeTypes = ConstantNodeTypes + ( 

30 ast.Name, 

31 ast.cmpop, 

32 ast.boolop, 

33 ast.operator, 

34 ast.unaryop, 

35 ast.expr_context, 

36) 

37 

38 

39ContainerNodes = (ast.List, ast.Set, ast.Tuple) 

40 

41 

42class BuildConfig(typ.NamedTuple): 

43 

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 

50 

51 

52class BuildContext(typ.NamedTuple): 

53 

54 cfg : BuildConfig 

55 filepath: str 

56 

57 

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) 

76 

77 

78# Additional items: 

79# 'filepath': "/path/to/inputfile.py" 

80 

81 

82class InvalidPackage(Exception): 

83 pass 

84 

85 

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 

95 

96 

97class CheckError(Exception): 

98 

99 lineno: int 

100 

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) 

104 

105 

106class FixerError(Exception): 

107 

108 msg : str 

109 node : ast.AST 

110 parent : typ.Optional[ast.AST] 

111 filepath: typ.Optional[str] 

112 

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) 

125 

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 

132 

133 

134class VersionInfo: 

135 """Compatibility info for Fixer and Checker classes. 

136 

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 """ 

142 

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]] 

148 

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: 

156 

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(".")] 

162 

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(".")] 

169 

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(".")] 

174 

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 

182 

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 

193 

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) 

196 

197 

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] 

206 

207 

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} 

376 

377# In case somebody is working on py4k or something 

378 

379BUILTIN_NAMES.update([name for name in dir(builtins) if not name.startswith("__")])