"use strict"; /** * @license * Copyright Google LLC 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 */ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; Object.defineProperty(exports, "__esModule", { value: true }); const architect_1 = require("@angular-devkit/architect"); const fs_1 = require("fs"); const glob = __importStar(require("glob")); const minimatch_1 = require("minimatch"); const path = __importStar(require("path")); const strip_bom_1 = require("../utils/strip-bom"); async function _run(options, context) { context.logger.warn(`TSLint's support is discontinued and we're deprecating its support in Angular CLI.\n` + 'To opt-in using the community driven ESLint builder, see: https://github.com/angular-eslint/angular-eslint#migrating-an-angular-cli-project-from-codelyzer-and-tslint.'); 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.'); } let tslint; try { tslint = await Promise.resolve().then(() => __importStar(require('tslint'))); } catch { throw new Error('Unable to find TSLint. Ensure TSLint is installed.'); } const tslintConfigPath = options.tslintConfig ? path.resolve(systemRoot, options.tslintConfig) : null; const Linter = tslint.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(tslint, 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(tslint, systemRoot, tslintConfigPath, options); } if (result == undefined) { throw new Error('Invalid lint configuration. Nothing to lint.'); } if (!options.silent) { const Formatter = tslint.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, }; } /** @deprecated since version 11 as part of the TSLint deprecation. */ 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 { throw new Error(`Could not read file '${file}'.`); } }