@primer/stylelint-config 12.9.2 → 13.0.0-rc.095cb19

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.
@@ -1,39 +1,12 @@
1
- const stylelint = require('stylelint')
2
- const declarationValueIndex = require('stylelint/lib/utils/declarationValueIndex')
3
- const valueParser = require('postcss-value-parser')
4
-
5
- // TODO: Pull this in from primer/primitives
6
- const spacerValues = {
7
- '$spacer-1': '4px',
8
- '$spacer-2': '8px',
9
- '$spacer-3': '16px',
10
- '$spacer-4': '24px',
11
- '$spacer-5': '32px',
12
- '$spacer-6': '40px',
13
- '$spacer-7': '48px',
14
- '$spacer-8': '64px',
15
- '$spacer-9': '80px',
16
- '$spacer-10': '96px',
17
- '$spacer-11': '112px',
18
- '$spacer-12': '128px',
19
- '$em-spacer-1': '0.0625em',
20
- '$em-spacer-2': '0.125em',
21
- '$em-spacer-3': '0.25em',
22
- '$em-spacer-4': '0.375em',
23
- '$em-spacer-5': '0.5em',
24
- '$em-spacer-6': '0.75em',
25
- }
1
+ import stylelint from 'stylelint'
2
+ import declarationValueIndex from 'stylelint/lib/utils/declarationValueIndex.cjs'
3
+ import valueParser from 'postcss-value-parser'
4
+ import {primitivesVariables} from './lib/primitives.js'
26
5
 
27
- const ruleName = 'primer/spacing'
28
- const messages = stylelint.utils.ruleMessages(ruleName, {
29
- rejected: (value, replacement) => {
30
- if (replacement === null) {
31
- return `Please use a primer spacer variable instead of '${value}'. Consult the primer docs for a suitable replacement. https://primer.style/css/support/spacing`
32
- }
33
-
34
- return `Please replace ${value} with spacing variable '${replacement}'.`
35
- },
36
- })
6
+ const {
7
+ createPlugin,
8
+ utils: {report, ruleMessages, validateOptions},
9
+ } = stylelint
37
10
 
38
11
  const walkGroups = (root, validate) => {
39
12
  for (const node of root.nodes) {
@@ -46,24 +19,47 @@ const walkGroups = (root, validate) => {
46
19
  return root
47
20
  }
48
21
 
49
- // eslint-disable-next-line no-unused-vars
50
- module.exports = stylelint.createPlugin(ruleName, (enabled, options = {}, context) => {
51
- if (!enabled) {
52
- return noop
53
- }
22
+ export const ruleName = 'primer/spacing'
23
+ export const messages = ruleMessages(ruleName, {
24
+ rejected: (value, replacement) => {
25
+ if (!replacement) {
26
+ return `Please use a primer size variable instead of '${value}'. Consult the primer docs for a suitable replacement. https://primer.style/foundations/primitives/size`
27
+ }
54
28
 
55
- const lintResult = (root, result) => {
56
- root.walk(decl => {
57
- if (decl.type !== 'decl' || !decl.prop.match(/^(padding|margin)/)) {
58
- return noop
59
- }
29
+ return `Please replace '${value}' with size variable '${replacement['name']}'. https://primer.style/foundations/primitives/size`
30
+ },
31
+ })
60
32
 
61
- const problems = []
33
+ const meta = {
34
+ fixable: true,
35
+ }
62
36
 
63
- const parsedValue = walkGroups(valueParser(decl.value), node => {
64
- // Remove leading negative sign, if any.
65
- const cleanValue = node.value.replace(/^-/g, '')
37
+ /** @type {import('stylelint').Rule} */
38
+ const ruleFunction = (primary, secondaryOptions, context) => {
39
+ return async (root, result) => {
40
+ // Props that we want to check
41
+ const propList = ['padding', 'margin', 'top', 'right', 'bottom', 'left']
42
+ // Values that we want to ignore
43
+ const valueList = ['${']
66
44
 
45
+ const sizes = await primitivesVariables('size')
46
+
47
+ const validOptions = validateOptions(result, ruleName, {
48
+ actual: primary,
49
+ possible: [true],
50
+ })
51
+
52
+ if (!validOptions) return
53
+
54
+ root.walkDecls(declNode => {
55
+ const {prop, value} = declNode
56
+
57
+ if (!propList.some(spacingProp => prop.startsWith(spacingProp))) return
58
+ if (valueList.some(valueToIgnore => value.includes(valueToIgnore))) return
59
+
60
+ const problems = []
61
+
62
+ const parsedValue = walkGroups(valueParser(value), node => {
67
63
  // Only check word types. https://github.com/TrySound/postcss-value-parser#word
68
64
  if (node.type !== 'word') {
69
65
  return
@@ -74,30 +70,36 @@ module.exports = stylelint.createPlugin(ruleName, (enabled, options = {}, contex
74
70
  return
75
71
  }
76
72
 
77
- const valueUnit = valueParser.unit(cleanValue)
73
+ const valueUnit = valueParser.unit(node.value)
74
+
75
+ if (valueUnit && (valueUnit.unit === '' || !/^-?[0-9.]+$/.test(valueUnit.number))) {
76
+ return
77
+ }
78
78
 
79
- if (valueUnit && (valueUnit.unit === '' || !/^[0-9.]+$/.test(valueUnit.number))) {
79
+ // Skip if the value unit isn't a supported unit.
80
+ if (valueUnit && !['px', 'rem', 'em'].includes(valueUnit.unit)) {
80
81
  return
81
82
  }
82
83
 
83
- // If the a variable is found in the value, skip it.
84
+ // If the variable is found in the value, skip it.
84
85
  if (
85
- Object.keys(spacerValues).some(variable =>
86
- new RegExp(`${variable.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\b`).test(cleanValue),
86
+ sizes.some(variable =>
87
+ new RegExp(`${variable['name'].replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\b`).test(node.value),
87
88
  )
88
89
  ) {
89
90
  return
90
91
  }
91
92
 
92
- const replacement = Object.keys(spacerValues).find(spacer => spacerValues[spacer] === cleanValue) || null
93
- const valueMatch = replacement ? spacerValues[replacement] : node.value
93
+ const replacement = sizes.find(variable => variable.values.includes(node.value.replace('-', '')))
94
+ const fixable = replacement && valueUnit && !valueUnit.number.includes('-')
94
95
 
95
- if (replacement && context.fix) {
96
- node.value = node.value.replace(valueMatch, replacement)
96
+ if (fixable && context.fix) {
97
+ node.value = node.value.replace(node.value, `var(${replacement['name']})`)
97
98
  } else {
98
99
  problems.push({
99
- index: declarationValueIndex(decl) + node.sourceIndex,
100
- message: messages.rejected(valueMatch, replacement),
100
+ index: declarationValueIndex(declNode) + node.sourceIndex,
101
+ endIndex: declarationValueIndex(declNode) + node.sourceIndex + node.value.length,
102
+ message: messages.rejected(node.value, replacement),
101
103
  })
102
104
  }
103
105
 
@@ -105,15 +107,16 @@ module.exports = stylelint.createPlugin(ruleName, (enabled, options = {}, contex
105
107
  })
106
108
 
107
109
  if (context.fix) {
108
- decl.value = parsedValue.toString()
110
+ declNode.value = parsedValue.toString()
109
111
  }
110
112
 
111
113
  if (problems.length) {
112
114
  for (const err of problems) {
113
- stylelint.utils.report({
115
+ report({
114
116
  index: err.index,
117
+ endIndex: err.endIndex,
115
118
  message: err.message,
116
- node: decl,
119
+ node: declNode,
117
120
  result,
118
121
  ruleName,
119
122
  })
@@ -121,11 +124,10 @@ module.exports = stylelint.createPlugin(ruleName, (enabled, options = {}, contex
121
124
  }
122
125
  })
123
126
  }
127
+ }
124
128
 
125
- return lintResult
126
- })
127
-
128
- function noop() {}
129
+ ruleFunction.ruleName = ruleName
130
+ ruleFunction.messages = messages
131
+ ruleFunction.meta = meta
129
132
 
130
- module.exports.ruleName = ruleName
131
- module.exports.messages = messages
133
+ export default createPlugin(ruleName, ruleFunction)
@@ -1,6 +1,6 @@
1
- const {createVariableRule} = require('./lib/variable-rules')
1
+ import {createVariableRule} from './lib/variable-rules.js'
2
2
 
3
- module.exports = createVariableRule(
3
+ export default createVariableRule(
4
4
  'primer/typography',
5
5
  {
6
6
  'font-size': {
package/property-order.js CHANGED
@@ -1,4 +1,4 @@
1
- module.exports = [
1
+ export default [
2
2
  'all',
3
3
  'position',
4
4
  'top',
package/browsers.js DELETED
@@ -1,7 +0,0 @@
1
- module.exports = [
2
- '> 5%',
3
- 'last 2 firefox versions',
4
- 'last 2 chrome versions',
5
- 'last 2 safari versions',
6
- 'last 2 edge versions',
7
- ]
package/index.js DELETED
@@ -1,106 +0,0 @@
1
- const browsers = require('./browsers')
2
- const propertyOrder = require('./property-order')
3
-
4
- module.exports = {
5
- extends: ['stylelint-config-standard'],
6
- customSyntax: require('postcss-scss'),
7
- ignoreFiles: ['**/*.js', '**/*.cjs'],
8
- plugins: [
9
- 'stylelint-no-unsupported-browser-features',
10
- 'stylelint-order',
11
- 'stylelint-scss',
12
- './plugins/borders',
13
- './plugins/box-shadow',
14
- './plugins/colors',
15
- './plugins/no-deprecated-colors',
16
- './plugins/no-experimental-vars',
17
- './plugins/no-override',
18
- './plugins/no-scale-colors',
19
- './plugins/no-undefined-vars',
20
- './plugins/no-unused-vars',
21
- './plugins/responsive-widths',
22
- './plugins/spacing',
23
- './plugins/typography',
24
- './plugins/utilities',
25
- './plugins/new-color-vars-have-fallback',
26
- './plugins/no-display-colors',
27
- ],
28
- rules: {
29
- 'alpha-value-notation': 'number',
30
- 'at-rule-disallowed-list': ['extend'],
31
- 'at-rule-no-unknown': null,
32
- 'block-no-empty': true,
33
- 'color-function-notation': null,
34
- 'color-named': 'never',
35
- 'color-no-invalid-hex': true,
36
- 'comment-no-empty': null,
37
- 'custom-property-pattern': null,
38
- 'declaration-block-no-duplicate-properties': [true, {ignore: ['consecutive-duplicates']}],
39
- 'declaration-block-no-redundant-longhand-properties': null,
40
- 'declaration-block-no-shorthand-property-overrides': true,
41
- 'declaration-property-value-disallowed-list': {
42
- '/^transition/': ['/all/'],
43
- '/^background/': ['http:', 'https:'],
44
- '/^border/': ['none'],
45
- '/.+/': ['initial'],
46
- },
47
- 'function-calc-no-unspaced-operator': true,
48
- 'function-linear-gradient-no-nonstandard-direction': true,
49
- 'function-no-unknown': null,
50
- 'keyframes-name-pattern': null,
51
- 'max-nesting-depth': 3,
52
- 'media-feature-name-no-unknown': null,
53
- 'media-feature-name-no-vendor-prefix': null,
54
- 'no-descending-specificity': null,
55
- 'no-duplicate-selectors': true,
56
- 'no-invalid-position-at-import-rule': [true, {ignoreAtRules: ['use']}],
57
- 'number-max-precision': null,
58
- 'order/properties-order': propertyOrder,
59
- 'plugin/no-unsupported-browser-features': [true, {severity: 'warning', browsers}],
60
- 'primer/borders': true,
61
- 'primer/box-shadow': true,
62
- 'primer/colors': true,
63
- 'primer/no-deprecated-colors': true,
64
- 'primer/no-experimental-vars': [
65
- true,
66
- {
67
- designTokens: '@primer/primitives/tokens-v2-private/docs/docValues.json',
68
- },
69
- ],
70
- 'primer/no-override': true,
71
- 'primer/no-undefined-vars': [
72
- true,
73
- {severity: 'warning', files: 'node_modules/@primer/primitives/dist/scss/colors*/*.scss'},
74
- ],
75
- 'primer/no-unused-vars': [true, {severity: 'warning'}],
76
- 'primer/responsive-widths': true,
77
- 'primer/spacing': true,
78
- 'primer/typography': true,
79
- 'primer/utilities': null,
80
- 'primer/new-color-vars-have-fallback': [true, {severity: 'error'}],
81
- 'scss/at-extend-no-missing-placeholder': true,
82
- 'scss/at-rule-no-unknown': true,
83
- 'scss/declaration-nested-properties-no-divided-groups': true,
84
- 'scss/dollar-variable-no-missing-interpolation': true,
85
- 'scss/function-quote-no-quoted-strings-inside': true,
86
- 'scss/function-unquote-no-unquoted-strings-inside': true,
87
- 'scss/no-duplicate-mixins': true,
88
- 'scss/selector-no-redundant-nesting-selector': true,
89
- 'selector-class-pattern': null,
90
- 'selector-max-compound-selectors': 3,
91
- 'selector-max-id': 0,
92
- 'selector-max-specificity': '0,4,0',
93
- 'selector-max-type': 0,
94
- 'selector-no-qualifying-type': true,
95
- 'selector-pseudo-element-no-unknown': true,
96
- 'string-no-newline': true,
97
- 'unit-no-unknown': true,
98
- 'value-keyword-case': null,
99
- 'selector-not-notation': null,
100
- 'import-notation': ['string'],
101
- 'annotation-no-unknown': null,
102
- 'keyframe-selector-notation': ['percentage-unless-within-keyword-only-block'],
103
- 'media-query-no-invalid': null,
104
- 'media-feature-range-notation': ['prefix'],
105
- },
106
- }