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 +21 -2
- package/package.json +14 -14
- package/rules/checkAlphabeticalOrder.js +17 -0
- package/rules/properties-alphabetical-order/README.md +17 -0
- package/rules/properties-order/README.md +176 -3
- package/rules/properties-order/checkEmptyLineBefore.js +33 -7
- package/rules/properties-order/checkNode.js +76 -48
- package/rules/properties-order/checkOrder.js +15 -15
- package/rules/properties-order/index.js +13 -3
- package/rules/properties-order/validatePrimaryOption.js +1 -1
- package/rules/shorthandData.js +144 -0
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
|
-
*
|
|
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
|
-
*
|
|
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": "
|
|
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": ">=
|
|
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.
|
|
32
|
-
"postcss": "^7.0.
|
|
33
|
-
"postcss-sorting": "^
|
|
31
|
+
"lodash": "^4.17.15",
|
|
32
|
+
"postcss": "^7.0.17",
|
|
33
|
+
"postcss-sorting": "^5.0.1"
|
|
34
34
|
},
|
|
35
35
|
"peerDependencies": {
|
|
36
|
-
"stylelint": "
|
|
36
|
+
"stylelint": ">=10.0.1"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
|
-
"eslint": "
|
|
40
|
-
"eslint-config-hudochenkov": "
|
|
41
|
-
"eslint-config-prettier": "
|
|
42
|
-
"husky": "^
|
|
43
|
-
"jest": "^24.
|
|
44
|
-
"lint-staged": "^
|
|
45
|
-
"prettier": "~1.
|
|
46
|
-
"stylelint": "^
|
|
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
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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 (
|
|
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 (
|
|
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
|
-
|
|
11
|
+
// First, check order
|
|
12
|
+
const allPropData = getAllPropData(node);
|
|
13
|
+
const propsCount = node.nodes.filter(item => utils.isProperty(item)).length;
|
|
12
14
|
|
|
13
|
-
|
|
14
|
-
.
|
|
15
|
-
|
|
15
|
+
if (!sharedInfo.isFixEnabled) {
|
|
16
|
+
allPropData.forEach(checkEveryPropForOrder);
|
|
17
|
+
}
|
|
16
18
|
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
|
|
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.
|
|
41
|
-
|
|
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
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
-
|
|
69
|
-
// so PostCSS Sorting can process it
|
|
70
|
-
const tempRoot = postcss.root({ nodes: [originalNode] });
|
|
58
|
+
postcssSorting(sortingOptions)(tempRoot);
|
|
71
59
|
|
|
72
|
-
|
|
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(
|
|
6
|
+
function report(isCorrect, firstNode = firstPropData, secondNode = secondPropData) {
|
|
7
7
|
return {
|
|
8
|
-
|
|
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
|
|
27
|
-
const
|
|
26
|
+
const firstPropIsSpecified = Boolean(firstPropData.orderData);
|
|
27
|
+
const secondPropIsSpecified = Boolean(secondPropData.orderData);
|
|
28
28
|
|
|
29
29
|
// Check actual known properties
|
|
30
|
-
if (
|
|
30
|
+
if (firstPropIsSpecified && secondPropIsSpecified) {
|
|
31
31
|
return report(
|
|
32
32
|
firstPropData.orderData.expectedPosition <= secondPropData.orderData.expectedPosition
|
|
33
33
|
);
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
if (
|
|
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' &&
|
|
52
|
+
if (unspecified === 'bottomAlphabetical' && firstPropIsSpecified && !secondPropIsSpecified) {
|
|
53
53
|
return report(true);
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
if (unspecified === 'bottomAlphabetical' &&
|
|
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' &&
|
|
64
|
+
if (unspecified === 'bottomAlphabetical' && !firstPropIsSpecified) {
|
|
65
65
|
return report(false);
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
if (
|
|
68
|
+
if (!firstPropIsSpecified && !secondPropIsSpecified) {
|
|
69
69
|
return report(true);
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
if (unspecified === 'ignore' && (
|
|
72
|
+
if (unspecified === 'ignore' && (!firstPropIsSpecified || !secondPropIsSpecified)) {
|
|
73
73
|
return report(true);
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
if (unspecified === 'top' &&
|
|
76
|
+
if (unspecified === 'top' && !firstPropIsSpecified) {
|
|
77
77
|
return report(true);
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
if (unspecified === 'top' &&
|
|
80
|
+
if (unspecified === 'top' && !secondPropIsSpecified) {
|
|
81
81
|
return report(false);
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
if (unspecified === 'bottom' &&
|
|
84
|
+
if (unspecified === 'bottom' && !secondPropIsSpecified) {
|
|
85
85
|
return report(true);
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
-
if (unspecified === 'bottom' &&
|
|
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
|
-
|
|
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
|
|
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
|
+
};
|