web_dev_style: discourage CSS *-left/right properties and values
Instead, suggest equivalent *-start/end values that automatically
flip in RTL. For example, if the presubmit finds something along
the lines of:
margin-left: 5px;
It'll flag it and encourage the directionally-aware:
margin-inline-start: 5px;
Unless annotated with:
margin-left: 5px; /* csschecker-disable-line left-right */
for code that's truly direction agnostic.
R=dpapad@chromium.org
BUG=921226
Change-Id: If649c4eed125af4dfd5cb8b3284dc1c900230847
Reviewed-on: https://chromium-review.googlesource.com/c/1407787
Reviewed-by: Demetrios Papadopoulos <dpapad@chromium.org>
Commit-Queue: Dan Beam <dbeam@chromium.org>
Cr-Commit-Position: refs/heads/master@{#623096}
diff --git a/tools/web_dev_style/css_checker.py b/tools/web_dev_style/css_checker.py
index ec5680ad..da17bd8 100644
--- a/tools/web_dev_style/css_checker.py
+++ b/tools/web_dev_style/css_checker.py
@@ -13,6 +13,10 @@
# TODO(dbeam): Real CSS parser? https://github.com/danbeam/css-py/tree/css3
class CSSChecker(object):
+ DISABLE_PREFIX = 'csschecker-disable'
+ DISABLE_FORMAT = DISABLE_PREFIX + '(-[a-z]+)+ [a-z-]+(-[a-z-]+)*'
+ DISABLE_LINE = DISABLE_PREFIX + '-line'
+
def __init__(self, input_api, output_api, file_filter=None):
self.input_api = input_api
self.output_api = output_api
@@ -31,7 +35,7 @@
def _remove_all(s):
s = _remove_grit(s) # Must be done first.
s = _remove_ats(s)
- s = _remove_comments(s)
+ s = _remove_comments_except_for_disables(s)
s = _remove_mixins_and_valid_vars(s)
s = _remove_template_expressions(s)
return s
@@ -47,8 +51,9 @@
.*?} # stuff up to the first end curly }
""", r'\1', s, flags=re.DOTALL | re.VERBOSE)
- def _remove_comments(s):
- return re.sub(r'/\*.*?\*/', '', s, flags=re.DOTALL)
+ def _remove_comments_except_for_disables(s):
+ return re.sub(r'/\*(?! %s \*/$).*?\*/' % self.DISABLE_FORMAT,'', s,
+ flags=re.DOTALL | re.MULTILINE)
def _remove_grit(s):
return re.sub(r"""
@@ -63,6 +68,11 @@
mixin_or_value = r'({.*?}|[^;}]+);?\s*'
return re.sub(valid_vars + mixin_or_value, '', s, flags=re.DOTALL)
+ def _remove_disable(content, lstrip=False):
+ prefix_reg = ('\s*' if lstrip else '')
+ disable_reg = '/\* %s \*/' % self.DISABLE_FORMAT
+ return re.sub(prefix_reg + disable_reg, '', content, re.MULTILINE)
+
def _remove_template_expressions(s):
return re.sub(r'\$i18n(Raw)?{[^}]*}', '', s, flags=re.DOTALL)
@@ -79,8 +89,9 @@
def alphabetize_props(contents):
errors = []
# TODO(dbeam): make this smart enough to detect issues in mixins.
+ strip_rule = lambda t: _remove_disable(t).strip()
for rule in re.finditer(r'{(.*?)}', contents, re.DOTALL):
- semis = map(lambda t: t.strip(), rule.group(1).split(';'))[:-1]
+ semis = map(strip_rule, rule.group(1).split(';'))[:-1]
rules = filter(lambda r: ': ' in r, semis)
props = map(lambda r: r[0:r.find(':')], rules)
if props != sorted(props):
@@ -176,6 +187,7 @@
return re.search('url\s*\(\s*["\']', line, re.IGNORECASE)
def one_rule_per_line(line):
+ line = _remove_disable(line)
one_rule_reg = re.compile(r"""
[\w-](?<!data): # a rule: but no data URIs
(?!//)[^;]+; # value; ignoring colons in protocols:// and };
@@ -285,9 +297,9 @@
""", re.VERBOSE)
def suggest_unprefixed_logical_axis(line):
- preffix, prop = prefixed_logical_axis_reg.search(line).groups()
+ prefix, prop = prefixed_logical_axis_reg.search(line).groups()
block_or_inline = 'block' if prop == 'height' else 'inline'
- return ' (replace with %s)' % (preffix + block_or_inline + '-size')
+ return ' (replace with %s)' % (prefix + block_or_inline + '-size')
def prefixed_logical_axis(line):
return prefixed_logical_axis_reg.search(line)
@@ -313,6 +325,23 @@
def prefixed_logical_side(line):
return prefixed_logical_side_reg.search(line)
+ _LEFT_RIGHT_REG = '(?:(border|margin|padding)-|(text-align): )' \
+ '(left|right)' \
+ '(?:(-[a-z-^:]+):)?(?!.*/\* %s left-right \*/)' % \
+ self.DISABLE_LINE
+
+ def start_end_instead_of_left_right(line):
+ return re.search(_LEFT_RIGHT_REG, line, re.IGNORECASE)
+
+ def suggest_start_end_from_left_right(line):
+ groups = re.search(_LEFT_RIGHT_REG, line, re.IGNORECASE).groups()
+ prop_start, text_align, left_right, prop_end = groups
+ start_end = {'left': 'start', 'right': 'end'}[left_right]
+ if text_align:
+ return ' (replace with text-align: %s)' % start_end
+ prop = '%s-inline-%s%s' % (prop_start, start_end, prop_end or '')
+ return ' (replace with %s)' % prop
+
def zero_width_lengths(contents):
hsl_reg = re.compile(r"""
hsl\([^\)]* # hsl(maybestuff
@@ -401,6 +430,13 @@
'test': prefixed_logical_side,
'after': suggest_unprefixed_logical_side,
},
+ {
+ 'desc': 'Use -start/end instead of -left/right ' \
+ '(https://goo.gl/gQYY7z, add /* %s left-right */ to ' \
+ 'suppress)' % self.DISABLE_LINE,
+ 'test': start_end_instead_of_left_right,
+ 'after': suggest_start_end_from_left_right,
+ },
{ 'desc': 'Use "0" for zero-width lengths (i.e. 0px -> 0)',
'test': zero_width_lengths,
'multiline': True,
@@ -445,7 +481,7 @@
lines = f[1].splitlines()
for lnum, line in enumerate(lines):
if check['test'](line):
- error = ' ' + line.strip()
+ error = ' ' + _remove_disable(line, lstrip=True).strip()
if 'after' in check:
error += check['after'](line)
check_errors.append(error)
diff --git a/tools/web_dev_style/css_checker_test.py b/tools/web_dev_style/css_checker_test.py
index fa066195..a122afd 100755
--- a/tools/web_dev_style/css_checker_test.py
+++ b/tools/web_dev_style/css_checker_test.py
@@ -133,11 +133,11 @@
def testCssAlphaWithLongerDashedProps(self):
self.VerifyContentsProducesOutput("""
div {
- border-left: 5px; /* A hopefully removed comment. */
+ border-inline-start: 5px; /* A hopefully removed comment. */
border: 5px solid red;
}""", """
- Alphabetize properties and list vendor specific (i.e. -webkit) above standard.
- border-left: 5px;
+ border-inline-start: 5px;
border: 5px solid red;""")
def testCssAlphaWithVariables(self):
@@ -466,6 +466,24 @@
-webkit-padding-end: 3px; (replace with padding-inline-end)
-webkit-padding-start: 4px; (replace with padding-inline-start)""")
+ def testStartEndInsteadOfLeftRight(self):
+ self.VerifyContentsProducesOutput("""
+.inline-node {
+ --var-is-ignored-left: 10px;
+ --var-is-ignored-right: 10px;
+ border-left-color: black;
+ border-right: 1px solid blue; /* csschecker-disable-line left-right */
+ margin-right: 5px;
+ padding-left: 10px; /* csschecker-disable-line some-other-thing */
+ text-align: right;
+}""", """
+- Use -start/end instead of -left/right (https://goo.gl/gQYY7z, add /* csschecker-disable-line left-right */ to suppress)
+ border-left-color: black; (replace with border-inline-start-color)
+ margin-right: 5px; (replace with margin-inline-end)
+ padding-left: 10px; (replace with padding-inline-start)
+ text-align: right; (replace with text-align: end)
+""")
+
def testCssZeroWidthLengths(self):
self.VerifyContentsProducesOutput("""
@-webkit-keyframe anim {