UNPKG

putout

Version:

the pluggable code transformer

429 lines (350 loc) • 10.3 kB
'use strict'; /* c8 ignore next */ //!process.env.CI && require('v8-compile-cache'); const {resolve} = require('path'); const {readFileSync} = require('fs'); const {red} = require('chalk'); const yargsParser = require('yargs-parser'); const {isCI} = require('ci-info'); const memo = require('nano-memoize'); const fullstore = require('fullstore'); const tryCatch = require('try-catch'); const tryToCatch = require('try-to-catch'); const wraptile = require('wraptile'); const { runProcessors, getFilePatterns, getProcessorRunners, defaultProcessors, } = require('@putout/engine-processor'); const merge = require('../merge'); const ignores = require('../ignores'); const initProcessFile = require('./process-file'); const getFiles = require('./get-files'); const cacheFiles = require('./cache-files'); const supportedFiles = require('./supported-files'); const getFormatter = memo(require('./formatter').getFormatter); const getOptions = require('./get-options'); const report = require('./report')(); const keyPress = require('@putout/cli-keypress'); const validateArgs = require('@putout/cli-validate-args'); const {parseError} = require('./parse-error'); const { OK, PLACE, STAGE, NO_FILES, NO_PROCESSORS, CANNOT_LOAD_PROCESSOR, WAS_STOP, INVALID_OPTION, UNHANDLED, } = require('./exit-codes'); const cwd = process.cwd(); const {PUTOUT_FILES = ''} = process.env; const envNames = !PUTOUT_FILES ? [] : PUTOUT_FILES.split(','); const maybeFirst = (a) => isArray(a) ? a.pop() : a; const maybeArray = (a) => isArray(a) ? a : a.split(','); const getExitCode = (wasStop) => wasStop() ? WAS_STOP : OK; const {isArray} = Array; const isParser = (rule) => /^parser/.test(rule); const isParsingError = ({rule}) => isParser(rule); const createFormatterProxy = (options) => { return new Proxy(options, { get(target, name) { if (target[name]) return target[name]; if (name === 'source') return readFileSync(target.name, 'utf8'); }, }); }; module.exports = async ({argv, halt, log, write, logError, readFile, writeFile}) => { log = !process.send ? log : process.send.bind(process); const {isStop} = keyPress(); const wasStop = fullstore(); const argvConfig = { configuration: { 'strip-aliased': true, 'strip-dashed': true, }, coerce: { format: maybeFirst, plugins: maybeArray, }, boolean: [ 'ci', 'cache', 'debug', 'version', 'help', 'fix', 'fresh', 'raw', 'enable-all', 'disable-all', 'jsx', 'flow', 'config', 'staged', ], number: [ 'fix-count', ], string: [ 'format', 'disable', 'enable', 'rulesdir', 'transform', 'plugins', ], alias: { version: 'v', help: 'h', format: 'f', staged: 's', transform: 't', }, default: { ci: true, fix: false, fixCount: 10, config: true, cache: true, fresh: false, enable: '', disable: '', debug: false, plugins: [], }, }; const args = yargsParser(argv, argvConfig); if (args.ci && isCI) { args.format = 'dump'; args.cache = false; } const { debug, fix, fixCount, raw, rulesdir, format, flow: isFlow, jsx: isJSX, disable, disableAll, enable, enableAll, staged, fresh, cache, transform, plugins, } = args; const exit = getExit({ raw, halt, logError, }); addOnce(process, 'unhandledRejection', wraptile(exit, UNHANDLED)); const optionsList = [ ...argvConfig.boolean, ...argvConfig.string, ...argvConfig.number, ]; const validationError = await validateArgs(args, optionsList); if (validationError) return exit(INVALID_OPTION, validationError); if (args.version) { const {version} = require('../../package.json'); log(`v${version}`); return exit(); } if (args.help) { const help = require('./help'); log(help()); return exit(); } if (enable || disable) { const rulerProcessor = require('./ruler-processor'); rulerProcessor({enable, disable}, []); } const noConfig = !args.config; const { formatter, ignore, processors = defaultProcessors, } = getOptions({ name: `${cwd}/*`, rulesdir, noConfig, transform, plugins, }); const [currentFormat, formatterOptions] = getFormatter(format || formatter, exit); const [error, processorRunners] = tryCatch(getProcessorRunners, processors); if (error) return exit(CANNOT_LOAD_PROCESSOR, error); const patterns = getFilePatterns(processorRunners); supportedFiles.add(patterns); const stagedNames = []; if (staged) { const {get} = require('./staged'); const names = await get(); stagedNames.push(...names); } const globFiles = [ ...stagedNames, ...args._.map(String), ...envNames, ]; const [e, names] = await getFiles(globFiles, { ignore, }); if (e) return exit(NO_FILES, e); if (!names.length) return exit(); const fileCache = await cacheFiles({ cache, fresh, }); const options = { debug, fix, fileCache, isFlow, isJSX, fixCount, raw, ruler: { disable, disableAll, enable, enableAll, }, log, logError, write, transform, plugins, }; const rawPlaces = []; const processFile = initProcessFile(options); const {length} = names; for (let index = 0; index < length; index++) { if (wasStop()) break; wasStop(isStop()); const currentIndex = isStop() ? length - 1 : index; const name = names[index]; const resolvedName = resolve(name) .replace(/^\./, cwd); const options = getOptions({ name: resolvedName, rulesdir, noConfig, transform, plugins, }); const {dir} = options; if (fileCache.canUseCache({options, name})) { const places = fileCache.getPlaces(name); const formatterProxy = createFormatterProxy({ report, formatterOptions, name, places, index: currentIndex, count: length, }); const line = report(currentFormat, formatterProxy); write(line || ''); rawPlaces.push(places); continue; } let isProcessed = true; let places = []; let rawSource = ''; let processedSource = ''; if (!ignores(dir, resolvedName, options)) { rawSource = await readFile(resolvedName, 'utf8'); const [error, result] = await tryToCatch(runProcessors, { name: resolvedName, fix, processFile, options, rawSource, processorRunners, }); if (error) { places = parseError(error, { debug, }); isProcessed = true; processedSource = rawSource; if (raw) log(error); } else { ({ isProcessed, places, processedSource, } = result); } } const line = report(currentFormat, { report, formatterOptions, name, source: rawSource, places, index: currentIndex, count: length, }); write(line || ''); if (!isProcessed) return exit(NO_PROCESSORS, Error(`No processors found for ${name}`)); if (rawSource !== processedSource) { fileCache.removeEntry(name); await writeFile(name, processedSource); } const fixable = !places.filter(isParsingError).length; if (fixable) fileCache.setInfo(name, places, options); rawPlaces.push(places); } const mergedPlaces = merge(...rawPlaces); fileCache.reconcile(); if (enableAll || disableAll) { const rulerProcessor = require('./ruler-processor'); await rulerProcessor({enableAll, disableAll}, mergedPlaces); return exit(); } if (fix && staged) { const {set} = require('./staged'); const stagedNames = await set(); if (!stagedNames.length) return exit(STAGE); } if (mergedPlaces.length) return exit(PLACE); const exitCode = getExitCode(wasStop); exit(exitCode); }; const getExit = ({halt, raw, logError}) => (code, e) => { if (!code) return halt(0); if (!e) return halt(code); const message = raw ? e : red(e.message); logError(message); halt(code); }; module.exports._addOnce = addOnce; function addOnce(emitter, name, fn) { if (!emitter.listenerCount(name)) emitter.on(name, fn); }