Skip to content

Commit

Permalink
Change checker instantiation to provide stdin compatibility
Browse files Browse the repository at this point in the history
Previously, when the checker instance is created it requested the filename from flake8 in order to load the source and parse to obtain a typed AST, as the flake8 provided AST is not guaranteed to contain type information. While sufficient for parsing files, this falls apart when attempting to parse code piped to stdin & causes our plugin to error out.

To mitigate these issues, the source is requested from flake8 as lines (list of strings), which is joined & passed into the typed AST generation rather than farming to a file IO task. This also allows us to drop the request for the AST from flake8 that we don't use, as requesting the lines will ensure that the plugin is registered.

The related test helpers have been updated to accommodate the change in instantiation.

Closes #52
  • Loading branch information
sco1 committed Dec 10, 2019
1 parent 95b3efa commit 975d0e3
Show file tree
Hide file tree
Showing 3 changed files with 21 additions and 24 deletions.
31 changes: 15 additions & 16 deletions flake8_annotations/checker.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
from functools import lru_cache
from pathlib import Path
from typing import Generator, List, Tuple
from typing import Generator, List

from flake8_annotations import (
Argument, Function, FunctionVisitor, PY_GTE_38, __version__, enums, error_codes
Argument,
Function,
FunctionVisitor,
PY_GTE_38,
__version__,
enums,
error_codes,
)

# Check if we can use the stdlib ast module instead of typed_ast
Expand All @@ -20,11 +25,10 @@ class TypeHintChecker:
name = "flake8-annotations"
version = __version__

def __init__(self, tree: ast.Module, filename: str):
# Unfortunately no way that I can find around requesting the ast-parsed tree from flake8
# Removing tree unregisters the plugin, and per the documentation the alternative is
# requesting per-line information
self.tree, self.lines = self.load_file(Path(filename))
def __init__(self, lines: List[str]):
# Request `lines` here and join to allow for correct handling of input from stdin
self.lines = lines
self.tree = self.get_typed_tree("".join(lines)) # flake8 doesn't strip newlines

def run(self) -> Generator[error_codes.Error, None, None]:
"""
Expand Down Expand Up @@ -68,21 +72,16 @@ def run(self) -> Generator[error_codes.Error, None, None]:
yield classify_error(function, arg).to_flake8()

@staticmethod
def load_file(src_filepath: Path) -> Tuple[ast.Module, List[str]]:
"""Parse the provided Python file and return an (typed AST, source) tuple."""
with src_filepath.open("r", encoding="utf-8") as f:
src = f.read()

def get_typed_tree(src: str) -> ast.Module:
"""Parse the provided source into a typed AST."""
if PY_GTE_38:
# Built-in ast requires a flag to parse type comments
tree = ast.parse(src, type_comments=True)
else:
# typed-ast will implicitly parse type comments
tree = ast.parse(src)

lines = src.splitlines()

return tree, lines
return tree


def classify_error(function: Function, arg: Argument) -> error_codes.Error:
Expand Down
8 changes: 3 additions & 5 deletions testing/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,15 @@ def parse_source(src: str) -> Tuple[ast.Module, List[str]]:
# typed-ast will implicitly parse type comments
tree = ast.parse(src)

lines = src.splitlines()
lines = src.splitlines(keepends=True)

return tree, lines


def check_source(src: str) -> Generator[Error, None, None]:
"""Helper for generating linting errors from the provided source code."""
# Because TypeHintChecker is expecting a filename to initialize, rather than change this logic
# we can use this file as a dummy, then update its tree & lines attributes as parsed from source
checker_instance = TypeHintChecker(None, __file__)
checker_instance.tree, checker_instance.lines = parse_source(src)
_, lines = parse_source(src)
checker_instance = TypeHintChecker(lines)

return checker_instance.run()

Expand Down
6 changes: 3 additions & 3 deletions testing/test_cases/variable_formatting_test_cases.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ def foo(some_arg, *some_args, **some_kwargs) -> int:
"protected_function": FormatTestCase(
src=dedent(
"""\
def _foo(some_arg, *some_args, **some_kwargs) -> int:
pass
"""
def _foo(some_arg, *some_args, **some_kwargs) -> int:
pass
"""
),
),
"private_function": FormatTestCase(
Expand Down

0 comments on commit 975d0e3

Please sign in to comment.