mirror of
https://github.com/pytorch/pytorch.git
synced 2025-10-20 21:14:14 +08:00
Pull Request resolved: https://github.com/pytorch/pytorch/pull/129375 Approved by: https://github.com/malfet
275 lines
7.8 KiB
Python
275 lines
7.8 KiB
Python
"""
|
|
Generic linter that greps for a pattern and optionally suggests replacements.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
import logging
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
import time
|
|
from enum import Enum
|
|
from typing import Any, NamedTuple
|
|
|
|
|
|
IS_WINDOWS: bool = os.name == "nt"
|
|
|
|
|
|
def eprint(*args: Any, **kwargs: Any) -> None:
|
|
print(*args, file=sys.stderr, flush=True, **kwargs)
|
|
|
|
|
|
class LintSeverity(str, Enum):
|
|
ERROR = "error"
|
|
WARNING = "warning"
|
|
ADVICE = "advice"
|
|
DISABLED = "disabled"
|
|
|
|
|
|
class LintMessage(NamedTuple):
|
|
path: str | None
|
|
line: int | None
|
|
char: int | None
|
|
code: str
|
|
severity: LintSeverity
|
|
name: str
|
|
original: str | None
|
|
replacement: str | None
|
|
description: str | None
|
|
|
|
|
|
def as_posix(name: str) -> str:
|
|
return name.replace("\\", "/") if IS_WINDOWS else name
|
|
|
|
|
|
def run_command(
|
|
args: list[str],
|
|
) -> subprocess.CompletedProcess[bytes]:
|
|
logging.debug("$ %s", " ".join(args))
|
|
start_time = time.monotonic()
|
|
try:
|
|
return subprocess.run(
|
|
args,
|
|
capture_output=True,
|
|
)
|
|
finally:
|
|
end_time = time.monotonic()
|
|
logging.debug("took %dms", (end_time - start_time) * 1000)
|
|
|
|
|
|
def lint_file(
|
|
matching_line: str,
|
|
allowlist_pattern: str,
|
|
replace_pattern: str,
|
|
linter_name: str,
|
|
error_name: str,
|
|
error_description: str,
|
|
) -> LintMessage | None:
|
|
# matching_line looks like:
|
|
# tools/linter/clangtidy_linter.py:13:import foo.bar.baz
|
|
split = matching_line.split(":")
|
|
filename = split[0]
|
|
|
|
if allowlist_pattern:
|
|
try:
|
|
proc = run_command(["grep", "-nEHI", allowlist_pattern, filename])
|
|
except Exception as err:
|
|
return LintMessage(
|
|
path=None,
|
|
line=None,
|
|
char=None,
|
|
code=linter_name,
|
|
severity=LintSeverity.ERROR,
|
|
name="command-failed",
|
|
original=None,
|
|
replacement=None,
|
|
description=(
|
|
f"Failed due to {err.__class__.__name__}:\n{err}"
|
|
if not isinstance(err, subprocess.CalledProcessError)
|
|
else (
|
|
"COMMAND (exit code {returncode})\n"
|
|
"{command}\n\n"
|
|
"STDERR\n{stderr}\n\n"
|
|
"STDOUT\n{stdout}"
|
|
).format(
|
|
returncode=err.returncode,
|
|
command=" ".join(as_posix(x) for x in err.cmd),
|
|
stderr=err.stderr.decode("utf-8").strip() or "(empty)",
|
|
stdout=err.stdout.decode("utf-8").strip() or "(empty)",
|
|
)
|
|
),
|
|
)
|
|
|
|
# allowlist pattern was found, abort lint
|
|
if proc.returncode == 0:
|
|
return None
|
|
|
|
original = None
|
|
replacement = None
|
|
if replace_pattern:
|
|
with open(filename) as f:
|
|
original = f.read()
|
|
|
|
try:
|
|
proc = run_command(["sed", "-r", replace_pattern, filename])
|
|
replacement = proc.stdout.decode("utf-8")
|
|
except Exception as err:
|
|
return LintMessage(
|
|
path=None,
|
|
line=None,
|
|
char=None,
|
|
code=linter_name,
|
|
severity=LintSeverity.ERROR,
|
|
name="command-failed",
|
|
original=None,
|
|
replacement=None,
|
|
description=(
|
|
f"Failed due to {err.__class__.__name__}:\n{err}"
|
|
if not isinstance(err, subprocess.CalledProcessError)
|
|
else (
|
|
"COMMAND (exit code {returncode})\n"
|
|
"{command}\n\n"
|
|
"STDERR\n{stderr}\n\n"
|
|
"STDOUT\n{stdout}"
|
|
).format(
|
|
returncode=err.returncode,
|
|
command=" ".join(as_posix(x) for x in err.cmd),
|
|
stderr=err.stderr.decode("utf-8").strip() or "(empty)",
|
|
stdout=err.stdout.decode("utf-8").strip() or "(empty)",
|
|
)
|
|
),
|
|
)
|
|
|
|
return LintMessage(
|
|
path=split[0],
|
|
line=int(split[1]) if len(split) > 1 else None,
|
|
char=None,
|
|
code=linter_name,
|
|
severity=LintSeverity.ERROR,
|
|
name=error_name,
|
|
original=original,
|
|
replacement=replacement,
|
|
description=error_description,
|
|
)
|
|
|
|
|
|
def main() -> None:
|
|
parser = argparse.ArgumentParser(
|
|
description="grep wrapper linter.",
|
|
fromfile_prefix_chars="@",
|
|
)
|
|
parser.add_argument(
|
|
"--pattern",
|
|
required=True,
|
|
help="pattern to grep for",
|
|
)
|
|
parser.add_argument(
|
|
"--allowlist-pattern",
|
|
help="if this pattern is true in the file, we don't grep for pattern",
|
|
)
|
|
parser.add_argument(
|
|
"--linter-name",
|
|
required=True,
|
|
help="name of the linter",
|
|
)
|
|
parser.add_argument(
|
|
"--match-first-only",
|
|
action="store_true",
|
|
help="only match the first hit in the file",
|
|
)
|
|
parser.add_argument(
|
|
"--error-name",
|
|
required=True,
|
|
help="human-readable description of what the error is",
|
|
)
|
|
parser.add_argument(
|
|
"--error-description",
|
|
required=True,
|
|
help="message to display when the pattern is found",
|
|
)
|
|
parser.add_argument(
|
|
"--replace-pattern",
|
|
help=(
|
|
"the form of a pattern passed to `sed -r`. "
|
|
"If specified, this will become proposed replacement text."
|
|
),
|
|
)
|
|
parser.add_argument(
|
|
"--verbose",
|
|
action="store_true",
|
|
help="verbose logging",
|
|
)
|
|
parser.add_argument(
|
|
"filenames",
|
|
nargs="+",
|
|
help="paths to lint",
|
|
)
|
|
args = parser.parse_args()
|
|
|
|
logging.basicConfig(
|
|
format="<%(threadName)s:%(levelname)s> %(message)s",
|
|
level=logging.NOTSET
|
|
if args.verbose
|
|
else logging.DEBUG
|
|
if len(args.filenames) < 1000
|
|
else logging.INFO,
|
|
stream=sys.stderr,
|
|
)
|
|
|
|
files_with_matches = []
|
|
if args.match_first_only:
|
|
files_with_matches = ["--files-with-matches"]
|
|
|
|
try:
|
|
proc = run_command(
|
|
["grep", "-nEHI", *files_with_matches, args.pattern, *args.filenames]
|
|
)
|
|
except Exception as err:
|
|
err_msg = LintMessage(
|
|
path=None,
|
|
line=None,
|
|
char=None,
|
|
code=args.linter_name,
|
|
severity=LintSeverity.ERROR,
|
|
name="command-failed",
|
|
original=None,
|
|
replacement=None,
|
|
description=(
|
|
f"Failed due to {err.__class__.__name__}:\n{err}"
|
|
if not isinstance(err, subprocess.CalledProcessError)
|
|
else (
|
|
"COMMAND (exit code {returncode})\n"
|
|
"{command}\n\n"
|
|
"STDERR\n{stderr}\n\n"
|
|
"STDOUT\n{stdout}"
|
|
).format(
|
|
returncode=err.returncode,
|
|
command=" ".join(as_posix(x) for x in err.cmd),
|
|
stderr=err.stderr.decode("utf-8").strip() or "(empty)",
|
|
stdout=err.stdout.decode("utf-8").strip() or "(empty)",
|
|
)
|
|
),
|
|
)
|
|
print(json.dumps(err_msg._asdict()), flush=True)
|
|
sys.exit(0)
|
|
|
|
lines = proc.stdout.decode().splitlines()
|
|
for line in lines:
|
|
lint_message = lint_file(
|
|
line,
|
|
args.allowlist_pattern,
|
|
args.replace_pattern,
|
|
args.linter_name,
|
|
args.error_name,
|
|
args.error_description,
|
|
)
|
|
if lint_message is not None:
|
|
print(json.dumps(lint_message._asdict()), flush=True)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|