From b3cbb13a79758bebca5c13a0700d99bfbafdb3ea Mon Sep 17 00:00:00 2001 From: adnrs96 Date: Fri, 17 Mar 2017 17:04:45 +0530 Subject: [PATCH] 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. --- tools/check-css | 6 +- tools/lib/css_parser.py | 125 ++++++++++++++++++++++++++++++--- tools/tests/test_css_parser.py | 44 ++++++++++-- 3 files changed, 158 insertions(+), 17 deletions(-) diff --git a/tools/check-css b/tools/check-css index 61257df4c5..85ff8c3542 100755 --- a/tools/check-css +++ b/tools/check-css @@ -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): diff --git a/tools/lib/css_parser.py b/tools/lib/css_parser.py index e3050a3d61..50a17f42e7 100644 --- a/tools/lib/css_parser.py +++ b/tools/lib/css_parser.py @@ -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): diff --git a/tools/tests/test_css_parser.py b/tools/tests/test_css_parser.py index 85b22c0b4e..b29ebdca42 100644 --- a/tools/tests/test_css_parser.py +++ b/tools/tests/test_css_parser.py @@ -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): '''