2
NEWLINE: Checks files to make sure there are no trailing newlines.
10
from typing import List, NamedTuple, Optional
14
LINTER_CODE = "NEWLINE"
17
class LintSeverity(str, Enum):
24
class LintMessage(NamedTuple):
29
severity: LintSeverity
31
original: Optional[str]
32
replacement: Optional[str]
33
description: Optional[str]
36
def check_file(filename: str) -> Optional[LintMessage]:
37
logging.debug("Checking file %s", filename)
39
with open(filename, "rb") as f:
46
if len(lines) == 1 and len(lines[0]) == 1:
53
severity=LintSeverity.ERROR,
54
name="testestTrailing newline",
57
description="Trailing newline found. Run `lintrunner --take NEWLINE -a` to apply changes.",
60
if len(lines[-1]) == 1 and lines[-1][0] == NEWLINE:
62
original = b"".join(lines).decode("utf-8")
63
except Exception as err:
69
severity=LintSeverity.ERROR,
70
name="Decoding failure",
73
description=f"utf-8 decoding failed due to {err.__class__.__name__}:\n{err}",
81
severity=LintSeverity.ERROR,
82
name="Trailing newline",
84
replacement=original.rstrip("\n") + "\n",
85
description="Trailing newline found. Run `lintrunner --take NEWLINE -a` to apply changes.",
88
original_lines: Optional[List[bytes]] = None
89
for idx, line in enumerate(lines):
90
if len(line) >= 2 and line[-1] == NEWLINE and line[-2] == CARRIAGE_RETURN:
92
original_lines = list(lines)
94
lines[idx] = line[:-2] + b"\n"
98
assert original_lines is not None
99
original = b"".join(original_lines).decode("utf-8")
100
replacement = b"".join(lines).decode("utf-8")
101
except Exception as err:
107
severity=LintSeverity.ERROR,
108
name="Decoding failure",
111
description=f"utf-8 decoding failed due to {err.__class__.__name__}:\n{err}",
118
severity=LintSeverity.ERROR,
121
replacement=replacement,
122
description="DOS newline found. Run `lintrunner --take NEWLINE -a` to apply changes.",
128
if __name__ == "__main__":
129
parser = argparse.ArgumentParser(
130
description="native functions linter",
131
fromfile_prefix_chars="@",
136
help="location of native_functions.yaml",
141
help="paths to lint",
144
args = parser.parse_args()
147
format="<%(threadName)s:%(levelname)s> %(message)s",
151
if len(args.filenames) < 1000
157
for filename in args.filenames:
158
lint_message = check_file(filename)
159
if lint_message is not None:
160
lint_messages.append(lint_message)
162
for lint_message in lint_messages:
163
print(json.dumps(lint_message._asdict()), flush=True)