From ba5107841897611534c2db914b960eea0c256827 Mon Sep 17 00:00:00 2001 From: Steve Howell Date: Mon, 13 Nov 2017 08:26:11 -0800 Subject: [PATCH] Simplify CSS linter and clean up CSS. The CSS linter was pretty hard to reason about. It was pretty flexible about certain things, but then it would prevent seemingly innocuous code from getting checked in. This commit overhauls the pretty-printer to be more composable, where every object in the AST knows how to render itself. It also cleans up a little bit of the pre_fluff/post_fluff logic in the parser itself, so comments are more likely to be "attached" to the AST node that make sense. The linter is actually a bit more finicky about newlines, but this is mostly a good thing, as most of the variations before this commit were pretty arbitrary. --- static/styles/app_components.css | 1 - static/styles/compose.css | 2 - static/styles/hotspots.css | 10 +- static/styles/landing-page.css | 14 +-- static/styles/lightbox.css | 16 +-- static/styles/media.css | 7 +- static/styles/portico-signin.css | 4 +- static/styles/portico.css | 5 +- static/styles/settings.css | 8 +- static/styles/stats.css | 1 + static/styles/zulip.css | 24 +++- templates/zerver/emails/email.css | 10 +- tools/check-css | 5 + tools/lib/css_parser.py | 197 ++++++++++++------------------ tools/tests/test_css_parser.py | 63 +--------- 15 files changed, 147 insertions(+), 220 deletions(-) diff --git a/static/styles/app_components.css b/static/styles/app_components.css index 196349cdfd..38bde0dafd 100644 --- a/static/styles/app_components.css +++ b/static/styles/app_components.css @@ -160,7 +160,6 @@ background-color: hsl(33, 48%, 96%); } - .new-style .button[disabled="disabled"] { cursor: not-allowed; -moz-filter: saturate(0); diff --git a/static/styles/compose.css b/static/styles/compose.css index 7e247bcfec..f50c16c7df 100644 --- a/static/styles/compose.css +++ b/static/styles/compose.css @@ -125,7 +125,6 @@ box-shadow: none !important; } - table.compose_table { table-layout: fixed; margin-left: auto; @@ -316,7 +315,6 @@ textarea.new_message_textarea:focus, border-left: none; } - input.recipient_box { margin: 0px; height: 1.1em; diff --git a/static/styles/hotspots.css b/static/styles/hotspots.css index d8f06d9db3..d34f5aca58 100644 --- a/static/styles/hotspots.css +++ b/static/styles/hotspots.css @@ -43,7 +43,6 @@ } } - /* popover */ .hotspot.overlay { z-index: 104; @@ -124,6 +123,7 @@ .hotspot-popover:after { border-width: 12px; } + .hotspot-popover:before { border-width: 13px; } @@ -133,10 +133,12 @@ bottom: 100%; right: 50%; } + .hotspot-popover.arrow-top:after { border-bottom-color: hsl(164, 44%, 47%); margin-right: -12px; } + .hotspot-popover.arrow-top:before { border-bottom-color: hsl(0, 0%, 80%); margin-right: -13px; @@ -147,10 +149,12 @@ right: 100%; top: 50%; } + .hotspot-popover.arrow-left:after { border-right-color: #fff; margin-top: -12px; } + .hotspot-popover.arrow-left:before { border-right-color: hsl(0, 0%, 80%); margin-top: -13px; @@ -161,10 +165,12 @@ top: 100%; right: 50%; } + .hotspot-popover.arrow-bottom:after { border-top-color: #fff; margin-right: -12px; } + .hotspot-popover.arrow-bottom:before { border-top-color: hsl(0, 0%, 80%); margin-right: -13px; @@ -175,10 +181,12 @@ left: 100%; top: 50%; } + .hotspot-popover.arrow-right:after { border-left-color: #fff; margin-top: -12px; } + .hotspot-popover.arrow-right:before { border-left-color: hsl(0, 0%, 80%); margin-top: -13px; diff --git a/static/styles/landing-page.css b/static/styles/landing-page.css index 9568291442..2f82681772 100644 --- a/static/styles/landing-page.css +++ b/static/styles/landing-page.css @@ -706,7 +706,6 @@ nav ul li.active::after { background: linear-gradient(0deg, #fff 0%, transparent 40%); } - .portico-landing.hello .hero .waves { position: absolute; @@ -759,9 +758,11 @@ nav ul li.active::after { 0% { transform: rotate(0); } + 50% { transform: rotate(180deg); } + 100% { transform: rotate(360deg); } @@ -771,9 +772,11 @@ nav ul li.active::after { 0% { transform: rotate(0); } + 50% { transform: rotate(-180deg); } + 100% { transform: rotate(-360deg); } @@ -1335,7 +1338,6 @@ nav ul li.active::after { font-weight: 400; } - .portico-landing.hello .testimonials blockquote { padding: 0px; @@ -1626,6 +1628,7 @@ nav ul li.active::after { .portico-landing.apps { padding-top: 0px; } + .portico-landing.apps .main, .portico-landing.features-app .main { background-color: #fff; @@ -1887,7 +1890,6 @@ nav ul li.active::after { text-align: left; } - .portico-landing.hello .apps .screen.ios { width: 200px; height: 350px; @@ -2194,7 +2196,6 @@ nav ul li.active::after { color: hsla(216, 23%, 13%, 0.5); } - /* -- integration instructions -- */ .portico-landing.integrations #integration-instructions-group { @@ -2479,7 +2480,6 @@ nav ul li.active::after { top: -220px; } - .pricing-overlay.pricing-model .pricing-container { padding: 50px; text-align: left; @@ -2568,9 +2568,11 @@ nav ul li.active::after { 0% { box-shadow: 0px 0px 0px rgba(106, 201, 185, 0); } + 50% { box-shadow: 0px 0px 25px rgba(106, 201, 185, 0.8); } + 100% { box-shadow: 0px 0px 0px rgba(106, 201, 185, 0); } @@ -3061,7 +3063,6 @@ nav ul li.active::after { min-height: 0px; } - .portico-landing.apps .other-apps { padding: 50px 5px 120px 5px; } @@ -3334,7 +3335,6 @@ nav ul li.active::after { .gradients .gradient.sunburst { background: linear-gradient(5deg, transparent 20%, #e8d275 80%); } - } @media (max-width: 375px) { diff --git a/static/styles/lightbox.css b/static/styles/lightbox.css index af097512d8..806f0f9307 100644 --- a/static/styles/lightbox.css +++ b/static/styles/lightbox.css @@ -25,15 +25,15 @@ background-color: #FFF; background-image: - -moz-linear-gradient(45deg, hsl(0, 0%, 80%) 25%, transparent 25%), - -moz-linear-gradient(-45deg, hsl(0, 0%, 80%) 25%, transparent 25%), - -moz-linear-gradient(45deg, transparent 75%, hsl(0, 0%, 0%) 75%), - -moz-linear-gradient(-45deg, transparent 75%, hsl(0, 0%, 0%) 75%); + -moz-linear-gradient(45deg, hsl(0, 0%, 80%) 25%, transparent 25%), + -moz-linear-gradient(-45deg, hsl(0, 0%, 80%) 25%, transparent 25%), + -moz-linear-gradient(45deg, transparent 75%, hsl(0, 0%, 0%) 75%), + -moz-linear-gradient(-45deg, transparent 75%, hsl(0, 0%, 0%) 75%); background-image: - -webkit-gradient(linear, 0 100%, 100% 0, color-stop(.25, hsl(0, 0%, 80%)), color-stop(.25, transparent)), - -webkit-gradient(linear, 0 0, 100% 100%, color-stop(.25, hsl(0, 0%, 80%)), color-stop(.25, transparent)), - -webkit-gradient(linear, 0 100%, 100% 0, color-stop(.75, transparent), color-stop(.75, hsl(0, 0%, 80%))), - -webkit-gradient(linear, 0 0, 100% 100%, color-stop(.75, transparent), color-stop(.75, hsl(0, 0%, 80%))); + -webkit-gradient(linear, 0 100%, 100% 0, color-stop(.25, hsl(0, 0%, 80%)), color-stop(.25, transparent)), + -webkit-gradient(linear, 0 0, 100% 100%, color-stop(.25, hsl(0, 0%, 80%)), color-stop(.25, transparent)), + -webkit-gradient(linear, 0 100%, 100% 0, color-stop(.75, transparent), color-stop(.75, hsl(0, 0%, 80%))), + -webkit-gradient(linear, 0 0, 100% 100%, color-stop(.75, transparent), color-stop(.75, hsl(0, 0%, 80%))); -moz-background-size: 20px 20px; background-size: 20px 20px; diff --git a/static/styles/media.css b/static/styles/media.css index e60944c475..705a807008 100644 --- a/static/styles/media.css +++ b/static/styles/media.css @@ -5,7 +5,6 @@ /* This max-width must be synced with message_viewport.is_narrow */ @media (max-width: 975px) { - .screen-full-show { display: none !important; } @@ -46,7 +45,6 @@ width: 30px; } - #top_navbar.rightside-userlist .navbar-search { margin-right: 100px; } @@ -59,7 +57,6 @@ margin-right: 60px; } - #userlist-toggle { display: block; } @@ -91,11 +88,9 @@ left: 300px; right: 50px; } - } @media (max-width: 775px) { - body { padding: 0px; } @@ -192,7 +187,6 @@ #subscriptions-status { left: 35px; } - } @media (max-width: 500px) { @@ -341,6 +335,7 @@ html { overflow-x: hidden; } + .stream_row .description { display: none; } diff --git a/static/styles/portico-signin.css b/static/styles/portico-signin.css index 161f0fb012..3535e64073 100644 --- a/static/styles/portico-signin.css +++ b/static/styles/portico-signin.css @@ -202,7 +202,6 @@ html { stroke: #fff !important; } - .new-style { -webkit-font-smoothing: antialiased; } @@ -491,7 +490,6 @@ html { width: 328px; } - .split-view .org-header .avatar, .register-page-container .org-header .avatar { display: inline-block; @@ -630,6 +628,7 @@ button.login-google-button { .forgot-password-container .actions { line-height: 0; } + .forgot-password-container .actions .back-to-login i { position: relative; top: 5px; @@ -691,7 +690,6 @@ button.login-google-button { margin-left: 2px; } - #registration [for="realm_in_root_domain"] { font-weight: normal !important; } diff --git a/static/styles/portico.css b/static/styles/portico.css index af40dc15b1..19dccbc7d6 100644 --- a/static/styles/portico.css +++ b/static/styles/portico.css @@ -299,6 +299,7 @@ body { .markdown ol > li > p:not(:first-child) { padding-left: 30px; } + .markdown ul > li:before { content: none; } @@ -598,7 +599,6 @@ input.text-error { line-height: 0.8; } - .header-main .logo .light { display: inline-block; vertical-align: top; @@ -1758,7 +1758,6 @@ input.new-organization-button { .footer-navigation { margin-left: 0px; } - } @media (max-width: 815px) { @@ -1767,6 +1766,7 @@ input.new-organization-button { margin-left: auto; margin-right: auto; } + .footer section { width: calc(50% - 40px); } @@ -1916,7 +1916,6 @@ input.new-organization-button { padding-left: 6px; padding-right: 6px; } - } @media (max-width: 475px) { diff --git a/static/styles/settings.css b/static/styles/settings.css index fe76ac2d8d..2193ffa254 100644 --- a/static/styles/settings.css +++ b/static/styles/settings.css @@ -254,6 +254,7 @@ td .button { font-family: FontAwesome, "Yantramanav", Source Sans Pro; font-weight: 600; } + .dynamic-input { display: inline-block; padding: 5px; @@ -468,7 +469,6 @@ input[type=checkbox].inline-block { } @media (max-width: 480px) { - #pw_strength { margin: auto; } @@ -508,13 +508,13 @@ input[type=checkbox].inline-block { margin: auto; text-align: center; } - } #organization .settings-section .settings-section-icon, #settings .settings-section .settings-section-icon { margin-right: 8px; } + #notification-settings .notification-reminder { text-align: left; } @@ -899,6 +899,7 @@ input[type=checkbox].inline-block { text-decoration: none; margin-right: 5px; } + /* -- new settings overlay -- */ #settings_page { height: 95vh; @@ -1115,7 +1116,6 @@ input[type=checkbox].inline-block { cursor: pointer; } - #deactivation_user_modal.fade.in { top: calc(50% - 120px); } @@ -1235,7 +1235,7 @@ input[type=text]#settings_search { position: absolute; bottom: 0px; } -/* -- end new settings overlay -- */ + @media (max-width: 953px) { .user-avatar-section, .realm-icon-section { diff --git a/static/styles/stats.css b/static/styles/stats.css index 923a97938b..9c25808c57 100644 --- a/static/styles/stats.css +++ b/static/styles/stats.css @@ -183,6 +183,7 @@ hr { .center-charts { width: calc(816px * 2); /* 790px + 4px for borders + 2px for il-block + 20px margins */ } + .center-charts .left, .center-charts .right { display: inline-block; diff --git a/static/styles/zulip.css b/static/styles/zulip.css index cda8488156..5617967287 100644 --- a/static/styles/zulip.css +++ b/static/styles/zulip.css @@ -133,9 +133,11 @@ p.n-margin { 0% { box-shadow: 0px 0px 30px hsla(0, 0%, 0%, 0.35); } + 50% { box-shadow: 0px 0px 30px hsla(0, 0%, 0%, 0.15); } + 100% { box-shadow: 0px 0px 30px hsla(0, 0%, 0%, 0.35); } @@ -299,7 +301,6 @@ input { } /* Override Bootstrap's fixed sizes for various elements */ - textarea, label { font-size: inherit; @@ -307,7 +308,6 @@ label { } /* List of text-like input types taken from Bootstrap */ - input[type="text"], input[type="password"], input[type="datetime"], @@ -900,6 +900,7 @@ td.pointer { from { -webkit-transform: rotate(0deg); } + to { -webkit-transform: rotate(359deg); } @@ -909,6 +910,7 @@ td.pointer { from { -moz-transform: rotate(0deg); } + to { -moz-transform: rotate(359deg); } @@ -917,7 +919,9 @@ td.pointer { @-ms-keyframes rotate { from { -ms-transform: rotate(0deg); + } + to { -ms-transform: rotate(359deg); } @@ -927,6 +931,7 @@ td.pointer { from { -o-transform: rotate(0deg); } + to { -o-transform: rotate(359deg); } @@ -936,6 +941,7 @@ td.pointer { from { transform: rotate(0deg); } + to { transform: rotate(359deg); } @@ -945,6 +951,7 @@ td.pointer { 0% { opacity: 0; } + 100% { opacity: 1; } @@ -954,6 +961,7 @@ td.pointer { 0% { opacity: 0; } + 100% { opacity: 1; } @@ -963,6 +971,7 @@ td.pointer { 0% { opacity: 0; } + 100% { opacity: 1; } @@ -972,6 +981,7 @@ td.pointer { 0% { transform: translateX(-10px); } + 100% { transform: translateX(0px); } @@ -981,6 +991,7 @@ td.pointer { 0% { transform: translateX(-10px); } + 100% { transform: translateX(0px); } @@ -990,6 +1001,7 @@ td.pointer { 0% { transform: translateX(-10px); } + 100% { transform: translateX(0px); } @@ -1418,6 +1430,7 @@ div.focused_table { max-height: 8.5em; overflow: hidden; } + .message_content.collapsed { max-height: 0em; overflow: hidden; @@ -1660,7 +1673,6 @@ blockquote p { padding-right: 2px; } - #tab_list .root a:hover { color: hsl(0, 0%, 0%); } @@ -1792,7 +1804,6 @@ blockquote p { z-index: 5; } - nav .column-left { text-align: center; } @@ -1853,7 +1864,6 @@ nav a .no-style { line-height: 27px; } - #search_query:focus { box-shadow: inset 0px 0px 0px 2px hsl(204, 20%, 74%); } @@ -2101,6 +2111,7 @@ div.floating_recipient { font-family: Monaco, Menlo, Consolas, "Courier New", monospace; color: maroon; } + .operator { font-family: Monaco, Menlo, Consolas, "Courier New", monospace; } @@ -2473,10 +2484,12 @@ button.topic_edit_cancel { #message_edit_form { margin-bottom: 10px; } + #message_edit_form .edit-controls { margin-left: 0px; margin-top: 0px; } + #message_edit_form textarea { width: 100%; min-width: 206px; @@ -2484,6 +2497,7 @@ button.topic_edit_cancel { -moz-box-sizing: border-box; box-sizing: border-box; } + #message_edit_form .control-group.no-margin { margin-bottom: 0px; } diff --git a/templates/zerver/emails/email.css b/templates/zerver/emails/email.css index 7c543e68d7..09aae59585 100644 --- a/templates/zerver/emails/email.css +++ b/templates/zerver/emails/email.css @@ -171,12 +171,12 @@ a.button:hover { z-index: 100; } - @media only screen and (max-width: 620px) { table[class=body] h1 { font-size: 28px !important; margin-bottom: 10px !important; } + table[class=body] p, table[class=body] ul, table[class=body] ol, @@ -193,24 +193,30 @@ a.button:hover { table[class=body] .article { padding: 10px !important; } + table[class=body] .content { padding: 0 !important; } + table[class=body] .container { padding: 0 !important; width: 100% !important; } + table[class=body] .main { border-left-width: 0 !important; border-radius: 0 !important; border-right-width: 0 !important; } + table[class=body] .btn table { width: 100% !important; } + table[class=body] .btn a { width: 100% !important; } + table[class=body] .img-responsive { height: auto !important; max-width: 100% !important; @@ -222,6 +228,7 @@ a.button:hover { .ExternalClass { width: 100%; } + .ExternalClass, .ExternalClass p, .ExternalClass span, @@ -230,6 +237,7 @@ a.button:hover { .ExternalClass div { line-height: 100%; } + /* iOS converts adreses in emails to links automatically*/ .apple-link a { color: inherit !important; diff --git a/tools/check-css b/tools/check-css index b453ca75d1..8645db3be4 100755 --- a/tools/check-css +++ b/tools/check-css @@ -22,6 +22,11 @@ def validate(fn): def check_our_files(filenames): # type: (Iterable[str]) -> None for filename in filenames: + if 'pygments.css' in filename: + # This just has really strange formatting that our + # parser doesn't like + continue + try: validate(filename) except CssParserException as e: diff --git a/tools/lib/css_parser.py b/tools/lib/css_parser.py index a6bd273358..31f41e924e 100644 --- a/tools/lib/css_parser.py +++ b/tools/lib/css_parser.py @@ -38,6 +38,17 @@ def find_end_brace(tokens, i, end): return i +def get_whitespace(tokens, i, end): + # type: (List[Token], int, int) -> Tuple[int, str] + + text = '' + while (i < end) and ws(tokens[i].s[0]): + s = tokens[i].s + text += s + i += 1 + + return i, text + def get_whitespace_and_comments(tokens, i, end, line=None): # type: (List[Token], int, int, int) -> Tuple[int, str] @@ -65,6 +76,43 @@ def get_whitespace_and_comments(tokens, i, end, line=None): return i, text +def indent_count(s): + # type: (str) -> int + return len(s) - len(s.lstrip()) + +def dedent_block(s): + # type: (str) -> (str) + s = s.lstrip() + lines = s.split('\n') + non_blank_lines = [line for line in lines if line] + if len(non_blank_lines) <= 1: + return s + min_indent = min(indent_count(line) for line in lines[1:]) + lines = [lines[0]] + [line[min_indent:] for line in lines[1:]] + return '\n'.join(lines) + +def indent_block(s): + # type: (str) -> (str) + lines = s.split('\n') + lines = [ + ' ' + line if line else '' + for line in lines + ] + return '\n'.join(lines) + +def ltrim(s): + # type: (str) -> (str) + content = s.lstrip() + padding = s[:-1 * len(content)] + s = padding.replace(' ', '')[1:] + content + return s + +def rtrim(s): + # type: (str) -> (str) + content = s.rstrip() + padding = s[len(content):] + s = content + padding.replace(' ', '')[:-1] + return s ############### Begin parsing here @@ -82,7 +130,7 @@ def parse_sections(tokens, start, end): i = find_end_brace(tokens, start, end) section_end = i + 1 - i, post_fluff = get_whitespace_and_comments(tokens, i+1, end) + i, post_fluff = get_whitespace(tokens, i+1, end) section = parse_section( tokens=tokens, @@ -228,7 +276,7 @@ def parse_declaration(tokens, start, end): semicolon = (i < end) and (tokens[i].s == ';') if semicolon: i += 1 - _, post_fluff = get_whitespace_and_comments(tokens, i, end) + _, post_fluff = get_whitespace_and_comments(tokens, i, end, line=tokens[i].line) declaration = CssDeclaration( tokens=tokens, pre_fluff=pre_fluff, @@ -254,100 +302,6 @@ 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 class CssSectionList: @@ -358,7 +312,7 @@ class CssSectionList: def text(self): # type: () -> str - res = ''.join(section.text() for section in self.sections) + res = '\n\n'.join(section.text().strip() for section in self.sections) + '\n' return res class CssNestedSection: @@ -373,19 +327,12 @@ class CssNestedSection: def text(self): # type: () -> str res = '' - res += self.pre_fluff - res += self.selector_list.text() - res += ' {' - 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 + res += ltrim(self.pre_fluff) + res += self.selector_list.text().strip() + res += ' {\n' + res += indent_block(self.section_list.text().strip()) + res += '\n}' + res += rtrim(self.post_fluff) return res class CssSection: @@ -400,11 +347,14 @@ class CssSection: def text(self): # type: () -> str res = '' - res += handle_prefluff(self.pre_fluff) - res += self.selector_list.text() + res += rtrim(dedent_block(self.pre_fluff)) + if res: + res += '\n' + res += self.selector_list.text().strip() res += ' ' res += self.declaration_block.text() - res += handle_postfluff(self.post_fluff, space_after_first_line=True) + res += '\n' + res += rtrim(self.post_fluff) return res class CssSelectorList: @@ -438,9 +388,9 @@ class CssDeclarationBlock: def text(self): # type: () -> str - res = '{' + res = '{\n' for declaration in self.declarations: - res += declaration.text() + res += ' ' + declaration.text() res += '}' return res @@ -457,18 +407,23 @@ class CssDeclaration: def text(self): # type: () -> str res = '' - res += handle_prefluff(self.pre_fluff, True) + res += ltrim(self.pre_fluff).rstrip() + if res: + res += '\n ' res += self.css_property res += ':' - value_text = self.css_value.text() - if '\n' in value_text: - # gradient values can be multi-line - res += value_text.rstrip() + value_text = self.css_value.text().rstrip() + if value_text.startswith('\n'): + res += value_text + elif '\n' in value_text: + res += ' ' + res += ltrim(value_text) else: res += ' ' res += value_text.strip() res += ';' - res += handle_postfluff(self.post_fluff, True, True) + res += rtrim(self.post_fluff) + res += '\n' return res class CssValue: diff --git a/tools/tests/test_css_parser.py b/tools/tests/test_css_parser.py index c969b9b4f7..78b155625b 100644 --- a/tools/tests/test_css_parser.py +++ b/tools/tests/test_css_parser.py @@ -9,8 +9,6 @@ try: CssParserException, CssSection, parse, - handle_prefluff, - handle_postfluff ) except ImportError: print('ERROR!!! You need to run this via tools/test-tools.') @@ -25,7 +23,7 @@ class ParserTestHappyPath(unittest.TestCase): }''' my_css = my_selector + ' ' + my_block res = parse(my_css) - self.assertEqual(res.text(), 'li.foo {\n color: red;\n}') + self.assertEqual(res.text().strip(), 'li.foo {\n color: red;\n}') section = cast(CssSection, res.sections[0]) block = section.declaration_block self.assertEqual(block.text().strip(), '{\n color: red;\n}') @@ -54,11 +52,11 @@ class ParserTestHappyPath(unittest.TestCase): p { color: red } ''' - reformatted_css = '\np {\n color: red;\n}\n' + reformatted_css = 'p {\n color: red;\n}' res = parse(my_css) - self.assertEqual(res.text(), reformatted_css) + self.assertEqual(res.text().strip(), reformatted_css) section = cast(CssSection, res.sections[0]) @@ -86,26 +84,6 @@ class ParserTestHappyPath(unittest.TestCase): selectors = section.selector_list.selectors self.assertEqual(len(selectors), 3) - def test_comment_at_end(self): - # type: () -> None - ''' - This test verifies the current behavior, which is to - attach comments to the preceding rule, but we should - probably change it so the comments gets attached to - the next block, if possible. - ''' - my_css = ''' - p { - color: black; - } - - /* comment at the end of the text */ - ''' - res = parse(my_css) - self.assertEqual(len(res.sections), 1) - section = res.sections[0] - self.assertIn('comment at the end', section.post_fluff) - def test_media_block(self): # type: () -> None my_css = ''' @@ -116,39 +94,8 @@ class ParserTestHappyPath(unittest.TestCase): }''' res = parse(my_css) self.assertEqual(len(res.sections), 1) - 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') + expected = '@media (max-width: 300px) {\n h5 {\n margin: 0;\n }\n}' + self.assertEqual(res.text().strip(), expected) class ParserTestSadPath(unittest.TestCase): '''