3
3
* @author Andres Suarez
4
4
*/
5
5
6
+ // @ts -check
7
+
8
+ /**
9
+ * @typedef {import('eslint').AST.Range } Range
10
+ * @typedef {import('eslint').AST.SourceLocation } SourceLocation
11
+ * @typedef {import('eslint').ESLint.Plugin } Plugin
12
+ * @typedef {import('prettier').FileInfoOptions } FileInfoOptions
13
+ * @typedef {import('prettier').Options & { onDiskFilepath: string, parserPath: string, usePrettierrc?: boolean } } Options
14
+ */
15
+
6
16
'use strict' ;
7
17
8
18
// ------------------------------------------------------------------------------
@@ -26,9 +36,9 @@ const { INSERT, DELETE, REPLACE } = generateDifferences;
26
36
27
37
// Lazily-loaded Prettier.
28
38
/**
29
- * @type {import('prettier') }
39
+ * @type {(source: string, options: Options, fileInfoOptions: FileInfoOptions) => string }
30
40
*/
31
- let prettier ;
41
+ let prettierFormat ;
32
42
33
43
// ------------------------------------------------------------------------------
34
44
// Rule Definition
@@ -43,7 +53,7 @@ let prettier;
43
53
*/
44
54
function reportDifference ( context , difference ) {
45
55
const { operation, offset, deleteText = '' , insertText = '' } = difference ;
46
- const range = [ offset , offset + deleteText . length ] ;
56
+ const range = /** @type { Range } */ ( [ offset , offset + deleteText . length ] ) ;
47
57
const [ start , end ] = range . map ( index =>
48
58
context . getSourceCode ( ) . getLocFromIndex ( index ) ,
49
59
) ;
@@ -63,7 +73,10 @@ function reportDifference(context, difference) {
63
73
// Module Definition
64
74
// ------------------------------------------------------------------------------
65
75
66
- module . exports = {
76
+ /**
77
+ * @type {Plugin }
78
+ */
79
+ const eslintPluginPrettier = {
67
80
configs : {
68
81
recommended : {
69
82
extends : [ 'prettier' ] ,
@@ -112,7 +125,10 @@ module.exports = {
112
125
create ( context ) {
113
126
const usePrettierrc =
114
127
! context . options [ 1 ] || context . options [ 1 ] . usePrettierrc !== false ;
115
- const eslintFileInfoOptions =
128
+ /**
129
+ * @type {FileInfoOptions }
130
+ */
131
+ const fileInfoOptions =
116
132
( context . options [ 1 ] && context . options [ 1 ] . fileInfoOptions ) || { } ;
117
133
const sourceCode = context . getSourceCode ( ) ;
118
134
const filepath = context . getFilename ( ) ;
@@ -125,134 +141,19 @@ module.exports = {
125
141
const source = sourceCode . text ;
126
142
127
143
return {
128
- // eslint-disable-next-line sonarjs/cognitive-complexity
129
144
Program ( ) {
130
- if ( ! prettier ) {
145
+ if ( ! prettierFormat ) {
131
146
// Prettier is expensive to load, so only load it if needed.
132
- prettier = require ( 'prettier' ) ;
147
+ prettierFormat = require ( 'synckit' ) . createSyncFn (
148
+ require . resolve ( './worker' ) ,
149
+ ) ;
133
150
}
134
151
152
+ /**
153
+ * @type {{} }
154
+ */
135
155
const eslintPrettierOptions = context . options [ 0 ] || { } ;
136
156
137
- const prettierRcOptions = usePrettierrc
138
- ? prettier . resolveConfig . sync ( onDiskFilepath , {
139
- editorconfig : true ,
140
- } )
141
- : null ;
142
-
143
- const { ignored, inferredParser } = prettier . getFileInfo . sync (
144
- onDiskFilepath ,
145
- {
146
- resolveConfig : false ,
147
- withNodeModules : false ,
148
- ignorePath : '.prettierignore' ,
149
- plugins : prettierRcOptions ? prettierRcOptions . plugins : null ,
150
- ...eslintFileInfoOptions ,
151
- } ,
152
- ) ;
153
-
154
- // Skip if file is ignored using a .prettierignore file
155
- if ( ignored ) {
156
- return ;
157
- }
158
-
159
- const initialOptions = { } ;
160
-
161
- // ESLint supports processors that let you extract and lint JS
162
- // fragments within a non-JS language. In the cases where prettier
163
- // supports the same language as a processor, we want to process
164
- // the provided source code as javascript (as ESLint provides the
165
- // rules with fragments of JS) instead of guessing the parser
166
- // based off the filename. Otherwise, for instance, on a .md file we
167
- // end up trying to run prettier over a fragment of JS using the
168
- // markdown parser, which throws an error.
169
- // Processors may set virtual filenames for these extracted blocks.
170
- // If they do so then we want to trust the file extension they
171
- // provide, and no override is needed.
172
- // If the processor does not set any virtual filename (signified by
173
- // `filepath` and `onDiskFilepath` being equal) AND we can't
174
- // infer the parser from the filename, either because no filename
175
- // was provided or because there is no parser found for the
176
- // filename, use javascript.
177
- // This is added to the options first, so that
178
- // prettierRcOptions and eslintPrettierOptions can still override
179
- // the parser.
180
- //
181
- // `parserBlocklist` should contain the list of prettier parser
182
- // names for file types where:
183
- // * Prettier supports parsing the file type
184
- // * There is an ESLint processor that extracts JavaScript snippets
185
- // from the file type.
186
- if ( filepath === onDiskFilepath ) {
187
- // The following list means the plugin process source into js content
188
- // but with same filename, so we need to change the parser to `babel`
189
- // by default.
190
- // Related ESLint plugins are:
191
- // 1. `eslint-plugin-graphql` (replacement: `@graphql-eslint/eslint-plugin`)
192
- // 2. `eslint-plugin-html`
193
- // 3. `eslint-plugin-markdown@1` (replacement: `eslint-plugin-markdown@2+`)
194
- // 4. `eslint-plugin-svelte3` (replacement: `eslint-plugin-svelte@2+`)
195
- const parserBlocklist = [ null , 'markdown' , 'html' ] ;
196
-
197
- let inferParserToBabel = parserBlocklist . includes ( inferredParser ) ;
198
-
199
- switch ( inferredParser ) {
200
- // it could be processed by `@graphql-eslint/eslint-plugin` or `eslint-plugin-graphql`
201
- case 'graphql' : {
202
- if (
203
- // for `eslint-plugin-graphql`, see https://github.com/apollographql/eslint-plugin-graphql/blob/master/src/index.js#L416
204
- source . startsWith ( 'ESLintPluginGraphQLFile`' )
205
- ) {
206
- inferParserToBabel = true ;
207
- }
208
- break ;
209
- }
210
- // it could be processed by `@ota-meshi/eslint-plugin-svelte`, `eslint-plugin-svelte` or `eslint-plugin-svelte3`
211
- case 'svelte' : {
212
- // The `source` would be modified by `eslint-plugin-svelte3`
213
- if ( ! context . parserPath . includes ( 'svelte-eslint-parser' ) ) {
214
- // We do not support `eslint-plugin-svelte3`,
215
- // the users should run `prettier` on `.svelte` files manually
216
- return ;
217
- }
218
- }
219
- }
220
-
221
- if ( inferParserToBabel ) {
222
- initialOptions . parser = 'babel' ;
223
- }
224
- } else {
225
- // Similar to https://github.com/prettier/stylelint-prettier/pull/22
226
- // In all of the following cases ESLint extracts a part of a file to
227
- // be formatted and there exists a prettier parser for the whole file.
228
- // If you're interested in prettier you'll want a fully formatted file so
229
- // you're about to run prettier over the whole file anyway.
230
- // Therefore running prettier over just the style section is wasteful, so
231
- // skip it.
232
- const parserBlocklist = [
233
- 'babel' ,
234
- 'babylon' ,
235
- 'flow' ,
236
- 'typescript' ,
237
- 'vue' ,
238
- 'markdown' ,
239
- 'html' ,
240
- 'mdx' ,
241
- 'angular' ,
242
- 'svelte' ,
243
- ] ;
244
- if ( parserBlocklist . includes ( inferredParser ) ) {
245
- return ;
246
- }
247
- }
248
-
249
- const prettierOptions = {
250
- ...initialOptions ,
251
- ...prettierRcOptions ,
252
- ...eslintPrettierOptions ,
253
- filepath,
254
- } ;
255
-
256
157
// prettier.format() may throw a SyntaxError if it cannot parse the
257
158
// source code it is given. Usually for JS files this isn't a
258
159
// problem as ESLint will report invalid syntax before trying to
@@ -261,29 +162,51 @@ module.exports = {
261
162
// files throw an error if they contain unclosed elements, such as
262
163
// `<template><div></template>. In this case report an error at the
263
164
// point at which parsing failed.
165
+ /**
166
+ * @type {string }
167
+ */
264
168
let prettierSource ;
265
169
try {
266
- prettierSource = prettier . format ( source , prettierOptions ) ;
170
+ prettierSource = prettierFormat (
171
+ source ,
172
+ {
173
+ ...eslintPrettierOptions ,
174
+ filepath,
175
+ onDiskFilepath,
176
+ parserPath : context . parserPath ,
177
+ usePrettierrc,
178
+ } ,
179
+ fileInfoOptions ,
180
+ ) ;
267
181
} catch ( err ) {
268
182
if ( ! ( err instanceof SyntaxError ) ) {
269
183
throw err ;
270
184
}
271
185
272
186
let message = 'Parsing error: ' + err . message ;
273
187
188
+ const error =
189
+ /** @type {SyntaxError & {codeFrame: string; loc: SourceLocation} } */ (
190
+ err
191
+ ) ;
192
+
274
193
// Prettier's message contains a codeframe style preview of the
275
194
// invalid code and the line/column at which the error occurred.
276
195
// ESLint shows those pieces of information elsewhere already so
277
196
// remove them from the message
278
- if ( err . codeFrame ) {
279
- message = message . replace ( `\n${ err . codeFrame } ` , '' ) ;
197
+ if ( error . codeFrame ) {
198
+ message = message . replace ( `\n${ error . codeFrame } ` , '' ) ;
280
199
}
281
- if ( err . loc ) {
200
+ if ( error . loc ) {
282
201
message = message . replace ( / \( \d + : \d + \) $ / , '' ) ;
283
202
}
284
203
285
- context . report ( { message, loc : err . loc } ) ;
204
+ context . report ( { message, loc : error . loc } ) ;
205
+
206
+ return ;
207
+ }
286
208
209
+ if ( prettierSource == null ) {
287
210
return ;
288
211
}
289
212
@@ -300,3 +223,5 @@ module.exports = {
300
223
} ,
301
224
} ,
302
225
} ;
226
+
227
+ module . exports = eslintPluginPrettier ;
0 commit comments