chromium/components/autofill/PRESUBMIT.py

# 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.

"""Chromium presubmit script for src/components/autofill.

See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts
for more details on the presubmit API built into depot_tools.
"""

PRESUBMIT_VERSION = '2.0.0'

import re

def IsComponentsAutofillFile(f, name_suffix):
  # The exact path can change. Only check the containing folder.
  return (f.LocalPath().startswith('components/autofill/') and
          f.LocalPath().endswith(name_suffix))

def AnyAffectedFileMatches(input_api, matcher):
  return any(matcher(f) for f in input_api.change.AffectedTestableFiles())

def IsComponentsAutofillFileAffected(input_api, name_suffix):
  return AnyAffectedFileMatches(
      input_api, lambda f: IsComponentsAutofillFile(f, name_suffix))

def CheckNoBaseTimeCalls(input_api, output_api):
  """Checks that no files call base::Time::Now()."""
  pattern = input_api.re.compile(r'(base::Time::Now)\(\)')
  files = []
  for f in input_api.AffectedSourceFiles(input_api.FilterSourceFile):
    if (f.LocalPath().startswith('components/autofill/') and
        not f.LocalPath().endswith("PRESUBMIT.py")):
      if any(pattern.search(line) for _, line in f.ChangedContents()):
          files.append(f)

  if len(files):
    return [ output_api.PresubmitPromptWarning(
        'Consider to not call base::Time::Now() directly but use ' +
        'AutofillClock::Now(). This clock can be manipulated through ' +
        'TestAutofillClock for testing purposes, and using AutofillClock and ' +
        'throughout Autofill code makes sure Autofill tests refers to the '+
        'same (potentially manipulated) clock.',
        files) ]
  return []

def CheckNoFieldTypeCasts(input_api, output_api):
  """Checks that no files cast (e.g., raw integers to) FieldTypes."""
  pattern = input_api.re.compile(
      r'_cast<\s*FieldType\b',
      input_api.re.MULTILINE)
  files = []
  for f in input_api.AffectedSourceFiles(input_api.FilterSourceFile):
    if (f.LocalPath().startswith('components/autofill/') and
        not f.LocalPath().endswith("PRESUBMIT.py")):
      contents = input_api.ReadFile(f)
      if pattern.search(contents):
        files.append(f)

  if len(files):
    return [ output_api.PresubmitPromptWarning(
        'Do not cast raw integers to FieldType to prevent values that ' +
        'have no corresponding enum constant or are deprecated. Use '+
        'ToSafeFieldType() instead.',
        files) ]
  return []

def CheckFeatureNames(input_api, output_api):
  """Checks that no features are enabled."""

  pattern = input_api.re.compile(
          r'\bBASE_FEATURE\s*\(\s*k(\w*)\s*,\s*"(\w*)"',
          input_api.re.MULTILINE)
  warnings = []

  for f in input_api.AffectedSourceFiles(input_api.FilterSourceFile):
    if IsComponentsAutofillFile(f, 'features.cc'):
      contents = input_api.ReadFile(f)
      mismatches = [(constant, feature)
              for (constant, feature) in pattern.findall(contents)
              if constant != feature]
      if mismatches:
        mismatch_strings = ['\t{} -- {}'.format(*m) for m in mismatches]
        mismatch_string = format('\n').join(mismatch_strings)
        warnings += [ output_api.PresubmitPromptWarning(
            'Feature names should be identical to variable names:\n{}'
                .format(mismatch_string),
            [f]) ]

  return warnings

def CheckWebViewExposedExperiments(input_api, output_api):
  """Checks that changes to autofill features are exposed to webview."""

  _PRODUCTION_SUPPORT_FILE = ('android_webview/java/src/org/chromium/' +
      'android_webview/common/ProductionSupportedFlagList.java')

  warnings = []
  if (IsComponentsAutofillFileAffected(input_api, 'features.cc') and
      not AnyAffectedFileMatches(
          input_api, lambda f: f.LocalPath() == _PRODUCTION_SUPPORT_FILE)):
    warnings += [
        output_api.PresubmitPromptWarning(
            (
                'You may need to modify {} instructions if your feature affects'
                ' WebView.'
            ).format(_PRODUCTION_SUPPORT_FILE)
        )
    ]

  return warnings

def CheckModificationOfLegacyRegexPatterns(input_api, output_api):
  """Reminds to update internal regex patterns when legacy ones are modified."""

  if IsComponentsAutofillFileAffected(input_api, "legacy_regex_patterns.json"):
    return [
        output_api.PresubmitPromptWarning(
            "You may need to modify the parsing patterns in src-internal. " +
            "See go/autofill-internal-parsing-patterns for more details. " +
            "Ideally, the legacy patterns should not be modified.")
    ]

  return []

def CheckModificationOfFormAutofillUtil(input_api, output_api):
  """Reminds to keep form_autofill_util.cc and the iOS counterpart in sync."""

  if (IsComponentsAutofillFileAffected(input_api, "fill.ts") !=
      IsComponentsAutofillFileAffected(input_api, "form_autofill_util.cc")):
    return [
        output_api.PresubmitNotifyResult(
            'Form extraction/label inference has a separate iOS ' +
            'implementation in components/autofill/ios/form_util/resources/' +
            'fill.ts. Try to keep it in sync with form_autofill_util.cc.')
    ]

  return []

# Checks that UniqueRendererForm(Control)Id() is not used and suggests to use
# form_util::Get(Form|Field)RendererId() instead.
def CheckNoUsageOfUniqueRendererId(
        input_api, output_api):
  autofill_files_pattern = re.compile(
      r'(autofill|password_manager).*\.(mm|cc|h)')
  special_file = re.compile(r'form_autofill_util.cc')
  concerned_files = [(f, input_api.ReadFile(f))
                     for f in input_api.AffectedFiles(include_deletes=False)
                     if autofill_files_pattern.search(f.LocalPath())]

  warning_files = []
  renderer_id_call = re.compile(
      r'\.UniqueRendererForm(Control)?Id', re.MULTILINE)
  for autofill_file, file_content in concerned_files:
    allowed_matches = 2 if special_file.search(autofill_file.LocalPath()) else 0
    matches = re.finditer(renderer_id_call, file_content)
    if (len(list(matches)) > allowed_matches):
      warning_files.append(autofill_file)

  return [output_api.PresubmitError(
      'Do not use (Form|Field)RendererId(*.UniqueRendererForm(Control)?Id()). '
      'Consider using form_util::Get(Form|Field)RendererId(*) instead.',
      warning_files)] if len(warning_files) else []