You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
191 lines
6.6 KiB
191 lines
6.6 KiB
// @ts-check |
|
|
|
/** |
|
* @typedef {import('prettier').FileInfoOptions} FileInfoOptions |
|
* @typedef {import('eslint').ESLint.ObjectMetaProperties} ObjectMetaProperties |
|
* @typedef {import('prettier').Options & { onDiskFilepath: string, parserMeta?: ObjectMetaProperties['meta'], parserPath?: string, usePrettierrc?: boolean }} Options |
|
*/ |
|
|
|
const { runAsWorker } = require('synckit'); |
|
|
|
/** |
|
* @type {typeof import('prettier')} |
|
*/ |
|
let prettier; |
|
|
|
runAsWorker( |
|
/** |
|
* @param {string} source - The source code to format. |
|
* @param {Options} options - The prettier options. |
|
* @param {FileInfoOptions} eslintFileInfoOptions - The file info options. |
|
* @returns {Promise<string | undefined>} The formatted source code. |
|
*/ |
|
async ( |
|
source, |
|
{ |
|
filepath, |
|
onDiskFilepath, |
|
parserMeta, |
|
parserPath, |
|
usePrettierrc, |
|
...eslintPrettierOptions |
|
}, |
|
eslintFileInfoOptions, |
|
) => { |
|
if (!prettier) { |
|
prettier = await import('prettier'); |
|
} |
|
|
|
const prettierRcOptions = usePrettierrc |
|
? await prettier.resolveConfig(onDiskFilepath, { |
|
editorconfig: true, |
|
}) |
|
: null; |
|
|
|
const { ignored, inferredParser } = await prettier.getFileInfo( |
|
onDiskFilepath, |
|
{ |
|
resolveConfig: false, |
|
withNodeModules: false, |
|
ignorePath: '.prettierignore', |
|
plugins: /** @type {string[] | undefined} */ ( |
|
prettierRcOptions ? prettierRcOptions.plugins : undefined |
|
), |
|
...eslintFileInfoOptions, |
|
}, |
|
); |
|
|
|
// Skip if file is ignored using a .prettierignore file |
|
if (ignored) { |
|
return; |
|
} |
|
|
|
const initialOptions = { parser: inferredParser ?? 'babel' }; |
|
|
|
// ESLint supports processors that let you extract and lint JS |
|
// fragments within a non-JS language. In the cases where prettier |
|
// supports the same language as a processor, we want to process |
|
// the provided source code as javascript (as ESLint provides the |
|
// rules with fragments of JS) instead of guessing the parser |
|
// based off the filename. Otherwise, for instance, on a .md file we |
|
// end up trying to run prettier over a fragment of JS using the |
|
// markdown parser, which throws an error. |
|
// Processors may set virtual filenames for these extracted blocks. |
|
// If they do so then we want to trust the file extension they |
|
// provide, and no override is needed. |
|
// If the processor does not set any virtual filename (signified by |
|
// `filepath` and `onDiskFilepath` being equal) AND we can't |
|
// infer the parser from the filename, either because no filename |
|
// was provided or because there is no parser found for the |
|
// filename, use javascript. |
|
// This is added to the options first, so that |
|
// prettierRcOptions and eslintPrettierOptions can still override |
|
// the parser. |
|
// |
|
// `parserBlocklist` should contain the list of prettier parser |
|
// names for file types where: |
|
// * Prettier supports parsing the file type |
|
// * There is an ESLint processor that extracts JavaScript snippets |
|
// from the file type. |
|
if (filepath === onDiskFilepath) { |
|
// The following list means the plugin process source into js content |
|
// but with same filename, so we need to change the parser to `babel` |
|
// by default. |
|
// Related ESLint plugins are: |
|
// 1. `eslint-plugin-graphql` (replacement: `@graphql-eslint/eslint-plugin`) |
|
// 2. `eslint-plugin-html` |
|
// 3. `eslint-plugin-markdown@1` (replacement: `eslint-plugin-markdown@2+`) |
|
// 4. `eslint-plugin-svelte3` (replacement: `eslint-plugin-svelte@2+`) |
|
let inferParserToBabel = false; |
|
|
|
switch (inferredParser) { |
|
// it could be processed by `@graphql-eslint/eslint-plugin` or `eslint-plugin-graphql` |
|
case 'graphql': { |
|
if ( |
|
// for `eslint-plugin-graphql`, see https://github.com/apollographql/eslint-plugin-graphql/blob/master/src/index.js#L416 |
|
source.startsWith('ESLintPluginGraphQLFile`') |
|
) { |
|
inferParserToBabel = true; |
|
} |
|
break; |
|
} |
|
case 'html': { |
|
// it could be processed by `eslint-plugin-html` or correctly parsed by `@html-eslint/parser` |
|
if ( |
|
(typeof parserMeta !== 'undefined' && |
|
parserMeta.name !== '@html-eslint/parser') || |
|
(typeof parserPath === 'string' && |
|
!/([\\/])@html-eslint\1parser\1/.test(parserPath)) |
|
) { |
|
inferParserToBabel = true; |
|
} |
|
break; |
|
} |
|
case 'markdown': { |
|
// it could be processed by `eslint-plugin-markdown@1` or correctly parsed by `eslint-mdx` |
|
if ( |
|
(typeof parserMeta !== 'undefined' && |
|
parserMeta.name !== 'eslint-mdx') || |
|
(typeof parserPath === 'string' && |
|
!/([\\/])eslint-mdx\1/.test(parserPath)) |
|
) { |
|
inferParserToBabel = true; |
|
} |
|
break; |
|
} |
|
// it could be processed by `@ota-meshi/eslint-plugin-svelte`, `eslint-plugin-svelte` or `eslint-plugin-svelte3` |
|
case 'svelte': { |
|
// The `source` would be modified by `eslint-plugin-svelte3` |
|
if ( |
|
typeof parserPath === 'string' && |
|
!/([\\/])svelte-eslint-parser\1/.test(parserPath) |
|
) { |
|
// We do not support `eslint-plugin-svelte3`, |
|
// the users should run `prettier` on `.svelte` files manually |
|
return; |
|
} |
|
} |
|
} |
|
|
|
if (inferParserToBabel) { |
|
initialOptions.parser = 'babel'; |
|
} |
|
} else { |
|
// Similar to https://github.com/prettier/stylelint-prettier/pull/22 |
|
// In all of the following cases ESLint extracts a part of a file to |
|
// be formatted and there exists a prettier parser for the whole file. |
|
// If you're interested in prettier you'll want a fully formatted file so |
|
// you're about to run prettier over the whole file anyway. |
|
// Therefore running prettier over just the style section is wasteful, so |
|
// skip it. |
|
const parserBlocklist = [ |
|
'babel', |
|
'babylon', |
|
'flow', |
|
'typescript', |
|
'vue', |
|
'markdown', |
|
'html', |
|
'mdx', |
|
'angular', |
|
'svelte', |
|
'pug', |
|
]; |
|
if (parserBlocklist.includes(/** @type {string} */ (inferredParser))) { |
|
return; |
|
} |
|
} |
|
|
|
/** |
|
* @type {import('prettier').Options} |
|
*/ |
|
const prettierOptions = { |
|
...initialOptions, |
|
...prettierRcOptions, |
|
...eslintPrettierOptions, |
|
filepath, |
|
}; |
|
|
|
return prettier.format(source, prettierOptions); |
|
}, |
|
);
|
|
|