Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

runner now skips unselected visitors #91

Merged
merged 2 commits into from
Jan 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,9 @@ Lines containing `error:` are parsed as expecting an error of the code matching
#### `TRIOxxx:`
You can instead of `error` specify the error code.

### `# INCLUDE`
Test files by default filter out all errors not matching the file name, but if there's a line `# INCLUDE TRIO\d\d\d TRIO\d\d\d` those additional error codes are not filtered out and will be an error if encountered.
### `# ARGS`
With a line `# ARGS` you can also specify command-line arguments that should be passed to the plugin when parsing a file. Can be specified multiple times for several different arguments.
### `# ARG`
With `# ARG` lines you can also specify command-line arguments that should be passed to the plugin when parsing a file. Can be specified multiple times for several different arguments.
Generated tests will by default `--select` the error code of the file, which will enable any visitors that can generate that code (and if those visitors can raise other codes they might be raised too). This can be overriden by adding an `# ARG --select=...` line.

## Style Guide

Expand Down
39 changes: 36 additions & 3 deletions flake8_trio.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,16 @@

import ast
import keyword
import re
import tokenize
from argparse import ArgumentTypeError, Namespace
from collections.abc import Iterable
from fnmatch import fnmatch
from typing import Any, NamedTuple, Union, cast
from typing import TYPE_CHECKING, Any, NamedTuple, Union, cast

from flake8.options.manager import OptionManager
# guard against internal flake8 changes
if TYPE_CHECKING:
from flake8.options.manager import OptionManager

# CalVer: YY.month.patch, e.g. first release of July 2022 == "22.7.1"
__version__ = "22.12.5"
Expand Down Expand Up @@ -96,12 +99,20 @@ class Flake8TrioRunner(ast.NodeVisitor):
def __init__(self, options: Namespace):
super().__init__()
self._problems: list[Error] = []
self.options = options

self.visitors = {
v(options, self._problems)
for v in Flake8TrioVisitor.__subclasses__()
# TODO: could here refrain from including subclasses for disabled checks
if self.selected(v.error_codes)
}

def selected(self, error_codes: dict[str, str]) -> bool:
return any(
re.match(self.options.enable_visitor_codes_regex, code)
for code in error_codes
)

@classmethod
def run(cls, tree: ast.AST, options: Namespace) -> Iterable[Error]:
runner = cls(options)
Expand All @@ -110,6 +121,11 @@ def run(cls, tree: ast.AST, options: Namespace) -> Iterable[Error]:

def visit(self, node: ast.AST):
"""Visit a node."""
# don't bother visiting if no visitors are enabled, or all enabled visitors
# in parent nodes have marked novisit
if not self.visitors:
return

# tracks the subclasses that, from this node on, iterated through it's subfields
# we need to remember it so we can restore it at the end of the function.
novisit: set[Flake8TrioVisitor] = set()
Expand Down Expand Up @@ -210,6 +226,9 @@ def error(
if error_code is None:
assert len(self.error_codes) == 1
error_code = next(iter(self.error_codes))
# don't emit an error if this code is disabled in a multi-code visitor
elif not re.match(self.options.enable_visitor_codes_regex, error_code):
return

if not self.suppress_errors:
self._problems.append(
Expand Down Expand Up @@ -1515,6 +1534,20 @@ def add_options(option_manager: OptionManager):
"suggesting it be replaced with {value}"
),
)
option_manager.add_option(
"--enable-visitor-codes-regex",
type=re.compile,
default=".*",
parse_from_config=True,
required=False,
help=(
"Regex string of visitors to enable. Can be used to disable broken "
"visitors, or instead of --select/--disable to select error codes "
"in a way that is more performant. If a visitor raises multiple codes "
"it will not be disabled unless all codes are disabled, but it will "
"not report codes matching this regex."
),
)

@staticmethod
def parse_options(options: Namespace):
Expand Down
10 changes: 10 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ def pytest_addoption(parser: pytest.Parser):
parser.addoption(
"--runfuzz", action="store_true", default=False, help="run fuzz tests"
)
parser.addoption(
"--enable-visitor-codes-regex",
default=".*",
help="select error codes whose visitors to run.",
)


def pytest_configure(config: pytest.Config):
Expand All @@ -23,3 +28,8 @@ def pytest_collection_modifyitems(config: pytest.Config, items: list[pytest.Item
for item in items:
if "fuzz" in item.keywords:
item.add_marker(skip_fuzz)


@pytest.fixture
def enable_visitor_codes_regex(request: pytest.FixtureRequest):
return request.config.getoption("--enable-visitor-codes-regex")
19 changes: 1 addition & 18 deletions tests/test_decorator.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@
import ast

from flake8.main.application import Application
from test_flake8_trio import _default_option_manager

from flake8_trio import Plugin, Statement, Visitor107_108, fnmatch_qualified_name
from flake8_trio import Statement, Visitor107_108, fnmatch_qualified_name


def dec_list(*decorators: str) -> ast.Module:
Expand Down Expand Up @@ -82,22 +81,6 @@ def test_pep614():
assert not wrap(("(any, expression, we, like)",), "no match here")


def test_plugin():
tree = dec_list("app.route")
plugin = Plugin(tree)

om = _default_option_manager()
plugin.add_options(om)

plugin.parse_options(om.parse_args(args=[]))
assert tuple(plugin.run())

arg = "--no-checkpoint-warning-decorators=app.route"
plugin.parse_options(om.parse_args(args=[arg]))

assert not tuple(plugin.run())


common_flags = ["--select=TRIO", "tests/trio_options.py"]


Expand Down
Loading