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

Implement feature request #189 (Option to use global tslint instance) #196

Merged
merged 1 commit into from
Sep 22, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions lib/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
}),
Expand Down
85 changes: 66 additions & 19 deletions lib/worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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) {
Expand Down Expand Up @@ -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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🐛 baseDir please 😉

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wasn't sure how you wanted me to deal with that, since it previously was called basedir, apparently for the sake of brevity on line 71. I guess changing that line to { basedir: baseDir } wouldn't be the end of the world.

return new Promise((resolve) => {
if (!requireResolve) {
requireResolve = require('resolve');
Expand All @@ -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);
},
);
});
Expand All @@ -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) {
Expand Down Expand Up @@ -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') {
Expand Down
50 changes: 35 additions & 15 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you're going to add order to these, re-order them in package.json itself to match the order 😉.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hah, I don't know why I didn't just do that to begin with. I guess I was a bit shy contributing to a new repo and trying to keep my "lines modified" count down. 😝 It's fixed!

},
"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",
Expand Down