Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
79 commits
Select commit Hold shift + click to select a range
3cd0da4
add gitpython as dependency
mshafer-NI Mar 25, 2022
9b92744
get git import working
mshafer-NI Mar 25, 2022
3f920ff
allow windows style paths
mshafer-NI Mar 25, 2022
5fc3d2a
format
mshafer-NI Mar 25, 2022
9053cd0
add support for handling cases with no ignored errors
mshafer-NI Mar 25, 2022
8acb5aa
get initial setup working
mshafer-NI Mar 25, 2022
c0ae649
move git_utils to _utils.git
mshafer-NI Mar 25, 2022
33238c1
un-needed
mshafer-NI Mar 25, 2022
1f80f5f
fix imports
mshafer-NI Mar 25, 2022
3abb625
remove the blank lines
mshafer-NI Mar 25, 2022
c06a5f2
cleanup some more for the linter
mshafer-NI Mar 25, 2022
b81dd37
extract multiline string helpers to _utils
mshafer-NI Mar 25, 2022
0b6e870
typo
mshafer-NI Mar 25, 2022
0a35203
format
mshafer-NI Mar 25, 2022
0ba226f
make these work right
mshafer-NI Mar 25, 2022
b54f711
extract fixing multi-import lines
mshafer-NI Mar 25, 2022
7c4c1dc
add sorting imports
mshafer-NI Mar 25, 2022
c3bcddc
format
mshafer-NI Mar 25, 2022
2a647cd
add isort as dep
mshafer-NI Mar 25, 2022
6e66b8f
make the linter happy
mshafer-NI Mar 25, 2022
f377be6
add some more test cases
mshafer-NI Mar 25, 2022
6a08350
remove some dead code
mshafer-NI Mar 31, 2022
614b99a
make it ignore imports in docstrings
mshafer-NI Mar 31, 2022
e5f7dd0
skip if line does not have import for from as a statement
mshafer-NI Mar 31, 2022
10d0cbe
get it mostly working
mshafer-NI Mar 31, 2022
34df13c
add the pyproject.toml file to the test env for getting the app name
mshafer-NI Mar 31, 2022
f8f97f2
cleanup the test case
mshafer-NI Mar 31, 2022
da9430d
get app_import_names passing down to sort correctly
mshafer-NI Mar 31, 2022
96326b9
format
mshafer-NI Mar 31, 2022
4c50922
extract handling single file
mshafer-NI Mar 31, 2022
496c818
setup for --aggressive
mshafer-NI Mar 31, 2022
f5de92d
make the linter happy
mshafer-NI Mar 31, 2022
1b43209
we can dog-food ourselves!
mshafer-NI Mar 31, 2022
ab7ea20
only suppress remaining if --aggressive
mshafer-NI Mar 31, 2022
e6d2ea4
get snapshots to where linter is happy with output without --aggressive
mshafer-NI Mar 31, 2022
e171e2a
fix var reference
mshafer-NI Mar 31, 2022
bfe916e
add --aggressive cases
mshafer-NI Mar 31, 2022
ec5f687
format
mshafer-NI Mar 31, 2022
5073dee
remove unused code
mshafer-NI Mar 31, 2022
fbf3492
handle if dir is not in a git repo
mshafer-NI Mar 31, 2022
c444813
use the working tree dir
mshafer-NI Mar 31, 2022
a97127e
make black condense isort's output even with long comment after
mshafer-NI Mar 31, 2022
1db0982
handle comments
mshafer-NI Mar 31, 2022
7bf0621
cleanup tests
mshafer-NI Mar 31, 2022
450a7fc
add example case of previously suppressed that is no longer needed
mshafer-NI Mar 31, 2022
8b9beb3
revert testing change
mshafer-NI Mar 31, 2022
4a986ad
‾\_(ツ)_/‾
mshafer-NI Mar 31, 2022
243ed8a
try also locking gitdb
mshafer-NI Apr 1, 2022
b76d557
don't use git
mshafer-NI Apr 8, 2022
77d8f24
remove deleted import
mshafer-NI Apr 18, 2022
b077660
just rglob with filter for excludes list
mshafer-NI Apr 18, 2022
453c9eb
unextract handling the file
mshafer-NI Apr 18, 2022
5e6c168
update fix to match
mshafer-NI Apr 18, 2022
c891708
refactor getting lint output to utils
mshafer-NI Apr 18, 2022
298c164
finish extracting
mshafer-NI Apr 18, 2022
6949a85
format
mshafer-NI Apr 18, 2022
5e38cf3
fix terminology
mshafer-NI Apr 18, 2022
558c935
move lint errors_parser in with linter utils
mshafer-NI Apr 18, 2022
9c24309
update more occurances
mshafer-NI Apr 18, 2022
dc5c5a0
correct call
mshafer-NI Apr 18, 2022
7b4a57c
dog-food the fix command
mshafer-NI Apr 18, 2022
fa4179a
fix another reference
mshafer-NI Apr 18, 2022
77da803
document public api
mshafer-NI Apr 18, 2022
1a6e878
remove broken import
mshafer-NI Apr 18, 2022
4efba7e
fix indentation
mshafer-NI Apr 21, 2022
e0a9534
flip the indentation
mshafer-NI Apr 21, 2022
12eebc3
remove some unused imports
mshafer-NI Apr 21, 2022
622c82a
fix imports
mshafer-NI Apr 21, 2022
b5ddf54
update tests
mshafer-NI Apr 21, 2022
9c458ca
handle merge differences
mshafer-NI Apr 27, 2022
76ccc16
make the diff smaller
mshafer-NI May 2, 2022
0256d6f
fix reference to get lint output
mshafer-NI May 2, 2022
17c3807
flip that back around
mshafer-NI May 2, 2022
c9b989f
fix more references
mshafer-NI May 2, 2022
53c8f73
fix ni-python-styleguide linter errors
mshafer-NI May 2, 2022
dd9cfff
update tests
mshafer-NI May 2, 2022
c6b6d42
remove it from the input
mshafer-NI May 2, 2022
6ad06a4
fix verbage
mshafer-NI May 2, 2022
2c0d20d
remove unsupported argument
mshafer-NI May 2, 2022
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
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ def acknowledge_lint_errors(
_suppress_errors_in_file(bad_file, errors_in_file, encoding=_utils.DEFAULT_ENCODING)

if aggressive:

# some cases are expected to take up to 4 passes, making this 2x rounded
per_file_format_iteration_limit = 10
for _ in range(per_file_format_iteration_limit):
Expand Down
29 changes: 28 additions & 1 deletion ni_python_styleguide/_cli.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import pathlib

import click
import toml

from ni_python_styleguide import _acknowledge_existing_errors
from ni_python_styleguide import _fix
from ni_python_styleguide import _lint


Expand Down Expand Up @@ -147,7 +150,7 @@ def lint(obj, format, extend_ignore, file_or_dir):
)
@click.pass_obj
def acknowledge_existing_violations(obj, extend_ignore, file_or_dir, aggressive):
"""Lint existing violations and suppress.
"""Lint existing violations and acknowledge.

Use this command to acknowledge violations in existing code to allow for enforcing new code.
"""
Expand All @@ -158,3 +161,27 @@ def acknowledge_existing_violations(obj, extend_ignore, file_or_dir, aggressive)
file_or_dir=file_or_dir,
aggressive=aggressive,
)


@main.command()
@click.option(
"--extend-ignore",
type=str,
help="Comma-separated list of errors and warnings to ignore (or skip)",
)
@click.argument("file_or_dir", nargs=-1)
@click.option(
"--aggressive",
is_flag=True,
help="Remove any existing acknowledgments, fix what can be fixed, and re-acknowledge remaining.",
)
@click.pass_obj
def fix(obj, extend_ignore, file_or_dir, aggressive):
"""Fix basic linter/formatting errors in file(s)/directory(s) given.""" # noqa: D4
_fix.fix(
exclude=obj["EXCLUDE"],
app_import_names=obj["APP_IMPORT_NAMES"],
extend_ignore=extend_ignore,
file_or_dir=file_or_dir or [pathlib.Path.cwd()],
aggressive=aggressive,
)
165 changes: 165 additions & 0 deletions ni_python_styleguide/_fix.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
import fileinput
import logging
import pathlib
from collections import defaultdict
from fnmatch import fnmatch
from typing import List

import isort

from ni_python_styleguide import _acknowledge_existing_errors
from ni_python_styleguide import _format
from ni_python_styleguide import _utils

_module_logger = logging.getLogger(__name__)


def _split_imports_line(lines: str, *_, **__):
r"""Split multi-import lines to multiple lines.

>>> _split_imports_line("import os, collections\n")
'import os\nimport collections\n'

>>> _split_imports_line("import os\n")
'import os\n'

>>> _split_imports_line("from ni_python_styleguide import"
... " _acknowledge_existing_errors, _format")
'from ni_python_styleguide import _acknowledge_existing_errors\nfrom ni_python_styleguide import _format\n'

>>> _split_imports_line("from ni_python_styleguide import _acknowledge_existing_errors")
'from ni_python_styleguide import _acknowledge_existing_errors\n'

>>> _split_imports_line("import os, collections\nimport pathlib")
'import os\nimport collections\nimport pathlib\n'

>>> _split_imports_line("import os, collections\nimport pathlib, os")
'import os\nimport collections\nimport pathlib\nimport os\n'

>>> _split_imports_line("\n")
'\n'
""" # noqa W505: long lines...
result_parts = []
for line in lines.splitlines(keepends=True):
code_portion_of_line, *non_code = line.split("#", maxsplit=1)
first, _, rest = code_portion_of_line.partition(",")
if not all(
[
rest,
"import " in code_portion_of_line,
code_portion_of_line.strip().startswith("import ")
or code_portion_of_line.strip().startswith("from "),
]
):
result_parts.append(code_portion_of_line)
continue
prefix, first = " ".join(first.split()[:-1]), first.split()[-1]
split_up = [first] + rest.split(",")
result_parts.extend([prefix + " " + part.strip() for part in split_up])
suffix = ""
if non_code:
suffix = "#" + "".join(non_code)
result = "\n".join(result_parts) + suffix
if result.strip():
return result.rstrip() + "\n"
return result


def _sort_imports(file: pathlib.Path, app_import_names):
raw = file.read_text()
output = isort.code(
raw,
multi_line_output=3,
line_length=1,
known_first_party=filter(None, app_import_names.split(",")),
)
file.write_text(output)


def _handle_multiple_import_lines(bad_file: pathlib.Path):
multiline_string_checker = _utils.string_helpers.InMultiLineStringChecker(
lines=bad_file.read_text(encoding=_utils.DEFAULT_ENCODING).splitlines()
)
with fileinput.FileInput(files=[str(bad_file)], inplace=True) as f:
for line_no, line in enumerate(f):
working_line = line
if multiline_string_checker.in_multiline_string(line_no + 1):
print(working_line, end="")
continue
working_line = _split_imports_line(working_line)
print(working_line, end="")


def fix(
exclude: str,
app_import_names: str,
extend_ignore,
file_or_dir,
*_,
aggressive=False,
):
"""Fix basic linter errors and format."""
file_or_dir = file_or_dir or ["."]
if aggressive:
all_files = []
for file_or_dir_ in file_or_dir:
file_path = pathlib.Path(file_or_dir_)
if file_path.is_dir():
all_files.extend(file_path.rglob("*.py"))
else:
all_files.append(file_path)
all_files = filter(
lambda o: not any([fnmatch(o, exclude_) for exclude_ in exclude.split(",")]), all_files
)
for file in all_files:
if not file.is_file(): # doesn't really exist...
continue
_acknowledge_existing_errors.remove_auto_suppressions_from_file(file)
lint_errors_to_process = _acknowledge_existing_errors._utils.lint.get_errors_to_process(
exclude,
app_import_names,
extend_ignore,
[pathlib.Path(file_or_dir_) for file_or_dir_ in file_or_dir],
excluded_errors=[], # we fix black errors, so we don't need to filter it.
)

lint_errors_by_file = defaultdict(list)
for error in lint_errors_to_process:
lint_errors_by_file[pathlib.Path(error.file)].append(error)

failed_files = []
for bad_file, errors_in_file in lint_errors_by_file.items():
errors_in_file: List[_utils.lint.LintError]
try:
_format.format(bad_file)
line_to_codes_mapping = defaultdict(set)
for error in errors_in_file:
# humans talk 1-based, enumerate is 0-based
line_to_codes_mapping[int(error.line) - 1].add(error.code)
_sort_imports(bad_file, app_import_names=app_import_names)
_format.format(bad_file, "--line-length=300") # condense any split lines
_handle_multiple_import_lines(bad_file)
_format.format(bad_file)
remaining_lint_errors_in_file = _utils.lint.get_errors_to_process(
exclude,
app_import_names,
extend_ignore,
[bad_file],
excluded_errors=[],
)
if remaining_lint_errors_in_file and aggressive:
_acknowledge_existing_errors.acknowledge_lint_errors(
exclude=exclude,
app_import_names=app_import_names,
extend_ignore=extend_ignore,
aggressive=aggressive,
file_or_dir=[bad_file],
errors_in_file=remaining_lint_errors_in_file,
)
except AttributeError as e:
failed_files.append((bad_file, e))
if failed_files:
raise Exception(
"Failed to format files:\n"
+ "\n".join([f"{file}: {error}" for file, error in failed_files])
)
2 changes: 1 addition & 1 deletion ni_python_styleguide/_utils/lint.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ class Parser:
"""Lint errors parser."""

__MATCHER = re.compile(
r"^(?P<file>[\w\\/\.\-]+):(?P<line>\d+):(?P<column>\d+): (?P<code>\w+) (?P<explanation>.+)"
r"^(?P<file>[\w\\/\.\-\:]+):(?P<line>\d+):(?P<column>\d+): (?P<code>\w+) (?P<explanation>.+)"
)

@staticmethod
Expand Down
Loading