Skip to content
This repository was archived by the owner on Aug 7, 2023. It is now read-only.

Commit 6305f02

Browse files
refactor: try to avoid futile round-trips to the worker and back
1 parent 56fa375 commit 6305f02

File tree

3 files changed

+73
-41
lines changed

3 files changed

+73
-41
lines changed

lib/config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ const Config = {
9191
if (!configChanged(newConfig, this._currentConfig)) { return; }
9292
if (this._currentConfig) {
9393
for (let handler of this.handlers) {
94-
handler(newConfig, this._currentConfig || {});
94+
handler(newConfig, this._currentConfig);
9595
}
9696
}
9797
this._currentConfig = newConfig;

lib/main.js

Lines changed: 63 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { CompositeDisposable } from 'atom';
44
// eslint-disable-next-line import/no-unresolved
55
import { shell } from 'electron';
66
import Path from 'path';
7+
import { diff } from 'deep-object-diff';
78

89
import console from './console';
910
import Config from './config';
@@ -47,6 +48,7 @@ function eventInvolvesEslintrc (event) {
4748
export default {
4849

4950
shouldAutoFix (textEditor) {
51+
if (this.inactive) { return false; }
5052
if (textEditor.isModified()) { return false; }
5153
if (!Config.get('autofix.fixOnSave')) { return false; }
5254
if (!helpers.hasValidScope(textEditor, this.scopes)) { return false; }
@@ -58,10 +60,21 @@ export default {
5860
},
5961

6062
async activate () {
61-
Config.initialize(atom.project.getPaths()[0]);
63+
Config.initialize();
6264
this.workerPath = Path.join(__dirname, 'worker.js');
6365
this.jobManager = new JobManager();
6466

67+
// Inactive is the mode we enter when (a) there is no ESLint for this
68+
// project, or (b) there is a too-old ESLint, or (c) there is a v7 ESLint
69+
// that we're letting the legacy package handle. We don't bother to send
70+
// jobs to the worker in this mode; no use making the round-trip. But we
71+
// will wake up if literally anything changes about the project that might
72+
// invalidate our assumptions.
73+
//
74+
// TODO: Consider killing the worker in inactive mode and restarting it
75+
// if we leave?
76+
this.inactive = false;
77+
6578
// Keep track of whether the user has seen certain notifications. Absent
6679
// any user-initiated changes, they should see each of these no more than
6780
// once per session.
@@ -90,13 +103,17 @@ export default {
90103
),
91104

92105
// Scan for new .linter-eslint config files when project paths change.
93-
atom.project.onDidChangePaths(() => Config.rescan()),
106+
atom.project.onDidChangePaths(() => {
107+
this.inactive = false;
108+
Config.rescan();
109+
}),
94110

95111
// React to changes that happen either in a .linter-eslint file or the
96112
// base package settings.
97113
Config.onConfigDidChange(
98114
(config, prevConfig) => {
99-
console.debug('Config changed:', config, prevConfig);
115+
this.inactive = false;
116+
console.debug('Config changed:', config, prevConfig, diff(prevConfig, config));
100117
if (helpers.configShouldInvalidateWorkerCache(config, prevConfig)) {
101118
this.clearESLintCache();
102119
}
@@ -115,6 +132,7 @@ export default {
115132
this.jobManager.createWorker();
116133
})
117134
.catch(() => {
135+
this.inactive = true;
118136
this.notifyAboutInvalidNodeBin();
119137
});
120138
}
@@ -138,19 +156,13 @@ export default {
138156
if (event.path.includes('node_modules')) { return false; }
139157

140158
if (eventInvolvesFile(event, '.linter-eslint')) {
159+
this.inactive = false;
141160
Config.rescan();
142161
}
143-
// Any creation, deletion, renaming, or modification of an
144-
// `.eslintignore` file anywhere in this project will affect which
145-
// files this linter should ignore, and will confuse old instances of
146-
// `ESLint` inside the worker script because they seem to cache too
147-
// aggressively. So in these cases we've got to clear our cache and
148-
// force new `ESLint` instances to be created.
149-
if (eventInvolvesFile(event, '.eslintignore')) {
150-
this.clearESLintCache();
151-
}
152-
153-
if (eventInvolvesEslintrc(event)) {
162+
// Instances of `ESLint` cache whatever configuration details were
163+
// present at instantiation time. If any config changes, we can't
164+
// re-use old instances.
165+
if (eventInvolvesFile(event, '.eslintignore') || eventInvolvesEslintrc(event)) {
154166
this.clearESLintCache();
155167
}
156168
}
@@ -205,13 +217,8 @@ export default {
205217
this.notified.invalidNodeBin = true;
206218
},
207219

208-
// We need to tell the worker to clear its cache when
209-
// - any .eslintignore file is changed;
210-
// - certain options are changed that must be declared at `ESLint`
211-
// instantiation time.
212-
//
213220
clearESLintCache () {
214-
console.warn('Telling the worker to clear its cache!');
221+
console.debug('Telling the worker to clear its cache');
215222
this.jobManager.send({ type: 'clear-cache' });
216223
},
217224

@@ -309,8 +316,8 @@ export default {
309316
let debugMessage = [
310317
`Atom version: ${debug.atomVersion}`,
311318
`linter-eslint-node version: ${debug.packageVersion}`,
312-
`Node path: ${debug.nodePath}`,
313319
`Node version: ${debug.nodeVersion}`,
320+
`Node path: ${debug.nodePath}`,
314321
`ESLint version: ${debug.eslintVersion}`,
315322
`ESLint location: ${debug.eslintPath}`,
316323
`Linting in this project performed by: ${whichPackageWillLint}`,
@@ -324,9 +331,19 @@ export default {
324331
'linter-eslint-node debug information',
325332
{
326333
detail: debugMessage.join('\n'),
327-
dismissable: true
334+
dismissable: true,
335+
buttons: [
336+
{
337+
text: 'Copy',
338+
onDidClick () {
339+
atom.clipboard.write(debugMessage.join('\n'));
340+
}
341+
}
342+
]
328343
}
329344
);
345+
346+
return debug;
330347
},
331348

332349
// Here we're operating entirely outside the purview of the `linter` package.
@@ -343,6 +360,10 @@ export default {
343360
return;
344361
}
345362

363+
// If we're in inactive mode and we get this far, it's because the user
364+
// explicitly ran the `Fix Job` command, so we should wake up.
365+
this.inactive = false;
366+
346367
if (textEditor.isModified()) {
347368
atom.notifications.addError(
348369
`linter-eslint-node: Please save before fixing.`
@@ -391,11 +412,13 @@ export default {
391412
// this session), we should do nothing here so that an invalid worker
392413
// behaves as though no linter is present at all.
393414
this.notifyAboutInvalidNodeBin();
415+
this.inactive = true;
394416
return null;
395417
}
396418

397419
if (err.type && err.type === 'config-not-found') {
398420
if (Config.get('disabling.disableWhenNoEslintConfig')) {
421+
this.inactive = true;
399422
return [];
400423
}
401424
if (type === 'fix') {
@@ -431,12 +454,14 @@ export default {
431454

432455
if (err.type && err.type === 'no-project') {
433456
// No project means nowhere to look for an `.eslintrc`.
457+
this.inactive = true;
434458
return null;
435459
}
436460

437461
if (err.type && err.type === 'version-overlap') {
438462
// This is an easy one: we don't need to lint this file because
439463
// `linter-eslint` is present and can handle it. Do nothing.
464+
this.inactive = true;
440465
return null;
441466
}
442467

@@ -449,6 +474,7 @@ export default {
449474
// this window has been open; no use spamming it on every lint attempt.
450475
let linterEslintPresent = this.isLegacyPackagePresent();
451476
let didNotify = this.notified.incompatibleVersion;
477+
this.inactive = true;
452478
if (linterEslintPresent || didNotify || !Config.get('warnAboutOldEslint')) {
453479
return null;
454480
} else {
@@ -471,17 +497,19 @@ export default {
471497
}
472498
);
473499
this.notified.incompatibleVersion = true;
500+
return null;
474501
}
475-
} else {
476-
atom.notifications.addError(
477-
`linter-eslint-node Error`,
478-
{
479-
description: err.error,
480-
dismissable: true
481-
}
482-
);
483-
return null;
484502
}
503+
504+
// Unknown/unhandled error from the worker.
505+
atom.notifications.addError(
506+
`linter-eslint-node Error`,
507+
{
508+
description: err.error,
509+
dismissable: true
510+
}
511+
);
512+
return null;
485513
},
486514

487515
provideLinter () {
@@ -495,6 +523,10 @@ export default {
495523
if (!atom.workspace.isTextEditor(textEditor)) {
496524
return null;
497525
}
526+
if (this.inactive) {
527+
console.debug('Inactive; skipping lint');
528+
return null;
529+
}
498530
const filePath = textEditor.getPath();
499531
if (!filePath) {
500532
// Can't report messages back to Linter without a path.

lib/worker.js

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -116,14 +116,6 @@ function clearESLintCache () {
116116
}
117117
}
118118

119-
function resolveESLint (filePath) {
120-
try {
121-
return createRequire(filePath).resolve('eslint');
122-
} catch (e) {
123-
return createRequire(__dirname).resolve('eslint');
124-
}
125-
}
126-
127119
let builtInEslintPath;
128120
function resolveBuiltInESLint () {
129121
if (!builtInEslintPath) {
@@ -132,6 +124,14 @@ function resolveBuiltInESLint () {
132124
return builtInEslintPath;
133125
}
134126

127+
function resolveESLint (filePath) {
128+
try {
129+
return createRequire(filePath).resolve('eslint');
130+
} catch (e) {
131+
return resolveBuiltInESLint();
132+
}
133+
}
134+
135135
function getESLint (filePath, config, { legacyPackagePresent, projectPath }) {
136136
let { advanced: { useCache } } = config;
137137
// If two files share a `cwd`, we can reuse any `ESLint` instance that was
@@ -177,7 +177,7 @@ function getESLint (filePath, config, { legacyPackagePresent, projectPath }) {
177177

178178
// Older versions of ESLint won't have this API.
179179
if (ESLint) {
180-
let commonOptions = buildCommonConstructorOptions(config, projectPath || resolveDir);
180+
let commonOptions = buildCommonConstructorOptions(config, resolveDir);
181181

182182
const eslintLint = new ESLint({ ...commonOptions, fix: false });
183183
const eslintFix = new ESLint({ ...commonOptions });

0 commit comments

Comments
 (0)