dtslint
Version:
Runs tests on TypeScript definition files
217 lines • 8.1 kB
JavaScript
;
// 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