# Copyright 2021 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
def _CheckSemanticColors(input_api, output_api):
"""Check that any colors present in HTML, CSS or JS files that are CSS
variables and start with a known CSS context (present in the ChromeOS root
semantic stylesheet) are valid.
NOTE: Calculating the valid CSS variables is an expensive operation so a
first attempt is made to ensure lines affected have a var() occurrence which
indicates we need to verify the variable.
"""
file_filter = lambda f: input_api.FilterSourceFile(
f, files_to_check=(r'.+\.css', r'.+\.htm(l){1,}$', r'.+\.js$'),
files_to_skip=[r'.*test.*'])
affected_files = input_api.AffectedFiles(include_deletes=False,
file_filter=file_filter)
if not affected_files:
return []
# Ensure the tools/ path is available to import style_variable_generator.
input_api.sys.path.append(
input_api.os_path.join(input_api.change.RepositoryRoot(), 'tools'))
from style_variable_generator.css_generator import CSSStyleGenerator
style_root = [
input_api.change.RepositoryRoot(), 'ui', 'chromeos', 'styles'
]
def ExtractPrefixesAndVariables(file_set):
# Identify the CSS variable prefixes currently in use by the semantic
# stylesheets.
css_prefixes = set()
style_generator = CSSStyleGenerator()
style_generator.AddJSONFilesToModel(cros_styles)
for file_path in cros_styles:
context = style_generator.in_file_to_context.get(file_path,
{}).get('CSS')
if (not context or 'prefix' not in context):
continue
css_prefixes.add('--' + context['prefix'] + '-')
valid_css_variables = style_generator.GetCSSVarNames()
return css_prefixes, valid_css_variables
cros_styles = [
input_api.os_path.join(*style_root, 'cros_colors.json5'),
input_api.os_path.join(*style_root, 'cros_palette.json5'),
input_api.os_path.join(*style_root, 'cros_ref_colors.json5'),
input_api.os_path.join(*style_root, 'cros_shadows.json5'),
input_api.os_path.join(*style_root, 'cros_sys_colors.json5'),
input_api.os_path.join(*style_root, 'cros_typography.json5'),
input_api.os_path.join(*style_root, 'cros_gm3_shadows.json5'),
]
cros_prefixes, cros_variables = ExtractPrefixesAndVariables(cros_styles)
gm3_styles = [
input_api.os_path.join(*style_root, 'cros_gm3_typography.json5'),
]
gm3_prefixes, gm3_variables = ExtractPrefixesAndVariables(gm3_styles)
cros_variables.update(gm3_variables)
valid_css_variables = cros_variables
css_prefixes = cros_prefixes | gm3_prefixes
css_regex = input_api.re.compile('(' + '|'.join(css_prefixes) +
'.+?)[^-a-zA-Z0-9_]+')
def FindInvalidCSSVariables(file_path, line_num, input_line):
"""Find invalid usages of CSS variables on |input_line| and return
output that is formatted nicely for a PRESUBMIT warning.
"""
css_variable_matches = input_api.re.findall(css_regex, input_line)
if not css_variable_matches:
return []
invalid_matches = []
for potential_invalid_match in css_variable_matches:
if potential_invalid_match not in valid_css_variables:
invalid_matches.append(
'%s:%d: %s' %
(file_path, line_num, potential_invalid_match))
return invalid_matches
invalid_variables = []
for f in affected_files:
for line_num, line in f.ChangedContents():
if any(prefix in line for prefix in css_prefixes):
invalid_variables.extend(
FindInvalidCSSVariables(f.LocalPath(), line_num, line))
if not invalid_variables:
return []
return [
output_api.PresubmitPromptWarning('CSS variables identified that ' +
'are not present in the root ' +
'ChromeOS semantic stylesheet.',
items=invalid_variables)
]