chromium/ui/chromeos/styles/presubmit_support.py

# 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)
    ]