stylelint-processor-styled-components
Version:
A stylelint processor for styled-components
125 lines (113 loc) • 4.08 kB
JavaScript
const path = require('path')
const micromatch = require('micromatch')
const parse = require('./parsers/index')
const { isCausedBySubstitution, getCorrectColumn } = require('./utils/result')
let inputId = 1
// Make sure that state for particular path will be cleaned before each run
// module may be kept in memory when used with vscode-stylelint
const taggedTemplateLocsMap = {}
const interpolationLinesMap = {}
const sourceMapsCorrections = {}
const errorWasThrown = {}
const DEFAULT_OPTIONS = {
moduleName: 'styled-components',
importName: 'default',
strict: false,
ignoreFiles: []
}
const realProcessor = options => ({
// Get string for stylelint to lint
code(input, filepath) {
let absolutePath
if (filepath) {
absolutePath = path.resolve(process.cwd(), filepath)
} else {
absolutePath = `<input css ${inputId}>`
inputId += 1
}
const fileIsIgnored = micromatch(filepath, options.ignoreFiles).length
if (fileIsIgnored) return input
try {
const { extractedCSS, interpolationLines, taggedTemplateLocs, sourceMap } = parse(
input,
absolutePath,
options
)
// Save `loc` of template literals
taggedTemplateLocsMap[absolutePath] = taggedTemplateLocs
// Save dummy interpolation lines
interpolationLinesMap[absolutePath] = interpolationLines
// Save source location
sourceMapsCorrections[absolutePath] = sourceMap
// Clean saved errors
delete errorWasThrown[absolutePath]
return extractedCSS
} catch (e) {
// Always save the error
errorWasThrown[absolutePath] = e
// Incorrect interpolations will throw CssSyntaxError and they'll be handled by stylelint
// so we can throw it out but not for others
if (e.name === 'CssSyntaxError') {
throw e
}
return ''
}
},
// Fix sourcemaps
result(stylelintResult, filepath) {
const err = errorWasThrown[filepath]
const fileIsIgnored = micromatch(filepath, options.ignoreFiles).length
if (fileIsIgnored) return stylelintResult
if (err) {
if (err.name === 'CssSyntaxError') {
// We threw an error ourselves, in this case we have already put correct
// line/column numbers so no source maps are needed
// (and would actually break the line numbers)
return stylelintResult
} else {
// For other errors, wrap them into the result
return {
...stylelintResult,
errored: true,
parseErrors: [
{
line: err.loc && err.loc.line,
column: err.loc && err.loc.column,
rule: 'parseError',
severity: 'error',
text: `${err.message}`
}
]
}
}
}
const taggedTemplateLocs = taggedTemplateLocsMap[filepath] || []
const interpolationLines = interpolationLinesMap[filepath] || []
const lineCorrection = sourceMapsCorrections[filepath]
const warnings = stylelintResult.warnings
.filter(
warning =>
// Filter false-positive warnings generated by interpolations substitution
!isCausedBySubstitution(warning, lineCorrection[warning.line], interpolationLines)
)
.map(warning => ({
...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] || warning.line,
column: getCorrectColumn(
taggedTemplateLocs,
lineCorrection[warning.line] || warning.line,
warning.column
)
}))
const result = { ...stylelintResult, warnings }
// Undo `errored` if no warnings with error severity any more
if (result.errored && !warnings.some(warning => warning.severity === 'error')) {
delete result.errored
}
return result
}
})
module.exports = options => realProcessor({ ...DEFAULT_OPTIONS, ...options })