linter: Add support for automatic checking for 4 space indents in CSS.

In this commit we modify our CSS parser not only to render the text from
a given CSS tokens produced but also enforce 4 space indentation on it.
Also we enforce some basic rules we would like our CSS to follow such as
* Always have "\n" in between the starting of body({) and body itself
  and ending of the body and the closing of body(}).
* Use 4 space indents while having but something within the block
  structure ( { .... } ).
* Have single space after ',' in between multiple selectors.
* Have only a single space in between selector and the starting of
  block structure ({ ... }) if block structure starts on same line as
  of selector.
  eg. body {
          body content here
      }
  Notice single space between 'body' and '{'.

Fixes: #1659.
This commit is contained in:
adnrs96 2017-03-17 17:04:45 +05:30 committed by Tim Abbott
parent 257187a239
commit b3cbb13a79
3 changed files with 158 additions and 17 deletions

View File

@ -16,9 +16,9 @@ def validate(fn):
text = open(fn).read()
section_list = parse(text)
if text != section_list.text():
print('BOO! %s broken' % (fn,))
open('foo.txt', 'w').write(section_list.text())
os.system('diff %s foo.txt' % (fn,))
print('%s seems to be broken:' % (fn,))
open('/var/tmp/pretty_css.txt', 'w').write(section_list.text())
os.system('diff %s /var/tmp/pretty_css.txt' % (fn,))
sys.exit(1)
def check_our_files(filenames):

View File

@ -257,6 +257,99 @@ def parse_value(tokens, start, end):
post_fluff=post_fluff,
)
def handle_prefluff(pre_fluff, indent=False):
# type: (str, bool) -> str
pre_fluff_lines = pre_fluff.split('\n')
formatted_pre_fluff_lines = []
comment_indent = ''
general_indent = ''
if indent:
general_indent = ' '
for i, ln in enumerate(pre_fluff_lines):
line_indent = ''
if ln.strip() != '':
if not i:
line_indent = general_indent
comment_indent = ' '
else:
if comment_indent:
if ('*/' in ln or '*' in ln) and (ln.strip()[:2] in ('*/', '* ', '*')):
line_indent = general_indent
if '*/' in ln:
comment_indent = ''
else:
line_indent = general_indent + comment_indent
else:
line_indent = general_indent
comment_indent = ' '
elif len(pre_fluff_lines) == 1 and indent and ln != '':
line_indent = ' '
formatted_pre_fluff_lines.append(line_indent + ln.strip())
if formatted_pre_fluff_lines[-1] != '':
if formatted_pre_fluff_lines[-1].strip() == '' and indent:
formatted_pre_fluff_lines[-1] = ''
formatted_pre_fluff_lines.append('')
pre_fluff = '\n'.join(formatted_pre_fluff_lines)
res = ''
if indent:
if '\n' in pre_fluff:
res = pre_fluff + ' '
elif pre_fluff == '':
res = ' '
else:
res = pre_fluff.rstrip() + ' '
else:
res = pre_fluff
return res
def handle_postfluff(post_fluff, indent=False, space_after_first_line=False):
# type: (str, bool, bool) -> str
post_fluff_lines = post_fluff.split('\n')
formatted_post_fluff_lines = []
comment_indent = ''
general_indent = ''
if indent:
general_indent = ' '
for i, ln in enumerate(post_fluff_lines):
line_indent = ''
if ln.strip() != '':
if i:
if comment_indent:
if ('*/' in ln or '*' in ln) and (ln.strip()[:2] in ('*/', '* ', '*')):
line_indent = general_indent
if '*/' in ln:
comment_indent = ''
else:
line_indent = general_indent + comment_indent
else:
line_indent = general_indent
comment_indent = ' '
elif indent and not i and len(post_fluff_lines) > 2:
formatted_post_fluff_lines.append('')
line_indent = general_indent
comment_indent = ' '
elif space_after_first_line:
line_indent = ' '
if not i:
comment_indent = ' '
elif not i:
comment_indent = ' '
formatted_post_fluff_lines.append(line_indent + ln.strip())
if len(formatted_post_fluff_lines) == 1 and not space_after_first_line:
if formatted_post_fluff_lines[-1].strip() == '':
if formatted_post_fluff_lines[-1] != '':
formatted_post_fluff_lines[-1] = ' '
else:
formatted_post_fluff_lines.append('')
elif formatted_post_fluff_lines[-1].strip() == '':
formatted_post_fluff_lines[-1] = ''
if len(formatted_post_fluff_lines) == 1 and indent:
formatted_post_fluff_lines.append('')
elif space_after_first_line:
formatted_post_fluff_lines.append('')
post_fluff = '\n'.join(formatted_post_fluff_lines)
return post_fluff
#### Begin CSS classes here
@ -286,7 +379,14 @@ class CssNestedSection(object):
res += self.pre_fluff
res += self.selector_list.text()
res += '{'
res += self.section_list.text()
section_list_lines = self.section_list.text().split('\n')
formatted_section_list = []
for ln in section_list_lines:
if ln.strip() == '':
formatted_section_list.append('')
else:
formatted_section_list.append(' ' + ln)
res += '\n'.join(formatted_section_list)
res += '}'
res += self.post_fluff
return res
@ -303,10 +403,10 @@ class CssSection(object):
def text(self):
# type: () -> str
res = ''
res += self.pre_fluff
res += handle_prefluff(self.pre_fluff)
res += self.selector_list.text()
res += self.declaration_block.text()
res += self.post_fluff
res += handle_postfluff(self.post_fluff, space_after_first_line=True)
return res
class CssSelectorList(object):
@ -317,7 +417,16 @@ class CssSelectorList(object):
def text(self):
# type: () -> str
res = ','.join(sel.text() for sel in self.selectors)
res = ''
for i, sel in enumerate(self.selectors):
sel_list_render = sel.text()
if i != 0 and sel_list_render[0] != '\n':
res += ' '
res += sel_list_render
if i != len(self.selectors) - 1:
res += ','
if res[-1] != ' ' and res[-1] != '\n':
res += ' '
return res
class CssSelector(object):
@ -331,9 +440,9 @@ class CssSelector(object):
def text(self):
# type: () -> str
res = ''
res += self.pre_fluff
res += handle_prefluff(self.pre_fluff)
res += ' '.join(level.s for level in self.levels)
res += self.post_fluff
res += handle_postfluff(self.post_fluff)
return res
class CssDeclarationBlock(object):
@ -363,7 +472,7 @@ class CssDeclaration(object):
def text(self):
# type: () -> str
res = ''
res += self.pre_fluff
res += handle_prefluff(self.pre_fluff, True)
res += self.css_property
res += ':'
value_text = self.css_value.text()
@ -374,7 +483,7 @@ class CssDeclaration(object):
res += ' '
res += value_text.strip()
res += ';'
res += self.post_fluff
res += handle_postfluff(self.post_fluff, True, True)
return res
class CssValue(object):

View File

@ -11,6 +11,8 @@ try:
CssParserException,
CssSection,
parse,
handle_prefluff,
handle_postfluff
)
except ImportError:
print('ERROR!!! You need to run this via tools/test-tools.')
@ -33,10 +35,10 @@ class ParserTestHappyPath(unittest.TestCase):
}'''
my_css = my_selector + ' ' + my_block
res = parse(my_css)
self.assertEqual(res.text(), my_css)
self.assertEqual(res.text(), 'li.foo {\n color: red;\n}')
section = cast(CssSection, res.sections[0])
block = section.declaration_block
self.assertEqual(block.text().strip(), my_block)
self.assertEqual(block.text().strip(), '{\n color: red;\n}')
declaration = block.declarations[0]
self.assertEqual(declaration.css_property, 'color')
self.assertEqual(declaration.css_value.text().strip(), 'red')
@ -62,9 +64,7 @@ class ParserTestHappyPath(unittest.TestCase):
p { color: red }
'''
reformatted_css = '''
p { color: red;}
'''
reformatted_css = '\np {\n color: red;\n}\n'
res = parse(my_css)
@ -126,7 +126,39 @@ class ParserTestHappyPath(unittest.TestCase):
}'''
res = parse(my_css)
self.assertEqual(len(res.sections), 1)
self.assertEqual(res.text(), my_css)
self.assertEqual(res.text(), '\n @media (max-width: 300px) {\n h5 {\n margin: 0;\n }\n}')
def test_handle_prefluff(self):
# type: () -> None
PREFLUFF = ' \n '
PREFLUFF1 = ' '
PREFLUFF2 = ' /* some comment \nhere */'
PREFLUFF3 = '\n /* some comment \nhere */'
self.assertEqual(handle_prefluff(PREFLUFF), '\n')
self.assertEqual(handle_prefluff(PREFLUFF, True), '\n ')
self.assertEqual(handle_prefluff(PREFLUFF1), '')
self.assertEqual(handle_prefluff(PREFLUFF1, True), '\n ')
self.assertEqual(handle_prefluff(PREFLUFF2), '/* some comment\n here */\n')
self.assertEqual(handle_prefluff(PREFLUFF3, True), '\n /* some comment\n here */\n ')
def test_handle_postfluff(self):
# type: () -> None
POSTFLUFF = '/* Comment Here */'
POSTFLUFF1 = '/* Comment \nHere */'
POSTFLUFF2 = ' '
POSTFLUFF3 = '\n /* some comment \nhere */'
self.assertEqual(handle_postfluff(POSTFLUFF), '/* Comment Here */\n')
self.assertEqual(handle_postfluff(POSTFLUFF, space_after_first_line=True), ' /* Comment Here */\n')
self.assertEqual(handle_postfluff(POSTFLUFF, indent=True, space_after_first_line=True), ' /* Comment Here */\n')
self.assertEqual(handle_postfluff(POSTFLUFF1), '/* Comment\n Here */')
self.assertEqual(handle_postfluff(POSTFLUFF1, space_after_first_line=True), ' /* Comment\n Here */\n')
self.assertEqual(handle_postfluff(POSTFLUFF1, indent=True, space_after_first_line=True), ' /* Comment\n Here */\n')
self.assertEqual(handle_postfluff(POSTFLUFF2), '')
self.assertEqual(handle_postfluff(POSTFLUFF2, space_after_first_line=True), '')
self.assertEqual(handle_postfluff(POSTFLUFF2, indent=True, space_after_first_line=True), '\n')
self.assertEqual(handle_postfluff(POSTFLUFF3), '\n/* some comment\n here */')
self.assertEqual(handle_postfluff(POSTFLUFF3, space_after_first_line=True), '\n/* some comment\n here */\n')
self.assertEqual(handle_postfluff(POSTFLUFF3, indent=True, space_after_first_line=True), '\n /* some comment\n here */\n')
class ParserTestSadPath(unittest.TestCase):
'''