Chromium Code Reviews| Index: presubmit_canned_checks.py |
| diff --git a/presubmit_canned_checks.py b/presubmit_canned_checks.py |
| index 41fc6d1926d1925086a55fe89e7bc86b173b0e22..a7eaf6bf1d9d3ca5aea7a483eb72b14fcf2a2c0b 100644 |
| --- a/presubmit_canned_checks.py |
| +++ b/presubmit_canned_checks.py |
| @@ -4,6 +4,7 @@ |
| """Generic presubmit checks that can be reused by other presubmit checks.""" |
| +import re |
|
M-A Ruel
2012/02/03 16:41:35
No need, input_api.re
Dan Beam
2012/02/04 07:26:22
Ah, OK, will do this in new file.
|
| ### Description checks |
| @@ -855,7 +856,7 @@ def PanProjectChecks(input_api, output_api, |
| r'All rights reserved\.\n' |
| r'.*? Use of this source code is governed by a BSD-style license that ' |
| r'can be\n' |
| - r'.*? found in the LICENSE file\.( \*/)?\n' |
| + r'.*? found in the LICENSE file\.(?: \*/)?\n' |
| ) % { |
| 'year': input_api.time.strftime('%Y'), |
| 'project': project_name, |
| @@ -871,6 +872,8 @@ def PanProjectChecks(input_api, output_api, |
| sources = lambda x: input_api.FilterSourceFile(x, black_list=black_list) |
| text_files = lambda x: input_api.FilterSourceFile( |
| x, black_list=black_list, white_list=white_list) |
| + resources = lambda x: input_api.FilterSourceFile(x, black_list=black_list, |
|
Evan Stade
2012/02/03 04:13:15
# TODO(dbeam): add more folders to this list
Dan Beam
2012/02/03 04:29:18
Will do soon, this is kind of a hacky version (sho
|
| + white_list='.*chrome/browser/resources/(?:options2|ntp4).*\.(?j:cs)s$') |
|
M-A Ruel
2012/02/03 16:41:35
This doesn't belong to canned checks. Then if shou
Dan Beam
2012/02/04 07:26:22
Yeah, I figured this yesterday (that this wasn't t
|
| snapshot_memory = [] |
| def snapshot(msg): |
| @@ -902,6 +905,9 @@ def PanProjectChecks(input_api, output_api, |
| snapshot("checking singletons") |
| results.extend(_CheckSingletonInHeaders( |
| input_api, output_api, source_file_filter=sources)) |
| + snapshot("checking resouces") |
|
Evan Stade
2012/02/03 04:13:15
sp
Dan Beam
2012/02/03 04:29:18
Thank you.
|
| + results.extend(_CheckWebDevStyleGuide( |
| + input_api, output_api, source_file_filter=resources)) |
| # The following checks are only done on commit, since the commit bot will |
| # auto-fix most of these. |
| @@ -920,3 +926,152 @@ def PanProjectChecks(input_api, output_api, |
| input_api, output_api)) |
| snapshot("done") |
| return results |
| + |
| +def _CheckWebDevStyleGuide(input_api, output_api, source_file_filter): |
|
M-A Ruel
2012/02/03 16:41:35
why the underscore?
Dan Beam
2012/02/04 07:26:22
It was just to match local style (all the other fu
|
| + def _collapseable_hex(s): |
| + return (len(s) == 6 and s[0] == s[1] and s[2] == s[3] and s[4] == s[5]) |
| + |
| + def _is_gray(s): |
| + return s[0] == s[1] == s[2] if len(s) == 3 else s[0:2] == s[2:4] == s[4:6] |
| + |
| + def _remove_ats(s): |
| + return re.sub(re.compile(r'@\w+.*?{(.*{.*?})+.*?}', re.DOTALL), '\\1', s) |
| + |
| + def _remove_comments(s): |
| + return re.sub(re.compile(r'/\*.*?\*/', re.DOTALL), '', s) |
| + |
| + def _rgb_from_hex(s): |
| + if len(s) == 3: |
| + r, g, b = s[0] + s[0], s[1] + s[1], s[2] + s[2] |
| + else: |
| + r, g, b = s[0:2], s[2:4], s[4:6] |
| + return 'rgb(%d, %d, %d)' % (int(r, base=16), |
| + int(g, base=16), |
| + int(b, base=16)) |
| + |
| + # Shared between hex_could_be_shorter and rgb_if_not_gray. |
| + hex_reg = r'#([a-fA-F0-9]{3}|[a-fA-F0-9]{6})(?=[^_a-zA-Z0-9-]|$)(?!.+[{,]$)' |
| + |
| + def alphabetize_props(f): |
| + errors = [] |
| + lines = _remove_ats(_remove_comments(input_api.ReadFile(f, 'rb'))) |
| + for block in re.finditer(r'{(.*?)}', lines, re.DOTALL): |
| + rules = filter(lambda l: l.find(': ') >= 0, |
| + map(lambda l: l.strip(), block.group(1).split(';'))[0:-1]) |
| + if rules != sorted(rules): |
| + errors.append('%s:\n %s' % (f, '\n '.join(rules))) |
| + return errors |
| + |
| + def braces_and_colons_have_space_after(f): |
| + errors = [] |
| + lines = _remove_comments(input_api.ReadFile(f, 'rb')).splitlines() |
| + for l in range(0, len(lines)): |
| + if re.search(r'(^\s*{)|(?:{|:(?!\/\/|.*[{,]))\S', lines[l]): |
| + errors.append(' %s:%d:%s' % (f, l, lines[l])) |
| + return errors |
| + |
| + def classes_use_dashes(f): |
| + errors = [] |
| + lines = _remove_comments(input_api.ReadFile(f, 'rb')).splitlines() |
| + for l in range(0, len(lines)): |
| + # Intentionally dumbed down version of CSS2.1 grammar for class without |
| + # non-ASCII, escape chars, or whitespace. |
| + m = re.search(r'(?:^| )\.(-?[_a-zA-Z0-9-]+)', lines[l]) |
| + if m: |
| + c = m.group(1) |
| + if (c.lower() != c or c.find('_') >= 0): |
| + errors.append(' %s:%d:%s' % (f, l, lines[l])) |
| + return errors |
| + |
| + def favor_single_quotes(f): |
| + errors = [] |
| + lines = _remove_comments(input_api.ReadFile(f, 'rb')).splitlines() |
| + for l in range(0, len(lines)): |
| + if lines[l].find('"') >= 0: |
| + errors.append(' %s:%d:%s' % (f, l, lines[l])) |
| + return errors |
| + |
| + def hex_could_be_shorter(f): |
| + errors = [] |
| + lines = input_api.ReadFile(f, 'rb').splitlines() |
| + for l in range(0, len(lines)): |
| + m = re.search(hex_reg, lines[l]) |
| + if (m and _collapseable_hex(m.group(1))): |
| + errors.append(' %s:%d:%s' % (f, l, lines[l])) |
| + return errors |
| + |
| + def one_selector_per_line(f): |
| + errors = [] |
| + lines = _remove_ats(_remove_comments(input_api.ReadFile(f, 'rb')) |
| + ).splitlines() |
| + for l in range(0, len(lines)): |
| + if re.search(r'[^,]+,[^,]+{', lines[l]): |
| + errors.append(' %s:%d:%s' % (f, l, lines[l])) |
| + return errors |
| + |
| + def rgb_if_not_gray(f): |
| + errors = [] |
| + lines = _remove_comments(input_api.ReadFile(f, 'rb')).splitlines() |
| + for l in range(0, len(lines)): |
| + m = re.search(hex_reg, lines[l]) |
| + if (m and not _is_gray(m.group(1))): |
| + rgb = _rgb_from_hex(m.group(1)) |
| + errors.append(' %s:%d:%s /* = %s */ ' % (f, l, lines[l], rgb)) |
| + return errors |
| + |
| + def zero_length_values(f): |
| + errors = [] |
| + zeros = ''.join((r'[^0-9](?:0?\.)?0', |
| + r'(?:px|em|%|in|cm|mm|pc|pt|ex|deg|g?rad|m?s|k?hz)', |
| + r'(?!\s*\{)')) |
| + hsl = r'hsl\([^\)]*(?:[, ]|(?<=\())(?:0?\.?)?0%' |
| + lines = input_api.ReadFile(f, 'rb').splitlines() |
| + for l in range(0, len(lines)): |
| + if (re.search(zeros, lines[l]) and not re.search(hsl, lines[l])): |
| + errors.append(' %s:%d:%s' % (f, l, lines[l])) |
| + return errors |
| + |
| + added_or_modified_files_checks = [ |
| + { 'desc': 'Alphabetize properties, but list vendor specific (i.e. -webkit) ' |
| + 'above standard.', |
| + 'test': alphabetize_props |
| + }, |
| + { 'desc': 'Braces ({} and colons (:) should have a space after them and no ' |
| + 'newlines before them.', |
| + 'test': braces_and_colons_have_space_after, |
| + }, |
| + { 'desc': 'Classes use .dash-form.', |
| + 'test': classes_use_dashes, |
| + }, |
| + { 'desc': 'Use single quotes (\') instead of double quotes (") in strings.', |
| + 'test': favor_single_quotes, |
| + }, |
| + { 'desc': 'Favor short hex form (#rgb) when #rrggbb.', |
| + 'test': hex_could_be_shorter, |
| + }, |
| + { 'desc': 'One selector per line (what not to do: a, b {}).', |
| + 'test': one_selector_per_line, |
| + }, |
| + { 'desc': 'Use rgb() over #hex when not a shade of gray (like #333).', |
| + 'test': rgb_if_not_gray, |
| + }, |
| + { 'desc': 'Make all zero length terms (i.e. 0px) 0 unless inside of hsl()' |
| + 'or @keyframe.', |
| + 'test': zero_length_values, |
| + }, |
| + ] |
| + |
| + errors = [] |
| + files = [f.AbsoluteLocalPath() for f in |
| + input_api.AffectedFiles(include_deletes=False, |
| + file_filter=source_file_filter)] |
| + |
| + # Only look at CSS files for now. |
| + for f in filter(lambda l: l.endswith('.css'), files): |
| + for check in added_or_modified_files_checks: |
| + errs = check['test'](f) |
| + if errs: |
| + msg = '%s\n%s' % (check['desc'], '\n'.join(errs)) |
| + errors.append(output_api.PresubmitNotifyResult(msg)) |
| + |
| + return errors |