stylelint-order 2.2.1 → 3.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,9 +2,28 @@
2
2
  All notable changes to this project will be documented in this file.
3
3
  This project adheres to [Semantic Versioning](https://semver.org/).
4
4
 
5
+ ## 3.1.1
6
+
7
+ * Added `stylelint@11` as a peer dependency.
8
+
9
+ ## 3.1.0
10
+
11
+ * Added `emptyLineBefore: "threshold"` option, and related options (`emptyLineMinimumPropertyThreshold`, `emptyLineBeforeUnspecified: "threshold"`) to `properties-order`.
12
+
13
+ ## 3.0.1
14
+
15
+ * Fixed `properties-order` not report warnings, if autofix didn't fix them.
16
+ * Fixed `properties-alphabetical-order` now puts shorthands before their longhand forms even if that isn't alphabetical to avoid broken CSS. E. g. `border-color` will be before `border-bottom-color`.
17
+
18
+ ## 3.0.0
19
+
20
+ * Dropped Node.js 6 support. Node.js 8.7.0 or greater is now required.
21
+ * Removed stylelint@9 as a peer dependency. stylelint 10 or greater is now required.
22
+ * Added `emptyLineBeforeUnspecified` option for `properties-order`.
23
+
5
24
  ## 2.2.1
6
25
 
7
- * Fix false negatives with `noEmptyLineBetween` in combination with the `order: "flexible"`.
26
+ * Fixed false negatives with `noEmptyLineBetween` in combination with the `order: "flexible"`.
8
27
 
9
28
  ## 2.2.0
10
29
 
@@ -20,7 +39,7 @@ This project adheres to [Semantic Versioning](https://semver.org/).
20
39
 
21
40
  This is a major release, because this plugin requires stylelint@9.8.0+ to work correctly with Less files.
22
41
 
23
- * Add optional groupName property for properties-order.
42
+ * Added optional groupName property for properties-order.
24
43
  * Adopted `postcss-less@3` parser changes, which is dependency of `stylelint@9.7.0+`.
25
44
  * Fixed incorrect fixing when properties order and empty lines should be changed at the same time.
26
45
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stylelint-order",
3
- "version": "2.2.1",
3
+ "version": "3.1.1",
4
4
  "description": "A collection of order related linting rules for stylelint.",
5
5
  "keywords": [
6
6
  "stylelint-plugin",
@@ -17,7 +17,7 @@
17
17
  },
18
18
  "homepage": "https://github.com/hudochenkov/stylelint-order",
19
19
  "engines": {
20
- "node": ">=6"
20
+ "node": ">=8.7.0"
21
21
  },
22
22
  "files": [
23
23
  "rules",
@@ -28,22 +28,22 @@
28
28
  ],
29
29
  "main": "index.js",
30
30
  "dependencies": {
31
- "lodash": "^4.17.10",
32
- "postcss": "^7.0.2",
33
- "postcss-sorting": "^4.1.0"
31
+ "lodash": "^4.17.15",
32
+ "postcss": "^7.0.17",
33
+ "postcss-sorting": "^5.0.1"
34
34
  },
35
35
  "peerDependencies": {
36
- "stylelint": "^9.10.1 || ^10.0.0"
36
+ "stylelint": ">=10.0.1"
37
37
  },
38
38
  "devDependencies": {
39
- "eslint": "~5.15.0",
40
- "eslint-config-hudochenkov": "~3.0.0",
41
- "eslint-config-prettier": "~4.1.0",
42
- "husky": "^1.1.3",
43
- "jest": "^24.1.0",
44
- "lint-staged": "^8.0.4",
45
- "prettier": "~1.16.4",
46
- "stylelint": "^9.10.1"
39
+ "eslint": "^6.2.2",
40
+ "eslint-config-hudochenkov": "^3.0.1",
41
+ "eslint-config-prettier": "^6.1.0",
42
+ "husky": "^3.0.4",
43
+ "jest": "^24.9.0",
44
+ "lint-staged": "^9.2.5",
45
+ "prettier": "~1.18.2",
46
+ "stylelint": "^11.0.0"
47
47
  },
48
48
  "scripts": {
49
49
  "pretest": "eslint .",
@@ -1,6 +1,23 @@
1
1
  const postcss = require('postcss');
2
+ const shorthandData = require('./shorthandData');
3
+
4
+ function isShorthand(a, b) {
5
+ const longhands = shorthandData[a] || [];
6
+
7
+ return longhands.includes(b);
8
+ }
2
9
 
3
10
  module.exports = function checkAlphabeticalOrder(firstPropData, secondPropData) {
11
+ // OK if the first is shorthand for the second:
12
+ if (isShorthand(firstPropData.unprefixedName, secondPropData.unprefixedName)) {
13
+ return true;
14
+ }
15
+
16
+ // Not OK if the second is shorthand for the first:
17
+ if (isShorthand(secondPropData.unprefixedName, firstPropData.unprefixedName)) {
18
+ return false;
19
+ }
20
+
4
21
  // If unprefixed prop names are the same, compare the prefixed versions
5
22
  if (firstPropData.unprefixedName === secondPropData.unprefixedName) {
6
23
  // If first property has no prefix and second property has prefix
@@ -11,6 +11,9 @@ a {
11
11
  * These properties */
12
12
  ```
13
13
 
14
+ Shorthand properties *must always* precede their longhand counterparts, even if that means they are not alphabetized.
15
+ (See also [`declaration-block-no-shorthand-property-overrides`](https://stylelint.io/user-guide/rules/declaration-block-no-shorthand-property-overrides/).)
16
+
14
17
  Prefixed properties *must always* precede the unprefixed version.
15
18
 
16
19
  This rule ignores variables (`$sass`, `@less`, `--custom-property`).
@@ -28,6 +31,13 @@ a {
28
31
  }
29
32
  ```
30
33
 
34
+ ```css
35
+ a {
36
+ border-bottom-color: pink;
37
+ border-color: transparent;
38
+ }
39
+ ```
40
+
31
41
  ```css
32
42
  a {
33
43
  -moz-transform: scale(1);
@@ -45,6 +55,13 @@ a {
45
55
  }
46
56
  ```
47
57
 
58
+ ```css
59
+ a {
60
+ border-color: transparent;
61
+ border-bottom-color: pink;
62
+ }
63
+ ```
64
+
48
65
  ```css
49
66
  a {
50
67
  -webkit-transform: scale(1);
@@ -18,6 +18,8 @@ This rule ignores variables (`$sass`, `@less`, `--custom-property`).
18
18
  * Options
19
19
  * Optional secondary options
20
20
  * [`unspecified: "top"|"bottom"|"bottomAlphabetical"|"ignore"`](#unspecified-topbottombottomalphabeticalignore)
21
+ * [`emptyLineBeforeUnspecified: "always"|"never"|"threshold"`](#emptyLineBeforeUnspecified-alwaysneverthreshold)
22
+ * [`emptyLineMinimumPropertyThreshold: <number>`](#emptylineminimumpropertythreshold-number)
21
23
  * [`disableFix: true`](#disablefix-true)
22
24
  * [Autofixing caveats](#autofixing-caveats)
23
25
 
@@ -33,7 +35,7 @@ Within an order array, you can include
33
35
  * group objects with these properties:
34
36
  * `order: "flexible"`: If property isn't set (the default), the properties in this group must come in the order specified. If `"flexible"`, the properties can be in any order as long as they are grouped correctly.
35
37
  * `properties (array of strings)`: The properties in this group.
36
- * `emptyLineBefore ("always"|"never")`: If `always`, this group must be separated from other properties by an empty newline. If emptyLineBefore is `never`, the group must have no empty lines separating it from other properties. By default this property isn't set. Rule will check empty lines between properties _only_. However, shared-line comments ignored by rule. Shared-line comment is a comment on the same line as declaration before this comment.
38
+ * `emptyLineBefore ("always"|"never"|"threshold")`: If `always`, this group must be separated from other properties by an empty newline. If emptyLineBefore is `never`, the group must have no empty lines separating it from other properties. By default this property isn't set. Rule will check empty lines between properties _only_. However, shared-line comments ignored by rule. Shared-line comment is a comment on the same line as declaration before this comment. For `threshold`, refer to the [`emptyLineMinimumPropertyThreshold` documentation](#emptylineminimumpropertythreshold-number).
37
39
  * `noEmptyLineBetween`: If `true`, properties within group should not have empty lines between them.
38
40
  * `groupName`: An optional name for the group. This will be used in error messages.
39
41
 
@@ -458,7 +460,7 @@ These options only apply if you've defined your own array of properties.
458
460
 
459
461
  Default behavior is the same as `"ignore"`: an unspecified property can appear before or after any other property.
460
462
 
461
- With `"top"`, unspecified properties are expected *before* any specified properties. With `"bottom"`, unspecified properties are expected *after* any specified properties. With `"bottomAlphabetical"`, unspecified properties are expected *after* any specified properties, and the unspecified properties are expected to be in alphabetical order.
463
+ With `"top"`, unspecified properties are expected *before* any specified properties. With `"bottom"`, unspecified properties are expected *after* any specified properties. With `"bottomAlphabetical"`, unspecified properties are expected *after* any specified properties, and the unspecified properties are expected to be in alphabetical order. (See [properties-alphabetical-order](../properties-alphabetical-order/README.md) more more details on the alphabetization rules.)
462
464
 
463
465
  Given:
464
466
 
@@ -594,13 +596,184 @@ a {
594
596
  }
595
597
  ```
596
598
 
599
+ ### `emptyLineBeforeUnspecified: "always"|"never"|"threshold"`
600
+
601
+ Default behavior does not enforce the presence of an empty line before an unspecified block of properties.
602
+
603
+ If `"always"`, the unspecified group must be separated from other properties by an empty newline.
604
+ If `"never"`, the unspecified group must have no empty lines separating it from other properties.
605
+
606
+ For `"threshold"`, see the [`emptyLineMinimumPropertyThreshold` documentation](#emptylineminimumpropertythreshold-number) for more information.
607
+
608
+ Given:
609
+
610
+ ```js
611
+ [
612
+ [
613
+ "height",
614
+ "width",
615
+ ],
616
+ {
617
+ unspecified: "bottom",
618
+ emptyLineBeforeUnspecified: "always"
619
+ }
620
+ ]
621
+ ```
622
+
623
+ The following pattern is considered warnings:
624
+
625
+ ```css
626
+ a {
627
+ height: 1px;
628
+ width: 2px;
629
+ color: blue;
630
+ }
631
+ ```
632
+
633
+ The following patterns is *not* considered warnings:
634
+
635
+ ```css
636
+ a {
637
+ height: 1px;
638
+ width: 2px;
639
+
640
+ color: blue;
641
+ }
642
+ ```
643
+
644
+ ### `emptyLineMinimumPropertyThreshold: <number>`
645
+
646
+ If a group is configured with `emptyLineBefore: 'threshold'`, the empty line behaviour toggles based on the number of properties in the rule.
647
+
648
+ When the configured minimum property threshold is reached, empty lines are **inserted**. When the number of properties is **less than** the minimum property threshold, empty lines are **removed**.
649
+
650
+ _e.g. threshold set to **3**, and there are **5** properties in total, then groups set to `'threshold'` will have an empty line inserted._
651
+
652
+ The same behaviour is applied to unspecified groups when `emptyLineBeforeUnspecified: "threshold"`
653
+
654
+ Given:
655
+
656
+ ```js
657
+ [
658
+ [
659
+ {
660
+ emptyLineBefore: 'threshold',
661
+ properties: ['display'],
662
+ },
663
+ {
664
+ emptyLineBefore: 'threshold',
665
+ properties: ['height', 'width'],
666
+ },
667
+ {
668
+ emptyLineBefore: 'always',
669
+ properties: ['border'],
670
+ },
671
+ {
672
+ emptyLineBefore: 'never',
673
+ properties: ['transform'],
674
+ },
675
+ ],
676
+ {
677
+ unspecified: 'bottom',
678
+ emptyLineBeforeUnspecified: 'threshold',
679
+ emptyLineMinimumPropertyThreshold: 4,
680
+ }
681
+ ]
682
+ ```
683
+
684
+ The following patterns are considered warnings:
685
+
686
+ ```css
687
+ a {
688
+ display: block;
689
+
690
+ height: 1px;
691
+ width: 2px;
692
+ color: blue;
693
+ }
694
+
695
+ a {
696
+ display: block;
697
+
698
+ height: 1px;
699
+ width: 2px;
700
+ border: 0;
701
+ color: blue;
702
+ }
703
+
704
+ a {
705
+ display: block;
706
+
707
+ height: 1px;
708
+ width: 2px;
709
+ border: 0;
710
+
711
+ transform: none;
712
+ color: blue;
713
+ }
714
+ ```
715
+
716
+ The following patterns are *not* considered warnings:
717
+
718
+ ```css
719
+ a {
720
+ display: block;
721
+ height: 1px;
722
+ width: 2px;
723
+ }
724
+
725
+ a {
726
+ display: block;
727
+
728
+ height: 1px;
729
+ width: 2px;
730
+
731
+ border: 0;
732
+ }
733
+
734
+ a {
735
+ display: block;
736
+
737
+ height: 1px;
738
+ width: 2px;
739
+
740
+ border: 0;
741
+ transform: none;
742
+ }
743
+
744
+ a {
745
+ display: block;
746
+ height: 1px;
747
+
748
+ border: 0;
749
+ }
750
+
751
+ a {
752
+ border: 0;
753
+ transform: none;
754
+ color: blue;
755
+ }
756
+
757
+ a {
758
+ display: block;
759
+
760
+ height: 1px;
761
+ width: 2px;
762
+
763
+ border: 0;
764
+ transform: none;
765
+
766
+ color: blue;
767
+ }
768
+ ```
769
+
597
770
  ### `disableFix: true`
598
771
 
599
772
  Disable autofixing. Autofixing is enabled by default if it's enabled in stylelint configuration.
600
773
 
601
774
  ## Autofixing caveats
602
775
 
603
- Properties will be grouped together, if other node types between them (except comments). They will be groupped with the first found property. E. g.:
776
+ Properties will be grouped together, if other node types between them (except comments). They will be grouped with the first found property. E.g.:
604
777
 
605
778
  ```css
606
779
  /* Before: */
@@ -4,7 +4,12 @@ const addEmptyLineBefore = require('./addEmptyLineBefore');
4
4
  const hasEmptyLineBefore = require('./hasEmptyLineBefore');
5
5
  const removeEmptyLinesBefore = require('./removeEmptyLinesBefore');
6
6
 
7
- module.exports = function checkEmptyLineBefore(firstPropData, secondPropData, sharedInfo) {
7
+ module.exports = function checkEmptyLineBefore(
8
+ firstPropData,
9
+ secondPropData,
10
+ sharedInfo,
11
+ propsCount
12
+ ) {
8
13
  const firstPropIsUnspecified = !firstPropData.orderData;
9
14
  const secondPropIsUnspecified = !secondPropData.orderData;
10
15
 
@@ -18,15 +23,33 @@ module.exports = function checkEmptyLineBefore(firstPropData, secondPropData, sh
18
23
 
19
24
  sharedInfo.lastKnownSeparatedGroup = secondPropSeparatedGroup;
20
25
 
21
- if (firstPropSeparatedGroup !== secondPropSeparatedGroup && !secondPropIsUnspecified) {
26
+ const betweenGroupsInSpecified =
27
+ firstPropSeparatedGroup !== secondPropSeparatedGroup && !secondPropIsUnspecified;
28
+ const startOfUnspecifiedGroup = !firstPropIsUnspecified && secondPropIsUnspecified;
29
+
30
+ // Line threshold logic
31
+ const belowEmptyLineThreshold = propsCount < sharedInfo.emptyLineMinimumPropertyThreshold;
32
+
33
+ if (betweenGroupsInSpecified || startOfUnspecifiedGroup) {
22
34
  // Get an array of just the property groups, remove any solo properties
23
35
  const groups = _.reject(sharedInfo.expectation, _.isString);
24
36
 
25
- // secondProp seperatedGroups start at 2 so we minus 2 to get the 1st item
26
- // from our groups array
27
- const emptyLineBefore = _.get(groups[secondPropSeparatedGroup - 2], 'emptyLineBefore');
37
+ const emptyLineBefore = !startOfUnspecifiedGroup
38
+ ? // secondProp seperatedGroups start at 2 so we minus 2 to get the
39
+ // 1st item from our groups array
40
+ _.get(groups[secondPropSeparatedGroup - 2], 'emptyLineBefore')
41
+ : sharedInfo.emptyLineBeforeUnspecified;
42
+
43
+ // Threshold logic
44
+ const emptyLineThresholdInsertLines =
45
+ !belowEmptyLineThreshold && emptyLineBefore === 'threshold';
46
+ const emptyLineThresholdRemoveLines =
47
+ belowEmptyLineThreshold && emptyLineBefore === 'threshold';
28
48
 
29
- if (!hasEmptyLineBefore(secondPropData.node) && emptyLineBefore === 'always') {
49
+ if (
50
+ !hasEmptyLineBefore(secondPropData.node) &&
51
+ (emptyLineBefore === 'always' || emptyLineThresholdInsertLines)
52
+ ) {
30
53
  if (sharedInfo.isFixEnabled) {
31
54
  addEmptyLineBefore(secondPropData.node, sharedInfo.context.newline);
32
55
  } else {
@@ -37,7 +60,10 @@ module.exports = function checkEmptyLineBefore(firstPropData, secondPropData, sh
37
60
  ruleName: sharedInfo.ruleName,
38
61
  });
39
62
  }
40
- } else if (hasEmptyLineBefore(secondPropData.node) && emptyLineBefore === 'never') {
63
+ } else if (
64
+ hasEmptyLineBefore(secondPropData.node) &&
65
+ (emptyLineBefore === 'never' || emptyLineThresholdRemoveLines)
66
+ ) {
41
67
  if (sharedInfo.isFixEnabled) {
42
68
  removeEmptyLinesBefore(secondPropData.node, sharedInfo.context.newline);
43
69
  } else {
@@ -8,28 +8,30 @@ const getNodeData = require('./getNodeData');
8
8
  const createFlatOrder = require('./createFlatOrder');
9
9
 
10
10
  module.exports = function checkNode(node, sharedInfo, originalNode) {
11
- let shouldFixOrder = false;
11
+ // First, check order
12
+ const allPropData = getAllPropData(node);
13
+ const propsCount = node.nodes.filter(item => utils.isProperty(item)).length;
12
14
 
13
- const allPropData = node.nodes
14
- .filter(item => utils.isProperty(item))
15
- .map(item => getNodeData(item, sharedInfo.expectedOrder));
15
+ if (!sharedInfo.isFixEnabled) {
16
+ allPropData.forEach(checkEveryPropForOrder);
17
+ }
16
18
 
17
- // First, check order
18
- allPropData.forEach(function checkEveryPropForOrder(propData, index) {
19
- // return early if we know there is a violation and auto fix should be applied
20
- if (shouldFixOrder && sharedInfo.isFixEnabled) {
21
- return;
22
- }
19
+ if (sharedInfo.isFixEnabled) {
20
+ let shouldFixOrder = false;
23
21
 
24
- // current node should be a standard declaration
25
- if (!utils.isProperty(propData.node)) {
26
- return;
27
- }
22
+ allPropData.forEach(function checkEveryPropForOrder2(propData, index) {
23
+ // Skip first decl
24
+ if (index === 0) {
25
+ return;
26
+ }
27
+
28
+ // return early if we know there is a violation and auto fix should be applied
29
+ if (shouldFixOrder) {
30
+ return;
31
+ }
28
32
 
29
- const previousPropData = allPropData[index - 1];
33
+ const previousPropData = allPropData[index - 1];
30
34
 
31
- // Skip first decl
32
- if (previousPropData) {
33
35
  const checkedOrder = checkOrder({
34
36
  firstPropData: previousPropData,
35
37
  secondPropData: propData,
@@ -37,48 +39,37 @@ module.exports = function checkNode(node, sharedInfo, originalNode) {
37
39
  allPropData: allPropData.slice(0, index),
38
40
  });
39
41
 
40
- if (!checkedOrder.result) {
41
- if (sharedInfo.isFixEnabled) {
42
- shouldFixOrder = true;
43
- } else {
44
- const { orderData } = checkedOrder.secondNode;
45
-
46
- stylelint.utils.report({
47
- message: sharedInfo.messages.expected(
48
- checkedOrder.secondNode.name,
49
- checkedOrder.firstNode.name,
50
- orderData && orderData.groupName
51
- ),
52
- node: checkedOrder.secondNode.node,
53
- result: sharedInfo.result,
54
- ruleName: sharedInfo.ruleName,
55
- });
56
- }
42
+ if (!checkedOrder.isCorrect) {
43
+ shouldFixOrder = true;
57
44
  }
58
- }
59
- });
45
+ });
46
+
47
+ if (shouldFixOrder) {
48
+ const sortingOptions = {
49
+ 'properties-order': createFlatOrder(sharedInfo.expectation),
50
+ 'unspecified-properties-position':
51
+ sharedInfo.unspecified === 'ignore' ? 'bottom' : sharedInfo.unspecified,
52
+ };
60
53
 
61
- if (shouldFixOrder && sharedInfo.isFixEnabled) {
62
- const sortingOptions = {
63
- 'properties-order': createFlatOrder(sharedInfo.expectation),
64
- 'unspecified-properties-position':
65
- sharedInfo.unspecified === 'ignore' ? 'bottom' : sharedInfo.unspecified,
66
- };
54
+ // creating PostCSS Root node with current node as a child,
55
+ // so PostCSS Sorting can process it
56
+ const tempRoot = postcss.root({ nodes: [originalNode] });
67
57
 
68
- // creating PostCSS Root node with current node as a child,
69
- // so PostCSS Sorting can process it
70
- const tempRoot = postcss.root({ nodes: [originalNode] });
58
+ postcssSorting(sortingOptions)(tempRoot);
71
59
 
72
- postcssSorting(sortingOptions)(tempRoot);
60
+ const allPropData2 = getAllPropData(node);
61
+
62
+ allPropData2.forEach(checkEveryPropForOrder);
63
+ }
73
64
  }
74
65
 
66
+ // Second, check emptyLineBefore
75
67
  sharedInfo.lastKnownSeparatedGroup = 1;
76
68
 
77
69
  const allNodesData = node.nodes.map(function collectDataForEveryNode(child) {
78
70
  return getNodeData(child, sharedInfo.expectedOrder);
79
71
  });
80
72
 
81
- // Second, check emptyLineBefore
82
73
  allNodesData.forEach(function checkEveryPropForEmptyLine(nodeData, index) {
83
74
  let previousNodeData = allNodesData[index - 1];
84
75
 
@@ -101,6 +92,43 @@ module.exports = function checkNode(node, sharedInfo, originalNode) {
101
92
  return;
102
93
  }
103
94
 
104
- checkEmptyLineBefore(previousNodeData, nodeData, sharedInfo);
95
+ checkEmptyLineBefore(previousNodeData, nodeData, sharedInfo, propsCount);
105
96
  });
97
+
98
+ function checkEveryPropForOrder(propData, index, listOfProps) {
99
+ // Skip first decl
100
+ if (index === 0) {
101
+ return;
102
+ }
103
+
104
+ const previousPropData = listOfProps[index - 1];
105
+
106
+ const checkedOrder = checkOrder({
107
+ firstPropData: previousPropData,
108
+ secondPropData: propData,
109
+ unspecified: sharedInfo.unspecified,
110
+ allPropData: listOfProps.slice(0, index),
111
+ });
112
+
113
+ if (!checkedOrder.isCorrect) {
114
+ const { orderData } = checkedOrder.secondNode;
115
+
116
+ stylelint.utils.report({
117
+ message: sharedInfo.messages.expected(
118
+ checkedOrder.secondNode.name,
119
+ checkedOrder.firstNode.name,
120
+ orderData && orderData.groupName
121
+ ),
122
+ node: checkedOrder.secondNode.node,
123
+ result: sharedInfo.result,
124
+ ruleName: sharedInfo.ruleName,
125
+ });
126
+ }
127
+ }
128
+
129
+ function getAllPropData(inputNode) {
130
+ return inputNode.nodes
131
+ .filter(item => utils.isProperty(item))
132
+ .map(item => getNodeData(item, sharedInfo.expectedOrder));
133
+ }
106
134
  };
@@ -3,9 +3,9 @@ const _ = require('lodash');
3
3
  const checkAlphabeticalOrder = require('../checkAlphabeticalOrder');
4
4
 
5
5
  module.exports = function checkOrder({ firstPropData, secondPropData, allPropData, unspecified }) {
6
- function report(result, firstNode = firstPropData, secondNode = secondPropData) {
6
+ function report(isCorrect, firstNode = firstPropData, secondNode = secondPropData) {
7
7
  return {
8
- result,
8
+ isCorrect,
9
9
  firstNode,
10
10
  secondNode,
11
11
  };
@@ -23,17 +23,17 @@ module.exports = function checkOrder({ firstPropData, secondPropData, allPropDat
23
23
  return report(true);
24
24
  }
25
25
 
26
- const firstPropIsUnspecified = !firstPropData.orderData;
27
- const secondPropIsUnspecified = !secondPropData.orderData;
26
+ const firstPropIsSpecified = Boolean(firstPropData.orderData);
27
+ const secondPropIsSpecified = Boolean(secondPropData.orderData);
28
28
 
29
29
  // Check actual known properties
30
- if (!firstPropIsUnspecified && !secondPropIsUnspecified) {
30
+ if (firstPropIsSpecified && secondPropIsSpecified) {
31
31
  return report(
32
32
  firstPropData.orderData.expectedPosition <= secondPropData.orderData.expectedPosition
33
33
  );
34
34
  }
35
35
 
36
- if (firstPropIsUnspecified && !secondPropIsUnspecified) {
36
+ if (!firstPropIsSpecified && secondPropIsSpecified) {
37
37
  // If first prop is unspecified, look for a specified prop before it to
38
38
  // compare to the current prop
39
39
  const priorSpecifiedPropData = _.findLast(allPropData.slice(0, -1), d => Boolean(d.orderData));
@@ -49,11 +49,11 @@ module.exports = function checkOrder({ firstPropData, secondPropData, allPropDat
49
49
 
50
50
  // Now deal with unspecified props
51
51
  // Starting with bottomAlphabetical as it requires more specific conditionals
52
- if (unspecified === 'bottomAlphabetical' && !firstPropIsUnspecified && secondPropIsUnspecified) {
52
+ if (unspecified === 'bottomAlphabetical' && firstPropIsSpecified && !secondPropIsSpecified) {
53
53
  return report(true);
54
54
  }
55
55
 
56
- if (unspecified === 'bottomAlphabetical' && firstPropIsUnspecified && secondPropIsUnspecified) {
56
+ if (unspecified === 'bottomAlphabetical' && !firstPropIsSpecified && !secondPropIsSpecified) {
57
57
  if (checkAlphabeticalOrder(firstPropData, secondPropData)) {
58
58
  return report(true);
59
59
  }
@@ -61,31 +61,31 @@ module.exports = function checkOrder({ firstPropData, secondPropData, allPropDat
61
61
  return report(false);
62
62
  }
63
63
 
64
- if (unspecified === 'bottomAlphabetical' && firstPropIsUnspecified) {
64
+ if (unspecified === 'bottomAlphabetical' && !firstPropIsSpecified) {
65
65
  return report(false);
66
66
  }
67
67
 
68
- if (firstPropIsUnspecified && secondPropIsUnspecified) {
68
+ if (!firstPropIsSpecified && !secondPropIsSpecified) {
69
69
  return report(true);
70
70
  }
71
71
 
72
- if (unspecified === 'ignore' && (firstPropIsUnspecified || secondPropIsUnspecified)) {
72
+ if (unspecified === 'ignore' && (!firstPropIsSpecified || !secondPropIsSpecified)) {
73
73
  return report(true);
74
74
  }
75
75
 
76
- if (unspecified === 'top' && firstPropIsUnspecified) {
76
+ if (unspecified === 'top' && !firstPropIsSpecified) {
77
77
  return report(true);
78
78
  }
79
79
 
80
- if (unspecified === 'top' && secondPropIsUnspecified) {
80
+ if (unspecified === 'top' && !secondPropIsSpecified) {
81
81
  return report(false);
82
82
  }
83
83
 
84
- if (unspecified === 'bottom' && secondPropIsUnspecified) {
84
+ if (unspecified === 'bottom' && !secondPropIsSpecified) {
85
85
  return report(true);
86
86
  }
87
87
 
88
- if (unspecified === 'bottom' && firstPropIsUnspecified) {
88
+ if (unspecified === 'bottom' && !firstPropIsSpecified) {
89
89
  return report(false);
90
90
  }
91
91
  };
@@ -8,10 +8,10 @@ const validatePrimaryOption = require('./validatePrimaryOption');
8
8
  const ruleName = utils.namespace('properties-order');
9
9
 
10
10
  const messages = stylelint.utils.ruleMessages(ruleName, {
11
- expected: (first, second, groupName) => `
12
- Expected "${first}" to come before "${second}"${groupName ? ` in group "${groupName}"` : ''}`,
11
+ expected: (first, second, groupName) =>
12
+ `Expected "${first}" to come before "${second}"${groupName ? ` in group "${groupName}"` : ''}`,
13
13
  expectedEmptyLineBefore: property => `Expected an empty line before property "${property}"`,
14
- rejectedEmptyLineBefore: property => `Unexpected an empty line before property "${property}"`,
14
+ rejectedEmptyLineBefore: property => `Unexpected empty line before property "${property}"`,
15
15
  });
16
16
 
17
17
  const rule = function(expectation, options, context = {}) {
@@ -27,7 +27,9 @@ const rule = function(expectation, options, context = {}) {
27
27
  actual: options,
28
28
  possible: {
29
29
  unspecified: ['top', 'bottom', 'ignore', 'bottomAlphabetical'],
30
+ emptyLineBeforeUnspecified: ['always', 'never', 'threshold'],
30
31
  disableFix: _.isBoolean,
32
+ emptyLineMinimumPropertyThreshold: _.isNumber,
31
33
  },
32
34
  optional: true,
33
35
  }
@@ -39,6 +41,12 @@ const rule = function(expectation, options, context = {}) {
39
41
 
40
42
  // By default, ignore unspecified properties
41
43
  const unspecified = _.get(options, 'unspecified', 'ignore');
44
+ const emptyLineBeforeUnspecified = _.get(options, 'emptyLineBeforeUnspecified', '');
45
+ const emptyLineMinimumPropertyThreshold = _.get(
46
+ options,
47
+ 'emptyLineMinimumPropertyThreshold',
48
+ 0
49
+ );
42
50
  const disableFix = _.get(options, 'disableFix', false);
43
51
  const isFixEnabled = context.fix && !disableFix;
44
52
 
@@ -48,6 +56,8 @@ const rule = function(expectation, options, context = {}) {
48
56
  expectedOrder,
49
57
  expectation,
50
58
  unspecified,
59
+ emptyLineBeforeUnspecified,
60
+ emptyLineMinimumPropertyThreshold,
51
61
  messages,
52
62
  ruleName,
53
63
  result,
@@ -42,7 +42,7 @@ module.exports = function validatePrimaryOption(actualOptions) {
42
42
  return true;
43
43
  }
44
44
 
45
- return _.includes(['always', 'never'], item.emptyLineBefore);
45
+ return _.includes(['always', 'never', 'threshold'], item.emptyLineBefore);
46
46
  })
47
47
  ) {
48
48
  return false;
@@ -0,0 +1,144 @@
1
+ 'use strict';
2
+
3
+ // See https://github.com/stylelint/stylelint/blob/10.1.0/lib/reference/shorthandData.js
4
+ module.exports = {
5
+ margin: ['margin-top', 'margin-bottom', 'margin-left', 'margin-right'],
6
+ padding: ['padding-top', 'padding-bottom', 'padding-left', 'padding-right'],
7
+ background: [
8
+ 'background-image',
9
+ 'background-size',
10
+ 'background-position',
11
+ 'background-repeat',
12
+ 'background-origin',
13
+ 'background-clip',
14
+ 'background-attachment',
15
+ 'background-color',
16
+ ],
17
+ font: [
18
+ 'font-style',
19
+ 'font-variant',
20
+ 'font-weight',
21
+ 'font-stretch',
22
+ 'font-size',
23
+ 'font-family',
24
+ 'line-height',
25
+ ],
26
+ border: [
27
+ 'border-top-width',
28
+ 'border-bottom-width',
29
+ 'border-left-width',
30
+ 'border-right-width',
31
+ 'border-top-style',
32
+ 'border-bottom-style',
33
+ 'border-left-style',
34
+ 'border-right-style',
35
+ 'border-top-color',
36
+ 'border-bottom-color',
37
+ 'border-left-color',
38
+ 'border-right-color',
39
+ ],
40
+ 'border-top': ['border-top-width', 'border-top-style', 'border-top-color'],
41
+ 'border-bottom': ['border-bottom-width', 'border-bottom-style', 'border-bottom-color'],
42
+ 'border-left': ['border-left-width', 'border-left-style', 'border-left-color'],
43
+ 'border-right': ['border-right-width', 'border-right-style', 'border-right-color'],
44
+ 'border-width': [
45
+ 'border-top-width',
46
+ 'border-bottom-width',
47
+ 'border-left-width',
48
+ 'border-right-width',
49
+ ],
50
+ 'border-style': [
51
+ 'border-top-style',
52
+ 'border-bottom-style',
53
+ 'border-left-style',
54
+ 'border-right-style',
55
+ ],
56
+ 'border-color': [
57
+ 'border-top-color',
58
+ 'border-bottom-color',
59
+ 'border-left-color',
60
+ 'border-right-color',
61
+ ],
62
+ 'list-style': ['list-style-type', 'list-style-position', 'list-style-image'],
63
+ 'border-radius': [
64
+ 'border-top-right-radius',
65
+ 'border-top-left-radius',
66
+ 'border-bottom-right-radius',
67
+ 'border-bottom-left-radius',
68
+ ],
69
+ transition: [
70
+ 'transition-delay',
71
+ 'transition-duration',
72
+ 'transition-property',
73
+ 'transition-timing-function',
74
+ ],
75
+ animation: [
76
+ 'animation-name',
77
+ 'animation-duration',
78
+ 'animation-timing-function',
79
+ 'animation-delay',
80
+ 'animation-iteration-count',
81
+ 'animation-direction',
82
+ 'animation-fill-mode',
83
+ 'animation-play-state',
84
+ ],
85
+ 'border-block-end': [
86
+ 'border-block-end-width',
87
+ 'border-block-end-style',
88
+ 'border-block-end-color',
89
+ ],
90
+ 'border-block-start': [
91
+ 'border-block-start-width',
92
+ 'border-block-start-style',
93
+ 'border-block-start-color',
94
+ ],
95
+ 'border-image': [
96
+ 'border-image-source',
97
+ 'border-image-slice',
98
+ 'border-image-width',
99
+ 'border-image-outset',
100
+ 'border-image-repeat',
101
+ ],
102
+ 'border-inline-end': [
103
+ 'border-inline-end-width',
104
+ 'border-inline-end-style',
105
+ 'border-inline-end-color',
106
+ ],
107
+ 'border-inline-start': [
108
+ 'border-inline-start-width',
109
+ 'border-inline-start-style',
110
+ 'border-inline-start-color',
111
+ ],
112
+ 'column-rule': ['column-rule-width', 'column-rule-style', 'column-rule-color'],
113
+ columns: ['column-width', 'column-count'],
114
+ flex: ['flex-grow', 'flex-shrink', 'flex-basis'],
115
+ 'flex-flow': ['flex-direction', 'flex-wrap'],
116
+ grid: [
117
+ 'grid-template-rows',
118
+ 'grid-template-columns',
119
+ 'grid-template-areas',
120
+ 'grid-auto-rows',
121
+ 'grid-auto-columns',
122
+ 'grid-auto-flow',
123
+ 'grid-column-gap',
124
+ 'grid-row-gap',
125
+ ],
126
+ 'grid-area': ['grid-row-start', 'grid-column-start', 'grid-row-end', 'grid-column-end'],
127
+ 'grid-column': ['grid-column-start', 'grid-column-end'],
128
+ 'grid-gap': ['grid-row-gap', 'grid-column-gap'],
129
+ 'grid-row': ['grid-row-start', 'grid-row-end'],
130
+ 'grid-template': ['grid-template-columns', 'grid-template-rows', 'grid-template-areas'],
131
+ outline: ['outline-color', 'outline-style', 'outline-width'],
132
+ 'text-decoration': ['text-decoration-color', 'text-decoration-style', 'text-decoration-line'],
133
+ 'text-emphasis': ['text-emphasis-style', 'text-emphasis-color'],
134
+ mask: [
135
+ 'mask-image',
136
+ 'mask-mode',
137
+ 'mask-position',
138
+ 'mask-size',
139
+ 'mask-repeat',
140
+ 'mask-origin',
141
+ 'mask-clip',
142
+ 'mask-composite',
143
+ ],
144
+ };