@acuris/eslint-config
Version:
Acuris eslint shareable configuration
436 lines (370 loc) • 11.5 kB
JavaScript
const { basename } = require('path')
const { getPrettier, getPrettierConfig } = require('../prettier-interface')
const jsonUtils = require('../json-utils')
const fs = require('fs')
const { resolve: pathResolve, dirname: pathDirname, sep: pathSep } = require('path')
const { isArray } = Array
const { keys: objectKeys, create: objectCreate, assign: objectAssign } = Object
const messages = {}
const meta = {
docs: {
url: 'https://github.com/SalvatorePreviti/eslint-plugin-quick-prettier'
},
fixable: 'code',
messages
}
let schema = [
{
type: 'object',
'prettify-package-json': { type: 'boolean' },
rules: { type: 'object' }
}
]
exports.meta = meta
exports.create = create
Object.defineProperty(exports, 'schema', {
get() {
try {
patchEslintApi()
} catch (_error) {}
return schema
},
set(value) {
schema = value
},
configurable: true,
enumerable: true
})
const linterContextes = []
function create(context) {
const result = {}
const linterContext = linterContextes[linterContextes.length - 1]
if (!linterContext || linterContext.id) {
return result
}
linterContext.id = context.id
if (linterContext.filename.endsWith('.json')) {
return result
}
const options = context.options
let rules
if (options) {
const settings = options[0]
if (settings) {
linterContext.options = settings
rules = settings.rules
}
}
if (!rules) {
return result
}
for (const key of objectKeys(rules)) {
const rule = rules[key]
const pluginContext = objectCreate(context, {
id: { value: key },
options: { value: isArray(rule) ? rule.slice(1) : [] }
})
const requiredPlugin = linterContext.requirePlugin(key)
const plugin = requiredPlugin.create(pluginContext)
for (const key of objectKeys(plugin)) {
const value = plugin[key]
if (typeof value === 'function') {
const prev = result[key]
if (typeof prev === 'function') {
result[key] = (...args) => {
prev(...args)
return value(...args)
}
} else {
result[key] = value
}
}
}
}
return result
}
let _shouldFixInIde = undefined
const patchedEslintSet = new Set()
/**
* Patches eslint public API to support prettier fix afterwards
*/
function patchEslintApi() {
const eslintPath = resolveCallerEslintApi(patchEslintApi)
//const eslintApi = require(eslintPath)
if (patchedEslintSet.has(eslintPath)) {
return
}
patchedEslintSet.add(eslintPath)
let _legacyLinter
function getEslintApiLegacyLinter() {
if (_legacyLinter === undefined) {
_legacyLinter = require(eslintPath).linter || null
}
return _legacyLinter
}
let _SourceCodeFixerClass
function getSourceCodeFixer() {
if (_SourceCodeFixerClass === undefined) {
_SourceCodeFixerClass = requireSourceCodeFixer(eslintPath) || null
}
return _SourceCodeFixerClass
}
const LinterClass = requireLinterClass(eslintPath)
let linterPrototype = (LinterClass && LinterClass.prototype) || getEslintApiLegacyLinter()
const eslintRequireMap = new Map()
class LinterContext {
constructor(filename) {
this.id = null
this.filename = filename
}
requirePlugin(id) {
let result = eslintRequireMap.get(id)
if (result === undefined) {
result = requirePluginRule(id, eslintPath)
eslintRequireMap.set(id, result)
if (result && result.meta && result.meta.messages) {
// Add loaded plugin warning messages to this plugin meta messages
objectAssign(messages, result.meta.messages)
}
}
return result
}
}
LinterContext.prototype.settings = {}
const oldVerifyAndFix = linterPrototype.verifyAndFix
linterPrototype.verifyAndFix = function verifyAndFix(code, config, options) {
const self = LinterClass ? this : getEslintApiLegacyLinter() || this
let fix, filename
if (typeof options === 'string') {
fix = true
filename = options
} else {
fix = !!options.fix
filename = options.filename
}
if (!fix && _shouldFixInIde === undefined) {
_shouldFixInIde = loadShouldFixInIde()
}
if ((!fix && !_shouldFixInIde) || !filename) {
if (!linterContextes[linterContextes.length - 1]) {
return oldVerifyAndFix.call(self, code, config, options)
}
linterContextes.push(null)
try {
return oldVerifyAndFix.call(self, code, config, options)
} finally {
linterContextes.pop()
}
}
const linterContext = new LinterContext(filename)
linterContextes.push(linterContext)
try {
let result = oldVerifyAndFix.call(self, code, config, options)
if (fix && linterContext.id) {
result = verifyAndFixAndPrettify(self, linterContext, result, filename, config, options, getSourceCodeFixer)
}
return result
} finally {
linterContext.id = null
linterContextes.pop()
}
}
}
function requireLinterClass(eslintPath) {
try {
const r = require(pathResolve(eslintPath, 'lib/linter/linter')).Linter
if (r) {
return r
}
} catch (_error) {}
try {
const r = require(pathResolve(eslintPath, 'lib/linter')).Linter
if (r) {
return r
}
} catch (_error) {}
try {
return require(eslintPath).Linter || null
} catch (_error) {}
}
function requireSourceCodeFixer(eslintPath) {
try {
return require(pathResolve(eslintPath, 'lib/linter/source-code-fixer'))
} catch (_error1) {}
try {
return require(pathResolve(eslintPath, 'lib/linter')).SourceCodeFixer
} catch (_error2) {}
return null
}
function loadShouldFixInIde() {
if (process.env.VSCODE_PID) {
try {
const vsCodeSettings = fs.readFileSync('.vscode/settings.json', 'utf8')
return (
/fixAll.eslint"\s*\s*:\s*true/.test(vsCodeSettings) ||
/eslint.autoFixOnSave"\s*\s*:\s*true/.test(vsCodeSettings)
)
} catch (_error) {}
}
return false
}
function verifyAndFixAndPrettify(linter, linterContext, result, filename, config, options, getSourceCodeFixer) {
const prettier = getPrettier()
const prettierFileInfo = prettier.getFileInfo.sync(filename, { ignorePath: '.prettierignore' })
if (prettierFileInfo.ignored) {
return result
}
let parser = prettierFileInfo.parser || prettierFileInfo.inferredParser
if (!parser) {
return result
}
const prettierConfig = getPrettierConfig()
let prettifiedCode = result.output
if (
parser === 'json-stringify' &&
filename &&
(linterContext.settings['prettify-package-json'] ||
linterContext.settings['prettify-package-json'] === undefined) &&
basename(filename) === 'package.json'
) {
try {
const manifest = JSON.parse(prettifiedCode)
if (typeof manifest === 'object' && manifest !== null && !Array.isArray(manifest)) {
if (typeof manifest.name === 'string' && typeof manifest.version === 'string') {
prettifiedCode = JSON.stringify(jsonUtils.sortPackageJson(manifest), null, 2)
}
}
} catch (_error) {}
}
try {
prettifiedCode = prettier.format(prettifiedCode, {
parser,
...prettierConfig,
filepath: filename
})
} catch (e) {
if (parser === null || parser === undefined) {
return result
}
const error = e instanceof Error ? e : new Error()
// Prettier's message contains a codeframe style preview of the
// invalid code and the line/column at which the error occured.
// ESLint shows those pieces of information elsewhere already so
// remove them from the message
let message = (error instanceof SyntaxError ? 'Parsing error: ' : ' Prettier error: ') + error.message
message += ` - parser:${parser}`
if (error.codeFrame) {
message = message.replace(`${error.codeFrame}`, '')
}
const loc = error.loc
let line = undefined
let column = NaN
if (loc && loc.start && loc.start.line) {
line = loc.start.line || undefined
column = loc.start.column || NaN
message = message.replace(/ \(\d+:\d+\)\s?$/, '')
}
result.messages.push({
ruleId: linterContext.id,
severity: 2,
message,
line,
column,
nodeType: null
})
return result
}
if (result.output !== prettifiedCode) {
result.fixed = true
let messages = linter.verify(prettifiedCode, config, options)
if (messages.length !== 0) {
const SourceCodeFixer = getSourceCodeFixer()
if (SourceCodeFixer && SourceCodeFixer.applyFixes) {
const fixedResult = SourceCodeFixer.applyFixes(prettifiedCode, messages, true)
messages = fixedResult.messages
prettifiedCode = fixedResult.output
}
}
result.messages = messages
result.output = prettifiedCode
}
return result
}
const _eslintExpectedPath = `node_modules${pathSep}eslint${pathSep}`
function resolveCallerEslintApi(callerFunction) {
const oldPrepareStackTrace = Error.prepareStackTrace
const oldStackTraceLimit = Error.stackTraceLimit
let stack
Error.stackTraceLimit = 25
try {
Error.prepareStackTrace = (_error, callinfos) => callinfos
try {
const error = new Error()
Error.captureStackTrace(error, callerFunction)
throw error
} catch (error) {
stack = error.stack
} finally {
Error.prepareStackTrace = oldPrepareStackTrace
}
} finally {
Error.stackTraceLimit = oldStackTraceLimit
}
if (isArray(stack)) {
try {
for (const item of stack) {
if (typeof item.getFileName === 'function') {
const name = item.getFileName()
if (typeof name === 'string') {
const idx = name.lastIndexOf(_eslintExpectedPath)
if (idx >= 0) {
return pathResolve(name.slice(0, idx + _eslintExpectedPath.length))
}
}
}
}
} catch (_error) {}
}
for (let p = module; p; p = p.parent) {
const name = p.id
if (typeof name === 'string') {
const idx = name.lastIndexOf(_eslintExpectedPath)
if (idx >= 0) {
return pathResolve(name.slice(0, idx + _eslintExpectedPath.length))
}
}
}
return pathDirname(require.resolve('eslint/package.json'))
}
function normalizePackageName(name) {
if (name.includes('\\')) {
name = name.replace(/\\/gu, '/')
}
if (name.startsWith('@')) {
const scopedPackageShortcutRegex = /^(@[^/]+)(?:\/(?:eslint-plugin)?)?$/u
const scopedPackageNameRegex = /^eslint-plugin(-|$)/u
if (scopedPackageShortcutRegex.test(name)) {
name = name.replace(scopedPackageShortcutRegex, '$1/eslint-plugin')
} else if (!scopedPackageNameRegex.test(name.split('/')[1])) {
name = name.replace(/^@([^/]+)\/(.*)$/u, '@$1/eslint-plugin-$2')
}
}
return name.startsWith('eslint-plugin-') ? name : `eslint-plugin-${name}`
}
function requirePluginRule(id, eslintPath) {
if (id.indexOf('/') <= 0) {
return require(pathResolve(eslintPath, 'lib', 'rules', id))
}
const n = normalizePackageName(id)
const indexOfSlash = n.indexOf('/')
const plugin = require(n.slice(0, indexOfSlash))
if (!plugin || !plugin.rules) {
throw new Error('Invalid eslint rule ' + id)
}
const found = plugin.rules[n.slice(indexOfSlash + 1)]
if (!found || typeof found.create !== 'function') {
throw new Error('Invalid eslint rule ' + id)
}
return found
}