diff --git a/.eslintignore b/.eslintignore index 59dc28f1..9011ad48 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1 +1,2 @@ spec/fixtures/**/*.js +lib/**/*.js diff --git a/.gitignore b/.gitignore index 07ff0ddf..360b2a64 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,4 @@ -node_modules +/lib/ +/node_modules/ *.log .idea - -# Keep node_modules test fixtures -!spec/fixtures/local-eslint/node_modules diff --git a/.travis.yml b/.travis.yml index 274d083f..743cd919 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,4 +10,7 @@ notifications: on_failure: change os: osx -script: 'curl -s https://raw.githubusercontent.com/atom/ci/master/build-package.sh | sh' +script: + - npm i + - npm run compile + - 'curl -s https://raw.githubusercontent.com/atom/ci/master/build-package.sh | sh' diff --git a/.ucompilerrc b/.ucompilerrc new file mode 100644 index 00000000..59cea202 --- /dev/null +++ b/.ucompilerrc @@ -0,0 +1,10 @@ +{ + "plugins": ["babel"], + "rules": [{ + "path": "src/*.js", + "outputPath": "lib/{name}.js", + "babel": { + "presets": ["steelbrain", "steelbrain-legacy"] + } + }] +} diff --git a/appveyor.yml b/appveyor.yml index 94066a95..51b2fc0f 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -14,5 +14,7 @@ build_script: - cd %APPVEYOR_BUILD_FOLDER% - SET PATH=%LOCALAPPDATA%\atom\bin;%PATH% - apm clean + - npm i + - npm run compile - apm install - apm test diff --git a/circle.yml b/circle.yml index cc91716f..daf8ff20 100644 --- a/circle.yml +++ b/circle.yml @@ -1,13 +1,15 @@ dependencies: - override: - - wget -O atom-amd64.deb https://atom.io/download/deb + pre: - sudo apt-get update + - sudo apt-get install wget + post: + - wget -O atom-amd64.deb https://atom.io/download/deb - sudo dpkg --install atom-amd64.deb || true - sudo apt-get -f install -y - - atom -v - - apm install test: override: - - ./node_modules/.bin/eslint lib - - ./node_modules/.bin/eslint spec + - npm run lint + - npm run compile + - atom -v + - apm install - apm test diff --git a/lib/es5-helpers.js b/lib/es5-helpers.js deleted file mode 100644 index a1591855..00000000 --- a/lib/es5-helpers.js +++ /dev/null @@ -1,80 +0,0 @@ -'use strict' - -const ChildProcess = require('child_process') -const Path = require('path') -const FS = require('fs') -const find = require('atom-linter').findFile - -let prefixPath = null -const atomEslintPath = Path.join(FS.realpathSync(Path.join(__dirname, '..')), 'node_modules', 'eslint') - -function findEslintDir(params) { - const modulesPath = find(params.fileDir, 'node_modules') - let eslintNewPath = null - - if (params.global) { - if (params.nodePath === '' && prefixPath === null) { - const npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm' - try { - prefixPath = ChildProcess.spawnSync(npmCommand, ['get', 'prefix']).output[1].toString().trim() - } catch (e) { - throw new Error('Unable to execute `npm get prefix`. Please make sure Atom is getting $PATH correctly') - } - } - if (process.platform === 'win32') { - eslintNewPath = Path.join(params.nodePath || prefixPath, 'node_modules', 'eslint') - } else { - eslintNewPath = Path.join(params.nodePath || prefixPath, 'lib', 'node_modules', 'eslint') - } - } else { - try { - FS.accessSync(eslintNewPath = Path.join(modulesPath, 'eslint'), FS.R_OK) - } catch (_) { - eslintNewPath = atomEslintPath - } - } - - return eslintNewPath -} - -// Check for project config file or eslint config in package.json and determine -// whether to bail out or use config specified in package options -function determineConfigFile(params) { - // config file - const configFile = find(params.fileDir, ['.eslintrc.js', '.eslintrc.yaml', '.eslintrc.yml', '.eslintrc.json', '.eslintrc']) || null - if (configFile) { - return configFile - } - // package.json - const packagePath = find(params.fileDir, 'package.json') - if (packagePath && Boolean(require(packagePath).eslintConfig)) { - return packagePath - } - // Couldn't find a config - if (params.canDisable) { - return null - } - // If all else fails, use the configFile specified in the linter-eslint options - if (params.configFile) { - return params.configFile - } -} - -function getEslintCli(path) { - try { - const eslint = require(Path.join(path, 'lib', 'cli.js')) - return eslint - } catch (e) { - if (e.code === 'MODULE_NOT_FOUND') { - throw new Error('ESLint not found, Please install or make sure Atom is getting $PATH correctly') - } else throw e - } -} - - -module.exports = { - findEslintDir: findEslintDir, - find: find, - determineConfigFile: determineConfigFile, - getEslintCli: getEslintCli -} diff --git a/lib/helpers.js b/lib/helpers.js deleted file mode 100644 index 9665e46e..00000000 --- a/lib/helpers.js +++ /dev/null @@ -1,36 +0,0 @@ -'use babel' - -import ChildProcess from 'child_process' -import CP from 'childprocess-promise' - -export function spawnWorker() { - let shouldLive = true - const env = Object.create(process.env) - delete env.NODE_PATH - delete env.NODE_ENV - const data = {stdout: [], stderr: []} - const child = ChildProcess.fork(__dirname + '/worker.js', [], {env, silent: true}) - const worker = new CP(child) - function killer() { - shouldLive = false - child.kill() - } - child.stdout.on('data', function(chunk) { - data.stdout.push(chunk) - }) - child.stderr.on('data', function(chunk) { - data.stderr.push(chunk) - }) - child.on('exit', function() { - if (shouldLive) { - console.log('ESLint Worker Info', {stdout: data.stdout.join(''), stderr: data.stderr.join('')}) - atom.notifications.addWarning('[Linter-ESLint] Worker died unexpectedly', {detail: 'Check your console for more info. A new worker will be spawned instantly.', dismissable: true}) - } - child.emit('exit-linter', shouldLive) - }) - process.on('exit', killer) - return {child, worker, subscription: {dispose: function() { - killer() - process.removeListener('exit', killer) - }}} -} diff --git a/lib/worker.js b/lib/worker.js deleted file mode 100644 index 3a950187..00000000 --- a/lib/worker.js +++ /dev/null @@ -1,109 +0,0 @@ -'use strict' -// Note: 'use babel' doesn't work in forked processes -process.title = 'linter-eslint helper' - -const CP = require('childprocess-promise') -const execFileSync = require('child_process').execFileSync -const Path = require('path') - -const resolveEnv = require('resolve-env') -const Helpers = require('./es5-helpers') - -const findEslintDir = Helpers.findEslintDir -const find = Helpers.find -const determineConfigFile = Helpers.determineConfigFile -const getEslintCli = Helpers.getEslintCli -const Communication = new CP() - -// closed-over module-scope variables -let eslintPath = null -let eslint = null - -Communication.on('JOB', function(job) { - const params = job.Message - const modulesPath = find(params.fileDir, 'node_modules') - const eslintignoreDir = Path.dirname(find(params.fileDir, '.eslintignore')) - let configFile = null - global.__LINTER_RESPONSE = [] - - // Check for config file and determine whether to bail out - configFile = determineConfigFile(params) - - if (params.canDisable && configFile === null) { - job.Response = [] - return - } - - if (modulesPath) { - process.env.NODE_PATH = modulesPath - } else process.env.NODE_PATH = '' - require('module').Module._initPaths() - - // Determine which eslint instance to use - const eslintNewPath = findEslintDir(params) - if (eslintNewPath !== eslintPath) { - eslint = getEslintCli(eslintNewPath) - eslintPath = eslintNewPath - } - - job.Response = new Promise(function(resolve) { - let filePath - if (eslintignoreDir) { - filePath = Path.relative(eslintignoreDir, params.filePath) - process.chdir(eslintignoreDir) - } else { - filePath = Path.basename(params.filePath) - process.chdir(params.fileDir) - } - const argv = [ - process.execPath, - eslintPath, - '--stdin', - '--format', - Path.join(__dirname, 'reporter.js') - ] - if (params.rulesDir) { - let rulesDir = resolveEnv(params.rulesDir) - if (!Path.isAbsolute(rulesDir)) { - rulesDir = find(params.fileDir, rulesDir) - } - argv.push('--rulesdir', rulesDir) - } - if (configFile !== null) { - argv.push('--config', resolveEnv(configFile)) - } - if (params.disableIgnores) { - argv.push('--no-ignore') - } - argv.push('--stdin-filename', filePath) - process.argv = argv - eslint.execute(process.argv, params.contents) - resolve(global.__LINTER_RESPONSE) - }) -}) - -Communication.on('FIX', function(fixJob) { - const params = fixJob.Message - const eslintDir = findEslintDir(params) - const configFile = determineConfigFile(params) - const eslintBinPath = Path.normalize(Path.join(eslintDir, 'bin', 'eslint.js')) - - const argv = [ - params.filePath, - '--fix' - ] - if (configFile !== null) { - argv.push('--config', resolveEnv(configFile)) - } - - fixJob.Response = new Promise(function(resolve, reject) { - try { - execFileSync(eslintBinPath, argv, {cwd: params.fileDir}) - } catch (err) { - reject('Linter-ESLint: Fix Attempt Completed, Linting Errors Remain') - } - resolve('Linter-ESLint: Fix Complete') - }) -}) - -process.exit = function() { /* Stop eslint from closing the daemon */ } diff --git a/package.json b/package.json index 934cee9d..e11c0b4a 100644 --- a/package.json +++ b/package.json @@ -7,10 +7,13 @@ "repository": "https://github.com/AtomLinter/linter-eslint.git", "license": "MIT", "engines": { - "atom": ">0.50.0" + "atom": ">=1.0.0" }, "scripts": { - "lint": "eslint ." + "clean": "rm -rf lib", + "lint": "eslint .", + "compile": "babel src --out-dir lib", + "apm-publish": "npm run clean && npm run compile && apm publish" }, "dependencies": { "atom-linter": "^3.4.0", @@ -21,7 +24,10 @@ "resolve-env": "^1.0.0" }, "devDependencies": { - "babel-eslint": "^4.1.5", + "babel-cli": "^6.2.0", + "babel-eslint": "^4.1.6", + "babel-preset-steelbrain": "^1.0.0", + "babel-preset-steelbrain-legacy": "^1.0.0", "eslint-config-airbnb": "latest", "eslint-config-steelbrain": "latest", "eslint-plugin-import": "^0.11.0", @@ -44,7 +50,10 @@ "no-empty": 0, "no-console": 0, "no-new": 0, - "semi": [2, "never"] + "semi": [ + 2, + "never" + ] }, "parser": "babel-eslint", "globals": { diff --git a/spec/es5-helpers-spec.js b/spec/es5-helpers-spec.js index 0c99df18..01bb16ec 100644 --- a/spec/es5-helpers-spec.js +++ b/spec/es5-helpers-spec.js @@ -7,7 +7,7 @@ import { determineConfigFile, findEslintDir, getEslintCli -} from '../lib/es5-helpers' +} from '../lib/helpers' let fixtureDir diff --git a/src/helpers.js b/src/helpers.js new file mode 100644 index 00000000..27664a33 --- /dev/null +++ b/src/helpers.js @@ -0,0 +1,138 @@ +'use babel' + +import Path from 'path' +import FS from 'fs' +import ChildProcess from 'child_process' +import CP from 'childprocess-promise' +import {findFile as find} from 'atom-linter' + +export function spawnWorker() { + let shouldLive = true + const env = Object.create(process.env) + delete env.NODE_PATH + delete env.NODE_ENV + const data = {stdout: [], stderr: []} + const child = ChildProcess.fork(__dirname + '/worker.js', [], {env, silent: true}) + const worker = new CP(child) + function killer() { + shouldLive = false + child.kill() + } + child.stdout.on('data', function(chunk) { + data.stdout.push(chunk) + }) + child.stderr.on('data', function(chunk) { + data.stderr.push(chunk) + }) + child.on('exit', function() { + if (shouldLive) { + console.log('ESLint Worker Info', {stdout: data.stdout.join(''), stderr: data.stderr.join('')}) + atom.notifications.addWarning('[Linter-ESLint] Worker died unexpectedly', {detail: 'Check your console for more info. A new worker will be spawned instantly.', dismissable: true}) + } + child.emit('exit-linter', shouldLive) + }) + process.on('exit', killer) + return {child, worker, subscription: {dispose: function() { + killer() + process.removeListener('exit', killer) + }}} +} + +export function getModulesDirectory(fileDir) { + return find(fileDir, 'node_modules') +} + +export function getIgnoresFile(fileDir) { + return Path.dirname(find(fileDir, '.eslintignore')) +} + +export function getEslintFromDirectory(path) { + try { + return require(Path.join(path, 'lib', 'cli.js')) + } catch (e) { + if (e.code === 'MODULE_NOT_FOUND') { + throw new Error('ESLint not found, Please install or make sure Atom is getting $PATH correctly') + } else throw e + } +} + +let nodePrefixPath = null + +export function getNodePrefixPath() { + if (nodePrefixPath === null) { + const npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm' + try { + nodePrefixPath = ChildProcess.spawnSync(npmCommand, ['get', 'prefix']).output[1].toString().trim() + } catch (e) { + throw new Error('Unable to execute `npm get prefix`. Please make sure Atom is getting $PATH correctly') + } + } + return nodePrefixPath +} + +let bundledEslintDirectory = null + +export function getBundledEslintDirectory() { + if (bundledEslintDirectory === null) { + bundledEslintDirectory = Path.normalize(Path.join(__dirname, '..', 'node_modules', 'eslint')) + } + return bundledEslintDirectory +} + +export function getEslintDirectory(params, modulesPath = null) { + if (params.global) { + const prefixPath = getNodePrefixPath() + if (process.platform === 'win32') { + return Path.join(params.nodePath || prefixPath, 'node_modules', 'eslint') + } + return Path.join(params.nodePath || prefixPath, 'lib', 'node_modules', 'eslint') + } + const eslintPath = Path.join(modulesPath || getModulesDirectory(params.fileDir), 'eslint') + try { + FS.accessSync(eslintPath, FS.R_OK) + return eslintPath + } catch (_) { + return getBundledEslintDirectory() + } +} + +export function getEslintConfig(params) { + const configFile = find(params.fileDir, ['.eslintrc.js', '.eslintrc.yaml', '.eslintrc.yml', '.eslintrc.json', '.eslintrc']) || null + if (configFile) { + return configFile + } + + const packagePath = find(params.fileDir, 'package.json') + if (packagePath && Boolean(require(packagePath).eslintConfig)) { + return packagePath + } + + if (params.canDisable) { + return null + } + + if (params.configFile) { + return params.configFile + } +} + +let eslint +let lastEslintDirectory +let lastModulesPath + +export function getEslint(params) { + const modulesPath = getModulesDirectory(params.fileDir) + const eslintDirectory = getEslintDirectory(params, modulesPath) + if (eslintDirectory !== lastEslintDirectory) { + lastEslintDirectory = eslintDirectory + eslint = getEslintFromDirectory(eslintDirectory) + } + if (lastModulesPath !== modulesPath) { + lastModulesPath = modulesPath + if (modulesPath) { + process.env.NODE_PATH = modulesPath + } else process.env.NODE_PATH = '' + require('module').Module._initPaths() + } + return {eslint, eslintDirectory} +} diff --git a/lib/main.js b/src/main.js similarity index 87% rename from lib/main.js rename to src/main.js index e231856b..5f48bc12 100644 --- a/lib/main.js +++ b/src/main.js @@ -83,12 +83,6 @@ export default { return } - if (this.worker === null) { - // Abort if worker is not yet ready - atom.notifications.addError('Linter-ESLint: Not ready, please try again') - return - } - this.worker.request('FIX', { fileDir: fileDir, filePath: filePath, @@ -103,10 +97,6 @@ export default { } })) - // Reason: I (steelbrain) have observed that if we spawn a - // process while atom is starting up, it can increase startup - // time by several seconds, But if we do this after 5 seconds, - // we barely feel a thing. const initializeWorker = () => { if (this.active) { const {child, worker, subscription} = spawnWorker() @@ -121,7 +111,7 @@ export default { }) } } - setTimeout(initializeWorker, 5 * 1000) + initializeWorker() }, deactivate: function() { this.active = false @@ -143,15 +133,6 @@ export default { const fileDir = Path.dirname(filePath) const showRule = atom.config.get('linter-eslint.showRuleIdInMessage') - if (this.worker === null) { - return Promise.resolve([{ - filePath: filePath, - type: 'Info', - text: 'Worker initialization is delayed. Please try saving or typing to begin linting.', - range: Helpers.rangeFromLineNumber(textEditor, 0) - }]) - } - return this.worker.request('JOB', { fileDir: fileDir, filePath: filePath, diff --git a/lib/reporter.js b/src/reporter.js similarity index 100% rename from lib/reporter.js rename to src/reporter.js diff --git a/src/worker.js b/src/worker.js new file mode 100644 index 00000000..32cb7506 --- /dev/null +++ b/src/worker.js @@ -0,0 +1,89 @@ +'use strict' +// Note: 'use babel' doesn't work in forked processes +process.title = 'linter-eslint helper' + +import Path from 'path' +import CP from 'childprocess-promise' +import resolveEnv from 'resolve-end' +import * as Helpers from './helpers' + +const Communication = new CP() + +Communication.on('JOB', function(Job) { + global.__LINTER_RESPONSE = [] + + const params = Job.Message + const ignoreFile = Helpers.getIgnoresFile(params.fileDir) + const configFile = Helpers.getEslintConfig(params.fileDir) + const {eslint, eslintDirectory} = Helpers.getEslint(params) + + if (params.canDisable && configFile === null) { + Job.Response = [] + return Job.Response + } + + + Job.Response = new Promise(function(resolve) { + let filePath + if (ignoreFile) { + filePath = Path.relative(ignoreFile, params.filePath) + process.chdir(ignoreFile) + } else { + filePath = Path.basename(params.filePath) + process.chdir(params.fileDir) + } + + const argv = [ + process.execPath, + eslintDirectory, + '--stdin', + '--format', + Path.join(__dirname, 'reporter.js') + ] + if (params.rulesDir) { + let rulesDir = resolveEnv(params.rulesDir) + if (!Path.isAbsolute(rulesDir)) { + rulesDir = find(params.fileDir, rulesDir) + } + argv.push('--rulesdir', rulesDir) + } + if (configFile !== null) { + argv.push('--config', resolveEnv(configFile)) + } + if (params.disableIgnores) { + argv.push('--no-ignore') + } + argv.push('--stdin-filename', filePath) + process.argv = argv + + eslint.execute(process.argv, params.contents) + resolve(global.__LINTER_RESPONSE) + }) +}) + +Communication.on('FIX', function(Job) { + const params = Job.Message + const {eslint, eslintDirectory} = Helpers.getEslint(params) + const configFile = Helpers.getEslintConfig(params) + + const argv = [ + process.execPath, + eslintDirectory, + params.filePath, + '--fix' + ] + if (configFile !== null) { + argv.push('--config', resolveEnv(configFile)) + } + process.argv = argv + process.chdir(params.fileDir) + + try { + eslint.execute(process.argv) + } catch (_) { + throw new Error('Linter-ESLint: Fix Attempt Completed, Linting Errors Remain') + } + return 'Linter-ESLint: Fix Complete' +}) + +process.exit = function() { /* Stop eslint from closing the daemon */ }