| # Copyright 2020 The Chromium Authors |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| import argparse |
| import sys |
| import os |
| import re |
| |
| sys.path += [os.path.dirname(os.path.dirname(__file__))] |
| |
| from style_variable_generator.css_generator import CSSStyleGenerator |
| from style_variable_generator.presubmit_support import RunGit |
| |
| |
| # TODO(calamity): extend this checker to find unused C++ variables |
| def FindInvalidCSSVariables(file_to_json_strings, git_runner=RunGit): |
| style_generator = CSSStyleGenerator() |
| css_prefixes = set() |
| referenced_vars = set() |
| for f, json_string in file_to_json_strings.items(): |
| style_generator.AddJSONToModel(json_string, in_file=f) |
| referenced_vars |= set(re.findall(r'\$([a-z_0-9]+)', json_string)) |
| |
| context = style_generator.in_file_to_context.get(f, {}).get('CSS') |
| if (not context or 'prefix' not in context): |
| raise KeyError('This tool only works on files with a CSS prefix.') |
| |
| css_prefixes.add('--' + context['prefix'] + '-') |
| |
| unspecified_file_and_names = [] |
| css_var_names = style_generator.GetCSSVarNames() |
| valid_names = set(css_var_names.keys()) |
| unused = set(css_var_names.values()).difference(referenced_vars) |
| |
| for css_prefix in css_prefixes: |
| grep_result = git_runner([ |
| 'grep', '-on', |
| '\\%s[a-z0-9-]*' % css_prefix, '--', '*.css', '*.html', '*.js' |
| ]).decode('utf-8').splitlines() |
| found_files_and_names = [x.split(':') for x in grep_result] |
| found_names = set() |
| for (filename, line, name) in found_files_and_names: |
| found_names.add(name) |
| if name in valid_names and css_var_names[name] in unused: |
| unused.remove(css_var_names[name]) |
| |
| unspecified = found_names.difference(valid_names) |
| for (filename, line, name) in found_files_and_names: |
| if filename.find('test') != -1: |
| continue |
| |
| if name in unspecified: |
| unspecified_file_and_names.append('%s:%s:%s' % |
| (filename, line, name)) |
| |
| return { |
| 'unspecified': unspecified_file_and_names, |
| # TODO(calamity): This should also account for names referenced in json5 |
| # files. |
| 'unused': unused, |
| 'css_prefix': css_prefix, |
| } |
| |
| |
| def main(): |
| parser = argparse.ArgumentParser( |
| description='''Finds CSS variables in the codebase that are prefixed |
| with |input_files|' CSS prefix but aren't specified in |input_files|.''' |
| ) |
| parser.add_argument('targets', nargs='+', help='source json5 color files', ) |
| args = parser.parse_args() |
| |
| input_files = args.targets |
| |
| file_to_json_strings = {} |
| for input_file in input_files: |
| with open(input_file, 'r') as f: |
| file_to_json_strings[input_file] = f.read() |
| |
| result = FindInvalidCSSVariables(file_to_json_strings) |
| |
| print('Has prefix %s but not in %s:' % (result['css_prefix'], input_files)) |
| for name in sorted(result['unspecified']): |
| print(name) |
| |
| print('\nGenerated by %s but not used in codebase:' % input_files) |
| for name in sorted(result['unused']): |
| print(name) |
| |
| return 0 |
| |
| |
| if __name__ == '__main__': |
| sys.exit(main()) |