stylelint-order 3.1.1 → 4.0.0

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,6 +2,12 @@
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
+ ## 4.0.0
6
+
7
+ * Breaking change: Dropped Node.js 8 support. Node.js 10 or greater is now required.
8
+ * Breaking change: Always remove empty line before the first property if this property has any `emptyLineBefore*` option targeting it in `properties-order`. Even if option set to `always` empty line before the first property will be removed.
9
+ * Fixed false positives for `emptyLineBeforeUnspecified`.
10
+
5
11
  ## 3.1.1
6
12
 
7
13
  * Added `stylelint@11` as a peer dependency.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stylelint-order",
3
- "version": "3.1.1",
3
+ "version": "4.0.0",
4
4
  "description": "A collection of order related linting rules for stylelint.",
5
5
  "keywords": [
6
6
  "stylelint-plugin",
@@ -16,9 +16,6 @@
16
16
  "url": "https://github.com/hudochenkov/stylelint-order/issues"
17
17
  },
18
18
  "homepage": "https://github.com/hudochenkov/stylelint-order",
19
- "engines": {
20
- "node": ">=8.7.0"
21
- },
22
19
  "files": [
23
20
  "rules",
24
21
  "utils",
@@ -29,21 +26,21 @@
29
26
  "main": "index.js",
30
27
  "dependencies": {
31
28
  "lodash": "^4.17.15",
32
- "postcss": "^7.0.17",
29
+ "postcss": "^7.0.26",
33
30
  "postcss-sorting": "^5.0.1"
34
31
  },
35
32
  "peerDependencies": {
36
- "stylelint": ">=10.0.1"
33
+ "stylelint": "^10.0.1 || ^11.0.0 || ^12.0.0 || ^13.0.0"
37
34
  },
38
35
  "devDependencies": {
39
- "eslint": "^6.2.2",
36
+ "eslint": "^6.8.0",
40
37
  "eslint-config-hudochenkov": "^3.0.1",
41
- "eslint-config-prettier": "^6.1.0",
42
- "husky": "^3.0.4",
38
+ "eslint-config-prettier": "^6.9.0",
39
+ "husky": "^3.1.0",
43
40
  "jest": "^24.9.0",
44
- "lint-staged": "^9.2.5",
45
- "prettier": "~1.18.2",
46
- "stylelint": "^11.0.0"
41
+ "lint-staged": "^9.5.0",
42
+ "prettier": "~1.19.1",
43
+ "stylelint": "^12.0.1"
47
44
  },
48
45
  "scripts": {
49
46
  "pretest": "eslint .",
@@ -1,10 +1,9 @@
1
1
  const stylelint = require('stylelint');
2
- const _ = require('lodash');
3
2
  const checkOrder = require('./checkOrder');
4
3
  const getOrderData = require('./getOrderData');
5
4
 
6
5
  module.exports = function checkNode(node, sharedInfo) {
7
- const allNodesData = [];
6
+ let allNodesData = [];
8
7
 
9
8
  node.each(function processEveryNode(child) {
10
9
  // Skip comments
@@ -13,27 +12,24 @@ module.exports = function checkNode(node, sharedInfo) {
13
12
  }
14
13
 
15
14
  // Receive node description and expectedPosition
16
- const nodeOrderData = getOrderData(sharedInfo.expectedOrder, child);
15
+ let nodeOrderData = getOrderData(sharedInfo.orderInfo, child);
17
16
 
18
- const nodeData = {
19
- description: nodeOrderData.description,
17
+ let nodeData = {
20
18
  node: child,
19
+ description: nodeOrderData.description,
20
+ expectedPosition: nodeOrderData.expectedPosition,
21
21
  };
22
22
 
23
- if (nodeOrderData.expectedPosition) {
24
- nodeData.expectedPosition = nodeOrderData.expectedPosition;
25
- }
26
-
27
- const previousNodeData = _.last(allNodesData);
28
-
29
23
  allNodesData.push(nodeData);
30
24
 
25
+ let previousNodeData = allNodesData[allNodesData.length - 2];
26
+
31
27
  // Skip first node
32
28
  if (!previousNodeData) {
33
29
  return;
34
30
  }
35
31
 
36
- const isCorrectOrder = checkOrder(previousNodeData, nodeData, allNodesData, sharedInfo);
32
+ let isCorrectOrder = checkOrder(previousNodeData, nodeData, allNodesData, sharedInfo);
37
33
 
38
34
  if (isCorrectOrder) {
39
35
  return;
@@ -1,19 +1,19 @@
1
- const stylelint = require('stylelint');
2
- const _ = require('lodash');
1
+ let stylelint = require('stylelint');
2
+ let _ = require('lodash');
3
3
 
4
4
  module.exports = function checkOrder(firstNodeData, secondNodeData, allNodesData, sharedInfo) {
5
- const firstNodeIsUnspecified = !firstNodeData.expectedPosition;
6
- const secondNodeIsUnspecified = !secondNodeData.expectedPosition;
5
+ let firstNodeIsSpecified = Boolean(firstNodeData.expectedPosition);
6
+ let secondNodeIsSpecified = Boolean(secondNodeData.expectedPosition);
7
7
 
8
8
  // If both nodes have their position
9
- if (!firstNodeIsUnspecified && !secondNodeIsUnspecified) {
9
+ if (firstNodeIsSpecified && secondNodeIsSpecified) {
10
10
  return firstNodeData.expectedPosition <= secondNodeData.expectedPosition;
11
11
  }
12
12
 
13
- if (firstNodeIsUnspecified && !secondNodeIsUnspecified) {
14
- // If first node is unspecified, look for a specified node before it to
15
- // compare to the current node
16
- const priorSpecifiedNodeData = _.findLast(allNodesData.slice(0, -1), d =>
13
+ if (!firstNodeIsSpecified && secondNodeIsSpecified) {
14
+ // If first node is unspecified, look for a specified node before it
15
+ // to compare to the current node
16
+ let priorSpecifiedNodeData = _.findLast(allNodesData.slice(0, -1), d =>
17
17
  Boolean(d.expectedPosition)
18
18
  );
19
19
 
@@ -43,29 +43,29 @@ module.exports = function checkOrder(firstNodeData, secondNodeData, allNodesData
43
43
  }
44
44
  }
45
45
 
46
- if (firstNodeIsUnspecified && secondNodeIsUnspecified) {
46
+ if (!firstNodeIsSpecified && !secondNodeIsSpecified) {
47
47
  return true;
48
48
  }
49
49
 
50
- const { unspecified } = sharedInfo;
50
+ let { unspecified } = sharedInfo;
51
51
 
52
- if (unspecified === 'ignore' && (firstNodeIsUnspecified || secondNodeIsUnspecified)) {
52
+ if (unspecified === 'ignore' && (!firstNodeIsSpecified || !secondNodeIsSpecified)) {
53
53
  return true;
54
54
  }
55
55
 
56
- if (unspecified === 'top' && firstNodeIsUnspecified) {
56
+ if (unspecified === 'top' && !firstNodeIsSpecified) {
57
57
  return true;
58
58
  }
59
59
 
60
- if (unspecified === 'top' && secondNodeIsUnspecified) {
60
+ if (unspecified === 'top' && !secondNodeIsSpecified) {
61
61
  return false;
62
62
  }
63
63
 
64
- if (unspecified === 'bottom' && secondNodeIsUnspecified) {
64
+ if (unspecified === 'bottom' && !secondNodeIsSpecified) {
65
65
  return true;
66
66
  }
67
67
 
68
- if (unspecified === 'bottom' && firstNodeIsUnspecified) {
68
+ if (unspecified === 'bottom' && !firstNodeIsSpecified) {
69
69
  return false;
70
70
  }
71
71
  };
@@ -1,34 +1,27 @@
1
1
  const _ = require('lodash');
2
2
  const getDescription = require('./getDescription');
3
3
 
4
- module.exports = function createExpectedOrder(input) {
5
- const order = {};
4
+ module.exports = function createOrderInfo(input) {
5
+ let order = {};
6
6
  let expectedPosition = 0;
7
7
 
8
8
  input.forEach(item => {
9
9
  expectedPosition += 1;
10
10
 
11
- if ((_.isString(item) && item !== 'at-rules' && item !== 'rules') || item === 'less-mixins') {
12
- order[item] = {
13
- expectedPosition,
14
- description: getDescription(item),
11
+ // Convert 'rules' into extended pattern
12
+ if (item === 'rules') {
13
+ item = {
14
+ type: 'rule',
15
15
  };
16
16
  }
17
17
 
18
- if (item === 'rules' || item.type === 'rule') {
19
- // Convert 'rules' into extended pattern
20
- if (item === 'rules') {
21
- item = {
22
- type: 'rule',
23
- };
24
- }
25
-
18
+ if (item.type === 'rule') {
26
19
  // It there are no nodes like that create array for them
27
20
  if (!order[item.type]) {
28
21
  order[item.type] = [];
29
22
  }
30
23
 
31
- const nodeData = {
24
+ let nodeData = {
32
25
  expectedPosition,
33
26
  description: getDescription(item),
34
27
  };
@@ -44,20 +37,20 @@ module.exports = function createExpectedOrder(input) {
44
37
  order[item.type].push(nodeData);
45
38
  }
46
39
 
47
- if (item === 'at-rules' || item.type === 'at-rule') {
48
- // Convert 'at-rules' into extended pattern
49
- if (item === 'at-rules') {
50
- item = {
51
- type: 'at-rule',
52
- };
53
- }
40
+ // Convert 'at-rules' into extended pattern
41
+ if (item === 'at-rules') {
42
+ item = {
43
+ type: 'at-rule',
44
+ };
45
+ }
54
46
 
47
+ if (item.type === 'at-rule') {
55
48
  // It there are no nodes like that create array for them
56
49
  if (!order[item.type]) {
57
50
  order[item.type] = [];
58
51
  }
59
52
 
60
- const nodeData = {
53
+ let nodeData = {
61
54
  expectedPosition,
62
55
  description: getDescription(item),
63
56
  };
@@ -80,6 +73,13 @@ module.exports = function createExpectedOrder(input) {
80
73
 
81
74
  order[item.type].push(nodeData);
82
75
  }
76
+
77
+ if (_.isString(item)) {
78
+ order[item] = {
79
+ expectedPosition,
80
+ description: getDescription(item),
81
+ };
82
+ }
83
83
  });
84
84
 
85
85
  return order;
@@ -3,7 +3,7 @@ const calcAtRulePatternPriority = require('./calcAtRulePatternPriority');
3
3
  const calcRulePatternPriority = require('./calcRulePatternPriority');
4
4
  const getDescription = require('./getDescription');
5
5
 
6
- module.exports = function getOrderData(expectedOrder, node) {
6
+ module.exports = function getOrderData(orderInfo, node) {
7
7
  let nodeType;
8
8
 
9
9
  if (utils.isAtVariable(node)) {
@@ -24,7 +24,7 @@ module.exports = function getOrderData(expectedOrder, node) {
24
24
  selector: node.selector,
25
25
  };
26
26
 
27
- const rules = expectedOrder.rule;
27
+ const rules = orderInfo.rule;
28
28
 
29
29
  // Looking for most specified pattern, because it can match many patterns
30
30
  if (rules && rules.length) {
@@ -59,7 +59,7 @@ module.exports = function getOrderData(expectedOrder, node) {
59
59
  nodeType.parameter = node.params;
60
60
  }
61
61
 
62
- const atRules = expectedOrder['at-rule'];
62
+ const atRules = orderInfo['at-rule'];
63
63
 
64
64
  // Looking for most specified pattern, because it can match many patterns
65
65
  if (atRules && atRules.length) {
@@ -81,8 +81,8 @@ module.exports = function getOrderData(expectedOrder, node) {
81
81
  }
82
82
  }
83
83
 
84
- if (expectedOrder[nodeType]) {
85
- return expectedOrder[nodeType];
84
+ if (orderInfo[nodeType]) {
85
+ return orderInfo[nodeType];
86
86
  }
87
87
 
88
88
  // Return only description if there no patterns for that node
@@ -1,26 +1,24 @@
1
1
  const stylelint = require('stylelint');
2
2
  const _ = require('lodash');
3
3
  const postcssSorting = require('postcss-sorting');
4
- const utils = require('../../utils');
4
+ const { namespace, getContainingNode, isRuleWithNodes } = require('../../utils');
5
5
  const checkNode = require('./checkNode');
6
- const createExpectedOrder = require('./createExpectedOrder');
6
+ const createOrderInfo = require('./createOrderInfo');
7
7
  const validatePrimaryOption = require('./validatePrimaryOption');
8
8
 
9
- const ruleName = utils.namespace('order');
9
+ const ruleName = namespace('order');
10
10
 
11
11
  const messages = stylelint.utils.ruleMessages(ruleName, {
12
12
  expected: (first, second) => `Expected ${first} to come before ${second}`,
13
13
  });
14
14
 
15
- function rule(expectation, options, context) {
16
- context = context || {};
17
-
15
+ function rule(primaryOption, options = {}, context = {}) {
18
16
  return function(root, result) {
19
- const validOptions = stylelint.utils.validateOptions(
17
+ let validOptions = stylelint.utils.validateOptions(
20
18
  result,
21
19
  ruleName,
22
20
  {
23
- actual: expectation,
21
+ actual: primaryOption,
24
22
  possible: validatePrimaryOption,
25
23
  },
26
24
  {
@@ -37,17 +35,13 @@ function rule(expectation, options, context) {
37
35
  return;
38
36
  }
39
37
 
40
- const disableFix = _.get(options, 'disableFix', false);
41
- const isFixEnabled = context.fix && !disableFix;
42
-
43
- const expectedOrder = createExpectedOrder(expectation);
44
-
45
- // By default, ignore unspecified properties
46
- const unspecified = _.get(options, 'unspecified', 'ignore');
38
+ let disableFix = options.disableFix || false;
39
+ let isFixEnabled = context.fix && !disableFix;
47
40
 
48
- const sharedInfo = {
49
- expectedOrder,
50
- unspecified,
41
+ // Contains information which will be shared in many files and this info would be mutated to share state between them
42
+ let sharedInfo = {
43
+ orderInfo: createOrderInfo(primaryOption),
44
+ unspecified: options.unspecified || 'ignore',
51
45
  messages,
52
46
  ruleName,
53
47
  result,
@@ -55,7 +49,7 @@ function rule(expectation, options, context) {
55
49
  shouldFix: false,
56
50
  };
57
51
 
58
- const processedParents = [];
52
+ let processedParents = [];
59
53
 
60
54
  // Check all rules and at-rules recursively
61
55
  root.walk(function processRulesAndAtrules(input) {
@@ -64,28 +58,28 @@ function rule(expectation, options, context) {
64
58
  return;
65
59
  }
66
60
 
67
- const node = utils.getContainingNode(input);
61
+ let node = getContainingNode(input);
68
62
 
69
- // Avoid warnings duplication, caused by interfering in `root.walk()` algorigthm with `utils.getContainingNode()`
63
+ // Avoid warnings duplication, caused by interfering in `root.walk()` algorigthm with `getContainingNode()`
70
64
  if (processedParents.includes(node)) {
71
65
  return;
72
66
  }
73
67
 
74
68
  processedParents.push(node);
75
69
 
76
- if (utils.isRuleWithNodes(node)) {
70
+ if (isRuleWithNodes(node)) {
77
71
  checkNode(node, sharedInfo);
78
72
  }
79
73
  });
80
74
 
81
75
  if (sharedInfo.shouldFix) {
82
- postcssSorting({ order: expectation })(root);
76
+ postcssSorting({ order: primaryOption })(root);
83
77
  }
84
78
  };
85
79
  }
86
80
 
81
+ rule.ruleName = ruleName;
82
+ rule.messages = messages;
87
83
  rule.primaryOptionArray = true;
88
84
 
89
85
  module.exports = rule;
90
- module.exports.ruleName = ruleName;
91
- module.exports.messages = messages;
@@ -11,18 +11,15 @@ module.exports = function validatePrimaryOption(actualOptions) {
11
11
  if (
12
12
  !actualOptions.every(item => {
13
13
  if (_.isString(item)) {
14
- return _.includes(
15
- [
16
- 'custom-properties',
17
- 'dollar-variables',
18
- 'at-variables',
19
- 'declarations',
20
- 'rules',
21
- 'at-rules',
22
- 'less-mixins',
23
- ],
24
- item
25
- );
14
+ return [
15
+ 'custom-properties',
16
+ 'dollar-variables',
17
+ 'at-variables',
18
+ 'declarations',
19
+ 'rules',
20
+ 'at-rules',
21
+ 'less-mixins',
22
+ ].includes(item);
26
23
  }
27
24
 
28
25
  return _.isPlainObject(item) && !_.isUndefined(item.type);
@@ -0,0 +1,62 @@
1
+ let stylelint = require('stylelint');
2
+ let _ = require('lodash');
3
+ let postcss = require('postcss');
4
+ let checkAlphabeticalOrder = require('../checkAlphabeticalOrder');
5
+ let { isStandardSyntaxProperty, isCustomProperty } = require('../../utils');
6
+
7
+ module.exports = function checkNode(node, result, ruleName, messages) {
8
+ let allPropData = [];
9
+
10
+ node.each(function processEveryNode(child) {
11
+ if (child.type !== 'decl') {
12
+ return;
13
+ }
14
+
15
+ let { prop } = child;
16
+
17
+ if (!isStandardSyntaxProperty(prop)) {
18
+ return;
19
+ }
20
+
21
+ if (isCustomProperty(prop)) {
22
+ return;
23
+ }
24
+
25
+ let unprefixedPropName = postcss.vendor.unprefixed(prop);
26
+
27
+ // Hack to allow -moz-osx-font-smoothing to be understood
28
+ // just like -webkit-font-smoothing
29
+ if (unprefixedPropName.startsWith('osx-')) {
30
+ unprefixedPropName = unprefixedPropName.slice(4);
31
+ }
32
+
33
+ let propData = {
34
+ name: prop,
35
+ unprefixedName: unprefixedPropName,
36
+ index: allPropData.length,
37
+ node: child,
38
+ };
39
+
40
+ let previousPropData = _.last(allPropData);
41
+
42
+ allPropData.push(propData);
43
+
44
+ // Skip first decl
45
+ if (!previousPropData) {
46
+ return;
47
+ }
48
+
49
+ let isCorrectOrder = checkAlphabeticalOrder(previousPropData, propData);
50
+
51
+ if (isCorrectOrder) {
52
+ return;
53
+ }
54
+
55
+ stylelint.utils.report({
56
+ message: messages.expected(propData.name, previousPropData.name),
57
+ node: child,
58
+ result,
59
+ ruleName,
60
+ });
61
+ });
62
+ };
@@ -1,31 +1,27 @@
1
- const stylelint = require('stylelint');
2
- const _ = require('lodash');
3
- const postcss = require('postcss');
4
- const postcssSorting = require('postcss-sorting');
5
- const checkAlphabeticalOrder = require('../checkAlphabeticalOrder');
6
- const utils = require('../../utils');
1
+ let stylelint = require('stylelint');
2
+ let postcssSorting = require('postcss-sorting');
3
+ let { namespace, getContainingNode, isRuleWithNodes } = require('../../utils');
4
+ let checkNode = require('./checkNode');
7
5
 
8
- const ruleName = utils.namespace('properties-alphabetical-order');
6
+ let ruleName = namespace('properties-alphabetical-order');
9
7
 
10
- const messages = stylelint.utils.ruleMessages(ruleName, {
8
+ let messages = stylelint.utils.ruleMessages(ruleName, {
11
9
  expected: (first, second) => `Expected ${first} to come before ${second}`,
12
10
  });
13
11
 
14
- function rule(actual, options, context) {
15
- context = context || {};
16
-
12
+ function rule(actual, options = {}, context = {}) {
17
13
  return function(root, result) {
18
- const validOptions = stylelint.utils.validateOptions(
14
+ let validOptions = stylelint.utils.validateOptions(
19
15
  result,
20
16
  ruleName,
21
17
  {
22
18
  actual,
23
- possible: _.isBoolean,
19
+ possible: Boolean,
24
20
  },
25
21
  {
26
22
  actual: options,
27
23
  possible: {
28
- disableFix: _.isBoolean,
24
+ disableFix: Boolean,
29
25
  },
30
26
  optional: true,
31
27
  }
@@ -35,7 +31,7 @@ function rule(actual, options, context) {
35
31
  return;
36
32
  }
37
33
 
38
- const disableFix = _.get(options, ['disableFix'], false);
34
+ let disableFix = options.disableFix || false;
39
35
 
40
36
  if (context.fix && !disableFix) {
41
37
  postcssSorting({ 'properties-order': 'alphabetical' })(root);
@@ -43,82 +39,26 @@ function rule(actual, options, context) {
43
39
  return;
44
40
  }
45
41
 
46
- const processedParents = [];
42
+ let processedParents = [];
47
43
 
48
44
  root.walk(function processRulesAndAtrules(input) {
49
- const node = utils.getContainingNode(input);
45
+ let node = getContainingNode(input);
50
46
 
51
- // Avoid warnings duplication, caused by interfering in `root.walk()` algorigthm with `utils.getContainingNode()`
47
+ // Avoid warnings duplication, caused by interfering in `root.walk()` algorigthm with `getContainingNode()`
52
48
  if (processedParents.includes(node)) {
53
49
  return;
54
50
  }
55
51
 
56
52
  processedParents.push(node);
57
53
 
58
- if (utils.isRuleWithNodes(node)) {
59
- checkNode(node, result);
54
+ if (isRuleWithNodes(node)) {
55
+ checkNode(node, result, ruleName, messages);
60
56
  }
61
57
  });
62
58
  };
63
59
  }
64
60
 
65
- module.exports = rule;
66
- module.exports.ruleName = ruleName;
67
- module.exports.messages = messages;
68
-
69
- function checkNode(node, result) {
70
- const allPropData = [];
71
-
72
- node.each(function processEveryNode(child) {
73
- if (child.type !== 'decl') {
74
- return;
75
- }
76
-
77
- const { prop } = child;
78
-
79
- if (!utils.isStandardSyntaxProperty(prop)) {
80
- return;
81
- }
82
-
83
- if (utils.isCustomProperty(prop)) {
84
- return;
85
- }
86
-
87
- let unprefixedPropName = postcss.vendor.unprefixed(prop);
88
-
89
- // Hack to allow -moz-osx-font-smoothing to be understood
90
- // just like -webkit-font-smoothing
91
- if (unprefixedPropName.indexOf('osx-') === 0) {
92
- unprefixedPropName = unprefixedPropName.slice(4);
93
- }
61
+ rule.ruleName = ruleName;
62
+ rule.messages = messages;
94
63
 
95
- const propData = {
96
- name: prop,
97
- unprefixedName: unprefixedPropName,
98
- index: allPropData.length,
99
- node: child,
100
- };
101
-
102
- const previousPropData = _.last(allPropData);
103
-
104
- allPropData.push(propData);
105
-
106
- // Skip first decl
107
- if (!previousPropData) {
108
- return;
109
- }
110
-
111
- const isCorrectOrder = checkAlphabeticalOrder(previousPropData, propData);
112
-
113
- if (isCorrectOrder) {
114
- return;
115
- }
116
-
117
- stylelint.utils.report({
118
- message: messages.expected(propData.name, previousPropData.name),
119
- node: child,
120
- result,
121
- ruleName,
122
- });
123
- });
124
- }
64
+ module.exports = rule;
@@ -35,7 +35,14 @@ Within an order array, you can include
35
35
  * group objects with these properties:
36
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.
37
37
  * `properties (array of strings)`: The properties in this group.
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).
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.
39
+
40
+ 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.
41
+
42
+ If `emptyLineBefore` specified, regardless of it's value, the first property in a rule would be forced to not have an empty line before it.
43
+
44
+ For `threshold`, refer to the [`emptyLineMinimumPropertyThreshold` documentation](#emptylineminimumpropertythreshold-number).
45
+
39
46
  * `noEmptyLineBetween`: If `true`, properties within group should not have empty lines between them.
40
47
  * `groupName`: An optional name for the group. This will be used in error messages.
41
48
 
@@ -600,11 +607,13 @@ a {
600
607
 
601
608
  Default behavior does not enforce the presence of an empty line before an unspecified block of properties.
602
609
 
603
- If `"always"`, the unspecified group must be separated from other properties by an empty newline.
610
+ If `"always"`, the unspecified group must be separated from other properties by an empty newline.
604
611
  If `"never"`, the unspecified group must have no empty lines separating it from other properties.
605
612
 
606
613
  For `"threshold"`, see the [`emptyLineMinimumPropertyThreshold` documentation](#emptylineminimumpropertythreshold-number) for more information.
607
614
 
615
+ If `emptyLineBeforeUnspecified` specified, regardless of it's value, if the first property in a rule is target of this option, that property would be forced to not have an empty line before it.
616
+
608
617
  Given:
609
618
 
610
619
  ```js
@@ -1,8 +1,8 @@
1
- const stylelint = require('stylelint');
2
- const _ = require('lodash');
3
- const addEmptyLineBefore = require('./addEmptyLineBefore');
4
- const hasEmptyLineBefore = require('./hasEmptyLineBefore');
5
- const removeEmptyLinesBefore = require('./removeEmptyLinesBefore');
1
+ let stylelint = require('stylelint');
2
+ let _ = require('lodash');
3
+ let addEmptyLineBefore = require('./addEmptyLineBefore');
4
+ let hasEmptyLineBefore = require('./hasEmptyLineBefore');
5
+ let removeEmptyLinesBefore = require('./removeEmptyLinesBefore');
6
6
 
7
7
  module.exports = function checkEmptyLineBefore(
8
8
  firstPropData,
@@ -10,45 +10,40 @@ module.exports = function checkEmptyLineBefore(
10
10
  sharedInfo,
11
11
  propsCount
12
12
  ) {
13
- const firstPropIsUnspecified = !firstPropData.orderData;
14
- const secondPropIsUnspecified = !secondPropData.orderData;
13
+ let firstPropIsSpecified = Boolean(firstPropData.orderData);
14
+ let secondPropIsSpecified = Boolean(secondPropData.orderData);
15
15
 
16
16
  // Check newlines between groups
17
- const firstPropSeparatedGroup = !firstPropIsUnspecified
17
+ let firstPropGroup = firstPropIsSpecified
18
18
  ? firstPropData.orderData.separatedGroup
19
19
  : sharedInfo.lastKnownSeparatedGroup;
20
- const secondPropSeparatedGroup = !secondPropIsUnspecified
20
+ let secondPropGroup = secondPropIsSpecified
21
21
  ? secondPropData.orderData.separatedGroup
22
22
  : sharedInfo.lastKnownSeparatedGroup;
23
23
 
24
- sharedInfo.lastKnownSeparatedGroup = secondPropSeparatedGroup;
24
+ sharedInfo.lastKnownSeparatedGroup = secondPropGroup;
25
25
 
26
- const betweenGroupsInSpecified =
27
- firstPropSeparatedGroup !== secondPropSeparatedGroup && !secondPropIsUnspecified;
28
- const startOfUnspecifiedGroup = !firstPropIsUnspecified && secondPropIsUnspecified;
26
+ let startOfSpecifiedGroup = secondPropIsSpecified && firstPropGroup !== secondPropGroup;
27
+ let startOfUnspecifiedGroup = firstPropIsSpecified && !secondPropIsSpecified;
29
28
 
30
- // Line threshold logic
31
- const belowEmptyLineThreshold = propsCount < sharedInfo.emptyLineMinimumPropertyThreshold;
32
-
33
- if (betweenGroupsInSpecified || startOfUnspecifiedGroup) {
29
+ if (startOfSpecifiedGroup || startOfUnspecifiedGroup) {
34
30
  // Get an array of just the property groups, remove any solo properties
35
- const groups = _.reject(sharedInfo.expectation, _.isString);
31
+ let groups = _.reject(sharedInfo.primaryOption, _.isString);
32
+
33
+ let emptyLineBefore = _.get(groups[secondPropGroup - 2], 'emptyLineBefore');
36
34
 
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;
35
+ if (startOfUnspecifiedGroup) {
36
+ emptyLineBefore = sharedInfo.emptyLineBeforeUnspecified;
37
+ }
42
38
 
43
39
  // Threshold logic
44
- const emptyLineThresholdInsertLines =
45
- !belowEmptyLineThreshold && emptyLineBefore === 'threshold';
46
- const emptyLineThresholdRemoveLines =
47
- belowEmptyLineThreshold && emptyLineBefore === 'threshold';
40
+ let belowEmptyLineThreshold = propsCount < sharedInfo.emptyLineMinimumPropertyThreshold;
41
+ let emptyLineThresholdInsertLines = emptyLineBefore === 'threshold' && !belowEmptyLineThreshold;
42
+ let emptyLineThresholdRemoveLines = emptyLineBefore === 'threshold' && belowEmptyLineThreshold;
48
43
 
49
44
  if (
50
- !hasEmptyLineBefore(secondPropData.node) &&
51
- (emptyLineBefore === 'always' || emptyLineThresholdInsertLines)
45
+ (emptyLineBefore === 'always' || emptyLineThresholdInsertLines) &&
46
+ !hasEmptyLineBefore(secondPropData.node)
52
47
  ) {
53
48
  if (sharedInfo.isFixEnabled) {
54
49
  addEmptyLineBefore(secondPropData.node, sharedInfo.context.newline);
@@ -61,8 +56,8 @@ module.exports = function checkEmptyLineBefore(
61
56
  });
62
57
  }
63
58
  } else if (
64
- hasEmptyLineBefore(secondPropData.node) &&
65
- (emptyLineBefore === 'never' || emptyLineThresholdRemoveLines)
59
+ (emptyLineBefore === 'never' || emptyLineThresholdRemoveLines) &&
60
+ hasEmptyLineBefore(secondPropData.node)
66
61
  ) {
67
62
  if (sharedInfo.isFixEnabled) {
68
63
  removeEmptyLinesBefore(secondPropData.node, sharedInfo.context.newline);
@@ -79,13 +74,14 @@ module.exports = function checkEmptyLineBefore(
79
74
 
80
75
  // Check newlines between properties inside a group
81
76
  if (
82
- !firstPropIsUnspecified &&
83
- !secondPropIsUnspecified &&
77
+ firstPropIsSpecified &&
78
+ secondPropIsSpecified &&
84
79
  firstPropData.orderData.groupPosition === secondPropData.orderData.groupPosition
85
80
  ) {
86
- const noEmptyLineBefore = secondPropData.orderData.noEmptyLineBeforeInsideGroup;
87
-
88
- if (hasEmptyLineBefore(secondPropData.node) && noEmptyLineBefore) {
81
+ if (
82
+ secondPropData.orderData.noEmptyLineBeforeInsideGroup &&
83
+ hasEmptyLineBefore(secondPropData.node)
84
+ ) {
89
85
  if (sharedInfo.isFixEnabled) {
90
86
  removeEmptyLinesBefore(secondPropData.node, sharedInfo.context.newline);
91
87
  } else {
@@ -0,0 +1,30 @@
1
+ const stylelint = require('stylelint');
2
+ const _ = require('lodash');
3
+ const hasEmptyLineBefore = require('./hasEmptyLineBefore');
4
+ const removeEmptyLinesBefore = require('./removeEmptyLinesBefore');
5
+
6
+ module.exports = function checkEmptyLineBeforeFirstProp(propData, sharedInfo) {
7
+ let emptyLineBefore = false;
8
+
9
+ if (propData.orderData) {
10
+ // Get an array of just the property groups, remove any solo properties
11
+ let groups = _.reject(sharedInfo.primaryOption, _.isString);
12
+
13
+ emptyLineBefore = _.get(groups[propData.orderData.separatedGroup - 2], 'emptyLineBefore');
14
+ } else if (sharedInfo.emptyLineBeforeUnspecified) {
15
+ emptyLineBefore = true;
16
+ }
17
+
18
+ if (emptyLineBefore && hasEmptyLineBefore(propData.node)) {
19
+ if (sharedInfo.isFixEnabled) {
20
+ removeEmptyLinesBefore(propData.node, sharedInfo.context.newline);
21
+ } else {
22
+ stylelint.utils.report({
23
+ message: sharedInfo.messages.rejectedEmptyLineBefore(propData.name),
24
+ node: propData.node,
25
+ result: sharedInfo.result,
26
+ ruleName: sharedInfo.ruleName,
27
+ });
28
+ }
29
+ }
30
+ };
@@ -1,16 +1,16 @@
1
1
  const stylelint = require('stylelint');
2
2
  const postcss = require('postcss');
3
3
  const postcssSorting = require('postcss-sorting');
4
- const utils = require('../../utils');
4
+ const { isProperty } = require('../../utils');
5
5
  const checkEmptyLineBefore = require('./checkEmptyLineBefore');
6
+ const checkEmptyLineBeforeFirstProp = require('./checkEmptyLineBeforeFirstProp');
6
7
  const checkOrder = require('./checkOrder');
7
8
  const getNodeData = require('./getNodeData');
8
9
  const createFlatOrder = require('./createFlatOrder');
9
10
 
10
11
  module.exports = function checkNode(node, sharedInfo, originalNode) {
11
12
  // First, check order
12
- const allPropData = getAllPropData(node);
13
- const propsCount = node.nodes.filter(item => utils.isProperty(item)).length;
13
+ let allPropData = getAllPropData(node);
14
14
 
15
15
  if (!sharedInfo.isFixEnabled) {
16
16
  allPropData.forEach(checkEveryPropForOrder);
@@ -19,6 +19,7 @@ module.exports = function checkNode(node, sharedInfo, originalNode) {
19
19
  if (sharedInfo.isFixEnabled) {
20
20
  let shouldFixOrder = false;
21
21
 
22
+ // Check if there order violation to avoid running re-ordering unnecessery
22
23
  allPropData.forEach(function checkEveryPropForOrder2(propData, index) {
23
24
  // Skip first decl
24
25
  if (index === 0) {
@@ -30,9 +31,9 @@ module.exports = function checkNode(node, sharedInfo, originalNode) {
30
31
  return;
31
32
  }
32
33
 
33
- const previousPropData = allPropData[index - 1];
34
+ let previousPropData = allPropData[index - 1];
34
35
 
35
- const checkedOrder = checkOrder({
36
+ let checkedOrder = checkOrder({
36
37
  firstPropData: previousPropData,
37
38
  secondPropData: propData,
38
39
  unspecified: sharedInfo.unspecified,
@@ -45,19 +46,19 @@ module.exports = function checkNode(node, sharedInfo, originalNode) {
45
46
  });
46
47
 
47
48
  if (shouldFixOrder) {
48
- const sortingOptions = {
49
- 'properties-order': createFlatOrder(sharedInfo.expectation),
49
+ let sortingOptions = {
50
+ 'properties-order': createFlatOrder(sharedInfo.primaryOption),
50
51
  'unspecified-properties-position':
51
52
  sharedInfo.unspecified === 'ignore' ? 'bottom' : sharedInfo.unspecified,
52
53
  };
53
54
 
54
55
  // creating PostCSS Root node with current node as a child,
55
56
  // so PostCSS Sorting can process it
56
- const tempRoot = postcss.root({ nodes: [originalNode] });
57
+ let tempRoot = postcss.root({ nodes: [originalNode] });
57
58
 
58
59
  postcssSorting(sortingOptions)(tempRoot);
59
60
 
60
- const allPropData2 = getAllPropData(node);
61
+ let allPropData2 = getAllPropData(node);
61
62
 
62
63
  allPropData2.forEach(checkEveryPropForOrder);
63
64
  }
@@ -66,7 +67,8 @@ module.exports = function checkNode(node, sharedInfo, originalNode) {
66
67
  // Second, check emptyLineBefore
67
68
  sharedInfo.lastKnownSeparatedGroup = 1;
68
69
 
69
- const allNodesData = node.nodes.map(function collectDataForEveryNode(child) {
70
+ let propsCount = node.nodes.filter(item => isProperty(item)).length;
71
+ let allNodesData = node.nodes.map(function collectDataForEveryNode(child) {
70
72
  return getNodeData(child, sharedInfo.expectedOrder);
71
73
  });
72
74
 
@@ -77,7 +79,7 @@ module.exports = function checkNode(node, sharedInfo, originalNode) {
77
79
  if (
78
80
  previousNodeData &&
79
81
  previousNodeData.node.type === 'comment' &&
80
- previousNodeData.node.raw('before').indexOf('\n') === -1
82
+ !previousNodeData.node.raw('before').includes('\n')
81
83
  ) {
82
84
  previousNodeData = allNodesData[index - 2];
83
85
  }
@@ -87,14 +89,19 @@ module.exports = function checkNode(node, sharedInfo, originalNode) {
87
89
  return;
88
90
  }
89
91
 
90
- // previous node should be a standard declaration
91
- if (!utils.isProperty(previousNodeData.node)) {
92
+ // Nodes should be standard declarations
93
+ if (!isProperty(previousNodeData.node) || !isProperty(nodeData.node)) {
92
94
  return;
93
95
  }
94
96
 
95
97
  checkEmptyLineBefore(previousNodeData, nodeData, sharedInfo, propsCount);
96
98
  });
97
99
 
100
+ // Check if empty line before first prop should be removed
101
+ if (isProperty(allNodesData[0].node)) {
102
+ checkEmptyLineBeforeFirstProp(allNodesData[0], sharedInfo);
103
+ }
104
+
98
105
  function checkEveryPropForOrder(propData, index, listOfProps) {
99
106
  // Skip first decl
100
107
  if (index === 0) {
@@ -128,7 +135,7 @@ module.exports = function checkNode(node, sharedInfo, originalNode) {
128
135
 
129
136
  function getAllPropData(inputNode) {
130
137
  return inputNode.nodes
131
- .filter(item => utils.isProperty(item))
138
+ .filter(item => isProperty(item))
132
139
  .map(item => getNodeData(item, sharedInfo.expectedOrder));
133
140
  }
134
141
  };
@@ -1,7 +1,7 @@
1
1
  const _ = require('lodash');
2
2
 
3
- module.exports = function createExpectedOrder(input) {
4
- const order = {};
3
+ module.exports = function createOrderInfo(input) {
4
+ let order = {};
5
5
  let expectedPosition = 0;
6
6
  let separatedGroup = 1;
7
7
  let groupPosition = -1;
@@ -38,7 +38,7 @@ module.exports = function createExpectedOrder(input) {
38
38
  separatedGroup += 1;
39
39
  }
40
40
 
41
- if (item.order && item.order === 'flexible') {
41
+ if (item.order === 'flexible') {
42
42
  expectedPosition += 1;
43
43
  groupPosition += 1;
44
44
 
@@ -1,18 +1,18 @@
1
1
  const postcss = require('postcss');
2
- const utils = require('../../utils');
2
+ const { isProperty } = require('../../utils');
3
3
 
4
4
  module.exports = function getNodeData(node, expectedOrder) {
5
- const nodeData = {
5
+ let nodeData = {
6
6
  node,
7
7
  };
8
8
 
9
- if (utils.isProperty(node)) {
10
- const { prop } = node;
9
+ if (isProperty(node)) {
10
+ let { prop } = node;
11
11
  let unprefixedPropName = postcss.vendor.unprefixed(prop);
12
12
 
13
13
  // Hack to allow -moz-osx-font-smoothing to be understood
14
14
  // just like -webkit-font-smoothing
15
- if (unprefixedPropName.indexOf('osx-') === 0) {
15
+ if (unprefixedPropName.startsWith('osx-')) {
16
16
  unprefixedPropName = unprefixedPropName.slice(4);
17
17
  }
18
18
 
@@ -1,11 +1,11 @@
1
1
  const stylelint = require('stylelint');
2
2
  const _ = require('lodash');
3
- const utils = require('../../utils');
3
+ const { namespace, getContainingNode, isRuleWithNodes } = require('../../utils');
4
4
  const checkNode = require('./checkNode');
5
- const createExpectedOrder = require('./createExpectedOrder');
5
+ const createOrderInfo = require('./createOrderInfo');
6
6
  const validatePrimaryOption = require('./validatePrimaryOption');
7
7
 
8
- const ruleName = utils.namespace('properties-order');
8
+ const ruleName = namespace('properties-order');
9
9
 
10
10
  const messages = stylelint.utils.ruleMessages(ruleName, {
11
11
  expected: (first, second, groupName) =>
@@ -14,13 +14,13 @@ const messages = stylelint.utils.ruleMessages(ruleName, {
14
14
  rejectedEmptyLineBefore: property => `Unexpected empty line before property "${property}"`,
15
15
  });
16
16
 
17
- const rule = function(expectation, options, context = {}) {
17
+ function rule(primaryOption, options = {}, context = {}) {
18
18
  return function(root, result) {
19
- const validOptions = stylelint.utils.validateOptions(
19
+ let validOptions = stylelint.utils.validateOptions(
20
20
  result,
21
21
  ruleName,
22
22
  {
23
- actual: expectation,
23
+ actual: primaryOption,
24
24
  possible: validatePrimaryOption,
25
25
  },
26
26
  {
@@ -39,54 +39,43 @@ const rule = function(expectation, options, context = {}) {
39
39
  return;
40
40
  }
41
41
 
42
- // By default, ignore unspecified properties
43
- const unspecified = _.get(options, 'unspecified', 'ignore');
44
- const emptyLineBeforeUnspecified = _.get(options, 'emptyLineBeforeUnspecified', '');
45
- const emptyLineMinimumPropertyThreshold = _.get(
46
- options,
47
- 'emptyLineMinimumPropertyThreshold',
48
- 0
49
- );
50
- const disableFix = _.get(options, 'disableFix', false);
51
- const isFixEnabled = context.fix && !disableFix;
52
-
53
- const expectedOrder = createExpectedOrder(expectation);
42
+ let disableFix = options.disableFix || false;
54
43
 
55
- const sharedInfo = {
56
- expectedOrder,
57
- expectation,
58
- unspecified,
59
- emptyLineBeforeUnspecified,
60
- emptyLineMinimumPropertyThreshold,
44
+ let sharedInfo = {
45
+ context,
46
+ emptyLineBeforeUnspecified: options.emptyLineBeforeUnspecified,
47
+ emptyLineMinimumPropertyThreshold: options.emptyLineMinimumPropertyThreshold || 0,
48
+ expectedOrder: createOrderInfo(primaryOption),
49
+ isFixEnabled: context.fix && !disableFix,
61
50
  messages,
62
- ruleName,
51
+ primaryOption,
63
52
  result,
64
- context,
65
- isFixEnabled,
53
+ ruleName,
54
+ unspecified: options.unspecified || 'ignore',
66
55
  };
67
56
 
68
- const processedParents = [];
57
+ let processedParents = [];
69
58
 
70
59
  // Check all rules and at-rules recursively
71
60
  root.walk(function processRulesAndAtrules(input) {
72
- const node = utils.getContainingNode(input);
61
+ let node = getContainingNode(input);
73
62
 
74
- // Avoid warnings duplication, caused by interfering in `root.walk()` algorigthm with `utils.getContainingNode()`
63
+ // Avoid warnings duplication, caused by interfering in `root.walk()` algorigthm with `getContainingNode()`
75
64
  if (processedParents.includes(node)) {
76
65
  return;
77
66
  }
78
67
 
79
68
  processedParents.push(node);
80
69
 
81
- if (utils.isRuleWithNodes(node)) {
70
+ if (isRuleWithNodes(node)) {
82
71
  checkNode(node, sharedInfo, input);
83
72
  }
84
73
  });
85
74
  };
86
- };
75
+ }
87
76
 
88
77
  rule.primaryOptionArray = true;
89
-
90
78
  rule.ruleName = ruleName;
91
79
  rule.messages = messages;
80
+
92
81
  module.exports = rule;
@@ -42,7 +42,7 @@ module.exports = function validatePrimaryOption(actualOptions) {
42
42
  return true;
43
43
  }
44
44
 
45
- return _.includes(['always', 'never', 'threshold'], item.emptyLineBefore);
45
+ return ['always', 'never', 'threshold'].includes(item.emptyLineBefore);
46
46
  })
47
47
  ) {
48
48
  return false;
@@ -6,5 +6,5 @@
6
6
  */
7
7
 
8
8
  module.exports = function isCustomProperty(property) {
9
- return property.slice(0, 2) === '--';
9
+ return property.startsWith('--');
10
10
  };
@@ -6,5 +6,5 @@
6
6
  */
7
7
 
8
8
  module.exports = function isDollarVariable(property) {
9
- return property[0] === '$';
9
+ return property.startsWith('$');
10
10
  };
@@ -7,12 +7,12 @@
7
7
 
8
8
  module.exports = function isStandardSyntaxProperty(property) {
9
9
  // SCSS var (e.g. $var: x), list (e.g. $list: (x)) or map (e.g. $map: (key:value))
10
- if (property[0] === '$') {
10
+ if (property.startsWith('$')) {
11
11
  return false;
12
12
  }
13
13
 
14
14
  // Less var (e.g. @var: x)
15
- if (property[0] === '@') {
15
+ if (property.startsWith('@')) {
16
16
  return false;
17
17
  }
18
18