5
5
6
6
require ( 'util' ) . inspect . defaultOptions . depth = null ;
7
7
8
- const { join, normalize , sep } = require ( 'path' ) ;
8
+ const { join, dirname } = require ( 'path' ) ;
9
9
const { createRequire } = require ( 'module' ) ;
10
10
const compareVersions = require ( 'compare-versions' ) ;
11
11
const ndjson = require ( 'ndjson' ) ;
12
12
13
- const PATHS_CACHE = { } ;
14
- const ESLINT_CACHE = { } ;
15
-
16
13
const MINIMUM_ESLINT_VERSION = '7.0.0' ;
17
14
15
+ const PATHS_CACHE = new Map ( ) ;
16
+ const ESLINT_CACHE = new Map ( ) ;
17
+
18
+
18
19
class IncompatibleVersionError extends Error {
19
20
constructor ( version ) {
20
21
// eslint-disable-next-line max-len
@@ -56,39 +57,28 @@ function log (message) {
56
57
emit ( { log : message } ) ;
57
58
}
58
59
59
- function getPathRoot ( filePath , eslintPath ) {
60
- log ( `getPathRoot ${ filePath } // ${ eslintPath } ` ) ;
61
- filePath = normalize ( filePath ) . split ( sep ) ;
62
- eslintPath = normalize ( eslintPath ) . split ( sep ) ;
63
-
64
- for ( let index in filePath ) {
65
- if ( eslintPath [ index ] !== filePath [ index ] ) {
66
- let ret = filePath . slice ( 0 , index ) . join ( sep ) ;
67
- return ret + sep ;
68
- }
69
- }
70
-
71
- throw new Error ( 'linter-eslint-node: Cannot determine root' ) ;
72
- }
73
-
74
- function buildCommonConstructorOptions ( config ) {
60
+ function buildCommonConstructorOptions ( config , cwd ) {
75
61
let {
76
62
advanced : { disableEslintIgnore } ,
77
63
autofix : { rulesToDisableWhileFixing }
78
64
} = config ;
79
65
80
66
return {
67
+ cwd,
81
68
ignore : ! disableEslintIgnore ,
69
+ // `fix` can be a function, so we'll use it to ignore any rules that the
70
+ // user has told us to ignore. This isn't a "common" option, but it's easy
71
+ // to overwrite with `fix: false` for the lint-only instance.
82
72
fix : ( { ruleId } ) => ! rulesToDisableWhileFixing . includes ( ruleId )
83
73
} ;
84
74
}
85
75
86
76
function clearESLintCache ( ) {
87
- for ( let key in PATHS_CACHE ) {
88
- delete PATHS_CACHE [ key ] ;
77
+ for ( let key of PATHS_CACHE ) {
78
+ PATHS_CACHE . delete ( key ) ;
89
79
}
90
- for ( let key in ESLINT_CACHE ) {
91
- delete ESLINT_CACHE [ key ] ;
80
+ for ( let key of ESLINT_CACHE ) {
81
+ ESLINT_CACHE . delete ( key ) ;
92
82
}
93
83
}
94
84
@@ -100,52 +90,72 @@ function resolveESLint (filePath) {
100
90
}
101
91
}
102
92
103
- function getESLint ( filePath , projectPath , config , { legacyPackagePresent } ) {
104
- if ( ! PATHS_CACHE [ filePath ] ) {
105
- PATHS_CACHE [ filePath ] = resolveESLint ( filePath ) ;
93
+ let builtInEslintPath ;
94
+ function resolveBuiltInESLint ( ) {
95
+ if ( ! builtInEslintPath ) {
96
+ builtInEslintPath = createRequire ( __dirname ) . resolve ( 'eslint' ) ;
106
97
}
98
+ return builtInEslintPath ;
99
+ }
107
100
108
- let eslintPath = PATHS_CACHE [ filePath ] ;
101
+ function getESLint ( filePath , config , { legacyPackagePresent } ) {
102
+ let resolveDir = dirname ( filePath ) ;
103
+ if ( ! PATHS_CACHE . has ( resolveDir ) ) {
104
+ PATHS_CACHE . set ( resolveDir , resolveESLint ( filePath ) ) ;
105
+ }
109
106
110
- if ( ! ESLINT_CACHE [ eslintPath ] ) {
107
+ let eslintPath = PATHS_CACHE . get ( resolveDir ) ;
108
+
109
+ // We need to manage an arbitrary number of different ESLint versions without
110
+ // having them clash. On top of that, a declaration of `ESLint` accepts a
111
+ // `cwd` option on instantiation and infers its configuration based on the
112
+ // `.eslintrc`s and `.eslintignore`s that it sees from that particular
113
+ // directory.
114
+ //
115
+ // Thus an `ESLint` instance is good for the directory it was created in and
116
+ // nothing more. If two files are siblings in a project's file tree, linting
117
+ // the first one will create an instance that can be reused when we lint the
118
+ // second, but can't be reused with any file in the parent directory or in
119
+ // any child directories.
120
+ //
121
+ // TODO: Add an option to disable this method of caching. Could be useful as
122
+ // a diagnostic tool and as a workaround when users report bugs.
123
+ if ( ! ESLINT_CACHE . has ( resolveDir ) ) {
111
124
const eslintRootPath = eslintPath . replace ( / e s l i n t ( [ / \\ ] ) .* ?$ / , 'eslint$1' ) ;
112
125
const packageMeta = require ( join ( eslintRootPath , 'package.json' ) ) ;
113
126
114
- let { ESLint } = createRequire ( eslintPath ) ( 'eslint' ) ;
115
- let commonOptions = buildCommonConstructorOptions ( config ) ;
127
+ const { ESLint } = createRequire ( eslintPath ) ( 'eslint' ) ;
128
+ let commonOptions = buildCommonConstructorOptions ( config , resolveDir ) ;
129
+
130
+ const eslintLint = new ESLint ( { ...commonOptions , fix : false } ) ;
131
+ const eslintFix = new ESLint ( { ...commonOptions } ) ;
116
132
117
- // `fix` is a predicate in `commonOptions` and represents the "yes,
118
- // except..." outcome. For the linter version, we overwrite it with `fix:
119
- // false`.
120
- ESLINT_CACHE [ eslintPath ] = {
133
+ ESLINT_CACHE . set ( resolveDir , {
121
134
ESLint,
122
- eslintLint : new ESLint ( { ...commonOptions , fix : false } ) ,
123
- eslintFix : new ESLint ( { ...commonOptions } ) ,
124
- workingDir : getPathRoot ( filePath , eslintPath ) ,
135
+ eslintLint,
136
+ eslintFix,
125
137
eslintPath : eslintRootPath ,
126
138
eslintVersion : packageMeta . version ,
127
- isBuiltIn : eslintPath === PATHS_CACHE [ __dirname ]
128
- } ;
139
+ isBuiltIn : eslintPath === resolveBuiltInESLint ( )
140
+ } ) ;
129
141
}
130
142
131
- let cache = ESLINT_CACHE [ eslintPath ] ;
143
+ let cached = ESLINT_CACHE . get ( resolveDir ) ;
132
144
133
- if ( compareVersions ( cache . eslintVersion , MINIMUM_ESLINT_VERSION ) < 1 ) {
145
+ if ( compareVersions ( cached . eslintVersion , MINIMUM_ESLINT_VERSION ) < 1 ) {
134
146
// Unsupported version.
135
- throw new IncompatibleVersionError ( cache . eslintVersion ) ;
136
- } else if ( ( compareVersions ( cache . eslintVersion , '8.0.0' ) < 1 ) && legacyPackagePresent ) {
147
+ throw new IncompatibleVersionError ( cached . eslintVersion ) ;
148
+ } else if ( ( compareVersions ( cached . eslintVersion , '8.0.0' ) < 1 ) && legacyPackagePresent ) {
137
149
// We're dealing with version 7 of ESLint. The legacy `linter-eslint`
138
150
// package is present and capable of linting with this version, so we
139
151
// should halt instead of trying to lint everything twice.
140
- throw new VersionOverlapError ( cache . eslintVersion ) ;
152
+ throw new VersionOverlapError ( cached . eslintVersion ) ;
141
153
}
142
154
143
- return ESLINT_CACHE [ eslintPath ] ;
155
+ return cached ;
144
156
}
145
157
146
158
async function lint ( eslint , workingDir , filePath , fileContent ) {
147
- process . chdir ( workingDir ) ;
148
-
149
159
if ( typeof fileContent === 'string' ) {
150
160
return eslint . lintText ( fileContent , { filePath } ) ;
151
161
} else {
@@ -235,7 +245,6 @@ async function processMessage (bundle) {
235
245
isModified,
236
246
key,
237
247
legacyPackagePresent,
238
- projectPath,
239
248
type
240
249
} = bundle ;
241
250
@@ -258,18 +267,9 @@ async function processMessage (bundle) {
258
267
return ;
259
268
}
260
269
261
- // if (!projectPath) {
262
- // emit({
263
- // key,
264
- // error: `Must provide projectPath`,
265
- // type: 'no-project'
266
- // });
267
- // return;
268
- // }
269
-
270
270
let eslint ;
271
271
try {
272
- eslint = getESLint ( filePath , projectPath , config , { legacyPackagePresent } ) ;
272
+ eslint = getESLint ( filePath , config , { legacyPackagePresent } ) ;
273
273
} catch ( err ) {
274
274
if ( err instanceof IncompatibleVersionError ) {
275
275
emit ( {
0 commit comments