1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114 | 3x
3x
3x
3x
3x
3x
3x
3x
3x
3x
44x
44x
44x
44x
44x
1784x
1784x
44x
69x
44x
44x
69x
30x
39x
1784x
1784x
10x
10x
1774x
132x
132x
132x
132x
132x
132x
725x
44x
44x
44x
156x
156x
156x
156x
44x
| const babylon = require('babylon')
const traverse = require('babel-traverse').default
const isStyled = require('./utils/styled').isStyled
const isHelper = require('./utils/styled').isHelper
const isStyledImport = require('./utils/styled').isStyledImport
const getCSS = require('./utils/general').getCSS
const getKeyframes = require('./utils/general').getKeyframes
// TODO Make it work for the UMD build, i.e. global vars
// TODO ENFORCE THESE RULES
// value-no-vendor-prefix – don't allow vendor prefixes
// property-no-vendor-prefix – don't allow vendor prefixes
const ignoredRules = [
// Don't throw if there's no styled-components in a file
'no-empty-source',
// We don't care about end-of-source newlines, users cannot control them
'no-missing-end-of-source-newline',
]
const sourceMapsCorrections = {}
module.exports = (/* options */) => ({
// Get string for stylelint to lint
code(input, filepath) {
sourceMapsCorrections[filepath] = {}
const ast = babylon.parse(input, {
sourceType: 'module',
plugins: [
'jsx',
'flow',
'objectRestSpread',
'decorators',
'classProperties',
'exportExtensions',
'asyncGenerators',
'functionBind',
'functionSent',
'dynamicImport',
],
})
let extractedCSS = ''
const importedNames = {
default: false,
css: false,
keyframes: false,
injectGlobal: false,
}
traverse(ast, {
enter(path) {
const node = path.node
if (isStyledImport(node)) {
const imports = node.specifiers.filter((specifier) => (
specifier.type === 'ImportDefaultSpecifier'
|| specifier.type === 'ImportSpecifier'
))
Iif (imports.length <= 0) return
imports.forEach((singleImport) => {
if (singleImport.imported) {
// Is helper method
importedNames[singleImport.imported.name] = singleImport.local.name
} else {
// Is default import
importedNames.default = singleImport.local.name
}
})
}
const helper = isHelper(node, importedNames)
if (helper === 'keyframes') {
extractedCSS += getKeyframes(node)
return
}
if (isStyled(node, importedNames.default) || helper === 'injectGlobal' || helper === 'css') {
const css = getCSS(node)
extractedCSS += css
// Save which line in the extracted CSS is which line in the source
const fullCSSLength = extractedCSS.split(/\n/).length
const currentCSSLength = css.split(/\n/).length
const currentCSSStart = (fullCSSLength - currentCSSLength) + 1
for (let i = 0; i < currentCSSLength + 1; i++) {
sourceMapsCorrections[filepath][currentCSSStart + i] = node.loc.start.line + i
}
}
},
})
return extractedCSS
},
// Fix sourcemaps
result(stylelintResult, filepath) {
const lineCorrection = sourceMapsCorrections[filepath]
const newWarnings = stylelintResult.warnings.reduce((prevWarnings, warning) => {
Iif (ignoredRules.includes(warning.rule)) return prevWarnings
const correctedWarning = Object.assign(warning, {
// Replace "brace" with "backtick" in warnings, e.g.
// "Unexpected empty line before closing backtick" (instead of "brace")
text: warning.text.replace(/brace/, 'backtick'),
line: lineCorrection[warning.line],
})
prevWarnings.push(correctedWarning)
return prevWarnings
}, [])
return Object.assign(stylelintResult, { warnings: newWarnings })
},
})
|