Coverage for src/lib3to6/checkers.py : 94%

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
10from . import utils
11from . import common
12from . import checker_base as cb
13from .checkers_backports import NoUnusableImportsChecker
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
22 for alias in node.names:
23 if alias.name == "*":
24 raise common.CheckError(f"Prohibited from {node.module} import *.", node)
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
41class NoOverriddenFixerImportsChecker(cb.CheckerBase):
42 """Don't override names that fixers may reference."""
44 prohibited_import_overrides = {"itertools", "six", "builtins"}
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
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)
62class NoOverriddenBuiltinsChecker(cb.CheckerBase):
63 """Don't override names that fixers may reference."""
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)
72PROHIBITED_OPEN_ARGUMENTS = {"encoding", "errors", "newline", "closefd", "opener"}
75class NoOpenWithEncodingChecker(cb.CheckerBase):
77 version_info = common.VersionInfo(apply_until="2.7")
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
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
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)
100 mode = mode_node.s
102 if len(node.args) > 3:
103 raise common.CheckError("Prohibited positional arguments to builtin.open", node)
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
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)
120 mode = mode_node.s
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)
130class NoAsyncAwait(cb.CheckerBase):
132 version_info = common.VersionInfo(apply_until="3.4", works_since="3.5")
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
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"
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)
159class NoYieldFromChecker(cb.CheckerBase):
161 version_info = common.VersionInfo(apply_until="3.2", works_since="3.3")
163 def __call__(self, ctx: common.BuildContext, tree: ast.Module) -> None:
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)
174class NoMatMultOpChecker(cb.CheckerBase):
176 version_info = common.VersionInfo(apply_until="3.4", works_since="3.5")
178 def __call__(self, ctx: common.BuildContext, tree: ast.Module) -> None:
179 if not hasattr(ast, 'MatMult'):
180 return
182 for node in ast.walk(tree):
183 if not isinstance(node, ast.BinOp):
184 continue
186 if not isinstance(node.op, ast.MatMult):
187 continue
189 msg = "Prohibited use of matrix multiplication '@' operator."
190 raise common.CheckError(msg, node)
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
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)
216class NoComplexNamedTuple(cb.CheckerBase):
218 version_info = common.VersionInfo(apply_until="3.4", works_since="3.5")
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"
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
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
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)
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
260__all__ = [
261 'NoStarImports',
262 'NoOverriddenFixerImportsChecker',
263 'NoOverriddenBuiltinsChecker',
264 'NoOpenWithEncodingChecker',
265 'NoAsyncAwait',
266 'NoComplexNamedTuple',
267 'NoUnusableImportsChecker',
268 'NoYieldFromChecker',
269 'NoMatMultOpChecker',
270]