Skip to content

Commit

Permalink
feat: configurable max length for string literals (#6)
Browse files Browse the repository at this point in the history
This is a way to allow short literals like:

```python
raise RuntimeError("Little msg")
```

Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com>
  • Loading branch information
henryiii committed Sep 7, 2022
1 parent a9f47f6 commit d0eff1e
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 11 deletions.
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,13 @@ applications should print nice errors, usually _without_ a traceback, unless
something _unexpected_ occurred. An app should not print a traceback for an
error that is known to be triggerable by a user.

## Options

There is one option, `--errmsg-max-string-length`, which defaults to 0 but can
be set to a larger value. The check will ignore string literals shorter than
this length. This option is supported in configuration mode as well. This will
only affect string literals and not f-strings.

## Usage

Just add this to your `.pre-commit-config.yaml` `flake8` check under
Expand Down
23 changes: 22 additions & 1 deletion noxfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import nox

nox.options.sessions = ["lint", "pylint", "tests"]
nox.options.sessions = ["lint", "pylint", "tests", "tests_flake8"]


@nox.session
Expand Down Expand Up @@ -42,3 +42,24 @@ def build(session: nox.Session) -> None:

session.install("build")
session.run("python", "-m", "build")


@nox.session
def tests_flake8(session: nox.Session) -> None:
"""
Run the flake8 tests.
"""
session.install(".", "flake8")
result = session.run("flake8", "tests/example1.py", silent=True, success_codes=[1])
if len(result.splitlines()) != 2:
session.error(f"Expected 2 errors from flake8\n{result}")

result = session.run(
"flake8",
"--errmsg-max-string-length=30",
"tests/example1.py",
silent=True,
success_codes=[1],
)
if len(result.splitlines()) != 1:
session.error(f"Expected 1 errors from flake8\n{result}")
49 changes: 39 additions & 10 deletions src/flake8_errmsg/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,17 @@

from __future__ import annotations

import argparse
import ast
import dataclasses
import sys
import traceback
from collections.abc import Iterator
from pathlib import Path
from typing import NamedTuple
from typing import Any, ClassVar, NamedTuple

__all__ = ("__version__", "run_on_file", "main", "ErrMsgASTPlugin")

__version__ = "0.2.4"
__version__ = "0.3.0"


class Flake8ASTErrorInfo(NamedTuple):
Expand All @@ -29,13 +29,15 @@ class Flake8ASTErrorInfo(NamedTuple):


class Visitor(ast.NodeVisitor):
def __init__(self) -> None:
def __init__(self, max_string_len: int) -> None:
self.errors: list[Flake8ASTErrorInfo] = []
self.max_string_len = max_string_len

def visit_Raise(self, node: ast.Raise) -> None:
match node.exc:
case ast.Call(args=[ast.Constant(value=str()), *_]):
self.errors.append(EM101(node))
case ast.Call(args=[ast.Constant(value=str(value)), *_]):
if len(value) >= self.max_string_len:
self.errors.append(EM101(node))
case ast.Call(args=[ast.JoinedStr(), *_]):
self.errors.append(EM102(node))
case _:
Expand All @@ -52,21 +54,41 @@ def EM102(node: ast.AST) -> Flake8ASTErrorInfo:
return Flake8ASTErrorInfo(node.lineno, node.col_offset, msg, Visitor)


MAX_STRING_LENGTH = 0


@dataclasses.dataclass
class ErrMsgASTPlugin:
max_string_length: ClassVar[int] = 0

tree: ast.AST

_: dataclasses.KW_ONLY
name: str = "flake8_errmsg"
version: str = "0.1.0"
options: Any = None

def run(self) -> Iterator[Flake8ASTErrorInfo]:
visitor = Visitor()
visitor = Visitor(self.max_string_length)
visitor.visit(self.tree)
yield from visitor.errors

@staticmethod
def add_options(optmanager: Any) -> None:
optmanager.add_option(
"--errmsg-max-string-length",
parse_from_config=True,
default=0,
type=int,
help="Set a maximum string length to allow inline strings. Default 0 (always disallow).",
)

@classmethod
def parse_options(cls, options: argparse.Namespace) -> None:
cls.max_string_length = options.errmsg_max_string_length

def run_on_file(path: str) -> None:

def run_on_file(path: str, max_string_length: int = 0) -> None:
code = Path(path).read_text(encoding="utf-8")

try:
Expand All @@ -78,13 +100,20 @@ def run_on_file(path: str) -> None:
raise SystemExit(1) from None

plugin = ErrMsgASTPlugin(node)
ErrMsgASTPlugin.max_string_length = max_string_length

for err in plugin.run():
print(f"{path}:{err.line_number}:{err.offset} {err.msg}")


def main() -> None:
for item in sys.argv[1:]:
run_on_file(item)
parser = argparse.ArgumentParser()
parser.add_argument("--errmsg-max-string-length", type=int, default=0)
parser.add_argument("files", nargs="+")
namespace = parser.parse_args()

for item in namespace.files:
run_on_file(item, namespace.errmsg_max_string_length)


if __name__ == "__main__":
Expand Down
14 changes: 14 additions & 0 deletions tests/test_package.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,17 @@ def test_err1():
results[1].msg
== "EM102 Exception must not use an f-string literal, assign to variable first"
)


def test_string_length():
node = ast.parse(ERR1)
plugin = m.ErrMsgASTPlugin(node)
m.ErrMsgASTPlugin.max_string_length = 10
results = list(plugin.run())
assert len(results) == 1
assert results[0].line_number == 2

assert (
results[0].msg
== "EM102 Exception must not use an f-string literal, assign to variable first"
)

0 comments on commit d0eff1e

Please sign in to comment.