alm
Version:
The best IDE for TypeScript
271 lines (270 loc) • 10.4 kB
JavaScript
;
/**
* The heart of the linter
*/
Object.defineProperty(exports, "__esModule", { value: true });
/**
* Load up TypeScript
*/
var byots = require("byots");
var ensureImport = byots;
var sw = require("../../utils/simpleWorker");
var contract = require("./lintContract");
var utils_1 = require("../../../common/utils");
var utils = require("../../../common/utils");
var types = require("../../../common/types");
var languageServiceHostNode_1 = require("../../../languageServiceHost/languageServiceHostNode");
var errorsCache_1 = require("../../utils/errorsCache");
/** Bring in tslint */
var tslint_1 = require("tslint");
/** The linter currently in use */
var Linter = tslint_1.Linter;
/**
* Horrible file access :)
*/
var fsu = require("../../utils/fsu");
var linterMessagePrefix = "[LINT]";
var Worker;
(function (Worker) {
Worker.setProjectData = function (data) {
/** Load a local linter if any */
var basedir = utils.getDirectory(data.configFile.projectFilePath);
return getLocalLinter(basedir).then(function (linter) {
Linter = linter;
LinterImplementation.setProjectData(data);
return {};
});
};
Worker.fileSaved = function (data) {
LinterImplementation.fileSaved(data);
return utils_1.resolve({});
};
})(Worker || (Worker = {}));
// Ensure that the namespace follows the contract
var _checkTypes = Worker;
// run worker
exports.master = sw.runWorker({
workerImplementation: Worker,
masterContract: contract.master
}).master;
/**
* The actual linter stuff lives in this namespace
*/
var LinterImplementation;
(function (LinterImplementation) {
var linterConfig = null;
/** We only do this once per project change */
var informedUserAboutMissingConfig = false;
/** Our error cache */
var errorCache = new errorsCache_1.ErrorsCache();
errorCache.errorsDelta.on(exports.master.receiveErrorCacheDelta);
/**
* This is the entry point for the linter to start its work
*/
function setProjectData(projectData) {
/** Reinit */
errorCache.clearErrors();
informedUserAboutMissingConfig = false;
linterConfig = null;
/**
* Create the program
*/
var languageServiceHost = new languageServiceHostNode_1.LanguageServiceHost(undefined, projectData.configFile.project.compilerOptions);
// Add all the files
projectData.filePathWithContents.forEach(function (_a) {
var filePath = _a.filePath, contents = _a.contents;
languageServiceHost.addScript(filePath, contents);
});
// And for incremental ones lint again
languageServiceHost.incrementallyAddedFile.on(function (data) {
// console.log(data); // DEBUG
loadLintConfigAndLint();
});
var languageService = ts.createLanguageService(languageServiceHost, ts.createDocumentRegistry());
/**
* We must call get program before making any changes to the files otherwise TypeScript throws up
* we don't actually use the program just yet :)
*/
var program = languageService.getProgram();
/**
* Now create the tslint config
*/
linterConfig = {
projectData: projectData,
ls: languageService,
lsh: languageServiceHost,
};
loadLintConfigAndLint();
}
LinterImplementation.setProjectData = setProjectData;
/**
* Called whenever
* - a file is edited
* - added to the compilation context
*/
function loadLintConfigAndLint() {
linterConfig.linterConfig = undefined;
errorCache.clearErrors();
/** Look for tslint.json by findup from the project dir */
var projectDir = linterConfig.projectData.configFile.projectFileDirectory;
var configurationPath = Linter.findConfigurationPath(null, projectDir);
// console.log({configurationPath}); // DEBUG
/** lint abort if the config is not ready present yet */
if (!configurationPath) {
if (!informedUserAboutMissingConfig) {
informedUserAboutMissingConfig = true;
console.log(linterMessagePrefix, 'No tslint configuration found.');
}
return;
}
/** We have our configuration file. Now lets convert it to configuration :) */
var configuration;
try {
configuration = Linter.loadConfigurationFromPath(configurationPath);
}
catch (err) {
console.log(linterMessagePrefix, 'Invalid config:', configurationPath);
errorCache.setErrorsByFilePaths([configurationPath], [types.makeBlandError(configurationPath, err.message, 'linter')]);
return;
}
/** Also need to setup the rules directory */
var possiblyRelativeRulesDirectory = configuration.rulesDirectory;
var rulesDirectory = Linter.getRulesDirectories(possiblyRelativeRulesDirectory, configurationPath);
/**
* The linter config is now also good to go
*/
linterConfig.linterConfig = {
configuration: configuration, rulesDirectory: rulesDirectory
};
/** Now start the lazy lint */
lintWithCancellationToken();
}
/** lint support cancellation token */
var cancellationToken = utils.cancellationToken();
function lintWithCancellationToken() {
/** Cancel any previous */
if (cancellationToken) {
cancellationToken.cancel();
cancellationToken = utils.cancellationToken();
}
var program = linterConfig.ls.getProgram();
var sourceFiles = program.getSourceFiles()
.filter(function (x) { return !x.isDeclarationFile; });
console.log(linterMessagePrefix, 'About to start linting files: ', sourceFiles.length); // DEBUG
// Note: tslint is a big stingy with its definitions so we use `any` to make our ts def compat with its ts defs.
var lintprogram = program;
/** Used to push to the errorCache */
var filePaths = [];
var errors = [];
var time = utils_1.timer();
/** create the Linter for each file and get its output */
utils
.cancellableForEach({
cancellationToken: cancellationToken,
items: sourceFiles,
cb: (function (sf) {
var filePath = sf.fileName;
var contents = sf.getFullText();
var linter = new Linter({
rulesDirectory: linterConfig.linterConfig.rulesDirectory,
fix: false,
}, lintprogram);
linter.lint(filePath, contents, linterConfig.linterConfig.configuration);
var lintResult = linter.getResult();
filePaths.push(filePath);
if (lintResult.errorCount || lintResult.warningCount) {
// console.log(linterMessagePrefix, filePath, lintResult.failureCount); // DEBUG
errors = errors.concat(lintResult.failures.map(function (le) { return lintErrorToCodeError(le, contents); }));
}
})
})
.then(function (res) {
/** Push to errorCache */
errorCache.setErrorsByFilePaths(filePaths, errors);
console.log(linterMessagePrefix, 'Lint complete', time.seconds);
})
.catch(function (e) {
if (e === utils.cancelled) {
console.log(linterMessagePrefix, 'Lint cancelled');
}
else {
console.log(linterMessagePrefix, 'Linter crashed', e);
}
});
}
function fileSaved(_a) {
var filePath = _a.filePath;
if (!linterConfig) {
return;
}
/** tslint : do the whole thing */
if (filePath.endsWith('tslint.json')) {
loadLintConfigAndLint();
return;
}
/**
* Now only proceed further if we have a linter config
* and the file is a ts file
* and in the current project
*/
if (!linterConfig.linterConfig) {
return;
}
if (!filePath.endsWith('.ts')) {
return;
}
var sf = linterConfig.ls.getProgram().getSourceFiles().find(function (sf) { return sf.fileName === filePath; });
if (!sf) {
return;
}
/** Update the file contents (so that when we get the program it just works) */
linterConfig.lsh.setContents(filePath, fsu.readFile(sf.fileName));
/**
* Since we use program and types flow we would still need to lint the whole thing
*/
lintWithCancellationToken();
}
LinterImplementation.fileSaved = fileSaved;
/** Utility */
function lintErrorToCodeError(lintError, contents) {
var start = lintError.getStartPosition().getLineAndCharacter();
var end = lintError.getEndPosition().getLineAndCharacter();
var preview = contents.substring(lintError.getStartPosition().getPosition(), lintError.getEndPosition().getPosition());
var result = {
source: 'linter',
filePath: lintError.getFileName(),
message: lintError.getFailure(),
from: {
line: start.line,
ch: start.character
},
to: {
line: end.line,
ch: end.character
},
preview: preview,
level: 'warning',
};
return result;
}
})(LinterImplementation || (LinterImplementation = {}));
/**
* From
* https://github.com/AtomLinter/linter-tslint/blob/2c8e99da92f9f2392adf26f5dd49d78a1a4ef753/lib/main.js
*/
var TSLINT_MODULE_NAME = 'tslint';
var requireResolve = require("resolve");
function getLocalLinter(basedir) {
return new Promise(function (resolve) {
return requireResolve(TSLINT_MODULE_NAME, { basedir: basedir }, function (err, linterPath, pkg) {
var linter;
if (!err && pkg && /^4\./.test(pkg.version)) {
linter = require(linterPath).Linter;
}
else {
linter = tslint_1.Linter;
}
return resolve(linter);
});
});
}