diff --git a/lib/main.js b/lib/main.js index 1770ab5d..5208021d 100644 --- a/lib/main.js +++ b/lib/main.js @@ -12,6 +12,8 @@ const idleCallbacks = new Set(); const config = { rulesDirectory: null, useLocalTslint: false, + useGlobalTslint: false, + globalNodePath: null, }; // Worker still hasn't initialized, since the queued idle callbacks are @@ -59,6 +61,14 @@ export default { config.enableSemanticRules = enableSemanticRules; workerHelper.changeConfig('enableSemanticRules', enableSemanticRules); }), + atom.config.observe('linter-tslint.useGlobalTslint', (use) => { + config.useGlobalTslint = use; + workerHelper.changeConfig('useGlobalTslint', use); + }), + atom.config.observe('linter-tslint.globalNodePath', (globalNodePath) => { + config.globalNodePath = globalNodePath; + workerHelper.changeConfig('globalNodePath', globalNodePath); + }), atom.config.observe('linter-tslint.ignoreTypings', (ignoreTypings) => { this.ignoreTypings = ignoreTypings; }), diff --git a/lib/worker.js b/lib/worker.js index 911e351b..4d57898a 100644 --- a/lib/worker.js +++ b/lib/worker.js @@ -6,6 +6,8 @@ import escapeHTML from 'escape-html'; import fs from 'fs'; import path from 'path'; import { getRuleUri } from 'tslint-rule-documentation'; +import ChildProcess from 'child_process'; +import getPath from 'consistent-path'; process.title = 'linter-tslint worker'; @@ -15,10 +17,10 @@ const config = { useLocalTslint: false, }; -let tslintDef; +let fallbackLinter; let requireResolve; -async function stat(pathname) { +function stat(pathname) { return new Promise((resolve, reject) => { fs.stat(pathname, (err, stats) => { if (err) { @@ -58,14 +60,8 @@ function shim(Linter) { return LinterShim; } -function loadDefaultTSLint() { - if (!tslintDef) { - // eslint-disable-next-line import/no-dynamic-require - tslintDef = require(tslintModuleName).Linter; - } -} - -async function getLocalLinter(basedir) { +function resolveAndCacheLinter(fileDir, moduleDir) { + const basedir = moduleDir || fileDir; return new Promise((resolve) => { if (!requireResolve) { requireResolve = require('resolve'); @@ -83,11 +79,26 @@ async function getLocalLinter(basedir) { // eslint-disable-next-line import/no-dynamic-require linter = require('loophole').allowUnsafeNewFunction(() => require(linterPath).Linter); } + tslintCache.set(fileDir, linter); + } + resolve(linter); + }, + ); + }); +} + +function getNodePrefixPath() { + return new Promise((resolve, reject) => { + const npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm'; + ChildProcess.exec( + `${npmCommand} get prefix`, + { env: Object.assign(Object.assign({}, process.env), { PATH: getPath() }) }, + (err, stdout, stderr) => { + if (err || stderr) { + reject(err || new Error(stderr)); } else { - linter = tslintDef; + resolve(stdout.trim()); } - tslintCache.set(basedir, linter); - return resolve(linter); }, ); }); @@ -99,15 +110,49 @@ async function getLinter(filePath) { return tslintCache.get(basedir); } - // Initialize the default instance if it hasn't already been initialized - loadDefaultTSLint(); - if (config.useLocalTslint) { - return getLocalLinter(basedir); + const localLint = await resolveAndCacheLinter(basedir); + if (localLint) { + return localLint; + } + } + + if (fallbackLinter) { + tslintCache.set(basedir, fallbackLinter); + return fallbackLinter; + } + + if (config.useGlobalTslint) { + if (config.globalNodePath) { + const globalLint = await resolveAndCacheLinter(basedir, config.globalNodePath); + if (globalLint) { + fallbackLinter = globalLint; + return globalLint; + } + } + + let prefix; + try { + prefix = await getNodePrefixPath(); + } catch (err) { + // eslint-disable-next-line no-console + console.warn(`Attempted to load global tslint, but \`npm get prefix\` +failed. Falling back to the packaged version of tslint. You can specify +your prefix manually in the settings or linter-tslint config file.\n +The error message encountered was:\n\n${err.message}`); + } + + if (prefix) { + const globalLint = await resolveAndCacheLinter(basedir, prefix); + fallbackLinter = globalLint; + return globalLint; + } } - tslintCache.set(basedir, tslintDef); - return tslintDef; + // eslint-disable-next-line import/no-dynamic-require + fallbackLinter = require(tslintModuleName).Linter; + tslintCache.set(basedir, fallbackLinter); + return fallbackLinter; } async function getProgram(Linter, configurationPath) { @@ -208,6 +253,8 @@ async function lint(content, filePath, options) { export default async function (initialConfig) { config.useLocalTslint = initialConfig.useLocalTslint; config.enableSemanticRules = initialConfig.enableSemanticRules; + config.useGlobalTslint = initialConfig.useGlobalTslint; + config.globalNodePath = initialConfig.globalNodePath; process.on('message', async (message) => { if (message.messageType === 'config') { diff --git a/package.json b/package.json index 345cf26f..8aa3e0e1 100644 --- a/package.json +++ b/package.json @@ -16,36 +16,56 @@ "atom": ">=1.14.0 <2.0.0" }, "configSchema": { + "enableSemanticRules": { + "type": "boolean", + "title": "Enable semantic rules", + "description": "Allow passing a TypeScript program object to the linter. May negatively affect performance. See this page for details: https://palantir.github.io/tslint/usage/type-checking/", + "default": false, + "order": 1 + }, "rulesDirectory": { "type": "string", "title": "Custom rules directory (absolute path)", - "default": "" + "default": "", + "order": 2 }, - "useLocalTslint": { - "type": "boolean", - "title": "Try using the local tslint package (if exist)", - "default": true - }, - "enableSemanticRules": { + "fixOnSave": { + "title": "Fix errors on save", + "description": "Have tslint attempt to fix some errors automatically when saving the file.", "type": "boolean", - "title": "Enable semantic rules", - "description": "Allow passing a TypeScript program object to the linter. May negatively affect performance. See this page for details: https://palantir.github.io/tslint/usage/type-checking/", - "default": false + "default": false, + "order": 3 }, "ignoreTypings": { "type": "boolean", "title": "Ignore typings files (.d.ts)", - "default": false + "default": false, + "order": 4 }, - "fixOnSave": { - "title": "Fix errors on save", - "description": "Have tslint attempt to fix some errors automatically when saving the file.", + "useLocalTslint": { + "type": "boolean", + "title": "Try to use the project's local tslint package, if it exists", + "default": true, + "order": 5 + }, + "useGlobalTslint": { "type": "boolean", - "default": false + "title": "Use the global tslint install", + "description": "If enabled, the global tslint installation will be used as a fallback, instead of the version packaged with linter-tslint.", + "default": false, + "order": 6 + }, + "globalNodePath": { + "type": "string", + "title": "Global node installation path", + "description": "The location of your global npm install. (Will default to `npm get prefix`.)", + "default": "", + "order": 7 } }, "dependencies": { "atom-package-deps": "^4.3.1", + "consistent-path": "^2.0.1", "crypto-random-string": "^1.0.0", "escape-html": "^1.0.3", "loophole": "^1.1.0",