UNPKG

alm

Version:

The best IDE for TypeScript

271 lines (270 loc) 10.4 kB
"use strict"; /** * 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); }); }); }