-
Notifications
You must be signed in to change notification settings - Fork 7.6k
Unit tests for LESS/SCSS #8905
Unit tests for LESS/SCSS #8905
Changes from all commits
1929094
6054ccb
468d75b
354eeee
c46364e
777977e
3a41b54
6669447
be1bcb9
b9a9fe1
899047f
c2b6dfb
dc6f227
5a1969e
8695cf2
afb2874
7e60ba5
30ea286
c2267e4
b741dfc
8a7da5e
0fbe8bd
2e35a1d
c1a62df
5bede5b
217f225
aff58c6
f056a53
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -640,6 +640,8 @@ define(function (require, exports, module) { | |
completeSelectors += info.selector; | ||
} | ||
return completeSelectors; | ||
} else if (useGroup && info.selectorGroup) { | ||
return info.selectorGroup; | ||
} | ||
|
||
return info.selector; | ||
|
@@ -843,17 +845,18 @@ define(function (require, exports, module) { | |
} | ||
|
||
function _maybeProperty() { | ||
return (state.state !== "top" && state.state !== "block" && | ||
return (/^-(moz|ms|o|webkit)-$/.test(token) || | ||
(state.state !== "top" && state.state !== "block" && | ||
// Has a semicolon as in "rgb(0,0,0);", but not one of those after a LESS | ||
// mixin parameter variable as in ".size(@width; @height)" | ||
stream.string.indexOf(";") !== -1 && !/\([^)]+;/.test(stream.string)); | ||
stream.string.indexOf(";") !== -1 && !/\([^)]+;/.test(stream.string))); | ||
} | ||
|
||
function _skipProperty() { | ||
var prevToken = ""; | ||
while (token !== ";") { | ||
// Skip tokens until the closing brace if we find an interpolated variable. | ||
if (/#\{$/.test(token) || (token === "{" && /@$/.test(prevToken))) { | ||
if (/#\{$/.test(token) || (token === "{" && /[#@]$/.test(prevToken))) { | ||
_skipToClosingBracket("{"); | ||
if (token === "}") { | ||
_nextToken(); // Skip the closing brace | ||
|
@@ -865,13 +868,14 @@ define(function (require, exports, module) { | |
// If there is a '{' or '}' before the ';', | ||
// then stop skipping. | ||
if (token === "{" || token === "}") { | ||
return; | ||
return false; // can't tell if the entire property is skipped | ||
} | ||
prevToken = token; | ||
if (!_nextTokenSkippingComments()) { | ||
break; | ||
} | ||
} | ||
return true; // skip the entire property | ||
} | ||
|
||
function _getParentSelectors() { | ||
|
@@ -892,7 +896,8 @@ define(function (require, exports, module) { | |
|
||
// Everything until the next ',' or '{' is part of the current selector | ||
while ((token !== "," && token !== "{") || | ||
(token === "{" && /@$/.test(currentSelector))) { | ||
(token === "{" && /[#@]$/.test(currentSelector)) || | ||
(token === "," && !_hasNonWhitespace(currentSelector))) { | ||
if (token === "{") { | ||
// Append the interpolated variable to selector | ||
currentSelector += _skipToClosingBracket("{"); | ||
|
@@ -913,7 +918,14 @@ define(function (require, exports, module) { | |
// Collect everything inside the parentheses as a whole chunk so that | ||
// commas inside the parentheses won't be identified as selector separators | ||
// by while loop. | ||
currentSelector += _skipToClosingBracket("("); | ||
if (_hasNonWhitespace(currentSelector)) { | ||
currentSelector += _skipToClosingBracket("("); | ||
} else { | ||
// Nothing in currentSelector yet. Skip to the closing parenthesis | ||
// without collecting the selector since a selector cannot start with | ||
// an opening parenthesis. | ||
_skipToClosingBracket("("); | ||
} | ||
} else if (_hasNonWhitespace(token) || _hasNonWhitespace(currentSelector)) { | ||
currentSelector += token; | ||
} | ||
|
@@ -1016,7 +1028,7 @@ define(function (require, exports, module) { | |
if (sgLine === declListStartLine) { | ||
endChar = declListStartChar; | ||
} | ||
selectorGroup += lines[sgLine].substring(startChar, endChar); | ||
selectorGroup += lines[sgLine].substring(startChar, endChar).trim(); | ||
} | ||
selectorGroup = selectorGroup.trim(); | ||
} | ||
|
@@ -1115,13 +1127,13 @@ define(function (require, exports, module) { | |
// Skip everything until the opening '{' | ||
while (token !== "{") { | ||
if (!_nextTokenSkippingComments()) { | ||
return false; // eof | ||
return; // eof | ||
} | ||
} | ||
|
||
// skip past '{', to next non-ws token | ||
if (!_nextTokenSkippingWhitespace()) { | ||
return false; // eof | ||
return; // eof | ||
} | ||
|
||
if (currentLevel <= level) { | ||
|
@@ -1137,30 +1149,25 @@ define(function (require, exports, module) { | |
currentLevel--; | ||
} | ||
|
||
} else if (/@(charset|import|namespace|include|extend|warn)/i.test(token) || | ||
!/\{/.test(stream.string)) { | ||
|
||
} else { | ||
// This code handles @rules in this format: | ||
// @rule ... ; | ||
// Or any less variable that starts with @var ... ; | ||
// Skip everything until the next ';' | ||
while (token !== ";") { | ||
// This code handle @rules that use this format: | ||
// @rule ... { ... } | ||
// such as @page, @keyframes (also -webkit-keyframes, etc.), and @font-face. | ||
// Skip everything including nested braces until the next matching '}' | ||
if (token === "{") { | ||
_skipToClosingBracket("{"); | ||
return; | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why was handling of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The main reason of combining the two cases is not for saving some lines of code. It is for handling both cases the right way since we want to skip all in both cases. We use |
||
if (!_nextTokenSkippingComments()) { | ||
return false; // eof | ||
return; // eof | ||
} | ||
} | ||
|
||
} else { | ||
// This code handle @rules that use this format: | ||
// @rule ... { ... } | ||
// such as @page, @keyframes (also -webkit-keyframes, etc.), and @font-face. | ||
// Skip everything including nested braces until the next matching '}' | ||
_skipToClosingBracket("{"); | ||
if (token === "}") { | ||
return false; | ||
} | ||
} | ||
return true; | ||
} | ||
|
||
// parse a style rule | ||
|
@@ -1174,17 +1181,16 @@ define(function (require, exports, module) { | |
} | ||
|
||
_parseRuleList = function (escapeToken, level) { | ||
var skipNext = true; | ||
while ((!escapeToken) || token !== escapeToken) { | ||
if (_isVariableInterpolatedProperty()) { | ||
_skipProperty(); | ||
if (!_skipProperty()) { | ||
// We found a "{" or "}" while skipping a property. Return false to handle the | ||
// opening or closing of a block properly. | ||
return false; | ||
} | ||
} else if (_isStartAtRule()) { | ||
// @rule | ||
if (!_parseAtRule(level) && level > 0) { | ||
skipNext = false; | ||
} else { | ||
skipNext = true; | ||
} | ||
_parseAtRule(level); | ||
} else if (_isStartComment()) { | ||
// comment - make this part of style rule | ||
if (includeCommentInNextRule()) { | ||
|
@@ -1194,15 +1200,12 @@ define(function (require, exports, module) { | |
_parseComment(); | ||
} else if (_maybeProperty()) { | ||
// Skip the property. | ||
_skipProperty(); | ||
// If we find a "{" or "}" while skipping a property, then don't skip it in | ||
// this while loop. Otherwise, we will get into an infinite loop in parsing | ||
// since we miss to handle the opening or closing of a block properly. | ||
if (token === "{" || token === "}") { | ||
if (!_skipProperty()) { | ||
// We found a "{" or "}" while skipping a property. Return false to handle the | ||
// opening or closing of a block properly. | ||
return false; | ||
} | ||
} else { | ||
skipNext = true; // reset skipNext | ||
// Otherwise, it's style rule | ||
if (!_parseRule(level === undefined ? 0 : level) && level > 0) { | ||
return false; | ||
|
@@ -1216,7 +1219,7 @@ define(function (require, exports, module) { | |
ruleStartLine = -1; | ||
} | ||
|
||
if (skipNext && !_nextTokenSkippingWhitespace()) { | ||
if (!_nextTokenSkippingWhitespace()) { | ||
break; | ||
} | ||
} | ||
|
@@ -1251,6 +1254,57 @@ define(function (require, exports, module) { | |
} | ||
*/ | ||
|
||
/** | ||
* Helper function to remove whitespaces before and after a selector | ||
* Returns trimmed selector if it is not an at-rule, or null if it starts with @. | ||
* | ||
* @param {string} selector | ||
* @return {string} | ||
*/ | ||
function _stripAtRules(selector) { | ||
selector = selector.trim(); | ||
if (selector.indexOf("@") === 0) { | ||
return ""; | ||
} | ||
return selector; | ||
} | ||
|
||
/** | ||
* Converts the given selector array into the actual CSS selectors similar to | ||
* those generated by a CSS preprocessor. | ||
* | ||
* @param {Array.<string>} selectorArray | ||
* @return {string} | ||
*/ | ||
function _getSelectorInFinalCSSForm(selectorArray) { | ||
var finalSelectorArray = [""], | ||
parentSelectorArray = [], | ||
group = []; | ||
_.forEach(selectorArray, function (selector) { | ||
selector = _stripAtRules(selector); | ||
group = selector.split(","); | ||
parentSelectorArray = []; | ||
_.forEach(group, function (cs) { | ||
var ampersandIndex = cs.indexOf("&"); | ||
_.forEach(finalSelectorArray, function (ps) { | ||
if (ampersandIndex === -1) { | ||
cs = _stripAtRules(cs); | ||
if (ps.length && cs.length) { | ||
ps += " "; | ||
} | ||
ps += cs; | ||
} else { | ||
// Replace all instances of & with regexp | ||
ps = _stripAtRules(cs.replace(/&/g, ps)); | ||
} | ||
parentSelectorArray.push(ps); | ||
}); | ||
}); | ||
finalSelectorArray = parentSelectorArray; | ||
}); | ||
return finalSelectorArray.join(", "); | ||
} | ||
|
||
/** | ||
* Finds all instances of the specified selector in "text". | ||
* Returns an Array of Objects with start and end properties. | ||
|
@@ -1293,11 +1347,17 @@ define(function (require, exports, module) { | |
|
||
var re = new RegExp(selector + "(\\[[^\\]]*\\]|:{1,2}[\\w-()]+|\\.[\\w-]+|#[\\w-]+)*\\s*$", classOrIdSelector ? "" : "i"); | ||
allSelectors.forEach(function (entry) { | ||
if (entry.selector.search(re) !== -1) { | ||
var actualSelector = entry.selector; | ||
if (entry.selector.indexOf("&") !== -1 && entry.parentSelectors) { | ||
var selectorArray = entry.parentSelectors.split(" / "); | ||
selectorArray.push(entry.selector); | ||
actualSelector = _getSelectorInFinalCSSForm(selectorArray); | ||
} | ||
if (actualSelector.search(re) !== -1) { | ||
result.push(entry); | ||
} else if (!classOrIdSelector) { | ||
// Special case for tag selectors - match "*" as the rightmost character | ||
if (/\*\s*$/.test(entry.selector)) { | ||
if (/\*\s*$/.test(actualSelector)) { | ||
result.push(entry); | ||
} | ||
} | ||
|
@@ -1443,41 +1503,6 @@ define(function (require, exports, module) { | |
var isPreprocessorDoc = FileUtils.isCSSPreprocessorFile(editor.document.file.fullPath); | ||
var selectorArray = []; | ||
|
||
function _stripAtRules(selector) { | ||
selector = selector.trim(); | ||
if (selector.indexOf("@") === 0) { | ||
return ""; | ||
} | ||
return selector; | ||
} | ||
|
||
function _getSelectorInFinalCSSForm() { | ||
var finalSelectorArray = [""], | ||
parentSelectorArray = [], | ||
group = []; | ||
_.forEach(selectorArray, function (selector) { | ||
selector = _stripAtRules(selector); | ||
group = selector.split(", "); | ||
parentSelectorArray = []; | ||
_.forEach(group, function (cs) { | ||
var ampersandIndex = cs.indexOf("&"); | ||
_.forEach(finalSelectorArray, function (ps) { | ||
if (ampersandIndex === -1) { | ||
if (ps.length) { | ||
ps += " "; | ||
} | ||
ps += cs; | ||
} else { | ||
ps = cs.replace("&", ps); | ||
} | ||
parentSelectorArray.push(ps); | ||
}); | ||
}); | ||
finalSelectorArray = parentSelectorArray; | ||
}); | ||
return finalSelectorArray.join(", "); | ||
} | ||
|
||
function _skipToOpeningBracket(ctx, startChar) { | ||
var unmatchedBraces = 0; | ||
if (!startChar) { | ||
|
@@ -1560,7 +1585,7 @@ define(function (require, exports, module) { | |
} else if (ctx.token.string === "{") { | ||
selector = _parseSelector(ctx); | ||
if (isPreprocessorDoc) { | ||
if (!skipPrevSibling && !/^\s*@media/i.test(selector)) { | ||
if (!skipPrevSibling && !/^\s*@/.test(selector)) { | ||
selectorArray.unshift(selector); | ||
} | ||
if (skipPrevSibling) { | ||
|
@@ -1608,7 +1633,7 @@ define(function (require, exports, module) { | |
if (ctx.token.type !== "comment") { | ||
if (ctx.token.string === "{") { | ||
selector = _parseSelector(ctx); | ||
if (isPreprocessorDoc) { | ||
if (isPreprocessorDoc && !/^\s*@/.test(selector)) { | ||
selectorArray.push(selector); | ||
} | ||
break; | ||
|
@@ -1623,7 +1648,7 @@ define(function (require, exports, module) { | |
} | ||
|
||
if (isPreprocessorDoc) { | ||
return _getSelectorInFinalCSSForm(); | ||
return _getSelectorInFinalCSSForm(selectorArray); | ||
} | ||
|
||
return _stripAtRules(selector); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Vendor prefixes can also appear as a prefix on values (such as
-webkit-transform
and-webkit-linear-gradient
in brackets/src/styles folder) or at-rules (e.g.@-webkit-keyframes
), so this may not be a good check that the token is a property.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's ok. We want to skip anything that is in property name or property value as long as it is not a selector. So this check will take care of any
-webkit-transform
that is placed on a separate line. Regarding@-webkit-keyframes
, it will be taking care of by_isStartAtRule()
check which is done before we call_maybeProperty()
.