forked from mozilla/rust-code-analysis
-
Notifications
You must be signed in to change notification settings - Fork 0
/
check-grammar-crate.py
executable file
·311 lines (251 loc) · 9.02 KB
/
check-grammar-crate.py
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
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
#!/usr/bin/env python3
"""check-grammar-crate
This script checks whether breaking changes could be introduced in
rust-code-analysis code after the update of a tree-sitter-grammar crate.
To do so, it compares the differences between the metrics, computed on a
chosen repository, before and after a tree-sitter-grammar update.
To compute metrics:
./check-grammar-crate.py compute-metrics -u REPO_URL -p LOCAL_DIR -l TREE_SITTER_GRAMMAR
NOTE: The compute-metrics subcommand MUST be run on a clean master branch!
To compute metrics on a continuous integration system:
./check-grammar-crate.py compute-ci-metrics -p LOCAL_DIR -l TREE_SITTER_GRAMMAR
To compare metrics and retrieve metrics differences and minimal tests:
1. Install json-minimal-tests from here: https://github.com/Luni-4/json-minimal-tests/releases
./check-grammar-crate.py compare-metrics -l TREE_SITTER_GRAMMAR
NOTE: Add the paths of the software above to the PATH environment variable!
"""
import argparse
import pathlib
import subprocess
import sys
import typing as T
# The /tmp directory will be used as workdir
WORKDIR = pathlib.Path("/tmp")
# Suffix for the directory containing the old metrics
OLD_SUFFIX = "-old"
# Suffix for the directory containing the new metrics
NEW_SUFFIX = "-new"
# Extensions parsed by each tree-sitter-grammar
EXTENSIONS = {
"tree-sitter-tsx": ["*.tsx"],
"tree-sitter-typescript": ["*.ts", "*.jsw", "*.jsmw"],
"tree-sitter-java": ["*.java"],
"tree-sitter-kotlin": ["*.kt", "*.kts"],
"tree-sitter-rust": ["*.rs"],
"tree-sitter-python": ["*.py"],
"tree-sitter-mozjs": ["*.js", "*.js2", "*.jsm", "*.mjs", "*.jsx"],
"tree-sitter-mozcpp": [
"*.cpp",
"*.cx",
"*.cxx",
"*.cc",
"*.hxx",
"*.hpp",
"*.c",
"*.h",
"*.hh",
"*.inc",
"*.mm",
"*.m",
],
}
# Run a subprocess.
def run_subprocess(cmd: str, *args: T.Union[str, pathlib.Path]) -> None:
subprocess.run([cmd, *args])
# Run rust-code-analysis on the chosen repository to compute metrics.
def run_rca(
repo_dir: pathlib.Path,
output_dir: pathlib.Path,
manifest_path: T.Optional[pathlib.Path],
include_grammars: T.List[str],
) -> None:
run_subprocess(
"cargo",
"run",
"--manifest-path",
manifest_path / "Cargo.toml" if manifest_path else "Cargo.toml",
"--release",
"--package",
"rust-code-analysis-cli",
"--",
"--metrics",
"--output-format=json",
"--pr",
"-I",
*include_grammars,
"-p",
repo_dir,
"-o",
output_dir,
)
# Compute continuous integration metrics before and after a
# tree-sitter-grammar update.
def compute_ci_metrics(args: argparse.Namespace) -> None:
if args.grammar != "tree-sitter" and args.grammar not in EXTENSIONS.keys():
print(args.grammar, "is not a valid tree-sitter grammar")
sys.exit(1)
# Use C/C++ files to test if there are any changes in metrics when
# the tree-sitter crate is updated
if args.grammar == "tree-sitter":
grammar = "tree-sitter-mozcpp"
else:
grammar = args.grammar
# Repository passed as input
repo_dir = pathlib.Path(args.path)
# Create rust-code-analysis repository path
rca_path = WORKDIR / "rust-code-analysis"
# Old metrics directory
old_dir = WORKDIR / (args.grammar + OLD_SUFFIX)
# New metrics directory
new_dir = WORKDIR / (args.grammar + NEW_SUFFIX)
# Create output directories
old_dir.mkdir(parents=True, exist_ok=True)
new_dir.mkdir(parents=True, exist_ok=True)
# Git clone rust-code-analysis master branch repository
print(f"Cloning rust-code-analysis master branch into /tmp")
run_subprocess(
"git",
"clone",
"--depth=1",
"-j8",
"https://github.com/mozilla/rust-code-analysis",
rca_path,
)
# Compute old metrics
print("\nComputing metrics before the update and saving them in", old_dir)
run_rca(repo_dir, old_dir, rca_path, EXTENSIONS[grammar])
# Compute new metrics
print("\nComputing metrics after the update and saving them in", new_dir)
run_rca(repo_dir, new_dir, None, EXTENSIONS[grammar])
# Compute metrics before and after a tree-sitter-grammar update.
def compute_metrics(args: argparse.Namespace) -> None:
if args.grammar not in EXTENSIONS.keys():
print(args.grammar, "is not a valid tree-sitter grammar")
sys.exit(1)
# Repository local directory
repo_dir = WORKDIR / args.path
# Old metrics directory
old_dir = WORKDIR / (args.grammar + OLD_SUFFIX)
# New metrics directory
new_dir = WORKDIR / (args.grammar + NEW_SUFFIX)
# Create output directories
old_dir.mkdir(parents=True, exist_ok=True)
new_dir.mkdir(parents=True, exist_ok=True)
# Skip if only new metrics are requested
if not args.only_new:
# Git clone the chosen repository
print(f"Cloning {args.url} into {repo_dir}")
run_subprocess("git", "clone", "--depth=1", args.url, repo_dir)
# Compute old metrics
print("\nComputing metrics before the update and saving them in", old_dir)
run_rca(repo_dir, old_dir, None, EXTENSIONS[args.grammar])
# Create a new branch
print("\nCreate a new branch called", args.grammar)
run_subprocess("git", "checkout", "-B", args.grammar)
# Compute new metrics
print("\nComputing metrics after the update and saving them in", new_dir)
run_rca(repo_dir, new_dir, None, EXTENSIONS[args.grammar])
# Compare metrics and dump the differences whether there are some.
def compare_metrics(args: argparse.Namespace) -> None:
# Old metrics directory
old_dir = WORKDIR / (args.grammar + OLD_SUFFIX)
# New metrics directory
new_dir = WORKDIR / (args.grammar + NEW_SUFFIX)
# Compare metrics directory
compare_dir = WORKDIR / (args.grammar + "-compare")
# Create compare directory
compare_dir.mkdir(parents=True, exist_ok=True)
# Get JSON differences and minimal tests
print("\nSave minimal tests in", compare_dir)
run_subprocess("json-minimal-tests", "-o", compare_dir, old_dir, new_dir)
def main() -> None:
parser = argparse.ArgumentParser(
prog="check-grammar-crate",
description="This tool computes the metrics of a chosen repository "
"before and after a tree-sitter grammar update.",
epilog="The source code of this program can be found on "
"GitHub at https://github.com/mozilla/rust-code-analysis",
)
# Subcommands parsers
commands = parser.add_subparsers(help="Sub-command help")
# Compute metrics command
compute_metrics_cmd = commands.add_parser(
"compute-metrics",
help="Computes the metrics of a chosen repository before and after "
"a tree-sitter grammar update.",
)
# Optional arguments
compute_metrics_cmd.add_argument(
"--only-new",
"-n",
action="store_true",
help="Only compute the metrics after a tree-sitter grammar update",
)
# Arguments
compute_metrics_cmd.add_argument(
"-u",
"--url",
type=str,
required=True,
help="URL of the repository used to compute the metrics",
)
compute_metrics_cmd.add_argument(
"-p",
"--path",
type=str,
required=True,
help="Path where the repository will be saved locally",
)
compute_metrics_cmd.add_argument(
"-g",
"--grammar",
type=str,
required=True,
help="tree-sitter grammar to be updated",
)
compute_metrics_cmd.set_defaults(func=compute_metrics)
# Compute continuous integration metrics command
compute_ci_metrics_cmd = commands.add_parser(
"compute-ci-metrics",
help="Computes the metrics of a chosen repository before and after "
"a tree-sitter grammar update on a continuous integration system.",
)
# Arguments
compute_ci_metrics_cmd.add_argument(
"-p",
"--path",
type=str,
required=True,
help="Path where the rust-code-analysis repository is saved on the "
"continuous integration system",
)
compute_ci_metrics_cmd.add_argument(
"-g",
"--grammar",
type=str,
required=True,
help="tree-sitter grammar to be updated",
)
compute_ci_metrics_cmd.set_defaults(func=compute_ci_metrics)
# Compare metrics command
compare_metrics_cmd = commands.add_parser(
"compare-metrics",
help="Compares the metrics before and after "
"a tree-sitter grammar update in order to discover whether "
"there are differences.",
)
# Arguments
compare_metrics_cmd.add_argument(
"-g",
"--grammar",
type=str,
required=True,
help="tree-sitter grammar used to compare the metrics",
)
compare_metrics_cmd.set_defaults(func=compare_metrics)
# Parse arguments
args = parser.parse_args()
# Call the command
args.func(args)
if __name__ == "__main__":
main()