blob: e8240d33d4eebc3d4bf943743755021338808ac7 [file] [log] [blame]
Hector Dearman6a62cef2023-11-06 16:07:18 +00001#!/usr/bin/env python3
2# Copyright (C) 2023 The Android Open Source Project
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15"""
16Force the reduction in use of some methods/types over time.
17Often a method ('LEGACY_registerTrackController') or a type ('any')
18gets replaced by a better alternative ('registerTrack', 'unknown') and
19we want to a. replace all existing uses, b. prevent the introduction of
20new uses. This presubmit helps with both. It keeps a count of the
21number of instances of "FOO" in the codebase. At presubmit time we run
22the script. If the "FOO" count has gone up we encourage the author to
23use the alternative. If the "FOO" count has gone down we congratulate
24them and prompt them to reduce the expected count.
25Since the number of "FOO"s can only go down eventually they will all
26be gone - completing the migration.
27See also https://qntm.org/ratchet.
28"""
29
30import sys
31import os
32import re
33import argparse
Hector Dearmandad58f12023-11-09 09:34:13 +000034import collections
35import dataclasses
36
37from dataclasses import dataclass
38
39EXPECTED_ANY_COUNT = 73
40EXPECTED_RUN_METRIC_COUNT = 5
Hector Dearman6a62cef2023-11-06 16:07:18 +000041
42ROOT_DIR = os.path.dirname(
43 os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
44UI_SRC_DIR = os.path.join(ROOT_DIR, 'ui', 'src')
45
Hector Dearmandad58f12023-11-09 09:34:13 +000046
47@dataclasses.dataclass
48class Check:
49 regex: str
50 expected_count: int
51 expected_variable_name: str
52 description: str
53
54
55CHECKS = [
56 # 'any' is too generic. It will show up in many comments etc. So
57 # instead of counting any directly we forbid it using eslint and count
58 # the number of suppressions.
59 Check(r"// eslint-disable-next-line @typescript-eslint/no-explicit-any",
60 EXPECTED_ANY_COUNT, "EXPECTED_ANY_COUNT",
61 "We should avoid using any whenever possible. Prefer unknown."),
62 Check(
63 r"RUN_METRIC\(", EXPECTED_RUN_METRIC_COUNT, "EXPECTED_RUN_METRIC_COUNT",
64 "RUN_METRIC() is not a stable trace_processor API. Use a stdlib function or macro. See https://perfetto.dev/docs/analysis/perfetto-sql-syntax#defining-functions."
65 ),
66]
Hector Dearman6a62cef2023-11-06 16:07:18 +000067
68
69def all_source_files():
70 for root, dirs, files in os.walk(UI_SRC_DIR, followlinks=False):
71 for name in files:
72 if name.endswith('.ts'):
73 yield os.path.join(root, name)
74
75
76def do_check(options):
Hector Dearmandad58f12023-11-09 09:34:13 +000077 c = collections.Counter()
78
Hector Dearman6a62cef2023-11-06 16:07:18 +000079 for path in all_source_files():
80 with open(path) as f:
81 s = f.read()
Hector Dearmandad58f12023-11-09 09:34:13 +000082 for check in CHECKS:
83 count = len(re.findall(check.regex, s))
84 c[check.expected_variable_name] += count
Hector Dearman6a62cef2023-11-06 16:07:18 +000085
Hector Dearmandad58f12023-11-09 09:34:13 +000086 for check in CHECKS:
87 actual_count = c[check.expected_variable_name]
88
89 if actual_count > check.expected_count:
90 print(f'More "{check.regex}" {check.expected_count} -> {actual_count}')
91 print(
92 f' Expected to find {check.expected_count} instances of "{check.regex}" accross the .ts & .d.ts files in the code base.'
93 )
94 print(f' Instead found {actual_count}.')
95 print(
96 f' It it likely your CL introduces additional uses of "{check.regex}".'
97 )
98 print(f' {check.description}')
99 return 1
100 elif actual_count < check.expected_count:
101 print(f'Less "{check.regex}" {check.expected_count} -> {actual_count}')
102 print(
103 f' Congratulations your CL reduces the instances of "{check.regex}" in the code base from {check.expected_count} to {actual_count}.'
104 )
105 print(
106 f' Please go to {__file__} and set {check.expected_variable_name} to {actual_count}.'
107 )
108 return 1
Hector Dearman6a62cef2023-11-06 16:07:18 +0000109
110 return 0
111
112
113def main():
114 parser = argparse.ArgumentParser(description=__doc__)
115 parser.set_defaults(func=do_check)
116 subparsers = parser.add_subparsers()
117
118 check_command = subparsers.add_parser(
119 'check', help='Check the rules (default)')
120 check_command.set_defaults(func=do_check)
121
122 options = parser.parse_args()
123 return options.func(options)
124
125
126if __name__ == '__main__':
127 sys.exit(main())