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 

9 

10from . import utils 

11from . import common 

12from . import checker_base as cb 

13from .checkers_backports import NoUnusableImportsChecker 

14 

15 

16class NoStarImports(cb.CheckerBase): 

17 def __call__(self, ctx: common.BuildContext, tree: ast.Module) -> None: 

18 for node in ast.walk(tree): 

19 if not isinstance(node, ast.ImportFrom): 

20 continue 

21 

22 for alias in node.names: 

23 if alias.name == "*": 

24 raise common.CheckError(f"Prohibited from {node.module} import *.", node) 

25 

26 

27def _iter_scope_names(tree: ast.Module) -> typ.Iterable[typ.Tuple[str, ast.AST]]: 

28 for node in ast.walk(tree): 

29 if isinstance(node, (ast.FunctionDef, ast.ClassDef)): 

30 yield node.name, node 

31 elif isinstance(node, ast.Name) and isinstance(node.ctx, ast.Store): 

32 yield node.id, node 

33 elif isinstance(node, (ast.ImportFrom, ast.Import)): 

34 for alias in node.names: 

35 name = alias.name if alias.asname is None else alias.asname 

36 yield name, node 

37 elif isinstance(node, ast.arg): 

38 yield node.arg, node 

39 

40 

41class NoOverriddenFixerImportsChecker(cb.CheckerBase): 

42 """Don't override names that fixers may reference.""" 

43 

44 prohibited_import_overrides = {"itertools", "six", "builtins"} 

45 

46 def __call__(self, ctx: common.BuildContext, tree: ast.Module) -> None: 

47 for name_in_scope, node in _iter_scope_names(tree): 

48 is_fixer_import = ( 

49 isinstance(node, ast.Import) 

50 and len(node.names) == 1 

51 and node.names[0].asname is None 

52 and node.names[0].name == name_in_scope 

53 ) 

54 if is_fixer_import: 

55 continue 

56 

57 if name_in_scope in self.prohibited_import_overrides: 

58 msg = f"Prohibited override of import '{name_in_scope}'" 

59 raise common.CheckError(msg, node) 

60 

61 

62class NoOverriddenBuiltinsChecker(cb.CheckerBase): 

63 """Don't override names that fixers may reference.""" 

64 

65 def __call__(self, ctx: common.BuildContext, tree: ast.Module) -> None: 

66 for name_in_scope, node in _iter_scope_names(tree): 

67 if name_in_scope in common.BUILTIN_NAMES: 

68 msg = f"Prohibited override of builtin '{name_in_scope}'" 

69 raise common.CheckError(msg, node) 

70 

71 

72PROHIBITED_OPEN_ARGUMENTS = {"encoding", "errors", "newline", "closefd", "opener"} 

73 

74 

75class NoOpenWithEncodingChecker(cb.CheckerBase): 

76 

77 version_info = common.VersionInfo(apply_until="2.7") 

78 

79 def __call__(self, ctx: common.BuildContext, tree: ast.Module) -> None: 

80 for node in ast.walk(tree): 

81 if not isinstance(node, ast.Call): 

82 continue 

83 

84 func_node = node.func 

85 if not isinstance(func_node, ast.Name): 

86 continue 

87 if func_node.id != "open" or not isinstance(func_node.ctx, ast.Load): 

88 continue 

89 

90 mode = "r" 

91 if len(node.args) >= 2: 

92 mode_node = node.args[1] 

93 if not isinstance(mode_node, ast.Str): 

94 msg = ( 

95 "Prohibited value for argument 'mode' of builtin.open. " 

96 + f"Expected ast.Str node, got: {mode_node}" 

97 ) 

98 raise common.CheckError(msg, node) 

99 

100 mode = mode_node.s 

101 

102 if len(node.args) > 3: 

103 raise common.CheckError("Prohibited positional arguments to builtin.open", node) 

104 

105 for keyword in node.keywords: 

106 if keyword.arg in PROHIBITED_OPEN_ARGUMENTS: 

107 msg = f"Prohibited keyword argument '{keyword.arg}' to builtin.open." 

108 raise common.CheckError(msg, node) 

109 if keyword.arg != 'mode': 

110 continue 

111 

112 mode_node = keyword.value 

113 if not isinstance(mode_node, ast.Str): 

114 msg = ( 

115 "Prohibited value for argument 'mode' of builtin.open. " 

116 + f"Expected ast.Str node, got: {mode_node}" 

117 ) 

118 raise common.CheckError(msg, node) 

119 

120 mode = mode_node.s 

121 

122 if "b" not in mode: 

123 msg = ( 

124 f"Prohibited value '{mode}' for argument 'mode' of builtin.open. " 

125 + "Only binary modes are allowed, use io.open as an alternative." 

126 ) 

127 raise common.CheckError(msg, node) 

128 

129 

130class NoAsyncAwait(cb.CheckerBase): 

131 

132 version_info = common.VersionInfo(apply_until="3.4", works_since="3.5") 

133 

134 def __call__(self, ctx: common.BuildContext, tree: ast.Module) -> None: 

135 async_await_node_types = (ast.AsyncFor, ast.AsyncWith, ast.AsyncFunctionDef, ast.Await) 

136 for node in ast.walk(tree): 

137 if not isinstance(node, async_await_node_types): 

138 continue 

139 

140 if isinstance(node, ast.AsyncFor): 

141 keywords = "async for" 

142 elif isinstance(node, ast.AsyncWith): 

143 keywords = "async with" 

144 elif isinstance(node, ast.AsyncFunctionDef): 

145 keywords = "async def" 

146 elif isinstance(node, ast.Await): 

147 keywords = "await" 

148 else: 

149 # probably dead codepath 

150 keywords = "async/await" 

151 

152 msg = ( 

153 f"Prohibited use of '{keywords}', which is not supported " 

154 f"for target_version='{ctx.cfg.target_version}'." 

155 ) 

156 raise common.CheckError(msg, node) 

157 

158 

159class NoYieldFromChecker(cb.CheckerBase): 

160 

161 version_info = common.VersionInfo(apply_until="3.2", works_since="3.3") 

162 

163 def __call__(self, ctx: common.BuildContext, tree: ast.Module) -> None: 

164 

165 for node in ast.walk(tree): 

166 if isinstance(node, ast.YieldFrom): 

167 msg = ( 

168 "Prohibited use of 'yield from', which is not supported " 

169 f"for your target_version={ctx.cfg.target_version}" 

170 ) 

171 raise common.CheckError(msg, node) 

172 

173 

174class NoMatMultOpChecker(cb.CheckerBase): 

175 

176 version_info = common.VersionInfo(apply_until="3.4", works_since="3.5") 

177 

178 def __call__(self, ctx: common.BuildContext, tree: ast.Module) -> None: 

179 if not hasattr(ast, 'MatMult'): 

180 return 

181 

182 for node in ast.walk(tree): 

183 if not isinstance(node, ast.BinOp): 

184 continue 

185 

186 if not isinstance(node.op, ast.MatMult): 

187 continue 

188 

189 msg = "Prohibited use of matrix multiplication '@' operator." 

190 raise common.CheckError(msg, node) 

191 

192 

193def _raise_if_complex_named_tuple(node: ast.ClassDef) -> None: 

194 for subnode in node.body: 

195 if isinstance(subnode, ast.Expr) and isinstance(subnode.value, ast.Str): 

196 # docstring is fine 

197 continue 

198 

199 if isinstance(subnode, ast.AnnAssign): 

200 if subnode.value: 

201 tgt = subnode.target 

202 assert isinstance(tgt, ast.Name) 

203 msg = ( 

204 "Prohibited use of default value " 

205 + f"for field '{tgt.id}' of class '{node.name}'" 

206 ) 

207 raise common.CheckError(msg, subnode, node) 

208 elif isinstance(subnode, ast.FunctionDef): 

209 msg = "Prohibited definition of method " + f"'{subnode.name}' for class '{node.name}'" 

210 raise common.CheckError(msg, subnode, node) 

211 else: 

212 msg = f"Unexpected subnode defined for class {node.name}: {subnode}" 

213 raise common.CheckError(msg, subnode, node) 

214 

215 

216class NoComplexNamedTuple(cb.CheckerBase): 

217 

218 version_info = common.VersionInfo(apply_until="3.4", works_since="3.5") 

219 

220 def __call__(self, ctx: common.BuildContext, tree: ast.Module) -> None: 

221 _typing_module_name : typ.Optional[str] = None 

222 _namedtuple_class_name: str = "NamedTuple" 

223 

224 for node in ast.walk(tree): 

225 if isinstance(node, ast.Import): 

226 for alias in node.names: 

227 if alias.name == 'typing': 

228 _typing_module_name = alias.name if alias.asname is None else alias.asname 

229 continue 

230 

231 if isinstance(node, ast.ImportFrom) and node.module == 'typing': 

232 for alias in node.names: 

233 if alias.name == 'NamedTuple': 

234 _namedtuple_class_name = ( 

235 alias.name if alias.asname is None else alias.asname 

236 ) 

237 continue 

238 

239 is_namedtuple_class = ( 

240 isinstance(node, ast.ClassDef) 

241 and (_typing_module_name or _namedtuple_class_name) 

242 and utils.has_base_class(node, _typing_module_name, _namedtuple_class_name) 

243 ) 

244 if is_namedtuple_class: 

245 assert isinstance(node, ast.ClassDef), "mypy is stupid sometimes" 

246 _raise_if_complex_named_tuple(node) 

247 

248 

249# NOTE (mb 2018-06-24): I don't know how this could be done reliably. 

250# The main issue is that there are objects other than dict, which 

251# have methods named items,keys,values which this check wouldn't 

252# apply to. 

253# class NoAssignedDictViews(cb.CheckerBase): 

254# 

255# check_before = "3.0" 

256# 

257# def __call__(self, ctx: common.BuildContext, tree: ast.Module): 

258# pass 

259 

260__all__ = [ 

261 'NoStarImports', 

262 'NoOverriddenFixerImportsChecker', 

263 'NoOverriddenBuiltinsChecker', 

264 'NoOpenWithEncodingChecker', 

265 'NoAsyncAwait', 

266 'NoComplexNamedTuple', 

267 'NoUnusableImportsChecker', 

268 'NoYieldFromChecker', 

269 'NoMatMultOpChecker', 

270]