From e56d739cc61a712730675f0f18a916ed77457b9d Mon Sep 17 00:00:00 2001 From: Roman Dvornov Date: Tue, 27 Oct 2020 18:26:34 +0100 Subject: [PATCH] Add properties and types options to extend syntax dictionary --- CHANGELOG.md | 1 + README.md | 74 +++++++++++++++++++++++++++++++++++++++++++++++++-- index.js | 22 ++++++++++++++- test/index.js | 31 ++++++++++++++++++++- 4 files changed, 124 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 07957f3..5e6f77c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ ## next - Bumped CSSTree to `^1.0.0` (mdn-data is bumped to `2.0.12`) +- Added `properties` and `types` options to extend syntax dictionary - Added `ignoreValue` option (#14) ## 1.8.0 (January 24, 2020) diff --git a/README.md b/README.md index cf1f268..c46ab6c 100644 --- a/README.md +++ b/README.md @@ -30,9 +30,79 @@ Setup plugin in your [stylelint config](http://stylelint.io/user-guide/configura ### Options +#### properties + +Type: `Object` or `null` +Default: `null` + +Overrides or/and extends property definition dictionary. CSS [Value Definition Syntax](https://github.com/csstree/csstree/blob/master/docs/definition-syntax.md) is used to define value's syntax. If definition starts with `|` it added to existing definition if any. See [CSS syntax reference](https://csstree.github.io/docs/syntax/) for default definitions. + +In the following example we extend `width` property and defines `size`: + +```json +{ + "plugins": [ + "stylelint-csstree-validator" + ], + "rules": { + "csstree/validator": { + "properties": { + "width": "| new-keyword | custom-function(, )", + "size": "" + } + } + } +} +``` + +Using property definitions with the syntax `` is an alternative for `ignore` option. + +```json +{ + "plugins": [ + "stylelint-csstree-validator" + ], + "rules": { + "csstree/validator": { + "properties": { + "my-custom-property": "" + } + } + } +} +``` + +#### types + +Type: `Object` or `null` +Default: `null` + +Overrides or/and extends type definition dictionary. CSS [Value Definition Syntax](https://github.com/csstree/csstree/blob/master/docs/definition-syntax.md) is used to define value's syntax. If definition starts with `|` it added to existing definition if any. See [CSS syntax reference](https://csstree.github.io/docs/syntax/) for default definitions. + +In the following example we define new functional type `my-fn()` and extend `color` type: + +```json +{ + "plugins": [ + "stylelint-csstree-validator" + ], + "rules": { + "csstree/validator": { + "properties": { + "some-property": "" + }, + "types": { + "color": "| darken(, [ | ])", + "my-fn()": "my-fn( )" + } + } + } +} +``` + #### ignore -Type: `Array` or `false` +Type: `Array` or `false` Default: `false` Defines a list of property names that should be ignored by the validator. @@ -54,7 +124,7 @@ In this example, plugin would not test declaration with property name `composes` #### ignoreValue -Type: `RegExp` +Type: `RegExp` Default: `false` Defines a pattern for values that should be ignored by the validator. diff --git a/index.js b/index.js index a4efa72..66626be 100644 --- a/index.js +++ b/index.js @@ -14,13 +14,33 @@ const messages = stylelint.utils.ruleMessages(ruleName, { module.exports = stylelint.createPlugin(ruleName, function(options) { options = options || {}; - const syntax = csstree.lexer; const ignoreValue = options.ignoreValue && (typeof options.ignoreValue === 'string' || toString.call(options.ignoreValue) === '[object RegExp]') ? new RegExp(options.ignoreValue) : false; const ignore = Array.isArray(options.ignore) ? new Set(options.ignore.map(name => String(name).toLowerCase())) : false; + const syntax = !options.properties && !options.types + ? csstree.lexer // default syntax + : csstree.fork((syntaxConfig) => { // syntax with custom properties or/and types + for (const [name, value] of Object.entries(options.properties || {})) { + syntaxConfig.properties[name] = value + .replace(/^\s*\|/, (m, index) => syntaxConfig.properties[name] + ? syntaxConfig.properties[name] + ' |' + : '' + ); + } + + for (const [name, value] of Object.entries(options.types || {})) { + syntaxConfig.types[name] = value + .replace(/^\s*\|/, (m, index) => syntaxConfig.types[name] + ? syntaxConfig.types[name] + ' |' + : '' + ); + } + + return syntaxConfig; + }).lexer; return function(root, result) { root.walkDecls(function(decl) { diff --git a/test/index.js b/test/index.js index 5e5ec30..c2dd539 100644 --- a/test/index.js +++ b/test/index.js @@ -75,6 +75,7 @@ css({ ignore: ['foo', 'bar'] }, function(tr) { tr.notOk('.foo { baz: 1 }', 'Unknown property `baz`'); }); +// should ignore by ignoreValue pattern css({ ignoreValue: "^patternToIgnore$", ignore: ['bar'] }, function(tr) { tr.ok('.foo { color: red }'); tr.ok('.foo { color: #fff }'); @@ -83,4 +84,32 @@ css({ ignoreValue: "^patternToIgnore$", ignore: ['bar'] }, function(tr) { tr.notOk('.foo { color: notMatchingPattern }', messages.invalid('color')); tr.notOk('.foo { foo: patternToIgnore }', 'Unknown property `foo`'); tr.notOk('.foo { foo: notMatchingPattern }', 'Unknown property `foo`'); -}) +}); + +// extend dictionary +css({ + properties: { + foo: '', + bar: '| ', + qux: '', + relax: '' + }, + types: { + 'my-fn()': 'my-fn()', + qux: '| ', + color: '| darken(, )' + } +}, function(tr) { + tr.ok('.foo { foo: my-fn(10px) }'); + tr.ok('.foo { foo: my-fn(10%) }'); + tr.ok('.foo { foo: my-fn(0) }'); + tr.ok('.bar { bar: my-fn(10px) }'); + tr.notOk('.baz { baz: my-fn(10px) }', 'Unknown property `baz`'); + tr.ok('.qux { qux: 10px }'); + tr.notOk('.foo { color: my-fn(10px) }', messages.invalid('color')); + tr.ok('.foo { color: darken(white, 5%) }'); + tr.ok('.foo { color: white }'); + tr.notOk('.foo { color: darken(white, .05) }', messages.invalid('color')); + tr.ok('.foo { relax: white }'); + tr.ok('.foo { relax: 10px solid whatever }'); +});