UNPKG

prettier-eslint

Version:

Formats your JavaScript using prettier followed by eslint --fix

210 lines (207 loc) 8.01 kB
import fs from 'node:fs/promises'; import path from 'node:path'; import { oneLine, stripIndent } from 'common-tags'; import indentString from 'indent-string'; import getLogger from 'loglevel-colored-level-prefix'; import { extractFileExtensions, formatForLog, getESLint, getModulePath, getOptionsForFormatting, importModule, mergeConfigs, } from "./utils.js"; const logger = getLogger({ prefix: 'prettier-eslint' }); 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); logger.trace('called analyze with options:', formatForLog(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); logger.debug('inferred options:', formatForLog({ 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.overrideConfigFile = true; eslintConfig.overrideConfig = { languageOptions: eslintConfig.languageOptions, rules: eslintConfig.rules, ignores: eslintConfig.ignorePatterns ?? [], plugins: eslintConfig.plugins ?? {}, settings: eslintConfig.settings ?? {}, ...eslintConfig.overrideConfig, }; 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, }); logger.trace('eslint.lintText returned the following report:', formatForLog(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, baseConfig: eslintConfig.baseConfig, overrideConfig: eslintConfig.overrideConfig, overrideConfigFile: eslintConfig.overrideConfigFile, plugins: eslintConfig.plugins, }; } async function getESLintConfig(filePath, eslintPath, eslintConfig) { if (filePath) { eslintConfig.cwd = path.dirname(filePath); } logger.trace(oneLine ` creating ESLint CLI Engine to get the config for "${filePath || process.cwd()}" `); const eslint = await getESLint(eslintPath, getESLintApiOptions(eslintConfig)); try { logger.debug(`getting eslint config for file at "${filePath}"`); const config = (await eslint.calculateConfigForFile(filePath)); logger.trace(`eslint config for "${filePath}" received`, formatForLog(config)); return { ...eslintConfig, ...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