1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239
|
# 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 filecmp
import os
import re
import subprocess
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 CheckNoAutofillClockTimeCalls(input_api, output_api):
"""Checks that no files call AutofillClock::Now()."""
pattern = input_api.re.compile(r'(AutofillClock::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 AutofillClock::Now() but use ' +
'base::Time::Now(). AutofillClock will be deprecated and deleted soon.',
files) ]
return []
def CheckNoFieldTypeCasts(input_api, output_api):
"""Makes sure that raw integers aren't cast to FieldTypes."""
explanation = """
Do not cast raw integers to FieldType to prevent values that
have no corresponding enum constant or are deprecated. Use
ToSafeFieldType() instead.
Add "// nocheck" to the end of the line to suppress this error."""
errors = []
file_filter = lambda f: (
f.LocalPath().startswith('components/autofill/')
and f.LocalPath().endswith(('.h', '.cc', '.mm'))
)
# There may be a line break in the cast, so we test multiple patterns.
pattern_full = input_api.re.compile(r'_cast<\s*FieldType\b')
pattern_prefix = input_api.re.compile(r'_cast<\s*$')
pattern_postfix = input_api.re.compile(r'^\s*FieldType\b')
for f in input_api.AffectedSourceFiles(file_filter):
contents = f.ChangedContents()
# We look at each line and their successor to check if
# - the line contains the full `static_cast<FieldType>` or similar, or
# - the line ends with `static_cast<` and the next line begins with
# `FieldType` or similar.
for i in range(len(contents)):
line_num = contents[i][0]
line = contents[i][1]
next_line = contents[i+1][1] if i+1 < len(contents) else ''
if line.endswith("// nocheck"):
continue
if next_line.endswith("// nocheck"):
next_line = ''
line = line.split('//')[0]
next_line = next_line.split('//')[0]
if pattern_full.search(line) or (
pattern_prefix.search(line) and pattern_postfix.search(next_line)
):
errors.append(
output_api.PresubmitError(
f'{f.LocalPath()}:{line_num}: {explanation}'
)
)
return errors
def CheckFieldTypeSets(input_api, output_api):
"""Produces errors if the changed code contains DenseSet<FieldType> instead
of FieldType, and similarly for FieldTypeGroupSet and HtmlFieldTypeSet."""
bad_patterns = [
(
input_api.re.compile(r'\bDenseSet<FieldType>'),
'Use FieldTypeSet instead of DenseSet<FieldType>',
),
(
input_api.re.compile(r'\bDenseSet<FieldTypeGroup>'),
'Use FieldTypeGroupSet instead of DenseSet<FieldTypeGroup>',
),
(
input_api.re.compile(r'\bDenseSet<HtmlFieldType>'),
'Use HtmlFieldTypeSet instead of DenseSet<HtmlFieldType>',
),
(
input_api.re.compile(r'\bFieldTypeSet::all()'),
'Use kAllFieldTypes instead of FieldTypeSet::all()',
),
(
input_api.re.compile(r'\bHtmlFieldTypeSet::all()'),
'Use kAllHtmlFieldTypes instead of HtmlFieldTypeSet::all()',
),
]
warnings = []
file_filter = lambda f: (
f.LocalPath().startswith('components/autofill/')
and f.LocalPath().endswith(('.h', '.cc', '.mm'))
)
for file in input_api.AffectedSourceFiles(file_filter):
for line_num, line in file.ChangedContents():
if line.endswith("// nocheck"):
continue
line = line.split('//')[0]
for regex, explanation in bad_patterns:
if regex.search(line):
warnings.append(
output_api.PresubmitError(
f'{file.LocalPath()}:{line_num}: {explanation}. Add '
'"// nocheck" to the end of the line to suppress this error.'
)
)
return warnings
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 whenever the regex transpiler is modified, the golden test files
# are updated to match the new output. This serves as a testing mechanism for
# the transpiler.
def CheckRegexTranspilerGoldenFiles(input_api, output_api):
if not IsComponentsAutofillFileAffected(input_api,
"transpile_regex_patterns.py"):
return []
relative_test_dir = input_api.os_path.join(
"components", "test", "data", "autofill", "regex-transpiler")
test_dir = input_api.os_path.join(
input_api.PresubmitLocalPath(), os.pardir, os.pardir, relative_test_dir)
# Transpiles `test_dir/file_name` into `output_file`.
def transpile(file_name, output_file):
transpiler = input_api.os_path.join(input_api.PresubmitLocalPath(),
"core", "browser", "form_parsing", "transpile_regex_patterns.py")
input_file = input_api.os_path.join(test_dir, file_name)
subprocess.run([input_api.python3_executable, transpiler,
"--input", input_file, "--output", output_file])
# Transpiles `test_name`.in and returns whether it matches `test_name`.out.
def run_test(test_name):
expected_output = input_api.os_path.join(test_dir, test_name + ".out")
with input_api.CreateTemporaryFile() as transpiled_output:
transpile(test_name + ".in", transpiled_output.name)
return filecmp.cmp(transpiled_output.name, expected_output, shallow=False)
tests = [name[:-3] for name in os.listdir(test_dir) if name.endswith(".in")]
if not all(run_test(test) for test in tests):
return [output_api.PresubmitError(
"Regex transpiler golden files don't match. "
"Regenerate the outputs at {}.".format(relative_test_dir))]
return []
|