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