postcss-bem-linter
Advanced tools
Comparing version 0.4.0 to 0.5.0
@@ -1,3 +0,6 @@ | ||
=== HEAD | ||
=== 0.5.0 (August 5, 2015) | ||
* Add alternate signature for designating preset and preset options. | ||
* Remove checks that `:root` rules only contain custom-properties, and that the `:root` selector is not grouped or combined with other selectors. Outsourcing these checks to [stylelint](https://github.com/stylelint/stylelint). | ||
=== 0.4.0 (June 23, 2015) | ||
@@ -4,0 +7,0 @@ |
103
index.js
@@ -1,7 +0,1 @@ | ||
'use strict'; | ||
/** | ||
* Module dependencies | ||
*/ | ||
var postcss = require('postcss'); | ||
@@ -11,3 +5,2 @@ var validateCustomProperties = require('./lib/validate-properties'); | ||
var validateSelectors = require('./lib/validate-selectors'); | ||
var validateRules = require('./lib/validate-rules'); | ||
var presetPatterns = require('./lib/preset-patterns'); | ||
@@ -19,57 +12,61 @@ | ||
/** | ||
* Check patterns or setup defaults. If the input CSS does not have a | ||
* Set things up and call the validators. | ||
* | ||
* If the input CSS does not have a | ||
* directive defining a component name according to the specified pattern, | ||
* do nothing -- or warn, if the directive is there but the name does not match. | ||
* Then call all of the validators. | ||
* | ||
* @param {Object} [patterns = 'suit'] | ||
* @param {RegExp} [patterns.componentName] | ||
* @param {RegExp} [patterns.utilitySelectors] | ||
* @param {Object|Function} [patterns.componentSelectors] | ||
* @param {Object} [opts] - Options that are can be used by | ||
* @param {Object|String} [primaryOptions = 'suit'] | ||
* @param {RegExp} [primaryOptions.componentName] | ||
* @param {RegExp} [primaryOptions.utilitySelectors] | ||
* @param {Object|Function} [primaryOptions.componentSelectors] | ||
* @param {String} [primaryOptions.preset] - The same as passing a string for `primaryOptions` | ||
* @param {Object} [primaryOptions.presetOptions] - Options that are can be used by | ||
* a pattern (e.g. `namespace`) | ||
* @param {Object} [secondaryOptions] - The same as `primaryOptions.presetOptions` | ||
*/ | ||
module.exports = postcss.plugin( | ||
'postcss-bem-linter', | ||
function (patterns, opts) { | ||
patterns = patterns || 'suit'; | ||
if (typeof patterns === 'string') { | ||
patterns = presetPatterns[patterns]; | ||
} | ||
var componentNamePattern = patterns.componentName || /[-_a-zA-Z0-9]+/; | ||
module.exports = postcss.plugin('postcss-bem-linter', function(primaryOptions, secondaryOptions) { | ||
var patterns = primaryOptions || 'suit'; | ||
if (typeof patterns === 'string') { | ||
patterns = presetPatterns[patterns]; | ||
} else if (patterns.preset) { | ||
patterns = presetPatterns[patterns.preset]; | ||
} | ||
return function (styles, result) { | ||
return new Promise(function (resolve) { | ||
var firstNode = styles.nodes[0]; | ||
if (!firstNode || firstNode.type !== 'comment') { resolve(); } | ||
var presetOptions = secondaryOptions || {}; | ||
if (primaryOptions && primaryOptions.presetOptions) { | ||
presetOptions = primaryOptions.presetOptions; | ||
} | ||
var initialComment = firstNode.text; | ||
if (!initialComment || !initialComment.match(RE_DIRECTIVE)) { | ||
resolve(); | ||
} | ||
var componentNamePattern = patterns.componentName || /[-_a-zA-Z0-9]+/; | ||
var defined = initialComment.match(RE_DIRECTIVE)[1].trim(); | ||
var isUtilities = defined === UTILITIES_IDENT; | ||
if (!isUtilities && !defined.match(componentNamePattern)) { | ||
result.warn( | ||
'Invalid component name in definition /*' + initialComment + '*/', | ||
{ node: firstNode } | ||
); | ||
} | ||
return function(root, result) { | ||
var firstNode = root.nodes[0]; | ||
if (!firstNode || firstNode.type !== 'comment') return; | ||
var weakMode = initialComment.match(RE_DIRECTIVE)[2] === 'weak'; | ||
var initialComment = firstNode.text; | ||
if (!initialComment || !initialComment.match(RE_DIRECTIVE)) return; | ||
validateRules(styles, result); | ||
if (isUtilities) { | ||
validateUtilities(styles, patterns.utilitySelectors, result); | ||
} else { | ||
validateSelectors( | ||
styles, defined, weakMode, patterns.componentSelectors, opts, result | ||
); | ||
} | ||
validateCustomProperties(styles, defined, result); | ||
resolve(); | ||
}); | ||
}; | ||
} | ||
); | ||
var defined = initialComment.match(RE_DIRECTIVE)[1].trim(); | ||
var isUtilities = defined === UTILITIES_IDENT; | ||
if (!isUtilities && !defined.match(componentNamePattern)) { | ||
result.warn( | ||
'Invalid component name in definition /*' + initialComment + '*/', | ||
{ node: firstNode } | ||
); | ||
} | ||
var weakMode = initialComment.match(RE_DIRECTIVE)[2] === 'weak'; | ||
if (isUtilities) { | ||
validateUtilities(root, patterns.utilitySelectors, result); | ||
return; | ||
} | ||
validateSelectors( | ||
root, defined, weakMode, patterns.componentSelectors, presetOptions, result | ||
); | ||
validateCustomProperties(root, defined, result); | ||
console.log(result.messages) | ||
}; | ||
}); |
@@ -1,16 +0,4 @@ | ||
'use strict'; | ||
/** | ||
* Module dependencies | ||
*/ | ||
var listSequences = require('./listSequences'); | ||
/** | ||
* Module exports | ||
*/ | ||
module.exports = isValidSelector; | ||
/** | ||
* A SelectorPattern defines acceptable patterns for selector sequences | ||
@@ -48,14 +36,14 @@ * in component stylesheets. | ||
* SelectorPattern, as described above | ||
* @param {Object} [opts] - Options to pass to the pattern functions | ||
* @param {Object} [presetOptions] - Options to pass to the pattern functions | ||
* @returns {Boolean} | ||
*/ | ||
function isValidSelector(selector, componentName, weakMode, pattern, opts) { | ||
module.exports = function(selector, componentName, weakMode, pattern, presetOptions) { | ||
// Don't bother with :root | ||
if (selector === ':root') { return true; } | ||
if (selector === ':root') return true; | ||
var initialPattern = (pattern.initial) ? | ||
pattern.initial(componentName, opts) : | ||
pattern(componentName, opts); | ||
pattern.initial(componentName, presetOptions) : | ||
pattern(componentName, presetOptions); | ||
var combinedPattern = (pattern.combined) ? | ||
pattern.combined(componentName, opts) : | ||
pattern.combined(componentName, presetOptions) : | ||
initialPattern; | ||
@@ -65,12 +53,12 @@ var sequences = listSequences(selector); | ||
// Error if an acceptable initialPattern does not begin the selector | ||
if (!initialPattern.test(sequences[0])) { return false; } | ||
if (!initialPattern.test(sequences[0])) return false; | ||
// Unless in weak mode, error if combined simple selectors do not match the | ||
// combinedPattern | ||
if (weakMode) { return true; } | ||
if (weakMode) return true; | ||
return sequences.slice(1).every(function (combinedSequence) { | ||
return initialPattern.test(combinedSequence) || | ||
combinedPattern.test(combinedSequence); | ||
return sequences.slice(1).every(function(combinedSequence) { | ||
return initialPattern.test(combinedSequence) | ||
|| combinedPattern.test(combinedSequence); | ||
}); | ||
} |
@@ -1,16 +0,4 @@ | ||
'use strict'; | ||
/** | ||
* Module dependencies | ||
*/ | ||
var listSequences = require('./listSequences'); | ||
/** | ||
* Module exports | ||
*/ | ||
module.exports = isValidUtility; | ||
/** | ||
* @param {String} selector | ||
@@ -20,7 +8,7 @@ * @param {RegExp} pattern | ||
*/ | ||
function isValidUtility(selector, pattern) { | ||
module.exports = function(selector, pattern) { | ||
var sequences = listSequences(selector); | ||
return sequences.every(function (sequence) { | ||
return sequences.every(function(sequence) { | ||
return pattern.test(sequence); | ||
}); | ||
} |
@@ -1,3 +0,1 @@ | ||
module.exports = listSequences; | ||
/** | ||
@@ -7,3 +5,3 @@ * Extract an array of selector sequences from a selector string --- | ||
* | ||
* CSS combinators are whitespace, >, +, ~ | ||
* CSS combinators are " ", >, +, ~ | ||
* (cf. http://www.w3.org/TR/css3-selectors/#selector-syntax) | ||
@@ -17,11 +15,11 @@ * | ||
*/ | ||
function listSequences(selector) { | ||
module.exports = function(selector) { | ||
return selector | ||
.split(/[\s>+~]/) | ||
.filter(function (s) { | ||
.filter(function(s) { | ||
return s !== ''; | ||
}) | ||
.map(function (s) { | ||
.map(function(s) { | ||
return s.split(':')[0]; | ||
}); | ||
} |
@@ -1,7 +0,1 @@ | ||
'use strict'; | ||
/** | ||
* Module exports | ||
*/ | ||
module.exports = { | ||
@@ -11,8 +5,8 @@ suit: { | ||
componentSelectors: suitSelector, | ||
utilitySelectors: /^\.u(?:-[a-z][a-zA-Z0-9]+)+$/ | ||
utilitySelectors: /^\.u(?:-[a-z][a-zA-Z0-9]+)+$/, | ||
}, | ||
bem: { | ||
componentName: /[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)*/, | ||
componentSelectors: bemSelector | ||
} | ||
componentSelectors: bemSelector, | ||
}, | ||
}; | ||
@@ -22,8 +16,8 @@ | ||
* @param {String} componentName | ||
* @param {Object} [opts] | ||
* @param {String} [opts.namespace] | ||
* @param {Object} [presetOptions] | ||
* @param {String} [presetOptions.namespace] | ||
* @returns {RegExp} | ||
*/ | ||
function suitSelector(componentName, opts) { | ||
var ns = (opts && opts.namespace) ? opts.namespace + '-' : ''; | ||
function suitSelector(componentName, presetOptions) { | ||
var ns = (presetOptions && presetOptions.namespace) ? presetOptions.namespace + '-' : ''; | ||
var OPTIONAL_PART = '(?:-[a-zA-Z0-9]+)?'; | ||
@@ -30,0 +24,0 @@ var OPTIONAL_MODIFIER = '(?:--[a-zA-Z0-9]+)?'; |
@@ -1,16 +0,2 @@ | ||
'use strict'; | ||
/** | ||
* Module dependencies | ||
*/ | ||
var isValidRule = require('./is-valid-rule'); | ||
/** | ||
* Module exports | ||
*/ | ||
module.exports = validateCustomProperties; | ||
/** | ||
* @param {Object} styles | ||
@@ -20,18 +6,12 @@ * @param {String} componentName | ||
*/ | ||
function validateCustomProperties(styles, componentName, result) { | ||
styles.eachRule(function (rule) { | ||
if (!isValidRule(rule) || rule.selectors[0] !== ':root') { return; } | ||
module.exports = function(styles, componentName, result) { | ||
styles.eachRule(function(rule) { | ||
if (rule.selectors[0] !== ':root') return; | ||
rule.eachDecl(function (declaration, i) { | ||
rule.eachDecl(function(declaration, i) { | ||
var property = declaration.prop; | ||
if (property.indexOf('--') !== 0) { | ||
if (property.indexOf(componentName + '-') !== 2) { | ||
result.warn( | ||
'Invalid custom property name "' + property + '": ' + | ||
'`:root` rules must only contain custom properties', | ||
{ node: declaration } | ||
); | ||
} else if (property.indexOf(componentName + '-') !== 2) { | ||
result.warn( | ||
'Invalid custom property name "' + property + '": ' + | ||
'a component\'s custom properties must start with the ' + | ||
@@ -38,0 +18,0 @@ 'component name', |
@@ -1,7 +0,1 @@ | ||
'use strict'; | ||
/** | ||
* Module dependencies | ||
*/ | ||
var isValid = require('./is-valid-selector'); | ||
@@ -11,8 +5,2 @@ var IGNORE_COMMENT = 'postcss-bem-linter: ignore'; | ||
/** | ||
* Module exports | ||
*/ | ||
module.exports = validateSelectors; | ||
/** | ||
* @param {Object} styles | ||
@@ -22,20 +10,21 @@ * @param {String} componentName | ||
* @param {Object} pattern | ||
* @param {Object} opts | ||
* @param {Object} presetOptions | ||
* @param {Result} result - PostCSS Result, for registering warnings | ||
*/ | ||
function validateSelectors( | ||
styles, componentName, weakMode, pattern, opts, result | ||
) { | ||
styles.eachRule(function (rule) { | ||
if (rule.parent && rule.parent.name === 'keyframes') { return; } | ||
module.exports = function(styles, componentName, weakMode, pattern, presetOptions, result) { | ||
styles.eachRule(function(rule) { | ||
if (rule.parent && rule.parent.name === 'keyframes') return; | ||
var prev = rule.prev(); | ||
if (prev && prev.type === 'comment' | ||
&& prev.text === IGNORE_COMMENT) { return; } | ||
if ( | ||
prev | ||
&& prev.type === 'comment' | ||
&& prev.text === IGNORE_COMMENT | ||
) return; | ||
var selectors = rule.selectors; | ||
selectors.forEach(function (selector) { | ||
selectors.forEach(function(selector) { | ||
// selectors must start with the componentName class, or be `:root` | ||
if (!isValid(selector, componentName, weakMode, pattern, opts)) { | ||
if (!isValid(selector, componentName, weakMode, pattern, presetOptions)) { | ||
result.warn( | ||
@@ -42,0 +31,0 @@ 'Invalid component selector "' + selector + '"', |
@@ -1,16 +0,4 @@ | ||
'use strict'; | ||
/** | ||
* Module dependencies | ||
*/ | ||
var isValid = require('./is-valid-utility'); | ||
/** | ||
* Module exports | ||
*/ | ||
module.exports = validateSelectors; | ||
/** | ||
* @param {Object} styles | ||
@@ -20,5 +8,5 @@ * @param {RegExp} pattern | ||
*/ | ||
function validateSelectors(styles, pattern, result) { | ||
styles.eachRule(function (rule) { | ||
rule.selectors.forEach(function (selector) { | ||
module.exports = function(styles, pattern, result) { | ||
styles.eachRule(function(rule) { | ||
rule.selectors.forEach(function(selector) { | ||
if (!isValid(selector, pattern)) { | ||
@@ -25,0 +13,0 @@ result.warn( |
{ | ||
"name": "postcss-bem-linter", | ||
"version": "0.4.0", | ||
"version": "0.5.0", | ||
"description": "A BEM linter for postcss", | ||
@@ -13,3 +13,3 @@ "files": [ | ||
"devDependencies": { | ||
"eslint": "0.23.0", | ||
"eslint": "1.0.0", | ||
"mocha": "2.2.5" | ||
@@ -25,3 +25,3 @@ }, | ||
"type": "git", | ||
"url": "git://github.com/necolas/postcss-bem-linter.git" | ||
"url": "git://github.com/postcss/postcss-bem-linter.git" | ||
}, | ||
@@ -28,0 +28,0 @@ "keywords": [ |
@@ -14,3 +14,3 @@ # postcss-bem-linter | ||
With this plugin, you can check the validity of stylesheets against a set of BEM-style conventions. | ||
**With this plugin, you can check the validity of selectors against a set of BEM-style conventions.** | ||
You can use preset patterns (SUIT and BEM, currently) or insert your own. The plugin will register | ||
@@ -25,3 +25,3 @@ warnings if it finds CSS that does not follow the specified conventions. | ||
This plugin registers warnings via PostCSS. Therefore, you'll want to use it with a PostCSS runner that prints warnings (e.g. [`gulp-postcss`](https://github.com/postcss/gulp-postcss)) or another PostCSS plugin that prints warnings (e.g. [`postcss-log-warnings`](https://github.com/davidtheclark/postcss-log-warnings)). | ||
This plugin registers warnings via PostCSS. Therefore, you'll want to use it with a PostCSS runner that prints warnings (e.g. [`gulp-postcss`](https://github.com/postcss/gulp-postcss)) or another PostCSS plugin that prints warnings (e.g. [`postcss-reporter`](https://github.com/postcss/postcss-reporter)). | ||
@@ -34,4 +34,2 @@ ## Conformance tests | ||
* Only allow custom-property names that *begin* with the defined `ComponentName`. | ||
* The `:root` selector can only contain custom-properties. | ||
* The `:root` cannot be combined with other selectors. | ||
@@ -43,2 +41,4 @@ **Weak mode**: | ||
*Prior to 0.5.0, this plugin checked two other details: that `:root` rules only contain custom-properties; and that the `:root` selector is not grouped or combined with other selectors. These checks can now be performed by [stylelint](https://github.com/stylelint/stylelint). So from 0.5.0 onwards, this plugin leaves that business to stylelint to focus on its more unique task.* | ||
## Use | ||
@@ -59,11 +59,8 @@ | ||
Also note that *pseudo-classes and pseudo-elements must be at the end of sequences, and | ||
will be ignored*. Instead of `.Component:first-child.is-open` you should use | ||
`.Component.is-open:first-child`. The former will trigger a warning. | ||
Also note that *pseudo-classes and pseudo-elements will be ignored if they occur at the end of the sequence*. | ||
Instead of `.Component:first-child.is-open`, you should use `.Component.is-open:first-child`. | ||
The former will trigger a warning unless you've written a silly complicated regular expression. | ||
#### Preset Patterns | ||
You can use a preset pattern by passing a string as the `pattern`, and, if needed, an `options` object, | ||
as in `bemLinter('suit', { namespace: 'twt' })`. Options are pattern-specific. | ||
The following preset patterns are available: | ||
@@ -77,2 +74,7 @@ | ||
You can use a preset pattern and its options in two ways: | ||
- pass the preset's name as the first argument, and, if needed, an `options` object as the second, | ||
e.g. `bemLinter('suit', { namespace: 'twt' })`. | ||
- pass an object as the first and only argument, with the preset's name as the `preset` property and, if need, `presetOptions`, e.g. `bemLinter({ preset: 'suit', presetOptions { namespace: 'twt' })`. | ||
**`'suit'` is the default pattern**; so if you do not pass any `pattern` argument, | ||
@@ -83,3 +85,3 @@ SUIT conventions will be enforced. | ||
You can define a custom pattern by passing an object with the following properties: | ||
You can define a custom pattern by passing as your first and only argument an object with the following properties: | ||
@@ -108,2 +110,3 @@ - `componentName` (optional): A regular expression describing valid component names. | ||
bemLinter('suit', { namespace: 'twt' }); | ||
bemLinter({ preset: 'suit', presetOptions: { namespace: 'twt' }}); | ||
@@ -213,3 +216,3 @@ // use 'bem' conventions | ||
conformance failures, which you can print to the console using | ||
[`postcss-log-warnings`](https://github.com/davidtheclark/postcss-log-warnings) or relying | ||
[`postcss-reporter`](https://github.com/postcss/postcss-reporter) or relying | ||
on a PostCSS runner (such as [`gulp-postcss`](https://github.com/postcss/gulp-postcss)). | ||
@@ -220,9 +223,9 @@ | ||
var bemLinter = require('postcss-bem-linter'); | ||
var logWarnings = require('postcss-log-warnings'); | ||
var reporter = require('postcss-reporter'); | ||
files.forEach(function (file) { | ||
files.forEach(function(file) { | ||
var css = fs.readFileSync(file, 'utf-8'); | ||
postcss() | ||
.use(bemLinter()) | ||
.use(logWarnings()) | ||
.use(reporter()) | ||
.process(css) | ||
@@ -229,0 +232,0 @@ .then(function(result) { .. }); |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
245
0
20272
12
260