mirror of https://github.com/zulip/zulip.git
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.
This commit is contained in:
parent
7e2b452f6a
commit
ba51078418
|
@ -160,7 +160,6 @@
|
|||
background-color: hsl(33, 48%, 96%);
|
||||
}
|
||||
|
||||
|
||||
.new-style .button[disabled="disabled"] {
|
||||
cursor: not-allowed;
|
||||
-moz-filter: saturate(0);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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):
|
||||
'''
|
||||
|
|
Loading…
Reference in New Issue