UNPKG

dtslint

Version:

Runs tests on TypeScript definition files

217 lines 8.1 kB
"use strict"; // This is a stand-alone script that updates TSLint configurations for DefinitelyTyped packages. // It runs all rules specified in `dt.json`, and updates the existing configuration for a package // by adding rule exemptions only for the rules that caused a lint failure. // For example, if a configuration specifies `"no-trailing-whitespace": false` and this rule // no longer produces an error, then it will not be disabled in the new configuration. // If you update or create a rule and now it causes new failures in DT, you can update the `dt.json` // configuration with your rule, then register a disabler function for your rule // (check `disableRules` function below), then run this script with your rule as argument. Object.defineProperty(exports, "__esModule", { value: true }); const cp = require("child_process"); const fs = require("fs"); const stringify = require("json-stable-stringify"); const path = require("path"); const tslint_1 = require("tslint"); const yargs = require("yargs"); const lint_1 = require("./lint"); const npmNamingRule_1 = require("./rules/npmNamingRule"); // Rule "expect" needs TypeScript version information, which this script doesn't collect. const ignoredRules = ["expect"]; function main() { const args = yargs .usage(`\`$0 --dt=path-to-dt\` or \`$0 --package=path-to-dt-package\` 'dt.json' is used as the base tslint config for running the linter.`) .option("package", { describe: "Path of DT package.", type: "string", conflicts: "dt", }) .option("dt", { describe: "Path of local DefinitelyTyped repository.", type: "string", conflicts: "package", }) .option("rules", { describe: "Names of the rules to be updated. Leave this empty to update all rules.", type: "array", string: true, default: [], }) .check(arg => { if (!arg.package && !arg.dt) { throw new Error("You must provide either argument 'package' or 'dt'."); } const unsupportedRules = arg.rules.filter(rule => ignoredRules.includes(rule)); if (unsupportedRules.length > 0) { throw new Error(`Rules ${unsupportedRules.join(", ")} are not supported at the moment.`); } return true; }).argv; if (args.package) { updatePackage(args.package, dtConfig(args.rules)); } else if (args.dt) { updateAll(args.dt, dtConfig(args.rules)); } } const dtConfigPath = "dt.json"; function dtConfig(updatedRules) { const config = tslint_1.Configuration.findConfiguration(dtConfigPath).results; if (!config) { throw new Error(`Could not load config at ${dtConfigPath}.`); } // Disable ignored or non-updated rules. for (const entry of config.rules.entries()) { const [rule, ruleOpts] = entry; if (ignoredRules.includes(rule) || (updatedRules.length > 0 && !updatedRules.includes(rule))) { ruleOpts.ruleSeverity = "off"; } } return config; } function updateAll(dtPath, config) { const packages = fs.readdirSync(path.join(dtPath, "types")); for (const pkg of packages) { updatePackage(path.join(dtPath, "types", pkg), config); } } function updatePackage(pkgPath, baseConfig) { installDependencies(pkgPath); const packages = walkPackageDir(pkgPath); const linterOpts = { fix: false, }; for (const pkg of packages) { const results = pkg.lint(linterOpts, baseConfig); if (results.failures.length > 0) { const disabledRules = disableRules(results.failures); const newConfig = mergeConfigRules(pkg.config(), disabledRules, baseConfig); pkg.updateConfig(newConfig); } } } function installDependencies(pkgPath) { if (fs.existsSync(path.join(pkgPath, "package.json"))) { cp.execSync("npm install --ignore-scripts --no-shrinkwrap --no-package-lock --no-bin-links", { encoding: "utf8", cwd: pkgPath, }); } } function mergeConfigRules(config, newRules, baseConfig) { const activeRules = []; baseConfig.rules.forEach((ruleOpts, ruleName) => { if (ruleOpts.ruleSeverity !== "off") { activeRules.push(ruleName); } }); const oldRules = config.rules || {}; let newRulesConfig = {}; for (const rule of Object.keys(oldRules)) { if (activeRules.includes(rule)) { continue; } newRulesConfig[rule] = oldRules[rule]; } newRulesConfig = Object.assign(Object.assign({}, newRulesConfig), newRules); return Object.assign(Object.assign({}, config), { rules: newRulesConfig }); } /** * Represents a package from the linter's perspective. * For example, `DefinitelyTyped/types/react` and `DefinitelyTyped/types/react/v15` are different * packages. */ class LintPackage { constructor(rootDir) { this.rootDir = rootDir; this.files = []; this.program = tslint_1.Linter.createProgram(path.join(this.rootDir, "tsconfig.json")); } config() { return tslint_1.Configuration.readConfigurationFile(path.join(this.rootDir, "tslint.json")); } addFile(filePath) { const file = this.program.getSourceFile(filePath); if (file) { this.files.push(file); } } lint(opts, config) { const linter = new tslint_1.Linter(opts, this.program); for (const file of this.files) { if (ignoreFile(file, this.rootDir, this.program)) { continue; } linter.lint(file.fileName, file.text, config); } return linter.getResult(); } updateConfig(config) { fs.writeFileSync(path.join(this.rootDir, "tslint.json"), stringify(config, { space: 4 }), { encoding: "utf8", flag: "w" }); } } function ignoreFile(file, dirPath, program) { return program.isSourceFileDefaultLibrary(file) || (0, lint_1.isExternalDependency)(file, path.resolve(dirPath), program); } function walkPackageDir(rootDir) { const packages = []; function walk(curPackage, dir) { for (const ent of fs.readdirSync(dir, { encoding: "utf8", withFileTypes: true })) { const entPath = path.join(dir, ent.name); if (ent.isFile()) { curPackage.addFile(entPath); } else if (ent.isDirectory() && ent.name !== "node_modules") { if (isVersionDir(ent.name)) { const newPackage = new LintPackage(entPath); packages.push(newPackage); walk(newPackage, entPath); } else { walk(curPackage, entPath); } } } } const lintPackage = new LintPackage(rootDir); packages.push(lintPackage); walk(lintPackage, rootDir); return packages; } /** * Returns true if directory name matches a TypeScript or package version directory. * Examples: `ts3.5`, `v11`, `v0.6` are all version names. */ function isVersionDir(dirName) { return /^ts\d+\.\d$/.test(dirName) || /^v\d+(\.\d+)?$/.test(dirName); } const defaultDisabler = () => { return false; }; function disableRules(allFailures) { const ruleToFailures = new Map(); for (const failure of allFailures) { const failureJson = failure.toJson(); if (ruleToFailures.has(failureJson.ruleName)) { ruleToFailures.get(failureJson.ruleName).push(failureJson); } else { ruleToFailures.set(failureJson.ruleName, [failureJson]); } } const newRulesConfig = {}; ruleToFailures.forEach((failures, rule) => { if (ignoredRules.includes(rule)) { return; } const disabler = rule === "npm-naming" ? npmNamingRule_1.disabler : defaultDisabler; const opts = disabler(failures); newRulesConfig[rule] = opts; }); return newRulesConfig; } if (!module.parent) { main(); } //# sourceMappingURL=updateConfig.js.map