UNPKG

prettier-eslint

Version:

Formats your JavaScript using prettier followed by eslint --fix

225 lines (222 loc) 8.54 kB
import fs from 'node:fs/promises'; import path from 'node:path'; import { oneLine, stripIndent } from 'common-tags'; import indentString from 'indent-string'; import { format as prettyFormat } from 'pretty-format'; import { hash } from 'stable-hash-x'; import { extractFileExtensions, getESLint, getModulePath, getOptionsForFormatting, importModule, isDebugEnabled, isTraceEnabled, logger, mergeConfigs, } from "./utils.js"; const eslintConfigCache = new Map(); export async function format(options) { const { output } = await analyze(options); return output; } export const DEFAULT_ESLINT_EXTENSIONS = [ '.cjs', '.cts', '.js', '.jsx', '.ts', '.tsx', '.mjs', '.mts', '.vue', '.svelte', ]; export const DEFAULT_ESLINT_FILES = DEFAULT_ESLINT_EXTENSIONS.map(ext => `**/*${ext}`); export async function analyze(options) { const { logLevel = getDefaultLogLevel() } = options; logger.setLevel(logLevel); if (isTraceEnabled()) { logger.trace('called analyze with options:', prettyFormat(options)); } const { filePath, text = await getTextFromFilePath(filePath), eslintPath = getModulePath(filePath, 'eslint'), prettierPath = getModulePath(filePath, 'prettier'), prettierLast, fallbackPrettierOptions, } = options; const eslintConfig = mergeConfigs(options.eslintConfig, await getESLintConfig(filePath, eslintPath, options.eslintConfig || {})); const prettierOptions = mergeConfigs(filePath ? { filepath: filePath } : { parser: 'babel' }, await getPrettierConfig(filePath, prettierPath), options.prettierOptions); const formattingOptions = getOptionsForFormatting(eslintConfig, prettierOptions, fallbackPrettierOptions); if (isDebugEnabled()) { logger.debug('inferred options:', prettyFormat({ filePath, text, eslintPath, prettierPath, eslintConfig: formattingOptions.eslint, prettierOptions: formattingOptions.prettier, logLevel, prettierLast, })); } const fileExtension = path.extname(filePath || ''); const eslintFiles = eslintConfig.files?.length ? eslintConfig.files : DEFAULT_ESLINT_FILES; const eslintExtensions = extractFileExtensions(eslintFiles.flat()); const onlyPrettier = filePath ? !eslintExtensions.includes(fileExtension) : false; const prettify = createPrettify(formattingOptions.prettier, prettierPath); if (onlyPrettier) { return prettify(text); } formattingOptions.eslint.languageOptions ??= {}; if (!formattingOptions.eslint.languageOptions.parser) { if (['.ts', '.tsx'].includes(fileExtension)) { formattingOptions.eslint.languageOptions.parser = await importModule('@typescript-eslint/parser'); } else if (['.vue'].includes(fileExtension)) { formattingOptions.eslint.languageOptions.parser = await importModule('vue-eslint-parser'); } else if (['.svelte'].includes(fileExtension)) { formattingOptions.eslint.languageOptions.parser = await importModule('svelte-eslint-parser'); } } const eslintFix = createEslintFix(formattingOptions.eslint, eslintPath); if (prettierLast) { const eslintFixed = await eslintFix(text, filePath); return prettify(eslintFixed); } const { output } = await prettify(text); return eslintFix(output, filePath); } function createPrettify(formatOptions, prettierPath) { return async function prettify(param) { let text = param; let messages = []; if (typeof text !== 'string') { messages = text.messages; text = text.output; } logger.debug('calling prettier on text'); logger.trace(stripIndent ` prettier input: ${indentString(text, 2)} `); const prettier = await importModule(prettierPath, 'prettier'); try { logger.trace('calling prettier.format with the text and prettierOptions'); const output = await prettier.format(text, formatOptions); logger.trace('prettier: output === input', output === text); logger.trace(stripIndent ` prettier output: ${indentString(output, 2)} `); return { output, messages }; } catch (error) { logger.error('prettier formatting failed due to a prettier error'); throw error; } }; } function createEslintFix(eslintConfig, eslintPath) { return async function eslintFix(text, filePath) { eslintConfig = { ...eslintConfig, overrideConfig: { languageOptions: eslintConfig.languageOptions, rules: eslintConfig.rules, ignores: eslintConfig.ignorePatterns ?? [], plugins: eslintConfig.plugins ?? {}, settings: eslintConfig.settings ?? {}, ...eslintConfig.overrideConfig, }, overrideConfigFile: true, }; delete eslintConfig.baseConfig; delete eslintConfig.language; delete eslintConfig.languageOptions; delete eslintConfig.linterOptions; delete eslintConfig.rules; delete eslintConfig.ignorePatterns; delete eslintConfig.plugins; delete eslintConfig.settings; delete eslintConfig.overrideConfig.plugins['@']; const eslint = await getESLint(eslintPath, eslintConfig); try { logger.trace('calling eslint.lintText with the text'); const report = await eslint.lintText(text, { filePath, warnIgnored: true, }); if (isTraceEnabled()) { logger.trace('eslint.lintText returned the following report:', prettyFormat(report)); } const [{ output = text, messages }] = report; logger.trace('eslint --fix: output === input', output === text); logger.trace(stripIndent ` eslint --fix output: ${indentString(output, 2)} `); return { output, messages }; } catch (error) { logger.error('eslint --fix failed due to an eslint error'); throw error; } }; } async function getTextFromFilePath(filePath) { try { logger.trace(oneLine ` attempting fs.readFile to get the text for file at "${filePath}" `); return await fs.readFile(filePath, 'utf8'); } catch (error) { logger.error(oneLine ` failed to get the text to format from the given filePath: "${filePath}" `); throw error; } } function getESLintApiOptions(eslintConfig) { return { ignore: eslintConfig.ignore ?? true, allowInlineConfig: eslintConfig.allowInlineConfig ?? true, cwd: eslintConfig.cwd, baseConfig: eslintConfig.baseConfig, overrideConfig: eslintConfig.overrideConfig, overrideConfigFile: eslintConfig.overrideConfigFile, plugins: eslintConfig.plugins, }; } async function getESLintConfig(filePath, eslintPath, eslintConfig) { const configPath = filePath || process.cwd(); const configOptions = getESLintApiOptions(eslintConfig); const cacheKey = hash({ filePath, eslintPath, configOptions }); const cachedConfig = eslintConfigCache.get(cacheKey); if (cachedConfig) { return cachedConfig; } logger.trace(oneLine ` creating ESLint CLI Engine to get the config for "${configPath}" `); const eslint = await getESLint(eslintPath, configOptions); try { logger.debug(`getting eslint config for file at "${filePath}"`); const config = (await eslint.calculateConfigForFile(filePath)); if (isTraceEnabled()) { logger.trace(`eslint config for "${filePath}" received`, prettyFormat(config)); } eslintConfigCache.set(cacheKey, config); return config; } catch { logger.debug('Unable to find config'); return { rules: {} }; } } async function getPrettierConfig(filePath, prettierPath) { const prettier = await importModule(prettierPath, 'prettier'); const { resolveConfig } = prettier; return resolveConfig?.(filePath); } function getDefaultLogLevel() { return process.env.LOG_LEVEL || 'warn'; } export * from "./utils.js"; export default format; //# sourceMappingURL=index.js.map