1 | const stylelint = require('stylelint')
|
2 | const createPostcssModulesValuesResolver = require('postcss-modules-values')
|
3 | const createPostcssCustomPropertiesResolver = require('postcss-custom-properties')
|
4 | const createPostcssCustomMediaResolver = require('postcss-custom-media')
|
5 | const createPostcssCustomSelectorsResolver = require('postcss-custom-selectors')
|
6 | const createPostcssCalcResolver = require('postcss-calc')
|
7 | const { findCssGlobalFiles } = require('../lib/findCssGlobalFiles')
|
8 | const { getNormalizedRoots } = require('./machinery/ast')
|
9 |
|
10 | const postcssModulesValuesResolver = createPostcssModulesValuesResolver()
|
11 | const postcssCalcResolver = createPostcssCalcResolver()
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 |
|
22 |
|
23 |
|
24 | const rules = toStyleLintPlugins(
|
25 | require('./rules/color-schemes'),
|
26 | require('./rules/css-global'),
|
27 | require('./rules/layout-related-properties'),
|
28 | require('./rules/naming-policy'),
|
29 | require('./rules/selector-policy'),
|
30 | require('./rules/parent-child-policy'),
|
31 | require('./rules/root-policy'),
|
32 | require('./rules/at-rule-restrictions'),
|
33 | require('./rules/index'),
|
34 | require('./rules/reset'),
|
35 | )
|
36 | module.exports = rules
|
37 |
|
38 | function toStyleLintPlugins(...rules) {
|
39 | const ruleInteraction = determineRuleInteraction(rules)
|
40 | const ruleConfiguration = convertToConfiguration(ruleInteraction)
|
41 |
|
42 | return rules.map(({ ruleName, cssRequirements, create }) =>
|
43 | createPlugin({
|
44 | ...(cssRequirements || {}),
|
45 | ruleName: `kaliber/${ruleName}`,
|
46 | plugin: create(ruleConfiguration[ruleName] || {}),
|
47 | })
|
48 | )
|
49 | }
|
50 |
|
51 | function determineRuleInteraction(rules) {
|
52 | |
53 |
|
54 |
|
55 |
|
56 |
|
57 |
|
58 |
|
59 | return rules.reduce(
|
60 | (result, rule) => ({
|
61 | ...result,
|
62 | ...Object.entries(rule.ruleInteraction || {}).reduce(
|
63 | (result, [rule, config]) => ({
|
64 | ...result,
|
65 | [rule]: [...(result[rule] || []), config]
|
66 | }),
|
67 | result
|
68 | )
|
69 | }),
|
70 | {}
|
71 | )
|
72 | }
|
73 |
|
74 | function convertToConfiguration(ruleInteraction) {
|
75 | |
76 |
|
77 |
|
78 |
|
79 |
|
80 |
|
81 |
|
82 | return Object.entries(ruleInteraction).reduce(
|
83 | (result, [rule, configs]) => ({ ...result, [rule]: merge(configs) }),
|
84 | {}
|
85 | )
|
86 |
|
87 | function merge(configs) {
|
88 | return configs.reduce(
|
89 | (result, config) => Object.entries(config).reduce(
|
90 | (result, [key, value]) => {
|
91 | if (typeof value !== 'function') throw new Error(`don't know how to handle config value`)
|
92 | const existing = result[key]
|
93 | return { ...result, [key]: existing ? x => existing(x) || value(x) : value }
|
94 | },
|
95 | result
|
96 | ),
|
97 | {}
|
98 | )
|
99 | }
|
100 | }
|
101 |
|
102 | function createPlugin({
|
103 | ruleName, plugin,
|
104 | normalizedCss = false,
|
105 | resolvedCustomProperties = false,
|
106 | resolvedCustomMedia = false,
|
107 | resolvedCustomSelectors = false,
|
108 | resolvedModuleValues = false,
|
109 | resolvedCalc = false,
|
110 | }) {
|
111 | const stylelintPlugin = stylelint.createPlugin(ruleName, pluginWrapper)
|
112 |
|
113 | return {
|
114 | ...stylelintPlugin,
|
115 | }
|
116 |
|
117 | function pluginWrapper(primaryOption, secondaryOptionObject, context) {
|
118 | return async (originalRoot, result) => {
|
119 | const check = { actual: primaryOption, possible: [true] }
|
120 | if (!stylelint.utils.validateOptions(result, ruleName, check)) return
|
121 |
|
122 | const reported = {}
|
123 | const importFrom = findCssGlobalFiles(originalRoot.source.input.file)
|
124 |
|
125 | const modifiedRoot = originalRoot.clone()
|
126 | if (resolvedModuleValues) {
|
127 | await postcssModulesValuesResolver(modifiedRoot, result)
|
128 | }
|
129 | if (resolvedCustomProperties) {
|
130 | const postcssCustomPropertiesResolver = createPostcssCustomPropertiesResolver({ preserve: false, importFrom })
|
131 | await postcssCustomPropertiesResolver(modifiedRoot, result)
|
132 | }
|
133 | if (resolvedCustomMedia) {
|
134 | const postcssCustomMediaResolver = createPostcssCustomMediaResolver({ preserve: false, importFrom })
|
135 | await postcssCustomMediaResolver(modifiedRoot, result)
|
136 | }
|
137 | if (resolvedCustomSelectors) {
|
138 | const postcssCustomSelectorsResolver = createPostcssCustomSelectorsResolver({ preserve: false, importFrom })
|
139 | await postcssCustomSelectorsResolver(modifiedRoot, result)
|
140 | }
|
141 | if (resolvedCalc) {
|
142 | await postcssCalcResolver(modifiedRoot, result)
|
143 | }
|
144 | callPlugin(modifiedRoot)
|
145 |
|
146 | |
147 |
|
148 |
|
149 |
|
150 |
|
151 |
|
152 | if (normalizedCss)
|
153 | Object.entries(getNormalizedRoots(modifiedRoot)).forEach(([mediaQuery, modifiedRoot]) => {
|
154 | callPlugin(modifiedRoot)
|
155 | })
|
156 |
|
157 | function callPlugin(modifiedRoot) {
|
158 | plugin({ modifiedRoot, originalRoot, report, context })
|
159 | }
|
160 |
|
161 | function report(node, message, index) {
|
162 | const id = getId(node, message, index)
|
163 | if (reported[id]) return
|
164 | else reported[id] = true
|
165 | if (!node.source) stylelint.utils.report({
|
166 | message: `A generated node (${getNodeId(node)}) caused a problem\n ${node.toString().split('\n').join('\n ')}\n${message}`,
|
167 | node: node.parent, result, ruleName
|
168 | })
|
169 | else stylelint.utils.report({ message, index, node, result, ruleName })
|
170 | }
|
171 | }
|
172 | }
|
173 | }
|
174 |
|
175 | function getId(node, message, index) {
|
176 | return `${getNodeId(node)}-${message}${index}`
|
177 | }
|
178 |
|
179 | function getNodeId({ type, prop, selector, name, params, parent }) {
|
180 | const nodeId =
|
181 | type === 'decl' ? `decl-${prop}` :
|
182 | type === 'rule' ? `rule-${selector}` :
|
183 | type === 'atrule' ? `atrule-${name}-${params}` :
|
184 | type
|
185 |
|
186 | const parentId = parent
|
187 | ? `${getNodeId(parent)}-`
|
188 | : ''
|
189 |
|
190 | return `${parentId}${nodeId}`
|
191 | }
|