• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
2# Copyright (C) 2024 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
16import argparse
17import os
18import sys
19import subprocess
20import fnmatch
21
22UI_DIR = os.path.abspath(os.path.dirname(__file__))
23ROOT_DIR = os.path.dirname(UI_DIR)
24PRETTIER_PATH = os.path.join(ROOT_DIR, 'ui/node_modules/.bin/prettier')
25ESLINT_PATH = os.path.join(ROOT_DIR, 'ui/node_modules/.bin/eslint')
26
27def glob_any(filename, patterns):
28  for pattern in patterns:
29    if fnmatch.fnmatch(filename, pattern):
30      return True
31  return False
32
33
34def main():
35  parser = argparse.ArgumentParser()
36  parser.add_argument('--check-only', action='store_true')
37  parser.add_argument('--no-prettier', action='store_true')
38  parser.add_argument('--no-eslint', action='store_true')
39  parser.add_argument(
40      '--all',
41      action='store_true',
42      help='Prettify all .ts sources, not just the changed ones')
43  parser.add_argument('filelist', nargs='*')
44  args = parser.parse_args()
45
46  # We want to execute all the commands relative to UI_DIR, because eslint looks
47  # for eslintrc in cwd. However, the user might pass paths that are relative to
48  # the current cwd, which might be != UI_DIR.
49  # So before running chdir relativize all the passed paths to UI_DIR
50  filelist = [
51      os.path.relpath(os.path.abspath(x), UI_DIR) for x in args.filelist
52  ]
53  os.chdir(UI_DIR)
54
55  # Need to add 'node' to the search path.
56  os.environ['PATH'] += os.pathsep + os.path.join(ROOT_DIR, 'tools')
57
58  if not os.path.exists(PRETTIER_PATH):
59    print('Cannot find %s' % PRETTIER_PATH)
60    print('Run tools/install-build-deps --ui')
61    return 1
62
63  with open('.prettierignore', 'r') as f:
64    ignorelist = set(f.read().strip().split('\n'))
65
66  all_files = set()
67  for root, _dirs, files in os.walk('src'):
68    for file in files:
69      file_path = os.path.join(root, file)
70      if glob_any(file_path, ignorelist):
71        continue
72      if os.path.splitext(file)[1].lower() in ['.ts', '.js', '.scss']:
73        all_files.add(file_path)
74
75  files_to_check = []
76  git_cmd = []
77  if args.all:
78    files_to_check = list(all_files)
79  elif filelist:
80    files_to_check = filelist
81  else:
82    upstream_branch = get_upstream_branch()
83    git_cmd = ['git', 'diff', '--name-only', upstream_branch]
84    git_output = subprocess.check_output(git_cmd, text=True).strip()
85    changed_files = set(git_output.split('\n') if git_output else [])
86    changed_files = [os.path.relpath(x, 'ui') for x in changed_files]
87    files_to_check = all_files.intersection(changed_files)
88
89  prettier_args = ['--log-level=warn']
90  eslint_args = []
91  if args.check_only:
92    prettier_args += ['--check']
93  else:
94    eslint_args += ['--fix']
95    prettier_args += ['--write']
96
97  if len(files_to_check) == 0:
98    if not args.check_only:
99      # Be quiet when invoked by git cl presubmit.
100      print('No changed files detected by `%s`' % ' '.join(git_cmd))
101      print('Pass --all to prettify all ts/js/scss files in the repo')
102    return 0
103
104  # Run prettier first
105  if not args.no_prettier:
106    print('Running prettier on %d files' % len(files_to_check))
107    call_or_die([PRETTIER_PATH] + prettier_args + list(files_to_check))
108
109  # Then run eslint (but not on .scss)
110  if not args.no_eslint:
111    ts_js_only = lambda f: f.endswith('.ts') or f.endswith('.js')
112    files_to_check = list(filter(ts_js_only, files_to_check))
113    if len(files_to_check) > 0:
114      print('Running eslint on %d files' % len(files_to_check))
115      call_or_die([ESLINT_PATH] + eslint_args + files_to_check)
116
117
118# Like subprocess.check_call, but in case of errors dies without printing a
119# useless stacktrace that just muddies the stdout.
120def call_or_die(cmd):
121  try:
122    subprocess.check_call(cmd)
123  except subprocess.CalledProcessError as ex:
124    print('`%s` returned %d' % (' '.join(cmd)[:128], ex.returncode))
125    sys.exit(ex.returncode)
126
127
128def get_upstream_branch():
129  try:
130    cmd = ['git', 'rev-parse', '--abbrev-ref', '--symbolic-full-name', '@{u}']
131    res = subprocess.check_output(cmd, text=True, stderr=subprocess.DEVNULL)
132    return res.strip()
133  except subprocess.CalledProcessError:
134    return 'origin/main'
135
136
137if __name__ == '__main__':
138  sys.exit(main())
139