-
Notifications
You must be signed in to change notification settings - Fork 8
/
Copy pathplugin.py
126 lines (103 loc) · 3.32 KB
/
plugin.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
import argparse
import ast
from contextlib import contextmanager
from typing import (
Any,
Generic,
Iterable,
Iterator,
List,
Optional,
Tuple,
Type,
TypeVar,
)
from flake8.options.manager import OptionManager
FLAKE8_ERROR = Tuple[int, int, str, 'Plugin']
TConfig = TypeVar('TConfig')
class Error:
code: str
message: str
lineno: int
col_offset: int
def __init__(self, lineno: int, col_offset: int, **kwargs: Any) -> None:
self.lineno = lineno
self.col_offset = col_offset
self.message = self.formatted_message(**kwargs)
@classmethod
def formatted_message(cls, **kwargs: Any) -> str:
return cls.message.format(**kwargs)
class Visitor(Generic[TConfig], ast.NodeVisitor):
def __init__(self, config: Optional[TConfig] = None) -> None:
self.errors: List[Error] = []
self._config: Optional[TConfig] = config
@property
def config(self) -> TConfig:
if self._config is None:
raise TypeError(
f'{self} was initialized without a config. Did you forget '
f'to override parse_options_to_config in your plugin class?'
)
return self._config
def error_from_node(
self, error: Type[Error], node: ast.AST, **kwargs: Any
) -> None:
self.errors.append(error(node.lineno, node.col_offset, **kwargs))
class Plugin(Generic[TConfig]):
name: str
version: str
visitors: List[Type[Visitor[TConfig]]]
config: TConfig
def __init__(self, tree: ast.AST) -> None:
self._tree: ast.AST = tree
def run(self) -> Iterable[FLAKE8_ERROR]:
for visitor_cls in self.visitors:
visitor = self._create_visitor(visitor_cls)
visitor.visit(self._tree)
for error in visitor.errors:
yield self._error(error)
def _error(self, error: Error) -> FLAKE8_ERROR:
return (
error.lineno,
error.col_offset,
f'{error.code} {error.message}',
self,
)
@classmethod
def _create_visitor(
cls, visitor_cls: Type[Visitor[TConfig]]
) -> Visitor[TConfig]:
if cls.config is None:
return visitor_cls()
return visitor_cls(config=cls.config)
@classmethod
def parse_options(
cls,
option_manager: OptionManager,
options: argparse.Namespace,
args: List[str],
) -> None:
cls.config = cls.parse_options_to_config(option_manager, options, args)
@classmethod
def parse_options_to_config( # pylint: disable=unused-argument
cls,
option_manager: OptionManager,
options: argparse.Namespace,
args: List[str],
) -> Optional[TConfig]:
return None
@classmethod
@contextmanager
def test_config(cls, config: TConfig) -> Iterator[None]:
"""
Context manager to add a config to the plugin class for testing.
Normally flake8 will call `parse_options` on the plugin, which will
set the config on the plugin class. However, this is not the case
when creating a plugin manually in tests. This context manager can
be used to pass in a config and clean up afterwards.
"""
cls.config = config
try:
yield
finally:
del cls.config