-
Notifications
You must be signed in to change notification settings - Fork 34
/
Copy pathjsonlint
executable file
·166 lines (125 loc) · 4.45 KB
/
jsonlint
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
#!/usr/bin/env python3
# Copyright 2020 The ChromiumOS Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Lint various JSON files."""
import collections
import difflib
import json
import logging
from pathlib import Path
import sys
import libdot
def filter_known_files(paths: list[Path]) -> list[Path]:
"""Figure out what files this linter supports."""
ret = []
for path in paths:
path = Path(path)
if path.suffix[1:] in {"json"} and path.exists():
ret += [path]
return [str(x) for x in ret]
def setup():
"""Initialize the tool settings."""
def check_common(
path: Path, old_data: bytes, new_data: bytes, fix: bool = False
) -> bool:
"""Verify the style is what we want."""
# We want a trailing new line on all text files.
new_data += "\n"
if old_data == new_data:
return True
if fix:
with open(path, "w", encoding="utf-8") as fp:
fp.write(new_data)
return True
diff = difflib.unified_diff(
old_data.splitlines(), new_data.splitlines(), n=2, lineterm=""
)
# Skip the --- line.
next(diff)
# Skip the +++ line.
next(diff)
logging.error("%s: needs reformatting\n%s", path, "\n".join(diff))
return False
def check_generic(path: Path, fix: bool = False) -> bool:
"""Check style on a JSON file."""
with open(path, "r", encoding="utf-8") as fp:
old_data = fp.read()
# We use 2 space tab indents for most files, and we do not require keys be
# sorted so they can be logically grouped.
data = json.loads(old_data, object_pairs_hook=collections.OrderedDict)
new_data = json.dumps(data, ensure_ascii=False, indent=2, sort_keys=False)
return check_common(path, old_data, new_data, fix=fix)
def check_messages(path: Path, fix: bool = False) -> bool:
"""Check style on a messages.json file."""
with open(path, "r", encoding="utf-8") as fp:
old_data = fp.read()
# We use tabs for messages.json to save a little on disk, and keep the keys
# sorted as this content is mostly machine managed.
data = json.loads(old_data)
new_data = json.dumps(data, ensure_ascii=False, indent="\t", sort_keys=True)
return check_common(path, old_data, new_data, fix=fix)
def check_package(path: Path, fix: bool = False) -> bool:
"""Check style on a package.json file."""
with open(path, "r", encoding="utf-8") as fp:
old_data = fp.read()
new_data = libdot.sync_package_json.format_data(path, old_data)
return check_common(path, old_data, new_data, fix=fix)
def check_files(paths: list[Path], fix: bool = False) -> bool:
"""Check all the JSON files."""
ret = True
for path in paths:
path = Path(path)
if not path.exists():
logging.error("%s: file does not exist", path)
ret = False
continue
logging.debug("Checking %s", path)
if path.name == "messages.json":
ret &= check_messages(path, fix=fix)
elif path.name == "package.json":
ret &= check_package(path, fix=fix)
else:
ret &= check_generic(path, fix=fix)
return ret
def run(argv=(), **kwargs):
"""Run the tool directly."""
setup()
logging.info("Linting JSON files %s", libdot.cmdstr(argv))
return check_files(argv, **kwargs)
def perform(
argv=(), paths=(), fix=False, gerrit_comments_file=None
): # pylint: disable=unused-argument
"""Run high level tool logic."""
argv = list(argv)
paths = list(paths)
# TODO(vapier): Add support for Gerrit comments.
return check_files(argv + paths, fix=fix)
def get_parser():
"""Get a command line parser."""
parser = libdot.ArgumentParser(description=__doc__, short_options=False)
parser.add_argument(
"--fix", action="store_true", help="Autofix format errors."
)
parser.add_argument(
"--gerrit-comments-file",
help="Save errors for posting files to Gerrit.",
)
parser.add_argument("paths", nargs="*", help="Paths to lint.")
return parser
def main(argv):
"""The main func!"""
parser = get_parser()
opts, args = parser.parse_known_args(argv)
return (
0
if perform(
argv=args,
paths=opts.paths,
fix=opts.fix,
gerrit_comments_file=opts.gerrit_comments_file,
)
else 1
)
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))