Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,15 @@ module.exports = {
}
};
```

Allow using tags or some set of tags with special option `allowTags`, which accepts array of tags or boolean value:

```js
module.exports = {
plugins: {
'bemhint-css-naming': {
allowTags: true // or ['body', 'html']
}
}
};
```
12 changes: 12 additions & 0 deletions README.ru.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,15 @@ module.exports = {
}
};
```

Разрешить использование тегов или определенного набор тегов специальной опцией `allowTags`, в которой допускается массив тегов или логическое значение.

```js
module.exports = {
plugins: {
'bemhint-css-naming': {
allowTags: true // or ['body', 'html']
}
}
};
```
60 changes: 46 additions & 14 deletions lib/css-naming.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,16 @@ var bemNaming = require('bem-naming'),
*
* @constructor
* @this {CssNaming}
* @param {string[]} excludes - Исключения для проверки соответствия названий классов БЭМ-нотации
* @param {Object} options - опции для проверки нейминга
* @param {boolean} options.allowTag - разрешает использование селекторов по тегу
* @param {string[]} options.excludes - Исключения для проверки соответствия названий классов БЭМ-нотации
* @param {CssNaming~errorCallback} errCallback - Обработчик ошибок
*/
function CssNaming(excludes, errCallback) {
this._excludeRegexp = excludes && this._buildExcludeRegexp(excludes);
function CssNaming(options, errCallback) {
options || (options = {});

this._options = options;
this._excludeRegexp = options.excludes && this._buildExcludeRegexp(options.excludes);
this._errCallback = errCallback;
};

Expand Down Expand Up @@ -70,22 +75,49 @@ CssNaming.prototype = {
* @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(
function callError(rule, ruleStart) {
_this._errCallback(
'Selector does not contain block name specified in the file name',
rule.selector,
ruleStart.line,
ruleStart.column
);
};

var _this = this,
allowTags = _this._options.allowTags,
ruleStart = rule.source.start;

selectors.each(function(selector) {
var classNodesCount = 0,
isNestingBefore = false,
hasTargetBlock = false;

selector.walk(function(node) {
if (node.type === 'class') {
++classNodesCount;
hasTargetBlock |= _this._validateClass(node, blockName, rule);
}
// в конструкции &__smth -> & - nesting, а следующий за ней __smth распознается как tag
if (node.type === 'tag' && !isNestingBefore) {
// теги не разрешены
if (!Boolean(allowTags)) {
callError(rule, ruleStart);

return;
}
// есть список разрешенных, он либо пустой либо встретился запрещенный
if (Array.isArray(allowTags) && (allowTags.length === 0 || allowTags.indexOf(node.value) === -1)) {
callError(rule, ruleStart);

return;
}
}

isNestingBefore = node.type === 'nesting';
});

hasTargetBlock || classNodesCount === 0 || callError(rule, ruleStart);
});
},

Expand Down
6 changes: 4 additions & 2 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,10 @@ module.exports = {
value: util.format('%s at line %s, column %s', target, line, column)
});
}

var validator = new CssNaming(config._config.excludeClasses, addError);
var configData = config.getConfig();
var validator = new CssNaming({
excludes: configData.excludeClasses, allowTags: configData.allowTags
}, addError);

validator.validateSelectors(tech.content, tech.entity.block);
}
Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"naming"
],
"scripts": {
"unit": "mocha test/"
"test": "mocha test/"
},
"repository": {
"type": "git",
Expand All @@ -24,8 +24,8 @@
"dependencies": {
"bem-naming": "^1.0.1",
"minimatch": "^3.0.0",
"postcss": "^5.0.19",
"postcss-selector-parser": "^1.3.3"
"postcss": "^6.0.4",
"postcss-selector-parser": "2.2.3"
},
"devDependencies": {
"chai": "^3.5.0",
Expand Down
74 changes: 73 additions & 1 deletion test/css-naming.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,33 @@ describe('css naming', function() {
assert.called(errorCallback);
assert.calledWith(errorCallback, sinon.match(/not contain block name/));
});

it('on tag selector without allowTags option', function() {
var errorCallback = sinon.spy(),
validator = new CssNaming(null, errorCallback);

validator.validateSelectors('body {}', 'block');

assert.called(errorCallback);
});

it('on combined tag selector with allowTags option', function() {
var errorCallback = sinon.spy(),
validator = new CssNaming({ allowTags: true }, errorCallback);

validator.validateSelectors('body.neblock {}', 'block');

assert.called(errorCallback);
});

it('on combined tag selector', function() {
var errorCallback = sinon.spy(),
validator = new CssNaming(null, errorCallback);

validator.validateSelectors('body.block {}', 'block');

assert.called(errorCallback);
});
});

describe('should pass', function() {
Expand All @@ -67,11 +94,56 @@ describe('css naming', function() {

it('if wrong class was added to excludes', function() {
var errorCallback = sinon.spy(),
validator = new CssNaming(['test-*'], errorCallback);
validator = new CssNaming({ excludes: ['test-*'] }, errorCallback);

validator.validateSelectors('.block .test-e_x_c_l_u_d_e_d{}', 'block');

assert.notCalled(errorCallback);
});

it('on nested rule', function() {
var errorCallback = sinon.spy(),
validator = new CssNaming(null, errorCallback);

validator.validateSelectors('.block &__elem {}', 'block');

assert.notCalled(errorCallback);
});

it('on pseudoclass', function() {
var errorCallback = sinon.spy(),
validator = new CssNaming(null, errorCallback);

validator.validateSelectors(':root {}', 'block');

assert.notCalled(errorCallback);
});

it('on tag selector with allowTags option', function() {
var errorCallback = sinon.spy(),
validator = new CssNaming({ allowTags: true }, errorCallback);

validator.validateSelectors('body {}', 'block');

assert.notCalled(errorCallback);
});

it('on combined selector with allowTags option', function() {
var errorCallback = sinon.spy(),
validator = new CssNaming({ allowTags: true }, errorCallback);

validator.validateSelectors('body.block {}', 'block');

assert.notCalled(errorCallback);
});

it('on tag selector with allowTags option as an array', function() {
var errorCallback = sinon.spy(),
validator = new CssNaming({ allowTags: ['body', 'html'] }, errorCallback);

validator.validateSelectors('body {}', 'block');

assert.notCalled(errorCallback);
});
});
});