Skip to content

Commit

Permalink
feat: salvage a rare case with rogue semi in front of !important in…
Browse files Browse the repository at this point in the history
… CSS rule
  • Loading branch information
revelt committed Feb 18, 2021
1 parent 9b3bdc0 commit 424dc7b
Show file tree
Hide file tree
Showing 9 changed files with 290 additions and 126 deletions.
5 changes: 4 additions & 1 deletion packages/emlint/coverage/coverage-summary.json
@@ -1 +1,4 @@
{"total":{"lines":{"total":2496,"covered":2435,"skipped":0,"pct":97.56},"statements":{"total":2784,"covered":2723,"skipped":0,"pct":97.81},"functions":{"total":1003,"covered":996,"skipped":0,"pct":99.3},"branches":{"total":2084,"covered":1973,"skipped":0,"pct":94.67}}}
{"total": {"lines":{"total":2498,"covered":2437,"skipped":0,"pct":97.56},"statements":{"total":2786,"covered":2725,"skipped":0,"pct":97.81},"functions":{"total":1003,"covered":996,"skipped":0,"pct":99.3},"branches":{"total":2091,"covered":1980,"skipped":0,"pct":94.69}}
,"/Users/royston/Documents/__PROJECTS/____libs2/packages/emlint/dist/emlint.esm.js": {"lines":{"total":2492,"covered":2431,"skipped":0,"pct":97.55},"functions":{"total":1001,"covered":994,"skipped":0,"pct":99.3},"statements":{"total":2780,"covered":2719,"skipped":0,"pct":97.81},"branches":{"total":2083,"covered":1972,"skipped":0,"pct":94.67}}
,"/Users/royston/Documents/__PROJECTS/____libs2/packages/emlint/t-util/util.js": {"lines":{"total":6,"covered":6,"skipped":0,"pct":100},"functions":{"total":2,"covered":2,"skipped":0,"pct":100},"statements":{"total":6,"covered":6,"skipped":0,"pct":100},"branches":{"total":8,"covered":8,"skipped":0,"pct":100}}
}
37 changes: 28 additions & 9 deletions packages/emlint/dist/emlint.cjs.js
Expand Up @@ -10606,15 +10606,34 @@ var cssRuleMalformed = function cssRuleMalformed(context) {


if (properties && properties.length) {
properties.forEach(function (property) {
if (property.value === null) {
context.report({
ruleId: "css-rule-malformed",
idxFrom: property.start,
idxTo: property.end,
message: "Missing value.",
fix: null
});
properties.forEach(function (property, idx) {
if (property.value === null) { // tend a rare case, a rogue semicolon:
// <style>.a{color:red; !important;}</style>
// ^

if (property.property === "!important" && property.value === null && idx && properties[idx - 1].semi && properties[idx - 1].important === null) {
// the property before (there can be text node,
// a whitespace in-between) is a property with
// semicolon (rogue) and without !important
context.report({
ruleId: "css-rule-malformed",
idxFrom: properties[idx - 1].semi,
idxTo: properties[idx - 1].semi + 1,
message: "Delete the semicolon.",
fix: {
ranges: [[properties[idx - 1].semi, properties[idx - 1].semi + 1]]
}
});
} else {
// business as usual then
context.report({
ruleId: "css-rule-malformed",
idxFrom: property.start,
idxTo: property.end,
message: "Missing value.",
fix: null
});
}
}
});
}
Expand Down
240 changes: 145 additions & 95 deletions packages/emlint/dist/emlint.dev.umd.js
Expand Up @@ -13721,6 +13721,7 @@ function startsEsp(str, i, token, layers, withinStyle) {
str[i] === "-" && str[i + 1] === "-" && str[i + 2] === ">" && Array.isArray(layers) && layers.length && layers[layers.length - 1].type === "esp" && layers[layers.length - 1].openingLump[0] === "<" && layers[layers.length - 1].openingLump[2] === "-" && layers[layers.length - 1].openingLump[3] === "-";
return !!res;
}
var importantStartsRegexp = /^\s*!?\s*[a-zA-Z]+(?:[\s;}<>'"]|$)/gm;
var defaults$2 = {
tagCb: null,
tagCbLookahead: 0,
Expand Down Expand Up @@ -13867,6 +13868,9 @@ function tokenizer(str, originalOpts) {
property: null,
propertyStarts: null,
propertyEnds: null,
importantStarts: null,
importantEnds: null,
important: null,
colon: null,
valueStarts: null,
valueEnds: null,
Expand Down Expand Up @@ -14304,6 +14308,13 @@ function tokenizer(str, originalOpts) {
// <div style="float:"left"">
//
isAttrClosing(str, attrib.attribOpeningQuoteAt || attrib.attribValueStartsAt, idx);
}

function attrEndsAt(idx) {
// either we're within normal head css styles:
return ";}/".includes(str[idx]) && (!attrib || !attrib.attribName || attrib.attribName !== "style") || // or within inline css styles within html
"/;'\"><".includes(str[idx]) && attrib && attrib.attribName === "style" && // and it's a real quote, not rogue double-wrapping around the value
ifQuoteThenAttrClosingQuote(idx);
} //
//
//
Expand Down Expand Up @@ -15138,127 +15149,136 @@ function tokenizer(str, originalOpts) {
});
} else if (!token.type) {
initToken("text", _i);
} // END OF if (!doNothing)

} // catch the end of a css property's value
}
} // catch the end of a css property (with or without !important)
// -------------------------------------------------------------------------

/* istanbul ignore else */

if (!doNothing && // token.type === "rule" &&
property && property.valueStarts && !property.valueEnds) {
if ( // either end was reached
!str[_i] || // or it's erroneous whitespace:
str[_i] && !str[_i].trim() || // normal head css styles:
";}".includes(str[_i]) && (!attrib || !attrib.attribName || attrib.attribName !== "style") || // inline css styles within html
";'\"".includes(str[_i]) && attrib && attrib.attribName === "style" && // it's real quote, not rogue double-wrapping around the value
ifQuoteThenAttrClosingQuote(_i)) {
if (lastNonWhitespaceCharAt) {
property.valueEnds = lastNonWhitespaceCharAt + 1;
property.value = str.slice(property.valueStarts, lastNonWhitespaceCharAt + 1);
}

if (str[_i] === ";") {
property.semi = _i;
} else if ( // it's whitespace
str[_i] && !str[_i].trim() && // semicolon follows
str[rightVal] === ";") {
property.semi = rightVal;
}
if (!doNothing && property && (property.valueStarts && !property.valueEnds || property.importantStarts && !property.importantEnds) && ( // either an end was reached
!str[_i] || attrEndsAt(_i))) {
/* istanbul ignore else */
if (property.importantStarts && !property.importantEnds) {
property.importantEnds = _i;
property.important = str.slice(property.importantStarts, _i);
}
/* istanbul ignore else */

if ( // if semicolon has been spotted...
property.semi) {
// set the ending too
property.end = property.semi + 1; // happy path, clean code has "end" at semi
}

if ( // if there's no semicolon in the view
!property.semi && // and semi is not coming next
str[rightVal] !== ";" && // and property hasn't ended
!property.end) {
// we need to end it because this is it
property.end = _i;
}
if (property.valueStarts && !property.valueEnds) {
property.valueEnds = _i;
property.value = str.slice(property.valueStarts, _i);
}
/* istanbul ignore else */

if (property.end) {
// push and init and patch up to resume
if (property.end > _i) {
// if ending is in the future, skip everything up to it
doNothing = property.end;
}

pushProperty(property);
propertyReset();
}
} else if (str[_i] === ":" && property && property.colon && property.colon < _i && lastNonWhitespaceCharAt && property.colon + 1 < lastNonWhitespaceCharAt) {
// .a{b:c d:e;}
// ^
// we're here
// // semicolon is missing...
// traverse backwards from "lastNonWhitespaceCharAt", just in case
// there's space before colon, .a{b:c d :e;}
// ^
// we're here
//
// we're looking to pinpoint where one rule ends and another starts.
var split = [];
if (str[_i] === ";") {
property.semi = _i;
property.end = _i + 1;
} else if (str[rightVal] === ";") {
property.semi = rightVal;
property.end = property.semi + 1;
}

if (right(str, property.colon)) {
split = str.slice(right(str, property.colon), lastNonWhitespaceCharAt + 1).split(/\s+/);
}
if (!property.end) {
property.end = _i;
}

if (split.length === 2) {
// it's missing semicol, like: .a{b:c d:e;}
// ^ ^
// |gap| we split
//
property.valueEnds = property.valueStarts + split[0].length;
property.value = str.slice(property.valueStarts, property.valueEnds);
property.end = property.valueEnds; // push and init and patch up to resume
pushProperty(property);
propertyReset();
} // catch the start of css property's semicolon
// -------------------------------------------------------------------------

pushProperty(property);
propertyReset();
property.propertyStarts = lastNonWhitespaceCharAt + 1 - split[1].length;
}
} else if (str[_i] === "/" && str[rightVal] === "*") {
// comment starts
// <a style="color: red/* zzz */">
// ^
// we're here

/* istanbul ignore else */
if (property.valueStarts && !property.valueEnds) {
property.valueEnds = _i;
property.value = str.slice(property.valueStarts, _i);
}
/* istanbul ignore else */
if (!doNothing && property && property.start && !property.end && str[_i] === ";") {
property.semi = _i;
property.end = _i + 1;

if (!property.propertyEnds) {
property.propertyEnds = _i;
}

if (!property.end) {
property.end = _i;
} // push and init and patch up to resume
if (property.propertyStarts && property.propertyEnds && !property.property) {
property.property = str.slice(property.propertyStarts, property.propertyEnds);
}

pushProperty(property);
propertyReset();
} // catch the start of css property's !important
// -------------------------------------------------------------------------

/* istanbul ignore else */


if (!doNothing && property && property.valueEnds && !property.importantStarts && (str[_i] === "!" || isLatinLetter$1(str[_i])) && importantStartsRegexp.test(str.slice(_i))) {
property.importantStarts = _i;
} // catch the end of a css property's value
// -------------------------------------------------------------------------

/* istanbul ignore else */


if (!doNothing && property && (property.valueStarts && !property.valueEnds || property.propertyEnds && !property.valueStarts && !rightVal) && str[_i] && (!str[_i].trim() || str[_i] === "!")) {
if (property.valueStarts && !property.valueEnds) {
property.valueEnds = _i;
property.value = str.slice(property.valueStarts, _i);
} // it depends what's on the right, is it !important (considering mangled)
// <div style="float:left impotant">
// ^
// we're here
//
// or a new property without semi:
// <div style="float:left color:red">
// ^
// we're here
//
// or spaced out semi:
// <div style="float:left ;">
// ^
// we're here

/* istanbul ignore else */


if (str[_i] === "!") {
property.importantStarts = _i;
} else if (rightVal && str[rightVal] === "!" || importantStartsRegexp.test(str.slice(_i))) {
property.importantStarts = right(str, _i);
} else if (!rightVal || str[rightVal] !== ";") {
property.end = left(str, _i + 1) + 1;
pushProperty(property);
propertyReset();
}

if (!property.start && str[_i] && !str[_i].trim()) {
pushProperty({
type: "text",
start: _i,
end: null,
value: null
});
}
} // catch the start of a css property's value
// -------------------------------------------------------------------------

/* istanbul ignore else */


if (!doNothing && // token.type === "rule" &&
property && property.colon && !property.valueStarts && str[_i] && str[_i].trim()) {
if (!doNothing && property && property.colon && !property.valueStarts && str[_i] && str[_i].trim()) {
/* istanbul ignore else */
if ( // stopper character met:
";}'\"".includes(str[_i]) && // either it's real closing quote or not a quote
ifQuoteThenAttrClosingQuote(_i)) {
/* istanbul ignore else */
if (str[_i] === ";") {
property.semi = _i;
}

var temp; // patch missing .end

/* istanbul ignore else */

if (!property.end) {
property.end = property.semi ? property.semi + 1 : left(str, _i) + 1;
temp = property.end;
Expand All @@ -15268,6 +15288,8 @@ function tokenizer(str, originalOpts) {
pushProperty(property);
propertyReset(); // if there was a whitespace gap, submit it as text token

/* istanbul ignore else */

if (temp && temp < _i) {
pushProperty({
type: "text",
Expand Down Expand Up @@ -16336,6 +16358,15 @@ function tokenizer(str, originalOpts) {

token.attribs.push(_objectSpread2({}, attrib));
attribReset();
} // if there was an unfinished CSS property, finish it


if (token && Array.isArray(token.properties) && token.properties.length && !token.properties[~-token.properties.length].end) {
token.properties[~-token.properties.length].end = _i;

if (token.properties[~-token.properties.length].start && !token.properties[~-token.properties.length].value) {
token.properties[~-token.properties.length].value = str.slice(token.properties[~-token.properties.length].start, _i);
}
} // if there is unfinished css property that has been
// recording, end it and push it as is. That's an
// abruptly ended css chunk.
Expand Down Expand Up @@ -44087,15 +44118,34 @@ var cssRuleMalformed = function cssRuleMalformed(context) {


if (properties && properties.length) {
properties.forEach(function (property) {
if (property.value === null) {
context.report({
ruleId: "css-rule-malformed",
idxFrom: property.start,
idxTo: property.end,
message: "Missing value.",
fix: null
});
properties.forEach(function (property, idx) {
if (property.value === null) { // tend a rare case, a rogue semicolon:
// <style>.a{color:red; !important;}</style>
// ^

if (property.property === "!important" && property.value === null && idx && properties[idx - 1].semi && properties[idx - 1].important === null) {
// the property before (there can be text node,
// a whitespace in-between) is a property with
// semicolon (rogue) and without !important
context.report({
ruleId: "css-rule-malformed",
idxFrom: properties[idx - 1].semi,
idxTo: properties[idx - 1].semi + 1,
message: "Delete the semicolon.",
fix: {
ranges: [[properties[idx - 1].semi, properties[idx - 1].semi + 1]]
}
});
} else {
// business as usual then
context.report({
ruleId: "css-rule-malformed",
idxFrom: property.start,
idxTo: property.end,
message: "Missing value.",
fix: null
});
}
}
});
}
Expand Down

0 comments on commit 424dc7b

Please sign in to comment.