prettier-standard
Version:
Prettier and standard brought together!
317 lines (256 loc) • 7.56 kB
JavaScript
const globby = require('globby')
const path = require('path')
const fs = require('fs')
const prettierx = require('prettierx')
const lintStaged = require('lint-staged')
const {
createIgnorer,
isSupportedExtension,
getOptions,
getScm,
getRanges,
getPathInHostNodeModules,
createMatcher
} = require('./utils')
const DEFAULT_IGNORE = [
'!**/*.min.js',
'!**/{node_modules,coverage,vendor}/**',
'!./{node_modules,coverage,vendor}/**',
'!**/.{git,svn,hg}/**',
'!./.{git,svn,hg}/**'
]
const LINT_REGEXP = /\.(js|mjs|cjs|jsx|ts|tsx)$/
function format (source, options) {
return prettierx.format(source, getOptions(options))
}
function check (source, options) {
return prettierx.check(source, getOptions(options))
}
function getFileInfo (filePath, options) {
return prettierx.getFileInfo(filePath, options)
}
function formatWithCursor (source, options) {
return prettierx.formatWithCursor(source, getOptions(options))
}
function formatWithRanges (source, ranges, options) {
if (!ranges || ranges.length === 0) {
return format(source, options)
}
let output = source
ranges.forEach(({ rangeStart, rangeEnd }) => {
output = format(
output,
Object.assign({}, options, { rangeStart, rangeEnd })
)
})
return output
}
function checkWithRanges (source, ranges, options) {
if (!ranges || ranges.length === 0) {
return check(source, options)
}
let valid = true
ranges.forEach(({ rangeStart, rangeEnd }) => {
valid =
valid &&
check(source, Object.assign({}, options, { rangeStart, rangeEnd }))
})
return valid
}
async function run (cwd, config) {
// Filepaths will be relative to this directory
let root = cwd
const scm = getScm(cwd)
let patterns = config.patterns || []
if (!Array.isArray(patterns)) {
return new Error('patterns should be an array')
}
if (patterns.length === 0) {
if (scm) {
patterns.push('**/*')
} else {
return new Error(
'Error: You must provide patterns to match outside of git repository'
)
}
}
const onStart = config.onStart || function () {}
const onProcessed = config.onProcessed || function () {}
// They ignore files relative to current directory
const filters = [
// Ignore unsupported extensions
isSupportedExtension,
// Ignore .prettierignore entries
createIgnorer(cwd)
]
const runFilters = path => filters.reduce((a, b) => a && b(path), true)
let engine
if (config.lint) {
const eslintPath = getPathInHostNodeModules('eslint')
const babelEslintPath = getPathInHostNodeModules('babel-eslint')
const configs = {
prettier: getPathInHostNodeModules('eslint-config-prettier'),
standard: getPathInHostNodeModules('eslint-config-standard'),
'standard-jsx': getPathInHostNodeModules('eslint-config-standard-jsx')
}
const getConfig = (config, file) => path.join(configs[config], file)
const eslint = require(eslintPath)
engine = new eslint.CLIEngine({
parser: babelEslintPath,
resolvePluginsRelativeTo: path.join(__dirname, 'vendor'),
baseConfig: {
plugins: ['jest'],
env: {
'jest/globals': true
},
extends: [
getConfig('standard', 'index.js'),
getConfig('standard-jsx', 'index.js'),
getConfig('prettier', 'index.js'),
getConfig('prettier', '@typescript-eslint.js'),
getConfig('prettier', 'babel.js'),
getConfig('prettier', 'flowtype.js'),
getConfig('prettier', 'react.js'),
getConfig('prettier', 'standard.js'),
getConfig('prettier', 'unicorn.js'),
getConfig('prettier', 'vue.js')
]
}
})
}
onStart({ engine })
let files
if (config.changed || config.since || config.lines || config.staged) {
if (!scm) {
return new Error('No git repository detected...')
}
root = scm.dir
// Ignore non-matched patterns
filters.push(createMatcher(path.relative(root, cwd), config.patterns))
const revision = scm.getRevision(config.since || 'HEAD')
if (!revision) {
return new Error(`Cannot find ${scm.name()} revision ${config.since}`)
}
files = scm.getChanges(revision).filter(p => runFilters(p.filepath))
} else {
patterns = patterns.concat(DEFAULT_IGNORE)
let filePaths = []
try {
filePaths = globby
.sync(patterns, { dot: true, nodir: true, cwd })
.map(filePath => path.relative(process.cwd(), filePath))
} catch (error) {
return new Error(`Unable to expand glob pattern: ${error.message}`)
}
files = filePaths.filter(runFilters).map(filepath => ({ filepath }))
}
if (config.staged) {
const lintConfig = {}
let command = 'node ' + path.join(__dirname, 'cli.js')
if (config.lint) {
command += ' --lint'
}
if (config.check) {
command += ' --check'
}
if (config.lines) {
command += ' --lines'
}
files.forEach(f => {
const mask = '*' + path.extname(f.filepath)
if (!lintConfig[mask]) {
lintConfig[mask] = [command, 'git add']
}
})
if (Object.keys(lintConfig).length > 0) {
if (config.lint) {
console.log('Formatting and linting all staged files...')
} else {
console.log('Formatting all staged files...')
}
const success = await lintStaged(
{
config: lintConfig,
shell: false,
quiet: true,
debug: false
},
{
log: msg => console.log(msg),
error: msg =>
console.error(
msg.replace(new RegExp(command, 'g'), 'prettier-standard').trim()
)
}
)
if (!success) {
process.exit(1)
}
}
return
}
for (const file of files) {
const start = Date.now()
const { filepath, changes } = file
const fullpath = path.join(root, filepath)
const input = fs.readFileSync(fullpath, 'utf8')
let ranges
if (config.lines && changes) {
ranges = getRanges(input, changes)
}
const fileOptions = Object.assign(
{},
prettierx.resolveConfig.sync(fullpath, {
editorconfig: true
}),
config.options,
{ filepath: fullpath }
)
const fileInfo = prettierx.getFileInfo.sync(fullpath, {})
if (!fileOptions.parser) {
fileOptions.parser = fileInfo.inferredParser
}
if (config.check) {
const formatted = checkWithRanges(input, ranges, fileOptions)
let report
if (config.lint && fullpath.match(LINT_REGEXP)) {
report = engine.executeOnText(input, fullpath)
}
onProcessed({
file: filepath,
runtime: Date.now() - start,
formatted,
report,
engine,
check: config.check
})
continue
}
const output = formatWithRanges(input, ranges, fileOptions)
const formatted = output === input
if (!formatted) {
fs.writeFileSync(fullpath, output)
}
let report
if (config.lint && fullpath.match(LINT_REGEXP)) {
report = engine.executeOnText(output, fullpath)
}
onProcessed({
file: filepath,
runtime: Date.now() - start,
formatted,
report,
check: config.check
})
}
}
module.exports = {
format,
check,
run,
getFileInfo,
formatWithCursor,
resolveConfig: prettierx.resolveConfig,
clearConfigCache: prettierx.clearConfigCache,
getSupportInfo: prettierx.getSupportInfo
}