1 | // @ts-check
|
2 |
|
3 | /**
|
4 | * @typedef {import('prettier').FileInfoOptions} FileInfoOptions
|
5 | * @typedef {import('eslint').ESLint.ObjectMetaProperties} ObjectMetaProperties
|
6 | * @typedef {import('prettier').Options & { onDiskFilepath: string, parserMeta?: ObjectMetaProperties['meta'], parserPath?: string, usePrettierrc?: boolean }} Options
|
7 | */
|
8 |
|
9 | const { runAsWorker } = require('synckit');
|
10 |
|
11 | /**
|
12 | * @type {typeof import('prettier')}
|
13 | */
|
14 | let prettier;
|
15 |
|
16 | runAsWorker(
|
17 | /**
|
18 | * @param {string} source - The source code to format.
|
19 | * @param {Options} options - The prettier options.
|
20 | * @param {FileInfoOptions} eslintFileInfoOptions - The file info options.
|
21 | * @returns {Promise<string | undefined>} The formatted source code.
|
22 | */
|
23 | async (
|
24 | source,
|
25 | {
|
26 | filepath,
|
27 | onDiskFilepath,
|
28 | parserMeta,
|
29 | parserPath,
|
30 | usePrettierrc,
|
31 | ...eslintPrettierOptions
|
32 | },
|
33 | eslintFileInfoOptions,
|
34 | ) => {
|
35 | if (!prettier) {
|
36 | prettier = await import('prettier');
|
37 | }
|
38 |
|
39 | const prettierRcOptions = usePrettierrc
|
40 | ? await prettier.resolveConfig(onDiskFilepath, {
|
41 | editorconfig: true,
|
42 | })
|
43 | : null;
|
44 |
|
45 | const { ignored, inferredParser } = await prettier.getFileInfo(
|
46 | onDiskFilepath,
|
47 | {
|
48 | resolveConfig: false,
|
49 | withNodeModules: false,
|
50 | ignorePath: '.prettierignore',
|
51 | plugins: /** @type {string[] | undefined} */ (
|
52 | prettierRcOptions ? prettierRcOptions.plugins : undefined
|
53 | ),
|
54 | ...eslintFileInfoOptions,
|
55 | },
|
56 | );
|
57 |
|
58 | // Skip if file is ignored using a .prettierignore file
|
59 | if (ignored) {
|
60 | return;
|
61 | }
|
62 |
|
63 | const initialOptions = { parser: inferredParser ?? 'babel' };
|
64 |
|
65 | // ESLint supports processors that let you extract and lint JS
|
66 | // fragments within a non-JS language. In the cases where prettier
|
67 | // supports the same language as a processor, we want to process
|
68 | // the provided source code as javascript (as ESLint provides the
|
69 | // rules with fragments of JS) instead of guessing the parser
|
70 | // based off the filename. Otherwise, for instance, on a .md file we
|
71 | // end up trying to run prettier over a fragment of JS using the
|
72 | // markdown parser, which throws an error.
|
73 | // Processors may set virtual filenames for these extracted blocks.
|
74 | // If they do so then we want to trust the file extension they
|
75 | // provide, and no override is needed.
|
76 | // If the processor does not set any virtual filename (signified by
|
77 | // `filepath` and `onDiskFilepath` being equal) AND we can't
|
78 | // infer the parser from the filename, either because no filename
|
79 | // was provided or because there is no parser found for the
|
80 | // filename, use javascript.
|
81 | // This is added to the options first, so that
|
82 | // prettierRcOptions and eslintPrettierOptions can still override
|
83 | // the parser.
|
84 | //
|
85 | // `parserBlocklist` should contain the list of prettier parser
|
86 | // names for file types where:
|
87 | // * Prettier supports parsing the file type
|
88 | // * There is an ESLint processor that extracts JavaScript snippets
|
89 | // from the file type.
|
90 | if (filepath === onDiskFilepath) {
|
91 | // The following list means the plugin process source into js content
|
92 | // but with same filename, so we need to change the parser to `babel`
|
93 | // by default.
|
94 | // Related ESLint plugins are:
|
95 | // 1. `eslint-plugin-graphql` (replacement: `@graphql-eslint/eslint-plugin`)
|
96 | // 2. `eslint-plugin-html`
|
97 | // 3. `eslint-plugin-markdown@1` (replacement: `eslint-plugin-markdown@2+`)
|
98 | // 4. `eslint-plugin-svelte3` (replacement: `eslint-plugin-svelte@2+`)
|
99 | const parserBlocklist = ['html'];
|
100 |
|
101 | let inferParserToBabel = parserBlocklist.includes(initialOptions.parser);
|
102 |
|
103 | switch (inferredParser) {
|
104 | // it could be processed by `@graphql-eslint/eslint-plugin` or `eslint-plugin-graphql`
|
105 | case 'graphql': {
|
106 | if (
|
107 | // for `eslint-plugin-graphql`, see https://github.com/apollographql/eslint-plugin-graphql/blob/master/src/index.js#L416
|
108 | source.startsWith('ESLintPluginGraphQLFile`')
|
109 | ) {
|
110 | inferParserToBabel = true;
|
111 | }
|
112 | break;
|
113 | }
|
114 | case 'markdown': {
|
115 | // it could be processed by `eslint-plugin-markdown@1` or correctly parsed by `eslint-mdx`
|
116 | if (
|
117 | (typeof parserMeta !== 'undefined' &&
|
118 | parserMeta.name !== 'eslint-mdx') ||
|
119 | (typeof parserPath === 'string' &&
|
120 | !/([\\/])eslint-mdx\1/.test(parserPath))
|
121 | ) {
|
122 | inferParserToBabel = true;
|
123 | }
|
124 | break;
|
125 | }
|
126 | // it could be processed by `@ota-meshi/eslint-plugin-svelte`, `eslint-plugin-svelte` or `eslint-plugin-svelte3`
|
127 | case 'svelte': {
|
128 | // The `source` would be modified by `eslint-plugin-svelte3`
|
129 | if (
|
130 | typeof parserPath === 'string' &&
|
131 | !/([\\/])svelte-eslint-parser\1/.test(parserPath)
|
132 | ) {
|
133 | // We do not support `eslint-plugin-svelte3`,
|
134 | // the users should run `prettier` on `.svelte` files manually
|
135 | return;
|
136 | }
|
137 | }
|
138 | }
|
139 |
|
140 | if (inferParserToBabel) {
|
141 | initialOptions.parser = 'babel';
|
142 | }
|
143 | } else {
|
144 | // Similar to https://github.com/prettier/stylelint-prettier/pull/22
|
145 | // In all of the following cases ESLint extracts a part of a file to
|
146 | // be formatted and there exists a prettier parser for the whole file.
|
147 | // If you're interested in prettier you'll want a fully formatted file so
|
148 | // you're about to run prettier over the whole file anyway.
|
149 | // Therefore running prettier over just the style section is wasteful, so
|
150 | // skip it.
|
151 | const parserBlocklist = [
|
152 | 'babel',
|
153 | 'babylon',
|
154 | 'flow',
|
155 | 'typescript',
|
156 | 'vue',
|
157 | 'markdown',
|
158 | 'html',
|
159 | 'mdx',
|
160 | 'angular',
|
161 | 'svelte',
|
162 | 'pug',
|
163 | ];
|
164 | if (parserBlocklist.includes(/** @type {string} */ (inferredParser))) {
|
165 | return;
|
166 | }
|
167 | }
|
168 |
|
169 | /**
|
170 | * @type {import('prettier').Options}
|
171 | */
|
172 | const prettierOptions = {
|
173 | ...initialOptions,
|
174 | ...prettierRcOptions,
|
175 | ...eslintPrettierOptions,
|
176 | filepath,
|
177 | };
|
178 |
|
179 | return prettier.format(source, prettierOptions);
|
180 | },
|
181 | );
|