UNPKG

6.35 kBJavaScriptView Raw
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
9const { runAsWorker } = require('synckit');
10
11/**
12 * @type {typeof import('prettier')}
13 */
14let prettier;
15
16runAsWorker(
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);