9
from typing import List, NamedTuple, Optional
12
LINTER_CODE = "SHELLCHECK"
15
class LintSeverity(str, Enum):
22
class LintMessage(NamedTuple):
27
severity: LintSeverity
29
original: Optional[str]
30
replacement: Optional[str]
31
description: Optional[str]
36
) -> "subprocess.CompletedProcess[bytes]":
37
logging.debug("$ %s", " ".join(args))
38
start_time = time.monotonic()
40
return subprocess.run(
45
end_time = time.monotonic()
46
logging.debug("took %dms", (end_time - start_time) * 1000)
51
) -> List[LintMessage]:
54
["shellcheck", "--external-sources", "--format=json1"] + files
56
except OSError as err:
63
severity=LintSeverity.ERROR,
64
name="command-failed",
67
description=(f"Failed due to {err.__class__.__name__}:\n{err}"),
70
stdout = str(proc.stdout, "utf-8").strip()
71
results = json.loads(stdout)["comments"]
75
name=f"SC{result['code']}",
76
description=result["message"],
78
char=result["column"],
80
severity=LintSeverity.ERROR,
88
if __name__ == "__main__":
89
parser = argparse.ArgumentParser(
90
description="shellcheck runner",
91
fromfile_prefix_chars="@",
99
if shutil.which("shellcheck") is None:
100
err_msg = LintMessage(
105
severity=LintSeverity.ERROR,
106
name="command-failed",
109
description="shellcheck is not installed, did you forget to run `lintrunner init`?",
111
print(json.dumps(err_msg._asdict()), flush=True)
114
args = parser.parse_args()
116
lint_messages = check_files(args.filenames)
117
for lint_message in lint_messages:
118
print(json.dumps(lint_message._asdict()), flush=True)