|  | #!/usr/bin/env python3 | 
|  | # Copyright (C) 2023 The Android Open Source Project | 
|  | # | 
|  | # Licensed under the Apache License, Version 2.0 (the "License"); | 
|  | # you may not use this file except in compliance with the License. | 
|  | # You may obtain a copy of the License at | 
|  | # | 
|  | #      http://www.apache.org/licenses/LICENSE-2.0 | 
|  | # | 
|  | # Unless required by applicable law or agreed to in writing, software | 
|  | # distributed under the License is distributed on an "AS IS" BASIS, | 
|  | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
|  | # See the License for the specific language governing permissions and | 
|  | # limitations under the License. | 
|  | """ | 
|  | Force the reduction in use of some methods/types over time. | 
|  | Often a method ('LEGACY_registerTrackController') or a type ('any') | 
|  | gets replaced by a better alternative ('registerTrack', 'unknown') and | 
|  | we want to a. replace all existing uses, b. prevent the introduction of | 
|  | new uses. This presubmit helps with both. It keeps a count of the | 
|  | number of instances of "FOO" in the codebase. At presubmit time we run | 
|  | the script. If the "FOO" count has gone up we encourage the author to | 
|  | use the alternative. If the "FOO" count has gone down we congratulate | 
|  | them and prompt them to reduce the expected count. | 
|  | Since the number of "FOO"s can only go down eventually they will all | 
|  | be gone - completing the migration. | 
|  | See also https://qntm.org/ratchet. | 
|  | """ | 
|  |  | 
|  | import sys | 
|  | import os | 
|  | import re | 
|  | import argparse | 
|  | import collections | 
|  | import dataclasses | 
|  |  | 
|  | from dataclasses import dataclass | 
|  |  | 
|  | EXPECTED_ANY_COUNT = 73 | 
|  | EXPECTED_RUN_METRIC_COUNT = 5 | 
|  |  | 
|  | ROOT_DIR = os.path.dirname( | 
|  | os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) | 
|  | UI_SRC_DIR = os.path.join(ROOT_DIR, 'ui', 'src') | 
|  |  | 
|  |  | 
|  | @dataclasses.dataclass | 
|  | class Check: | 
|  | regex: str | 
|  | expected_count: int | 
|  | expected_variable_name: str | 
|  | description: str | 
|  |  | 
|  |  | 
|  | CHECKS = [ | 
|  | # 'any' is too generic. It will show up in many comments etc. So | 
|  | # instead of counting any directly we forbid it using eslint and count | 
|  | # the number of suppressions. | 
|  | Check(r"// eslint-disable-next-line @typescript-eslint/no-explicit-any", | 
|  | EXPECTED_ANY_COUNT, "EXPECTED_ANY_COUNT", | 
|  | "We should avoid using any whenever possible. Prefer unknown."), | 
|  | Check( | 
|  | r"RUN_METRIC\(", EXPECTED_RUN_METRIC_COUNT, "EXPECTED_RUN_METRIC_COUNT", | 
|  | "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." | 
|  | ), | 
|  | ] | 
|  |  | 
|  |  | 
|  | def all_source_files(): | 
|  | for root, dirs, files in os.walk(UI_SRC_DIR, followlinks=False): | 
|  | for name in files: | 
|  | if name.endswith('.ts'): | 
|  | yield os.path.join(root, name) | 
|  |  | 
|  |  | 
|  | def do_check(options): | 
|  | c = collections.Counter() | 
|  |  | 
|  | for path in all_source_files(): | 
|  | with open(path) as f: | 
|  | s = f.read() | 
|  | for check in CHECKS: | 
|  | count = len(re.findall(check.regex, s)) | 
|  | c[check.expected_variable_name] += count | 
|  |  | 
|  | for check in CHECKS: | 
|  | actual_count = c[check.expected_variable_name] | 
|  |  | 
|  | if actual_count > check.expected_count: | 
|  | print(f'More "{check.regex}" {check.expected_count} -> {actual_count}') | 
|  | print( | 
|  | f'  Expected to find {check.expected_count} instances of "{check.regex}" accross the .ts & .d.ts files in the code base.' | 
|  | ) | 
|  | print(f'  Instead found {actual_count}.') | 
|  | print( | 
|  | f'  It it likely your CL introduces additional uses of "{check.regex}".' | 
|  | ) | 
|  | print(f'  {check.description}') | 
|  | return 1 | 
|  | elif actual_count < check.expected_count: | 
|  | print(f'Less "{check.regex}" {check.expected_count} -> {actual_count}') | 
|  | print( | 
|  | f'  Congratulations your CL reduces the instances of "{check.regex}" in the code base from {check.expected_count} to {actual_count}.' | 
|  | ) | 
|  | print( | 
|  | f'  Please go to {__file__} and set {check.expected_variable_name} to {actual_count}.' | 
|  | ) | 
|  | return 1 | 
|  |  | 
|  | return 0 | 
|  |  | 
|  |  | 
|  | def main(): | 
|  | parser = argparse.ArgumentParser(description=__doc__) | 
|  | parser.set_defaults(func=do_check) | 
|  | subparsers = parser.add_subparsers() | 
|  |  | 
|  | check_command = subparsers.add_parser( | 
|  | 'check', help='Check the rules (default)') | 
|  | check_command.set_defaults(func=do_check) | 
|  |  | 
|  | options = parser.parse_args() | 
|  | return options.func(options) | 
|  |  | 
|  |  | 
|  | if __name__ == '__main__': | 
|  | sys.exit(main()) |