UNPKG

@angular-devkit/build-angular

Version:
186 lines (185 loc) 7.67 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); /** * @license * Copyright Google Inc. All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ const architect_1 = require("@angular-devkit/architect"); const fs_1 = require("fs"); const glob = require("glob"); const minimatch_1 = require("minimatch"); const path = require("path"); const strip_bom_1 = require("../angular-cli-files/utilities/strip-bom"); async function _loadTslint() { let tslint; try { tslint = await Promise.resolve().then(() => require('tslint')); // tslint:disable-line:no-implicit-dependencies } catch (_a) { throw new Error('Unable to find TSLint. Ensure TSLint is installed.'); } const version = tslint.Linter.VERSION && tslint.Linter.VERSION.split('.'); if (!version || version.length < 2 || (Number(version[0]) === 5 && Number(version[1]) < 5) // 5.5+ || Number(version[0]) < 5 // 6.0+ ) { throw new Error('TSLint must be version 5.5 or higher.'); } return tslint; } async function _run(options, context) { const systemRoot = context.workspaceRoot; process.chdir(context.currentDirectory); const projectName = (context.target && context.target.project) || '<???>'; // Print formatter output only for non human-readable formats. const printInfo = ['prose', 'verbose', 'stylish'].includes(options.format || '') && !options.silent; context.reportStatus(`Linting ${JSON.stringify(projectName)}...`); if (printInfo) { context.logger.info(`Linting ${JSON.stringify(projectName)}...`); } if (!options.tsConfig && options.typeCheck) { throw new Error('A "project" must be specified to enable type checking.'); } const projectTslint = await _loadTslint(); const tslintConfigPath = options.tslintConfig ? path.resolve(systemRoot, options.tslintConfig) : null; const Linter = projectTslint.Linter; let result = undefined; if (options.tsConfig) { const tsConfigs = Array.isArray(options.tsConfig) ? options.tsConfig : [options.tsConfig]; context.reportProgress(0, tsConfigs.length); const allPrograms = tsConfigs.map(tsConfig => { return Linter.createProgram(path.resolve(systemRoot, tsConfig)); }); let i = 0; for (const program of allPrograms) { const partial = await _lint(projectTslint, systemRoot, tslintConfigPath, options, program, allPrograms); if (result === undefined) { result = partial; } else { result.failures = result.failures .filter(curr => { return !partial.failures.some(prev => curr.equals(prev)); }) .concat(partial.failures); // we are not doing much with 'errorCount' and 'warningCount' // apart from checking if they are greater than 0 thus no need to dedupe these. result.errorCount += partial.errorCount; result.warningCount += partial.warningCount; result.fileNames = [...new Set([...result.fileNames, ...partial.fileNames])]; if (partial.fixes) { result.fixes = result.fixes ? result.fixes.concat(partial.fixes) : partial.fixes; } } context.reportProgress(++i, allPrograms.length); } } else { result = await _lint(projectTslint, systemRoot, tslintConfigPath, options); } if (result == undefined) { throw new Error('Invalid lint configuration. Nothing to lint.'); } if (!options.silent) { const Formatter = projectTslint.findFormatter(options.format || ''); if (!Formatter) { throw new Error(`Invalid lint format "${options.format}".`); } const formatter = new Formatter(); const output = formatter.format(result.failures, result.fixes, result.fileNames); if (output.trim()) { context.logger.info(output); } } if (result.warningCount > 0 && printInfo) { context.logger.warn('Lint warnings found in the listed files.'); } if (result.errorCount > 0 && printInfo) { context.logger.error('Lint errors found in the listed files.'); } if (result.warningCount === 0 && result.errorCount === 0 && printInfo) { context.logger.info('All files pass linting.'); } return { success: options.force || result.errorCount === 0, }; } exports.default = architect_1.createBuilder(_run); async function _lint(projectTslint, systemRoot, tslintConfigPath, options, program, allPrograms) { const Linter = projectTslint.Linter; const Configuration = projectTslint.Configuration; const files = getFilesToLint(systemRoot, options, Linter, program); const lintOptions = { fix: !!options.fix, formatter: options.format, }; const linter = new Linter(lintOptions, program); let lastDirectory = undefined; let configLoad; const lintedFiles = []; for (const file of files) { if (program && allPrograms) { // If it cannot be found in ANY program, then this is an error. if (allPrograms.every(p => p.getSourceFile(file) === undefined)) { throw new Error(`File ${JSON.stringify(file)} is not part of a TypeScript project '${options.tsConfig}'.`); } else if (program.getSourceFile(file) === undefined) { // The file exists in some other programs. We will lint it later (or earlier) in the loop. continue; } } const contents = getFileContents(file); // Only check for a new tslint config if the path changes. const currentDirectory = path.dirname(file); if (currentDirectory !== lastDirectory) { configLoad = Configuration.findConfiguration(tslintConfigPath, file); lastDirectory = currentDirectory; } if (configLoad) { // Give some breathing space to other promises that might be waiting. await Promise.resolve(); linter.lint(file, contents, configLoad.results); lintedFiles.push(file); } } return { ...linter.getResult(), fileNames: lintedFiles, }; } function getFilesToLint(root, options, linter, program) { const ignore = options.exclude; const files = options.files || []; if (files.length > 0) { return files .map(file => glob.sync(file, { cwd: root, ignore, nodir: true })) .reduce((prev, curr) => prev.concat(curr), []) .map(file => path.join(root, file)); } if (!program) { return []; } let programFiles = linter.getFileNames(program); if (ignore && ignore.length > 0) { // normalize to support ./ paths const ignoreMatchers = ignore .map(pattern => new minimatch_1.Minimatch(path.normalize(pattern), { dot: true })); programFiles = programFiles .filter(file => !ignoreMatchers.some(matcher => matcher.match(path.relative(root, file)))); } return programFiles; } function getFileContents(file) { // NOTE: The tslint CLI checks for and excludes MPEG transport streams; this does not. try { return strip_bom_1.stripBom(fs_1.readFileSync(file, 'utf-8')); } catch (_a) { throw new Error(`Could not read file '${file}'.`); } }