bemhint-css-naming
Version:
Plugin for bemhint which validate css selectors
118 lines (99 loc) • 3.64 kB
JavaScript
var bemNaming = require('bem-naming'),
minimatch = require('minimatch'),
postcss = require('postcss'),
cssParser = require('postcss-selector-parser');
/**
* Функция для обработки найденных ошибок
*
* @callback CssNaming~errorCallback
* @param {string} msg - Сообщение об ошибке
* @param {string} target - Причина ошибки
* @param {string} line - Место ошибки: строка
* @param {string} column - Место ошибки: позиция в строке
*/
/**
* Создает экземпляр CssNaming
*
* @constructor
* @this {CssNaming}
* @param {string[]} excludes - Исключения для проверки соответствия названий классов БЭМ-нотации
* @param {CssNaming~errorCallback} errCallback - Обработчик ошибок
*/
function CssNaming(excludes, errCallback) {
this._excludeRegexp = excludes && this._buildExcludeRegexp(excludes);
this._errCallback = errCallback;
};
CssNaming.prototype = {
/**
* Проверяет селекторы в заданном css коде
* @param {string} content - Проверяемый css
* @param {string} blockName - Название целевого блока
*/
validateSelectors: function(content, blockName) {
var data;
try {
data = postcss.parse(content);
} catch(e) {
this._errCallback('Failed to check css naming', e.reason, e.line, e.column);
}
data && data.nodes.forEach(function(rule) {
if (rule.type === 'rule') {
cssParser(this._validateSelectors.bind(this, rule, blockName)).process(rule.selector);
}
}, this);
},
/**
* Преобразуем паттерны в regex через mimimatch и склеиваем в один
*
* @private
*/
_buildExcludeRegexp: function(excludes) {
var expr = excludes.map(function(val) {
return minimatch.makeRe(val).source;
}).join('|');
return new RegExp(expr);
},
/**
* Проверяет набор css селекторов
*
* @private
*/
_validateSelectors: function(rule, blockName, selectors) {
var _this = this;
selectors.each(function(selector) {
var hasTargetBlock = false,
ruleStart = rule.source.start;
selector.eachClass(function(cssClass) {
hasTargetBlock |= _this._validateClass(cssClass, blockName, rule);
});
hasTargetBlock || _this._errCallback(
'Selector does not contain block name specified in the file name',
rule.selector,
ruleStart.line,
ruleStart.column
);
});
},
/**
* Проверяет набор css селекторов
*
* @private
*/
_validateClass: function (cssClass, blockName, rule) {
var cssVal = cssClass.value,
cssEntity = bemNaming.parse(cssVal),
cssClassStart = cssClass.source.start,
errorLine = rule.source.start.line + cssClassStart.line - 1;
if (this._excludeRegexp && this._excludeRegexp.test(cssVal)) {
return;
}
if (cssEntity) {
return cssEntity.block === blockName;
} else {
this._errCallback('Invalid class naming', rule.selector, errorLine, cssClassStart.column);
}
return false;
}
};
module.exports = CssNaming;
;