[infra] Port scripts from TS -> JS (#61102)

This commit is contained in:
Jack Bates
2022-07-05 14:57:10 -07:00
committed by GitHub
parent fe6bfedb9a
commit 97558149e6
15 changed files with 217 additions and 195 deletions

5
.gitignore vendored
View File

@@ -3,8 +3,6 @@
*.cmd
*.pdb
*.suo
*.js
*.cjs
*.user
*.cache
*.cs
@@ -31,9 +29,6 @@ _infrastructure/tests/build
.idea
*.iml
*.js.map
!*.js/
# npm
node_modules
package-lock.json

View File

@@ -15,7 +15,6 @@
"node": ">=7.8.0"
},
"scripts": {
"compile-scripts": "tsc -p scripts",
"not-needed": "node scripts/not-needed.js",
"update-codeowners": "node scripts/update-codeowners.js",
"test-all": "node node_modules/@definitelytyped/dtslint-runner/dist/index.js --path .",

View File

@@ -1,5 +1,3 @@
// Usage: ts-node fix-tslint.ts
/// <reference types="node" />
import * as fs from 'node:fs';
@@ -25,29 +23,25 @@ for (const dirName of fs.readdirSync(home)) {
}
}
function fixTslint(dir: URL): void {
/**
* @param {URL} dir
*/
function fixTslint(dir) {
const target = new URL('tslint.json', dir);
if (!fs.existsSync(target)) return;
let json = JSON.parse(fs.readFileSync(target, 'utf-8'));
json = fix(json);
const text = Object.keys(json).length === 1 ? '{ "extends": "@definitelytyped/dtslint/dt.json" }' : JSON.stringify(json, undefined, 4);
fs.writeFileSync(target, text + "\n", "utf-8");
const json = JSON.parse(fs.readFileSync(target, 'utf-8'));
json.rules = fixRules(json.rules);
const text =
Object.keys(json).length === 1
? '{ "extends": "@definitelytyped/dtslint/dt.json" }'
: JSON.stringify(json, undefined, 4);
fs.writeFileSync(target, text + '\n', 'utf-8');
}
function fix(config: any): any {
const out: any = {};
for (const key in config) {
let value = config[key];
out[key] = key === "rules" ? fixRules(value) : value;
}
return out;
}
function fixRules(rules: any): any {
const out: any = {};
for (const key in rules) {
out[key] = rules[key];
}
return out;
/**
* @param {{}} rules
*/
function fixRules(rules) {
return Object.fromEntries(Object.entries(rules).map(([key, value]) => [key, value]));
}

View File

@@ -1,5 +1,3 @@
// Usage: ts-node generate-tsconfigs.ts
/// <reference types="node" />
import * as fs from 'node:fs';
@@ -24,30 +22,20 @@ for (const dirName of fs.readdirSync(home)) {
}
}
function fixTsconfig(dir: URL): void {
/**
* @param {URL} dir
*/
function fixTsconfig(dir) {
const target = new URL('tsconfig.json', dir);
let json = JSON.parse(fs.readFileSync(target, 'utf-8'));
json = fix(json);
const json = JSON.parse(fs.readFileSync(target, 'utf-8'));
json.compilerOptions = fixCompilerOptions(json.compilerOptions);
fs.writeFileSync(target, JSON.stringify(json, undefined, 4), 'utf-8');
}
function fix(config: any): any {
const out: any = {};
for (const key in config) {
let value = config[key];
if (key === 'compilerOptions') {
value = fixCompilerOptions(value);
}
out[key] = value;
}
return out;
}
function fixCompilerOptions(config: any): any {
const out: any = {};
for (const key in config) {
out[key] = config[key];
// Do something interesting here
}
return out;
/**
* @param {{}} compilerOptions
*/
function fixCompilerOptions(compilerOptions) {
// Do something interesting here
return Object.fromEntries(Object.entries(compilerOptions).map(([key, value]) => [key, value]));
}

View File

@@ -1,7 +1,5 @@
// @ts-check
import { flatMap, mapDefined } from "@definitelytyped/utils";
import * as os from "node:os";
import fsExtra from 'fs-extra';
import fsExtra from "fs-extra";
const { writeFileSync, readFileSync, readdirSync, existsSync } = fsExtra;
import hp from "@definitelytyped/header-parser";
import { Octokit } from "@octokit/core";
@@ -71,7 +69,7 @@ function getAllHeaders() {
parsed = hp.parseHeaderOrFail(indexContent);
} catch (e) {}
if (parsed) {
headers[index] = { ...parsed, raw: indexContent };
headers[/** @type {never} */ (index)] = { ...parsed, raw: indexContent };
}
}
});
@@ -87,6 +85,7 @@ async function fetchGhosts(users) {
const maxPageSize = 2000;
const pages = Math.ceil(users.size / maxPageSize);
const userArray = Array.from(users);
/** @type string[] */
const ghosts = [];
for (let page = 0; page < pages; page++) {
const startIndex = page * maxPageSize;
@@ -124,7 +123,8 @@ async function tryGQL(fn) {
const result = await fn();
if (result.data) return result.data;
return result;
} catch (resultWithErrors) {
// @ts-expect-error
} catch (/** @type {{}} */ resultWithErrors) {
if (resultWithErrors.data) {
return resultWithErrors.data;
}

View File

@@ -1,6 +1,7 @@
{
"exclude": ["fix-tslint.ts", "update-config"],
"exclude": ["close-old-issues.js", "fix-tslint.js"],
"compilerOptions": {
"noUnusedLocals": true,
"target": "es6",
"module": "esnext",
"strict": true,
@@ -9,7 +10,8 @@
"resolveJsonModule": true,
"typeRoots": ["../types"],
"types": [],
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true
"checkJs": true,
"forceConsistentCasingInFileNames": true,
"lib": ["es2019"]
}
}

View File

@@ -20,6 +20,9 @@ notNeededPackages.packages[typingsPackageName] = { libraryName, asOfVersion };
notNeededPackages.packages = Object.fromEntries(Object.entries(notNeededPackages.packages).sort());
fs.writeFileSync('notNeededPackages.json', JSON.stringify(notNeededPackages, undefined, 4) + '\n', 'utf-8');
/**
* @param {string} dir
*/
function rmdirRecursive(dir) {
for (let entry of fs.readdirSync(dir)) {
entry = path.join(dir, entry);

View File

@@ -25,18 +25,21 @@ const supported = Object.entries(data)
const x = scaleTime()
.domain([
min(
supported,
(d) =>
// Clip 1/4 of the earliest supported version. Cuts off the
// release date (unimportant?) but gives the visual impression
// of additional, unsupported versions?
new Date(
Number(d.releaseDate) +
((d.endDate as never) - (d.releaseDate as never)) / 4
)
)!,
max(supported, (d) => d.endDate)!,
/** @type {never} */ (
min(
supported,
(d) =>
// Clip 1/4 of the earliest supported version. Cuts off the
// release date (unimportant?) but gives the visual impression
// of additional, unsupported versions?
new Date(
Number(d.releaseDate) +
// prettier-ignore
(/** @type {never} */ (d.endDate) - /** @type {never} */ (d.releaseDate)) / 4
)
)
),
/** @type {never} */ (max(supported, (d) => d.endDate)),
])
.nice()
.range([margin.left, width - margin.right]);
@@ -80,7 +83,7 @@ svg
.data(supported)
.join("rect")
.attr("x", (d) => x(d.releaseDate))
.attr("y", (d) => y(d.version)!)
.attr("y", (d) => /** @type {never} */ (y(d.version)))
.attr("width", (d) => x(d.endDate) - x(d.releaseDate))
.attr("height", y.bandwidth())
.attr("fill", (d, i) => (i % 2 === 0 ? "#3178c6" : "#235a97"));
@@ -106,7 +109,7 @@ texts
.attr("fill", "#ffffff")
.append("text")
.attr("x", (d) => x(d.releaseDate) + (x(d.endDate) - x(d.releaseDate)) / 2)
.attr("y", (d) => y(d.version)!)
.attr("y", (d) => /** @type {never} */ (y(d.version)))
.attr("dy", "0.35em")
.text((d) => d.version);
texts
@@ -116,7 +119,7 @@ texts
.data(supported)
.join("text")
.attr("x", (d) => x(d.releaseDate) + (x(d.endDate) - x(d.releaseDate)) / 4)
.attr("y", (d) => y(d.version)!)
.attr("y", (d) => /** @type {never} */ (y(d.version)))
.attr("dy", "0.35em")
.text((d) => formatDate(d.releaseDate));
texts
@@ -129,7 +132,7 @@ texts
"x",
(d) => x(d.releaseDate) + ((x(d.endDate) - x(d.releaseDate)) * 3) / 4
)
.attr("y", (d) => y(d.version)!)
.attr("y", (d) => /** @type {never} */ (y(d.version)))
.attr("dy", "0.35em")
.text((d) => formatDate(d.endDate));
process.stdout.write(serialize(svg.node()!));
process.stdout.write(serialize(/** @type {never} */ (svg.node())));

View File

@@ -0,0 +1,73 @@
import * as fs from "node:fs";
import * as stringify from "json-stable-stringify";
import * as path from "node:path";
import { Configuration as Config, Linter } from "tslint";
import { isExternalDependency } from "./dependencies";
/**
* Represents a package from the linter's perspective.
* For example, `DefinitelyTyped/types/react` and `DefinitelyTyped/types/react/v15` are different
* packages.
*/
export class LintPackage {
/** @type {import("typescript").SourceFile[]} */
#files = [];
#rootDir;
#program;
/**
* @param {string} rootDir
*/
constructor(rootDir) {
this.#rootDir = rootDir;
this.#program = Linter.createProgram(path.join(this.#rootDir, "tsconfig.json"));
}
config() {
return Config.readConfigurationFile(path.join(this.#rootDir, "tslint.json"));
}
/**
* @param {string} filePath
*/
addFile(filePath) {
const file = this.#program.getSourceFile(filePath);
if (file) {
this.#files.push(file);
}
}
/**
* @param {import("tslint").ILinterOptions} opts
* @param {import("tslint").Configuration.IConfigurationFile} config
*/
lint(opts, config) {
const linter = new 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();
}
/**
* @param {import("tslint").Configuration.RawConfigFile} config
*/
updateConfig(config) {
fs.writeFileSync(path.join(this.#rootDir, "tslint.json"), stringify(config, { space: 4 }), {
encoding: "utf8",
flag: "w",
});
}
}
/**
* @param {import("typescript").SourceFile} file
* @param {string} dirPath
* @param {import("typescript").Program} program
*/
function ignoreFile(file, dirPath, program) {
return program.isSourceFileDefaultLibrary(file) || isExternalDependency(file, path.resolve(dirPath), program);
}

View File

@@ -1,53 +0,0 @@
import * as fs from "node:fs";
import * as stringify from "json-stable-stringify";
import * as path from "node:path";
import { Configuration as Config, ILinterOptions, Linter, LintResult } from "tslint";
import ts from "typescript";
import { isExternalDependency } from "./dependencies";
/**
* Represents a package from the linter's perspective.
* For example, `DefinitelyTyped/types/react` and `DefinitelyTyped/types/react/v15` are different
* packages.
*/
export class LintPackage {
private files: ts.SourceFile[] = [];
private program: ts.Program;
constructor(private rootDir: string) {
this.program = Linter.createProgram(path.join(this.rootDir, "tsconfig.json"));
}
config(): Config.RawConfigFile {
return Config.readConfigurationFile(path.join(this.rootDir, "tslint.json"));
}
addFile(filePath: string): void {
const file = this.program.getSourceFile(filePath);
if (file) {
this.files.push(file);
}
}
lint(opts: ILinterOptions, config: Config.IConfigurationFile): LintResult {
const linter = new 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: Config.RawConfigFile): void {
fs.writeFileSync(
path.join(this.rootDir, "tslint.json"),
stringify(config, { space: 4 }),
{ encoding: "utf8", flag: "w" });
}
}
function ignoreFile(file: ts.SourceFile, dirPath: string, program: ts.Program): boolean {
return program.isSourceFileDefaultLibrary(file) || isExternalDependency(file, path.resolve(dirPath), program);
}

View File

@@ -1,18 +1,29 @@
import * as path from "node:path";
import ts from "typescript";
export function isExternalDependency(file: ts.SourceFile, dirPath: string, program: ts.Program): boolean {
/**
* @param {import("typescript").SourceFile} file
* @param {string} dirPath
* @param {import("typescript").Program} program
*/
export function isExternalDependency(file, dirPath, program) {
return !startsWithDirectory(file.fileName, dirPath) || program.isSourceFileFromExternalLibrary(file);
}
export function normalizePath(file: string) {
/**
* @param {string} file
*/
export function normalizePath(file) {
// replaces '\' with '/' and forces all DOS drive letters to be upper-case
return path.normalize(file)
.replace(/\\/g, "/")
.replace(/^[a-z](?=:)/, c => c.toUpperCase());
}
function startsWithDirectory(filePath: string, dirPath: string): boolean {
/**
* @param {string} filePath
* @param {string} dirPath
*/
function startsWithDirectory(filePath, dirPath) {
const normalFilePath = normalizePath(filePath);
const normalDirPath = normalizePath(dirPath).replace(/\/$/, "");
return normalFilePath.startsWith(normalDirPath + "/") || normalFilePath.startsWith(normalDirPath + "\\");

View File

@@ -39,6 +39,7 @@ async function main() {
if (!arg.package && !arg.dt) {
throw new Error("You must provide either argument 'package' or 'dt'.");
}
// @ts-expect-error
const unsupportedRules = arg.rules.filter(rule => ignoredRules.has(rule));
if (unsupportedRules.length > 0) {
throw new Error(`Rules ${unsupportedRules.join(", ")} are not supported at the moment.`);
@@ -55,8 +56,11 @@ async function main() {
}
}
function dtConfig(updatedRules: string[]): Config.IConfigurationFile {
const resolvedDtslint = require.resolve('dtslint');
/**
* @param {string[]} updatedRules
*/
function dtConfig(updatedRules) {
const resolvedDtslint = require.resolve("dtslint");
const dtConfigPath = normalizePath(
path.join(
resolvedDtslint.slice(0, resolvedDtslint.lastIndexOf("dtslint")),
@@ -77,7 +81,11 @@ function dtConfig(updatedRules: string[]): Config.IConfigurationFile {
return config;
}
function updateAll(dtPath: string, config: Config.IConfigurationFile): void {
/**
* @param {string} dtPath
* @param {Config.IConfigurationFile} 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);

View File

@@ -1,11 +1,11 @@
import { defaultErrors, ErrorKind, ExportErrorKind, Mode } from "dts-critic";
import * as Lint from "tslint";
import { defaultErrors, ErrorKind, Mode } from "@definitelytyped/dts-critic";
/**
* Given npm-naming lint failures, returns a rule configuration that prevents such failures.
* @param {import("tslint").IRuleFailureJson[]} failures
*/
export function npmNamingDisabler(failures: Lint.IRuleFailureJson[]) {
const disabledErrors = new Set<ExportErrorKind>();
export function npmNamingDisabler(failures) {
const disabledErrors = new Set();
for (const ruleFailure of failures) {
if (ruleFailure.ruleName !== "npm-naming") {
throw new Error(`Expected failures of rule "npm-naming", found failures of rule ${ruleFailure.ruleName}.`);
@@ -28,10 +28,9 @@ export function npmNamingDisabler(failures: Lint.IRuleFailureJson[]) {
}
}
if ((defaultErrors as ExportErrorKind[]).every(error => disabledErrors.has(error))) {
if (defaultErrors.every(error => disabledErrors.has(error))) {
return [true, { mode: Mode.NameOnly }];
}
const errors: Array<[ExportErrorKind, boolean]> = [];
disabledErrors.forEach(error => errors.push([error, false]));
const errors = [...disabledErrors].map(error => [error, false]);
return [true, { mode: Mode.Code, errors }];
}

View File

@@ -1,17 +1,20 @@
import * as cp from "node:child_process";
import * as fs from "node:fs";
import * as path from "node:path";
import { Configuration as Config, ILinterOptions, IRuleFailureJson, RuleFailure } from "tslint";
import { ignoredRules } from "./ignoredRules";
import { LintPackage } from "./LintPackage";
import { npmNamingDisabler } from "./npmNamingDisabler";
export function updatePackage(pkgPath: string, baseConfig: Config.IConfigurationFile): void {
/**
* @param {string} pkgPath
* @param {import("tslint").Configuration.IConfigurationFile} baseConfig
*/
export function updatePackage(pkgPath, baseConfig) {
installDependencies(pkgPath);
const packages = walkPackageDir(pkgPath);
const linterOpts: ILinterOptions = {
const linterOpts = {
fix: false,
};
@@ -25,10 +28,17 @@ export function updatePackage(pkgPath: string, baseConfig: Config.IConfiguration
}
}
function walkPackageDir(rootDir: string): LintPackage[] {
const packages: LintPackage[] = [];
/**
* @param {string} rootDir
*/
function walkPackageDir(rootDir) {
const packages = [];
function walk(curPackage: LintPackage, dir: string): void {
/**
* @param {LintPackage} curPackage
* @param {string} dir
*/
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()) {
@@ -54,12 +64,16 @@ function walkPackageDir(rootDir: string): LintPackage[] {
/**
* Returns true if directory name matches a TypeScript or package version directory.
* Examples: `ts3.5`, `v11`, `v0.6` are all version names.
* @param {string} dirName
*/
function isVersionDir(dirName: string): boolean {
function isVersionDir(dirName) {
return /^ts\d+\.\d$/.test(dirName) || /^v\d+(\.\d+)?$/.test(dirName);
}
function installDependencies(pkgPath: string): void {
/**
* @param {string} pkgPath
*/
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",
@@ -70,55 +84,41 @@ function installDependencies(pkgPath: string): void {
}
}
function mergeConfigRules(
config: Config.RawConfigFile,
newRules: Config.RawRulesConfig,
baseConfig: Config.IConfigurationFile): Config.RawConfigFile {
const activeRules: string[] = [];
baseConfig.rules.forEach((ruleOpts, ruleName) => {
if (ruleOpts.ruleSeverity !== "off") {
activeRules.push(ruleName);
}
});
const oldRules: Config.RawRulesConfig = config.rules || {};
let newRulesConfig: Config.RawRulesConfig = {};
for (const rule of Object.keys(oldRules)) {
if (activeRules.includes(rule)) {
continue;
}
newRulesConfig[rule] = oldRules[rule];
}
newRulesConfig = { ...newRulesConfig, ...newRules };
/**
* @param {import("tslint").Configuration.RawConfigFile} config
* @param {import("tslint").Configuration.RawRulesConfig} newRules
* @param {import("tslint").Configuration.IConfigurationFile} baseConfig
*/
function mergeConfigRules(config, newRules, baseConfig) {
const activeRules = [...baseConfig.rules.entries()]
.filter(([, ruleOpts]) => ruleOpts.ruleSeverity !== "off")
.map(([ruleName]) => ruleName);
const oldRules = config.rules || {};
const newRulesConfig = {
...Object.fromEntries(Object.entries(oldRules).filter(([rule]) => !activeRules.includes(rule))),
...newRules,
};
return { ...config, rules: newRulesConfig };
}
function disableRules(allFailures: RuleFailure[]): Config.RawRulesConfig {
const ruleToFailures: Map<string, IRuleFailureJson[]> = new Map();
/**
* @param {import("tslint").RuleFailure[]} allFailures
*/
function disableRules(allFailures) {
/** @type {Record<string, import("tslint").IRuleFailureJson[]>} */
const ruleToFailures = {};
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]);
}
if (ignoredRules.has(failureJson.ruleName)) continue;
(ruleToFailures[failureJson.ruleName] ||= []).push(failureJson);
}
const newRulesConfig: Config.RawRulesConfig = {};
ruleToFailures.forEach((failures, rule) => {
if (ignoredRules.has(rule)) {
return;
}
const disabler = rule === "npm-naming" ? npmNamingDisabler : defaultDisabler;
const opts: RuleOptions = disabler(failures);
newRulesConfig[rule] = opts;
});
return newRulesConfig;
return Object.fromEntries(
Object.entries(ruleToFailures).map(([rule, failures]) => [
rule,
(rule === "npm-naming" ? npmNamingDisabler : defaultDisabler)(failures),
])
);
}
type RuleOptions = boolean | unknown[];
type RuleDisabler = (failures: IRuleFailureJson[]) => RuleOptions;
const defaultDisabler: RuleDisabler = () => {
return false;
};
const defaultDisabler = () => false;