From 206da504f6bfb781abf56bd26557a25758a319f0 Mon Sep 17 00:00:00 2001 From: Matt Wang Date: Fri, 18 Feb 2022 15:04:03 -0800 Subject: [PATCH 01/12] drafts declaration-property-max-values --- docs/user-guide/rules/list.md | 1 + .../declaration-property-max-values/README.md | 83 +++++++++++++++++++ .../__tests__/index.js | 78 +++++++++++++++++ .../declaration-property-max-values/index.js | 60 ++++++++++++++ lib/rules/index.js | 1 + 5 files changed, 223 insertions(+) create mode 100644 lib/rules/declaration-property-max-values/README.md create mode 100644 lib/rules/declaration-property-max-values/__tests__/index.js create mode 100644 lib/rules/declaration-property-max-values/index.js diff --git a/docs/user-guide/rules/list.md b/docs/user-guide/rules/list.md index bdf6b80a02..440d8b614a 100644 --- a/docs/user-guide/rules/list.md +++ b/docs/user-guide/rules/list.md @@ -165,6 +165,7 @@ Within each cateogory, the rules are grouped by the [_thing_](http://apps.workfl - [`declaration-block-no-redundant-longhand-properties`](../../../lib/rules/declaration-block-no-redundant-longhand-properties/README.md): Disallow longhand properties that can be combined into one shorthand property. - [`declaration-no-important`](../../../lib/rules/declaration-no-important/README.md): Disallow `!important` within declarations. +- [`declaration-property-max-values`](../../../lib/rules/declaration-property-max-values/README.md): Limit the number of values for a list of properties within declarations. - [`declaration-property-unit-allowed-list`](../../../lib/rules/declaration-property-unit-allowed-list/README.md): Specify a list of allowed property and unit pairs within declarations. - [`declaration-property-unit-disallowed-list`](../../../lib/rules/declaration-property-unit-disallowed-list/README.md): Specify a list of disallowed property and unit pairs within declarations. - [`declaration-property-value-allowed-list`](../../../lib/rules/declaration-property-value-allowed-list/README.md): Specify a list of allowed property and value pairs within declarations. diff --git a/lib/rules/declaration-property-max-values/README.md b/lib/rules/declaration-property-max-values/README.md new file mode 100644 index 0000000000..4519229bbb --- /dev/null +++ b/lib/rules/declaration-property-max-values/README.md @@ -0,0 +1,83 @@ +# declaration-property-max-values + +Limit the number of values for a list of properties within declarations. + +## Options + +`object - { "unprefixed-property-name": int }` + +If a property name is surrounded with `"/"` (e.g. `"/^margin/"`), it is interpreted as a regular expression. This allows, for example, easy targeting of shorthands: `/^margin/` will match `margin`, `margin-top`, `margin-inline`, etc. + +Given: + +```json +{ + "border": 1, + "/^margin/": 2, + "padding": 3, +}, +``` + +The following patterns are considered problems: + + +```css +div { margin: 1px 2px 3px; } +``` + + +```css +div { margin: 1px 2px 3px 4px; } +``` + + +```css +div { margin-inline: 1px 2px 3px; } +``` + + +```css +div { border: 1px solid blue; } +``` + + +```css +div { padding: 1px 2px 3px 4px; } +``` + +The following patterns are _not_ considered problems: + + +```css +div { margin: 0; } +``` + + +```css +div { margin: 1px 2px; } +``` + + +```css +div { margin: 1px 2px /* 3px */; } +``` + + +```css +div { margin-inline: 1px 2px; } +``` + + +```css +div { margin: ; } +``` + + +```css +div { padding: 1px 2px 3px; } +``` + + +```css +div { border: 1px; } +``` diff --git a/lib/rules/declaration-property-max-values/__tests__/index.js b/lib/rules/declaration-property-max-values/__tests__/index.js new file mode 100644 index 0000000000..654d133557 --- /dev/null +++ b/lib/rules/declaration-property-max-values/__tests__/index.js @@ -0,0 +1,78 @@ +'use strict'; + +const { messages, ruleName } = require('..'); + +testRule({ + ruleName, + + config: [ + { + border: 1, + '/^margin/': 2, + padding: 3, + }, + ], + + accept: [ + { + code: 'div { margin: 0; }', + }, + { + code: 'div { margin: 1px 2px; }', + }, + { + code: 'div { margin: 1px 2px /* 3px */; }', + }, + { + code: 'div { margin-inline: 1px 2px; }', + }, + { + code: 'div { margin: ; }', + }, + { + code: 'div { padding: 1px 2px 3px; }', + }, + { + code: 'div { border: 1px; }', + }, + ], + + reject: [ + { + code: 'div { margin: 1px 2px 3px; }', + message: messages.rejected('margin', 2), + line: 1, + column: 7, + }, + { + code: 'div { margin-inline: 1px 2px 3px; }', + message: messages.rejected('margin-inline', 2), + line: 1, + column: 7, + }, + { + code: 'div { margin: 1px 2px 3px 4px; }', + message: messages.rejected('margin', 2), + line: 1, + column: 7, + }, + { + code: 'div { margin: 0 0 0 0; }', + message: messages.rejected('margin', 2), + line: 1, + column: 7, + }, + { + code: 'div { border: 1px solid blue; }', + message: messages.rejected('border', 1), + line: 1, + column: 7, + }, + { + code: 'div { padding: 1px 2px 3px 4px; }', + message: messages.rejected('padding', 3), + line: 1, + column: 7, + }, + ], +}); diff --git a/lib/rules/declaration-property-max-values/index.js b/lib/rules/declaration-property-max-values/index.js new file mode 100644 index 0000000000..4c6fbbc5d6 --- /dev/null +++ b/lib/rules/declaration-property-max-values/index.js @@ -0,0 +1,60 @@ +'use strict'; + +const matchesStringOrRegExp = require('../../utils/matchesStringOrRegExp'); +const report = require('../../utils/report'); +const ruleMessages = require('../../utils/ruleMessages'); +const valueParser = require('postcss-value-parser'); +const vendor = require('../../utils/vendor'); + +const ruleName = 'declaration-property-max-values'; + +const messages = ruleMessages(ruleName, { + rejected: (property, max) => + `Expected "${property}" to have no more than ${max} ${max === 1 ? 'value' : 'values'}`, +}); + +const meta = { + url: 'https://stylelint.io/user-guide/rules/list/declaration-property-max-values', +}; + +/** @type {import('stylelint').Rule>} */ +const rule = (primary) => { + return (root, result) => { + root.walkDecls((decl) => { + const prop = decl.prop; + const value = decl.value; + const propLength = valueParser(value).nodes.filter((node) => node.type === 'word').length; + + const unprefixedProp = vendor.unprefixed(prop); + const propKey = Object.keys(primary).find((propIdentifier) => + matchesStringOrRegExp(unprefixedProp, propIdentifier), + ); + + if (!propKey) { + return; + } + + const max = primary[propKey]; + + if (!max) { + return; + } + + if (propLength <= max) { + return; + } + + report({ + message: messages.rejected(prop, max), + node: decl, + result, + ruleName, + }); + }); + }; +}; + +rule.ruleName = ruleName; +rule.messages = messages; +rule.meta = meta; +module.exports = rule; diff --git a/lib/rules/index.js b/lib/rules/index.js index e1f338f39a..a300e747b4 100644 --- a/lib/rules/index.js +++ b/lib/rules/index.js @@ -81,6 +81,7 @@ const rules = { 'declaration-colon-space-before': importLazy('./declaration-colon-space-before'), 'declaration-empty-line-before': importLazy('./declaration-empty-line-before'), 'declaration-no-important': importLazy('./declaration-no-important'), + 'declaration-property-max-values': importLazy('./declaration-property-max-values'), 'declaration-property-unit-allowed-list': importLazy('./declaration-property-unit-allowed-list'), 'declaration-property-unit-disallowed-list': importLazy( './declaration-property-unit-disallowed-list', From bf017ea8c4108afe95d446081e3bd38124ccf231 Mon Sep 17 00:00:00 2001 From: Matt Wang Date: Fri, 18 Feb 2022 15:19:53 -0800 Subject: [PATCH 02/12] adds extra test case for irrelevant property, removes redundant code --- lib/rules/declaration-property-max-values/__tests__/index.js | 5 +++++ lib/rules/declaration-property-max-values/index.js | 4 ---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/rules/declaration-property-max-values/__tests__/index.js b/lib/rules/declaration-property-max-values/__tests__/index.js index 654d133557..d3c9510958 100644 --- a/lib/rules/declaration-property-max-values/__tests__/index.js +++ b/lib/rules/declaration-property-max-values/__tests__/index.js @@ -22,6 +22,7 @@ testRule({ }, { code: 'div { margin: 1px 2px /* 3px */; }', + description: 'ignore values in comments', }, { code: 'div { margin-inline: 1px 2px; }', @@ -35,6 +36,10 @@ testRule({ { code: 'div { border: 1px; }', }, + { + code: 'div { transition: margin-right 2s ease-in-out; }', + description: 'irrelevant shorthand', + }, ], reject: [ diff --git a/lib/rules/declaration-property-max-values/index.js b/lib/rules/declaration-property-max-values/index.js index 4c6fbbc5d6..9866a38d0d 100644 --- a/lib/rules/declaration-property-max-values/index.js +++ b/lib/rules/declaration-property-max-values/index.js @@ -36,10 +36,6 @@ const rule = (primary) => { const max = primary[propKey]; - if (!max) { - return; - } - if (propLength <= max) { return; } From 9b6634a4917d44a91cb4f243eccb1ee496950754 Mon Sep 17 00:00:00 2001 From: Matt Wang Date: Mon, 21 Feb 2022 15:59:11 -0800 Subject: [PATCH 03/12] Apply suggestions from code review Co-authored-by: Masafumi Koba <473530+ybiquitous@users.noreply.github.com> --- lib/rules/declaration-property-max-values/README.md | 2 +- lib/rules/declaration-property-max-values/index.js | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/rules/declaration-property-max-values/README.md b/lib/rules/declaration-property-max-values/README.md index 4519229bbb..09f9e1bd7e 100644 --- a/lib/rules/declaration-property-max-values/README.md +++ b/lib/rules/declaration-property-max-values/README.md @@ -4,7 +4,7 @@ Limit the number of values for a list of properties within declarations. ## Options -`object - { "unprefixed-property-name": int }` +`object`: `{ "unprefixed-property-name": int }` If a property name is surrounded with `"/"` (e.g. `"/^margin/"`), it is interpreted as a regular expression. This allows, for example, easy targeting of shorthands: `/^margin/` will match `margin`, `margin-top`, `margin-inline`, etc. diff --git a/lib/rules/declaration-property-max-values/index.js b/lib/rules/declaration-property-max-values/index.js index 9866a38d0d..537a66c5a8 100644 --- a/lib/rules/declaration-property-max-values/index.js +++ b/lib/rules/declaration-property-max-values/index.js @@ -17,12 +17,11 @@ const meta = { url: 'https://stylelint.io/user-guide/rules/list/declaration-property-max-values', }; -/** @type {import('stylelint').Rule>} */ +/** @type {import('stylelint').Rule>} */ const rule = (primary) => { return (root, result) => { root.walkDecls((decl) => { - const prop = decl.prop; - const value = decl.value; + const { prop, value } = decl; const propLength = valueParser(value).nodes.filter((node) => node.type === 'word').length; const unprefixedProp = vendor.unprefixed(prop); From f94fc00ae901e40f682d6fa88210b01a7d68de74 Mon Sep 17 00:00:00 2001 From: Matt Wang Date: Mon, 21 Feb 2022 16:03:25 -0800 Subject: [PATCH 04/12] slims down docs/test cases to use valid CSS, div -> a, all value nodes Applies suggestions from code review: - removes invalid CSS from docs and test cases - removes redundant docs entries - uses `a` for all selectors - writes a new helper "isValueNode" to cover word, function, string value types - adds new test case for css variable --- .../declaration-property-max-values/README.md | 43 +++------------- .../__tests__/index.js | 49 +++++++++---------- .../declaration-property-max-values/index.js | 6 ++- 3 files changed, 33 insertions(+), 65 deletions(-) diff --git a/lib/rules/declaration-property-max-values/README.md b/lib/rules/declaration-property-max-values/README.md index 09f9e1bd7e..085e3ef7c0 100644 --- a/lib/rules/declaration-property-max-values/README.md +++ b/lib/rules/declaration-property-max-values/README.md @@ -14,7 +14,6 @@ Given: { "border": 1, "/^margin/": 2, - "padding": 3, }, ``` @@ -22,62 +21,32 @@ The following patterns are considered problems: ```css -div { margin: 1px 2px 3px; } +a { margin: 1px 2px 3px; } ``` ```css -div { margin: 1px 2px 3px 4px; } +a { margin: 1px 2px 3px 4px; } ``` ```css -div { margin-inline: 1px 2px 3px; } -``` - - -```css -div { border: 1px solid blue; } -``` - - -```css -div { padding: 1px 2px 3px 4px; } +a { border: 1px solid blue; } ``` The following patterns are _not_ considered problems: ```css -div { margin: 0; } -``` - - -```css -div { margin: 1px 2px; } -``` - - -```css -div { margin: 1px 2px /* 3px */; } -``` - - -```css -div { margin-inline: 1px 2px; } -``` - - -```css -div { margin: ; } +a { border: 1px; } ``` ```css -div { padding: 1px 2px 3px; } +a { margin: 0; } ``` ```css -div { border: 1px; } +a { margin-inline: 1px 2px; } ``` diff --git a/lib/rules/declaration-property-max-values/__tests__/index.js b/lib/rules/declaration-property-max-values/__tests__/index.js index d3c9510958..6fc427f9f1 100644 --- a/lib/rules/declaration-property-max-values/__tests__/index.js +++ b/lib/rules/declaration-property-max-values/__tests__/index.js @@ -9,75 +9,70 @@ testRule({ { border: 1, '/^margin/': 2, - padding: 3, }, ], accept: [ { - code: 'div { margin: 0; }', + code: 'a { margin: 0; }', }, { - code: 'div { margin: 1px 2px; }', + code: 'a { margin: 1px 2px; }', }, { - code: 'div { margin: 1px 2px /* 3px */; }', - description: 'ignore values in comments', + code: 'a { margin: var(--foo) var(--bar); }', + description: 'deals with CSS variables', }, { - code: 'div { margin-inline: 1px 2px; }', + code: 'a { margin: 1px 2px /* 3px */; }', + description: 'ignore values in comments', }, { - code: 'div { margin: ; }', + code: 'a { margin-inline: 1px 2px; }', }, { - code: 'div { padding: 1px 2px 3px; }', + code: 'a { margin: ; }', }, { - code: 'div { border: 1px; }', + code: 'a { border: 1px; }', }, { - code: 'div { transition: margin-right 2s ease-in-out; }', + code: 'a { transition: margin-right 2s ease-in-out; }', description: 'irrelevant shorthand', }, ], reject: [ { - code: 'div { margin: 1px 2px 3px; }', + code: 'a { margin: 1px 2px 3px; }', message: messages.rejected('margin', 2), line: 1, - column: 7, + column: 5, }, { - code: 'div { margin-inline: 1px 2px 3px; }', - message: messages.rejected('margin-inline', 2), + code: 'a { margin: var(--foo) var(--bar) var(--baz); }', + message: messages.rejected('margin', 2), line: 1, - column: 7, + column: 5, + description: 'deals with CSS variables', }, { - code: 'div { margin: 1px 2px 3px 4px; }', + code: 'a { margin: 1px 2px 3px 4px; }', message: messages.rejected('margin', 2), line: 1, - column: 7, + column: 5, }, { - code: 'div { margin: 0 0 0 0; }', + code: 'a { margin: 0 0 0 0; }', message: messages.rejected('margin', 2), line: 1, - column: 7, + column: 5, }, { - code: 'div { border: 1px solid blue; }', + code: 'a { border: 1px solid blue; }', message: messages.rejected('border', 1), line: 1, - column: 7, - }, - { - code: 'div { padding: 1px 2px 3px 4px; }', - message: messages.rejected('padding', 3), - line: 1, - column: 7, + column: 5, }, ], }); diff --git a/lib/rules/declaration-property-max-values/index.js b/lib/rules/declaration-property-max-values/index.js index 537a66c5a8..51b4e3f77a 100644 --- a/lib/rules/declaration-property-max-values/index.js +++ b/lib/rules/declaration-property-max-values/index.js @@ -17,12 +17,16 @@ const meta = { url: 'https://stylelint.io/user-guide/rules/list/declaration-property-max-values', }; +const isValueNode = (/** @type {valueParser.Node} */ node) => { + return node.type === 'word' || node.type === 'function' || node.type === 'string'; +}; + /** @type {import('stylelint').Rule>} */ const rule = (primary) => { return (root, result) => { root.walkDecls((decl) => { const { prop, value } = decl; - const propLength = valueParser(value).nodes.filter((node) => node.type === 'word').length; + const propLength = valueParser(value).nodes.filter(isValueNode).length; const unprefixedProp = vendor.unprefixed(prop); const propKey = Object.keys(primary).find((propIdentifier) => From 62ba6c1d09041d386b74120d542bb799eac585f0 Mon Sep 17 00:00:00 2001 From: Matt Wang Date: Mon, 21 Feb 2022 18:36:06 -0800 Subject: [PATCH 05/12] creates validateObjectWithProps, validates options for declaration-property-max-values --- .../declaration-property-max-values/README.md | 6 ++-- .../declaration-property-max-values/index.js | 12 ++++++++ lib/utils/validateObjectWithProps.js | 30 +++++++++++++++++++ 3 files changed, 45 insertions(+), 3 deletions(-) create mode 100644 lib/utils/validateObjectWithProps.js diff --git a/lib/rules/declaration-property-max-values/README.md b/lib/rules/declaration-property-max-values/README.md index 085e3ef7c0..cdb0ba788c 100644 --- a/lib/rules/declaration-property-max-values/README.md +++ b/lib/rules/declaration-property-max-values/README.md @@ -21,17 +21,17 @@ The following patterns are considered problems: ```css -a { margin: 1px 2px 3px; } +a { border: 1px solid blue; } ``` ```css -a { margin: 1px 2px 3px 4px; } +a { margin: 1px 2px 3px; } ``` ```css -a { border: 1px solid blue; } +a { margin: 1px 2px 3px 4px; } ``` The following patterns are _not_ considered problems: diff --git a/lib/rules/declaration-property-max-values/index.js b/lib/rules/declaration-property-max-values/index.js index 51b4e3f77a..b44ee58e16 100644 --- a/lib/rules/declaration-property-max-values/index.js +++ b/lib/rules/declaration-property-max-values/index.js @@ -5,6 +5,9 @@ const report = require('../../utils/report'); const ruleMessages = require('../../utils/ruleMessages'); const valueParser = require('postcss-value-parser'); const vendor = require('../../utils/vendor'); +const validateOptions = require('../../utils/validateOptions'); +const { isNumber } = require('../../utils/validateTypes'); +const validateObjectWithProps = require('../../utils/validateObjectWithProps'); const ruleName = 'declaration-property-max-values'; @@ -25,6 +28,15 @@ const isValueNode = (/** @type {valueParser.Node} */ node) => { const rule = (primary) => { return (root, result) => { root.walkDecls((decl) => { + const validOptions = validateOptions(result, ruleName, { + actual: primary, + possible: [validateObjectWithProps(isNumber)], + }); + + if (!validOptions) { + return; + } + const { prop, value } = decl; const propLength = valueParser(value).nodes.filter(isValueNode).length; diff --git a/lib/utils/validateObjectWithProps.js b/lib/utils/validateObjectWithProps.js new file mode 100644 index 0000000000..e58e43ccde --- /dev/null +++ b/lib/utils/validateObjectWithProps.js @@ -0,0 +1,30 @@ +'use strict'; + +const { isPlainObject } = require('is-plain-object'); + +/** + * @template T + * @typedef {(i: T) => boolean} Validator + */ + +/** + * Check whether the variable is an object and all its properties agree with the provided validator + * + * config = { + * value1: 1, + * value2: 2, + * value3: 3, + * } + * @template T + * @param {Validator} validator + * @returns {(value: unknown) => boolean} + */ +module.exports = (validator) => (value) => { + if (!isPlainObject(value)) { + return false; + } + + return Object.values(/** @type {Record} */ (value)).every((item) => { + return validator(item); + }); +}; From 1d2d40010e54165b58992a3eafe8927675b056de Mon Sep 17 00:00:00 2001 From: Matt Wang Date: Mon, 21 Feb 2022 18:48:25 -0800 Subject: [PATCH 06/12] adds tests for validateObjectWithProps --- .../__tests__/validateObjectWithProps.test.js | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 lib/utils/__tests__/validateObjectWithProps.test.js diff --git a/lib/utils/__tests__/validateObjectWithProps.test.js b/lib/utils/__tests__/validateObjectWithProps.test.js new file mode 100644 index 0000000000..46cca2b30a --- /dev/null +++ b/lib/utils/__tests__/validateObjectWithProps.test.js @@ -0,0 +1,32 @@ +'use strict'; + +const validateObjectWithProps = require('../validateObjectWithProps'); +const { isNumber } = require('../validateTypes'); + +describe('validateObjectWithProps', () => { + it('should return a function', () => { + expect(validateObjectWithProps((x) => x)).toBeInstanceOf(Function); + }); + + describe('simple isNumber validator', () => { + const validator = validateObjectWithProps(isNumber); + + it('should accept an object where the validators are true for any value', () => { + expect( + validator({ + value1: 1, + value2: 2, + }), + ).toBeTruthy(); + }); + + it('should be false if the validator is false for at least one value', () => { + expect( + validator({ + value1: 1, + value2: '2', + }), + ).toBeFalsy(); + }); + }); +}); From 7955ad1b39f6ee9696dec6bf49fd5b1b7db88d83 Mon Sep 17 00:00:00 2001 From: Matt Wang Date: Mon, 21 Feb 2022 18:58:39 -0800 Subject: [PATCH 07/12] adds additional test case for invalid input to validateObjectWithProps --- lib/utils/__tests__/validateObjectWithProps.test.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/utils/__tests__/validateObjectWithProps.test.js b/lib/utils/__tests__/validateObjectWithProps.test.js index 46cca2b30a..17fa0e0658 100644 --- a/lib/utils/__tests__/validateObjectWithProps.test.js +++ b/lib/utils/__tests__/validateObjectWithProps.test.js @@ -8,6 +8,10 @@ describe('validateObjectWithProps', () => { expect(validateObjectWithProps((x) => x)).toBeInstanceOf(Function); }); + it('rejects non-objects', () => { + expect(validateObjectWithProps((x) => x)(42)).toBeFalsy(); + }); + describe('simple isNumber validator', () => { const validator = validateObjectWithProps(isNumber); From 03ddbf8cf8431817c4ad1688619983fb4ee7a2e4 Mon Sep 17 00:00:00 2001 From: Matt Wang Date: Tue, 22 Feb 2022 13:52:08 -0800 Subject: [PATCH 08/12] Apply suggestions from code review - typo fixes and validator typing Co-authored-by: Masafumi Koba <473530+ybiquitous@users.noreply.github.com> --- .../__tests__/validateObjectWithProps.test.js | 2 +- lib/utils/validateObjectWithProps.js | 15 +++++++++------ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/lib/utils/__tests__/validateObjectWithProps.test.js b/lib/utils/__tests__/validateObjectWithProps.test.js index 17fa0e0658..17c7f94de9 100644 --- a/lib/utils/__tests__/validateObjectWithProps.test.js +++ b/lib/utils/__tests__/validateObjectWithProps.test.js @@ -8,7 +8,7 @@ describe('validateObjectWithProps', () => { expect(validateObjectWithProps((x) => x)).toBeInstanceOf(Function); }); - it('rejects non-objects', () => { + it('should reject non-objects', () => { expect(validateObjectWithProps((x) => x)(42)).toBeFalsy(); }); diff --git a/lib/utils/validateObjectWithProps.js b/lib/utils/validateObjectWithProps.js index e58e43ccde..393a280f67 100644 --- a/lib/utils/validateObjectWithProps.js +++ b/lib/utils/validateObjectWithProps.js @@ -1,6 +1,6 @@ 'use strict'; -const { isPlainObject } = require('is-plain-object'); +const { isPlainObject } = require('./validateTypes'); /** * @template T @@ -8,15 +8,18 @@ const { isPlainObject } = require('is-plain-object'); */ /** - * Check whether the variable is an object and all its properties agree with the provided validator + * Check whether the variable is an object and all its properties agree with the provided validator. * + * @example * config = { * value1: 1, * value2: 2, * value3: 3, - * } - * @template T - * @param {Validator} validator + * }; + * validateObjectWithProps(isNumber)(config); + * //=> true + * + * @param {(value: unknown) => boolean} validator * @returns {(value: unknown) => boolean} */ module.exports = (validator) => (value) => { @@ -24,7 +27,7 @@ module.exports = (validator) => (value) => { return false; } - return Object.values(/** @type {Record} */ (value)).every((item) => { + return Object.values(value).every((item) => { return validator(item); }); }; From 57c4f4e8789b238983910b8c64d51926a1ac7d6e Mon Sep 17 00:00:00 2001 From: Matt Wang Date: Tue, 22 Feb 2022 13:59:02 -0800 Subject: [PATCH 09/12] removes unused template type --- lib/utils/validateObjectWithProps.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lib/utils/validateObjectWithProps.js b/lib/utils/validateObjectWithProps.js index 393a280f67..36c1246449 100644 --- a/lib/utils/validateObjectWithProps.js +++ b/lib/utils/validateObjectWithProps.js @@ -2,11 +2,6 @@ const { isPlainObject } = require('./validateTypes'); -/** - * @template T - * @typedef {(i: T) => boolean} Validator - */ - /** * Check whether the variable is an object and all its properties agree with the provided validator. * From c5889cdc95c59dc5c55eabe50f5eb1ff8fa5ec18 Mon Sep 17 00:00:00 2001 From: Matt Wang Date: Tue, 22 Feb 2022 22:59:11 -0800 Subject: [PATCH 10/12] Apply suggestions from code review Moves `validateOptions` outside of `walkDecls`, better type annotation style! Co-authored-by: Masafumi Koba <473530+ybiquitous@users.noreply.github.com> --- .../declaration-property-max-values/index.js | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/lib/rules/declaration-property-max-values/index.js b/lib/rules/declaration-property-max-values/index.js index b44ee58e16..e68bd0a3c2 100644 --- a/lib/rules/declaration-property-max-values/index.js +++ b/lib/rules/declaration-property-max-values/index.js @@ -20,23 +20,26 @@ const meta = { url: 'https://stylelint.io/user-guide/rules/list/declaration-property-max-values', }; -const isValueNode = (/** @type {valueParser.Node} */ node) => { +/** + * @param {valueParser.Node} node + */ +const isValueNode = (node) => { return node.type === 'word' || node.type === 'function' || node.type === 'string'; }; /** @type {import('stylelint').Rule>} */ const rule = (primary) => { return (root, result) => { - root.walkDecls((decl) => { - const validOptions = validateOptions(result, ruleName, { - actual: primary, - possible: [validateObjectWithProps(isNumber)], - }); + const validOptions = validateOptions(result, ruleName, { + actual: primary, + possible: [validateObjectWithProps(isNumber)], + }); - if (!validOptions) { - return; - } + if (!validOptions) { + return; + } + root.walkDecls((decl) => { const { prop, value } = decl; const propLength = valueParser(value).nodes.filter(isValueNode).length; From 6a5d57b935ee8005b94f44f0fcd154ea7416a3df Mon Sep 17 00:00:00 2001 From: Matt Wang Date: Wed, 23 Feb 2022 14:39:47 -0800 Subject: [PATCH 11/12] Update lib/rules/declaration-property-max-values/index.js Moves external require to top of require list Co-authored-by: Richard Hallows --- lib/rules/declaration-property-max-values/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/rules/declaration-property-max-values/index.js b/lib/rules/declaration-property-max-values/index.js index e68bd0a3c2..eecced999c 100644 --- a/lib/rules/declaration-property-max-values/index.js +++ b/lib/rules/declaration-property-max-values/index.js @@ -1,9 +1,10 @@ 'use strict'; +const valueParser = require('postcss-value-parser'); + const matchesStringOrRegExp = require('../../utils/matchesStringOrRegExp'); const report = require('../../utils/report'); const ruleMessages = require('../../utils/ruleMessages'); -const valueParser = require('postcss-value-parser'); const vendor = require('../../utils/vendor'); const validateOptions = require('../../utils/validateOptions'); const { isNumber } = require('../../utils/validateTypes'); From 1d0efd9bfd38f5920e439115962831166a0cfc2b Mon Sep 17 00:00:00 2001 From: Matt Wang Date: Thu, 3 Mar 2022 17:46:50 -0800 Subject: [PATCH 12/12] swaps border, margin configuration properties to re-add margin-inline --- .../declaration-property-max-values/README.md | 14 ++++---- .../__tests__/index.js | 35 ++++++++++++------- 2 files changed, 29 insertions(+), 20 deletions(-) diff --git a/lib/rules/declaration-property-max-values/README.md b/lib/rules/declaration-property-max-values/README.md index cdb0ba788c..4a514b2aae 100644 --- a/lib/rules/declaration-property-max-values/README.md +++ b/lib/rules/declaration-property-max-values/README.md @@ -12,8 +12,8 @@ Given: ```json { - "border": 1, - "/^margin/": 2, + "border": 2, + "/^margin/": 1, }, ``` @@ -26,27 +26,27 @@ a { border: 1px solid blue; } ```css -a { margin: 1px 2px 3px; } +a { margin: 1px 2px; } ``` ```css -a { margin: 1px 2px 3px 4px; } +a { margin-inline: 1px 2px; } ``` The following patterns are _not_ considered problems: ```css -a { border: 1px; } +a { border: 1px solid; } ``` ```css -a { margin: 0; } +a { margin: 1px; } ``` ```css -a { margin-inline: 1px 2px; } +a { margin-inline: 1px; } ``` diff --git a/lib/rules/declaration-property-max-values/__tests__/index.js b/lib/rules/declaration-property-max-values/__tests__/index.js index 6fc427f9f1..8876516b94 100644 --- a/lib/rules/declaration-property-max-values/__tests__/index.js +++ b/lib/rules/declaration-property-max-values/__tests__/index.js @@ -7,8 +7,8 @@ testRule({ config: [ { - border: 1, - '/^margin/': 2, + border: 2, + '/^margin/': 1, }, ], @@ -17,18 +17,18 @@ testRule({ code: 'a { margin: 0; }', }, { - code: 'a { margin: 1px 2px; }', + code: 'a { margin: 1px; }', }, { - code: 'a { margin: var(--foo) var(--bar); }', + code: 'a { margin: var(--foo); }', description: 'deals with CSS variables', }, { - code: 'a { margin: 1px 2px /* 3px */; }', + code: 'a { margin: 1px /* 3px */; }', description: 'ignore values in comments', }, { - code: 'a { margin-inline: 1px 2px; }', + code: 'a { margin-inline: 1px; }', }, { code: 'a { margin: ; }', @@ -36,6 +36,9 @@ testRule({ { code: 'a { border: 1px; }', }, + { + code: 'a { border: 1px solid; }', + }, { code: 'a { transition: margin-right 2s ease-in-out; }', description: 'irrelevant shorthand', @@ -44,33 +47,39 @@ testRule({ reject: [ { - code: 'a { margin: 1px 2px 3px; }', - message: messages.rejected('margin', 2), + code: 'a { margin: 1px 2px; }', + message: messages.rejected('margin', 1), + line: 1, + column: 5, + }, + { + code: 'a { margin-inline: 1px 2px; }', + message: messages.rejected('margin-inline', 1), line: 1, column: 5, }, { - code: 'a { margin: var(--foo) var(--bar) var(--baz); }', - message: messages.rejected('margin', 2), + code: 'a { margin: var(--foo) var(--bar); }', + message: messages.rejected('margin', 1), line: 1, column: 5, description: 'deals with CSS variables', }, { code: 'a { margin: 1px 2px 3px 4px; }', - message: messages.rejected('margin', 2), + message: messages.rejected('margin', 1), line: 1, column: 5, }, { code: 'a { margin: 0 0 0 0; }', - message: messages.rejected('margin', 2), + message: messages.rejected('margin', 1), line: 1, column: 5, }, { code: 'a { border: 1px solid blue; }', - message: messages.rejected('border', 1), + message: messages.rejected('border', 2), line: 1, column: 5, },