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

Commit 73672bf

Browse files
committed
Merge pull request #307 from AtomLinter/issue300
Make `Linter Eslint: Fix File` use the same ESLint as when linting
2 parents 89445d0 + 8d80bf6 commit 73672bf

File tree

32 files changed

+414
-134
lines changed

32 files changed

+414
-134
lines changed

.eslintignore

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1 @@
1-
spec/files/bad.js
2-
spec/eslintignore/ignored.js
1+
spec/fixtures/**/*.js

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
node_modules
22
*.log
33
.idea
4+
5+
# Keep node_modules test fixtures
6+
!spec/fixtures/local-eslint/node_modules

lib/es5-helpers.js

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
'use strict'
2+
3+
const ChildProcess = require('child_process')
4+
const Path = require('path')
5+
const FS = require('fs')
6+
const find = require('atom-linter').findFile
7+
8+
let prefixPath = null
9+
const atomEslintPath = Path.join(FS.realpathSync(Path.join(__dirname, '..')), 'node_modules', 'eslint')
10+
11+
function findEslintDir(params) {
12+
const modulesPath = find(params.fileDir, 'node_modules')
13+
let eslintNewPath = null
14+
15+
if (params.global) {
16+
if (params.nodePath === '' && prefixPath === null) {
17+
const npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm'
18+
try {
19+
prefixPath = ChildProcess.spawnSync(npmCommand, ['get', 'prefix']).output[1].toString().trim()
20+
} catch (e) {
21+
throw new Error('Unable to execute `npm get prefix`. Please make sure Atom is getting $PATH correctly')
22+
}
23+
}
24+
if (process.platform === 'win32') {
25+
eslintNewPath = Path.join(params.nodePath || prefixPath, 'node_modules', 'eslint')
26+
} else {
27+
eslintNewPath = Path.join(params.nodePath || prefixPath, 'lib', 'node_modules', 'eslint')
28+
}
29+
} else {
30+
try {
31+
FS.accessSync(eslintNewPath = Path.join(modulesPath, 'eslint'), FS.R_OK)
32+
} catch (_) {
33+
eslintNewPath = atomEslintPath
34+
}
35+
}
36+
37+
return eslintNewPath
38+
}
39+
40+
// Check for project config file or eslint config in package.json and determine
41+
// whether to bail out or use config specified in package options
42+
function determineConfigFile(params) {
43+
// config file
44+
const configFile = find(params.fileDir, ['.eslintrc.js', '.eslintrc.yaml', '.eslintrc.yml', '.eslintrc.json', '.eslintrc']) || null
45+
if (configFile) {
46+
return configFile
47+
}
48+
// package.json
49+
const packagePath = find(params.fileDir, 'package.json')
50+
if (packagePath && Boolean(require(packagePath).eslintConfig)) {
51+
return packagePath
52+
}
53+
// Couldn't find a config
54+
if (params.canDisable) {
55+
return null
56+
}
57+
// If all else fails, use the configFile specified in the linter-eslint options
58+
if (params.configFile) {
59+
return params.configFile
60+
}
61+
}
62+
63+
function getEslintCli(path) {
64+
try {
65+
const eslint = require(Path.join(path, 'lib', 'cli.js'))
66+
return eslint
67+
} catch (e) {
68+
if (e.code === 'MODULE_NOT_FOUND') {
69+
throw new Error('ESLint not found, Please install or make sure Atom is getting $PATH correctly')
70+
} else throw e
71+
}
72+
}
73+
74+
75+
module.exports = {
76+
findEslintDir: findEslintDir,
77+
find: find,
78+
determineConfigFile: determineConfigFile,
79+
getEslintCli: getEslintCli
80+
}

lib/main.js

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import Path from 'path'
44
import {CompositeDisposable} from 'atom'
55
import {spawnWorker} from './helpers'
66
import escapeHTML from 'escape-html'
7-
import {BufferedNodeProcess} from 'atom'
87

98
export default {
109
config: {
@@ -73,20 +72,33 @@ export default {
7372
}
7473
}))
7574
this.subscriptions.add(atom.commands.add('atom-text-editor', {
76-
'linter-eslint:fix-file': function() {
75+
'linter-eslint:fix-file': () => {
7776
const textEditor = atom.workspace.getActiveTextEditor()
78-
if (!textEditor || textEditor.isModified()) {
79-
// Ignore invalid or unsaved text editors
80-
return;
81-
}
8277
const filePath = textEditor.getPath()
8378
const fileDir = Path.dirname(filePath)
84-
new BufferedNodeProcess({
85-
command: Path.normalize(Path.join(__dirname, '..', 'node_modules', 'eslint', 'bin', 'eslint.js')),
86-
args: [filePath, '--fix'],
87-
options: {
88-
cwd: fileDir
89-
}
79+
80+
if (!textEditor || textEditor.isModified()) {
81+
// Abort for invalid or unsaved text editors
82+
atom.notifications.addError('Linter-ESLint: Please save before fixing')
83+
return
84+
}
85+
86+
if (this.worker === null) {
87+
// Abort if worker is not yet ready
88+
atom.notifications.addError('Linter-ESLint: Not ready, please try again')
89+
return
90+
}
91+
92+
this.worker.request('FIX', {
93+
fileDir: fileDir,
94+
filePath: filePath,
95+
global: atom.config.get('linter-eslint.useGlobalEslint'),
96+
nodePath: atom.config.get('linter-eslint.globalNodePath'),
97+
configFile: atom.config.get('linter-eslint.eslintrcPath')
98+
}).then(function(response) {
99+
atom.notifications.addSuccess(response)
100+
}).catch(function(response) {
101+
atom.notifications.addWarning(response)
90102
})
91103
}
92104
}))
@@ -125,7 +137,7 @@ export default {
125137
lint: textEditor => {
126138
const text = textEditor.getText()
127139
if (text.length === 0) {
128-
return Promise.resolve([]);
140+
return Promise.resolve([])
129141
}
130142
const filePath = textEditor.getPath()
131143
const fileDir = Path.dirname(filePath)
@@ -137,7 +149,7 @@ export default {
137149
type: 'Info',
138150
text: 'Worker initialization is delayed. Please try saving or typing to begin linting.',
139151
range: Helpers.rangeFromLineNumber(textEditor, 0)
140-
}]);
152+
}])
141153
}
142154

143155
return this.worker.request('JOB', {

lib/worker.js

Lines changed: 42 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -3,101 +3,47 @@
33
process.title = 'linter-eslint helper'
44

55
const CP = require('childprocess-promise')
6-
const ChildProcess = require('child_process')
6+
const execFileSync = require('child_process').execFileSync
77
const Path = require('path')
8-
const FS = require('fs')
8+
99
const resolveEnv = require('resolve-env')
10+
const Helpers = require('./es5-helpers')
1011

12+
const findEslintDir = Helpers.findEslintDir
13+
const find = Helpers.find
14+
const determineConfigFile = Helpers.determineConfigFile
15+
const getEslintCli = Helpers.getEslintCli
1116
const Communication = new CP()
1217

18+
// closed-over module-scope variables
1319
let eslintPath = null
14-
const eslintPathLocal = Path.join(FS.realpathSync(Path.join(__dirname, '..')), 'node_modules', 'eslint')
1520
let eslint = null
16-
let prefixPath = null
17-
18-
function find(startDir, names) {
19-
let localNames;
20-
if (typeof names === 'string') {
21-
localNames = [names]
22-
} else {
23-
localNames = names
24-
}
25-
const chunks = startDir.split(Path.sep)
26-
while (chunks.length) {
27-
const currentDirectory = Path.join(chunks.join(Path.sep))
28-
for (let index = 0; index < localNames.length; index++) {
29-
const filePath = Path.join(currentDirectory, localNames[index])
30-
try {
31-
FS.accessSync(filePath, FS.R_OK)
32-
return filePath
33-
} catch (_) { }
34-
}
35-
36-
chunks.pop()
37-
}
38-
return null
39-
}
4021

4122
Communication.on('JOB', function(job) {
4223
const params = job.Message
24+
const modulesPath = find(params.fileDir, 'node_modules')
25+
const eslintignoreDir = Path.dirname(find(params.fileDir, '.eslintignore'))
4326
let configFile = null
44-
let configInPackage = false
4527
global.__LINTER_RESPONSE = []
4628

47-
configFile = find(params.fileDir, ['.eslintrc.js', '.eslintrc.yaml', '.eslintrc.yml', '.eslintrc.json', '.eslintrc'])
48-
if (!configFile) {
49-
const packagePath = find(params.fileDir, 'package.json')
50-
if (packagePath) {
51-
configInPackage = Boolean(require(packagePath).eslintConfig)
52-
}
53-
}
54-
if (params.canDisable && !configFile && !configInPackage) {
29+
// Check for config file and determine whether to bail out
30+
configFile = determineConfigFile(params)
31+
32+
if (params.canDisable && configFile === null) {
5533
job.Response = []
5634
return
57-
} else if (params.configFile) {
58-
configFile = params.configFile
5935
}
6036

61-
const modulesPath = find(params.fileDir, 'node_modules')
62-
const eslintignoreDir = Path.dirname(find(params.fileDir, '.eslintignore'))
63-
let eslintNewPath = null
6437
if (modulesPath) {
6538
process.env.NODE_PATH = modulesPath
6639
} else process.env.NODE_PATH = ''
6740
require('module').Module._initPaths()
6841

69-
70-
if (params.global) {
71-
if (params.nodePath === '' && prefixPath === null) {
72-
const npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm'
73-
try {
74-
prefixPath = ChildProcess.spawnSync(npmCommand, ['get', 'prefix']).output[1].toString().trim()
75-
} catch (e) {
76-
throw new Error('Unable to execute `npm get prefix`. Please make sure Atom is getting $PATH correctly')
77-
}
78-
}
79-
if (process.platform === 'win32') {
80-
eslintNewPath = Path.join(params.nodePath || prefixPath, 'node_modules', 'eslint')
81-
} else {
82-
eslintNewPath = Path.join(params.nodePath || prefixPath, 'lib', 'node_modules', 'eslint')
83-
}
84-
} else {
85-
try {
86-
FS.accessSync(eslintNewPath = Path.join(modulesPath, 'eslint'), FS.R_OK)
87-
} catch (_) {
88-
eslintNewPath = eslintPathLocal
89-
}
90-
}
91-
42+
// Determine which eslint instance to use
43+
const eslintNewPath = findEslintDir(params)
9244
if (eslintNewPath !== eslintPath) {
93-
try {
94-
eslint = require(Path.join(eslintNewPath, 'lib', 'cli.js'))
95-
eslintPath = eslintNewPath
96-
} catch (e) {
97-
if (e.code === 'MODULE_NOT_FOUND') {
98-
throw new Error('ESLint not found, Please install or make sure Atom is getting $PATH correctly')
99-
} else throw e
100-
}
45+
eslint = getEslintCli(eslintNewPath)
46+
eslintPath = eslintNewPath
10147
}
10248

10349
job.Response = new Promise(function(resolve) {
@@ -136,4 +82,28 @@ Communication.on('JOB', function(job) {
13682
})
13783
})
13884

85+
Communication.on('FIX', function(fixJob) {
86+
const params = fixJob.Message
87+
const eslintDir = findEslintDir(params)
88+
const configFile = determineConfigFile(params)
89+
const eslintBinPath = Path.normalize(Path.join(eslintDir, 'bin', 'eslint.js'))
90+
91+
const argv = [
92+
params.filePath,
93+
'--fix'
94+
]
95+
if (configFile !== null) {
96+
argv.push('--config', resolveEnv(configFile))
97+
}
98+
99+
fixJob.Response = new Promise(function(resolve, reject) {
100+
try {
101+
execFileSync(eslintBinPath, argv, {cwd: params.fileDir})
102+
} catch (err) {
103+
reject('Linter-ESLint: Fix Attempt Completed, Linting Errors Remain')
104+
}
105+
resolve('Linter-ESLint: Fix Complete')
106+
})
107+
})
108+
139109
process.exit = function() { /* Stop eslint from closing the daemon */ }

package.json

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,11 @@
99
"engines": {
1010
"atom": ">0.50.0"
1111
},
12+
"scripts": {
13+
"lint": "eslint ."
14+
},
1215
"dependencies": {
13-
"atom-linter": "^3.3.9",
16+
"atom-linter": "^3.4.0",
1417
"atom-package-deps": "^3.0.5",
1518
"childprocess-promise": "^3.0.0",
1619
"escape-html": "^1.0.3",
@@ -22,7 +25,8 @@
2225
"eslint-config-airbnb": "latest",
2326
"eslint-config-steelbrain": "latest",
2427
"eslint-plugin-import": "^0.11.0",
25-
"eslint-plugin-react": "^3.10.0"
28+
"eslint-plugin-react": "^3.10.0",
29+
"shelljs": "^0.5.3"
2630
},
2731
"package-deps": [
2832
"linter"
@@ -39,7 +43,8 @@
3943
"rules": {
4044
"no-empty": 0,
4145
"no-console": 0,
42-
"no-new": 0
46+
"no-new": 0,
47+
"semi": [2, "never"]
4348
},
4449
"parser": "babel-eslint",
4550
"globals": {

0 commit comments

Comments
 (0)