sass-lint
Version:
All Node Sass linter!
335 lines (282 loc) • 9.19 kB
JavaScript
;
var util = require('util'),
fs = require('fs'),
path = require('path'),
yaml = require('js-yaml'),
gonzales = require('gonzales-pe-sl');
var helpers = {};
helpers.log = function log (input) {
console.log(util.inspect(input, false, null));
};
helpers.propertySearch = function (haystack, needle, property) {
var length = haystack.length,
i;
for (i = 0; i < length; i++) {
if (haystack[i][property] === needle) {
return i;
}
}
return -1;
};
helpers.isEqual = function (a, b) {
var startLine = a.start.line === b.start.line ? true : false,
endLine = a.end.line === b.end.line ? true : false,
type = a.type === b.type ? true : false,
length = a.content.length === b.content.length ? true : false;
if (startLine && endLine && type && length) {
return true;
}
else {
return false;
}
};
helpers.isUnique = function (results, item) {
var search = this.propertySearch(results, item.line, 'line');
if (search === -1) {
return true;
}
else if (results[search].column === item.column && results[search].message === item.message) {
return false;
}
else {
return true;
}
};
helpers.addUnique = function (results, item) {
if (this.isUnique(results, item)) {
results.push(item);
}
return results;
};
helpers.sortDetects = function (a, b) {
if (a.line < b.line) {
return -1;
}
if (a.line > b.line) {
return 1;
}
if (a.line === b.line) {
if (a.column < b.column) {
return -1;
}
if (a.column > b.column) {
return 1;
}
return 0;
}
return 0;
};
helpers.isNumber = function (val) {
if (isNaN(parseInt(val, 10))) {
return false;
}
return true;
};
helpers.isUpperCase = function (str) {
var pieces = str.split(''),
i,
result = 0;
for (i = 0; i < pieces.length; i++) {
if (!helpers.isNumber(pieces[i])) {
if (pieces[i] === pieces[i].toUpperCase() && pieces[i] !== pieces[i].toLowerCase()) {
result++;
}
else {
return false;
}
}
}
if (result) {
return true;
}
return false;
};
helpers.isLowerCase = function (str) {
var pieces = str.split(''),
i,
result = 0;
for (i = 0; i < pieces.length; i++) {
if (!helpers.isNumber(pieces[i])) {
if (pieces[i] === pieces[i].toLowerCase() && pieces[i] !== pieces[i].toUpperCase()) {
result++;
}
else {
return false;
}
}
}
if (result) {
return true;
}
return false;
};
/**
* Determines if a given string adheres to camel-case format
* @param {string} str String to test
* @returns {boolean} Whether str adheres to camel-case format
*/
helpers.isCamelCase = function (str) {
return /^[a-z][a-zA-Z0-9]*$/.test(str);
};
/**
* Determines if a given string adheres to hyphenated-lowercase format
* @param {string} str String to test
* @returns {boolean} Whether str adheres to hyphenated-lowercase format
*/
helpers.isHyphenatedLowercase = function (str) {
return !(/[^\-a-z0-9]/.test(str));
};
/**
* Determines if a given string adheres to snake-case format
* @param {string} str String to test
* @returns {boolean} Whether str adheres to snake-case format
*/
helpers.isSnakeCase = function (str) {
return !(/[^_a-z0-9]/.test(str));
};
/**
* Determines if a given string adheres to strict-BEM format
* @param {string} str String to test
* @returns {boolean} Whether str adheres to strict-BEM format
*/
helpers.isStrictBEM = function (str) {
return /^[a-z](\-?[a-z0-9]+)*(__[a-z0-9](\-?[a-z0-9]+)*)?((_[a-z0-9](\-?[a-z0-9]+)*){2})?$/.test(str);
};
/**
* Determines if a given string adheres to hyphenated-BEM format
* @param {string} str String to test
* @returns {boolean} Whether str adheres to hyphenated-BEM format
*/
helpers.isHyphenatedBEM = function (str) {
return !(/[A-Z]|-{3}|_{3}|[^_]_[^_]/.test(str));
};
helpers.isValidHex = function (str) {
if (str.match(/^([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/)) {
return true;
}
return false;
};
helpers.loadConfigFile = function (configPath) {
var fileDir = path.dirname(configPath),
fileName = path.basename(configPath),
fileExtension = path.extname(fileName),
filePath = path.join(__dirname, 'config', fileDir, fileName),
file = fs.readFileSync(filePath, 'utf8') || false;
if (file) {
if (fileExtension === '.yml') {
return yaml.safeLoad(file);
}
}
return file;
};
helpers.hasEOL = function (str) {
return /\r\n|\n/.test(str);
};
helpers.isEmptyLine = function (str) {
return /(\r\n|\n){2}/.test(str);
};
helpers.stripQuotes = function (str) {
return str.substring(1, str.length - 1);
};
helpers.stripPrefix = function (str) {
var modProperty = str.slice(1),
prefixLength = modProperty.indexOf('-');
return modProperty.slice(prefixLength + 1);
};
/**
* Removes the trailing space from a string
* @param {string} curSelector - the current selector string
* @returns {string} curSelector - the current selector minus any trailing space.
*/
helpers.stripLastSpace = function (selector) {
if (selector.charAt(selector.length - 1) === ' ') {
return selector.substr(0, selector.length - 1);
}
return selector;
};
/**
* Checks the current selector value against the previous selector value and assesses whether they are
* a) currently an enforced selector type for nesting (user specified - all true by default)
* b) whether they should be nested
* @param {object} currentVal - the current node / part of our selector
* @param {object} previousVal - the previous node / part of our selector
* @param {array} elements - a complete array of nestable selector types
* @param {array} nestable - an array of the types of selector to nest
* @returns {object} Returns whether we or we should nest and the previous val
*/
helpers.isNestable = function (currentVal, previousVal, elements, nestable) {
// check if they are nestable by checking the previous element against one
// of the user specified selector types
if (elements.indexOf(previousVal) !== -1 && nestable.indexOf(currentVal) !== -1) {
return true;
}
return false;
};
/**
* Tries to traverse the AST, following a specified path
* @param {object} node Starting node
* @param {array} traversalPath Array of Node types to traverse, starting from the first element
* @returns {array} Nodes at the end of the path. Empty array if the traversal failed
*/
helpers.attemptTraversal = function (node, traversalPath) {
var i,
nextNodeList,
currentNodeList = [],
processChildNode = function processChildNode (child) {
child.forEach(traversalPath[i], function (n) {
nextNodeList.push(n);
});
};
node.forEach(traversalPath[0], function (n) {
currentNodeList.push(n);
});
for (i = 1; i < traversalPath.length; i++) {
if (currentNodeList.length === 0) {
return [];
}
nextNodeList = [];
currentNodeList.forEach(processChildNode);
currentNodeList = nextNodeList;
}
return currentNodeList;
};
/**
* Collects all suffix extensions for a selector
* @param {object} ruleset ASTNode of type ruleset, containing a selector with nested suffix extensions
* @param {string} selectorType Node type of the selector (e.g. class, id)
* @returns {array} Array of Nodes with the content property replaced by the complete selector
* (without '.', '#', etc) resulting from suffix extensions
*/
helpers.collectSuffixExtensions = function (ruleset, selectorType) {
var parentSelectors = helpers.attemptTraversal(ruleset, ['selector', selectorType, 'ident']),
childSuffixes = helpers.attemptTraversal(ruleset, ['block', 'ruleset']),
selectorList = [];
if (parentSelectors.length === 0) {
return [];
}
// Goes recursively through all nodes that look like suffix extensions. There may be multiple parents that are
// extended, so lots of looping is required.
var processChildSuffix = function (child, parents) {
var currentParents = [],
selectors = helpers.attemptTraversal(child, ['selector', 'parentSelectorExtension', 'ident']),
nestedChildSuffixes = helpers.attemptTraversal(child, ['block', 'ruleset']);
selectors.forEach(function (childSuffixNode) {
// append suffix extension to all parent selectors
parents.forEach(function (parent) {
// clone so we don't modify the actual AST
var clonedChildSuffixNode = gonzales.createNode(childSuffixNode);
clonedChildSuffixNode.content = parent.content + clonedChildSuffixNode.content;
currentParents.push(clonedChildSuffixNode);
});
});
selectorList = selectorList.concat(currentParents);
nestedChildSuffixes.forEach(function (childSuffix) {
processChildSuffix(childSuffix, currentParents);
});
};
childSuffixes.forEach(function (childSuffix) {
processChildSuffix(childSuffix, parentSelectors);
});
return parentSelectors.concat(selectorList);
};
module.exports = helpers;