diff --git a/packages/dtslint/src/check-package-in-attw.ts b/packages/dtslint/src/check-package-in-attw.ts new file mode 100644 index 0000000000..2d8c55638b --- /dev/null +++ b/packages/dtslint/src/check-package-in-attw.ts @@ -0,0 +1,61 @@ +#!/usr/bin/env node + +import { join as joinPaths } from "node:path"; + +import { getTypesVersions } from "@definitelytyped/header-parser"; +import { mangleScopedPackage } from "@definitelytyped/utils"; + +import { checkNpmVersionAndGetMatchingImplementationPackage, checkPackageJson } from "./checks"; +import { findDTRootAndPackageNameFrom, packageDirectoryNameWithVersionFromPath } from "./util"; + +async function main(): Promise { + const args = process.argv.slice(2); + + console.log(`dtslint@${require("../package.json").version}`); + if (args.length === 1 && args[0] === "types") { + console.log( + "Please provide a package name to test.\nTo test all changed packages at once, run `pnpm run test-all`.", + ); + process.exit(1); + } + + const dirPath = args.reduce((acc, arg) => joinPaths(acc, mangleScopedPackage(arg)), ""); + + console.log(`Should ${dirPath} stay in attw? ${await shouldPackageStayInAttw(dirPath)}`); +} + +/** + * @returns Warning text - should be displayed during the run, but does not indicate failure. + */ +async function shouldPackageStayInAttw(dirPath: string): Promise { + try { + await findDTRootAndPackageNameFrom(dirPath); + } catch { + return false; + } + + const packageJson = checkPackageJson(dirPath, getTypesVersions(dirPath)); + if (Array.isArray(packageJson)) { + console.error(new Error("\n\t* " + packageJson.join("\n\t* "))); + + return false; + } + + const packageDirectoryNameWithVersion = packageDirectoryNameWithVersionFromPath(dirPath); + + const { implementationPackage } = await checkNpmVersionAndGetMatchingImplementationPackage( + packageJson, + packageDirectoryNameWithVersion, + ); + + return Boolean(implementationPackage); +} + +if (require.main === module) { + main().catch((err) => { + console.error(err.stack); + process.exit(1); + }); +} else { + module.exports = shouldPackageStayInAttw; +} diff --git a/packages/dtslint/src/check-package-in-expectedVersionFailures.ts b/packages/dtslint/src/check-package-in-expectedVersionFailures.ts new file mode 100644 index 0000000000..0b0e179831 --- /dev/null +++ b/packages/dtslint/src/check-package-in-expectedVersionFailures.ts @@ -0,0 +1,66 @@ +#!/usr/bin/env node + +import { join as joinPaths } from "node:path"; + +import { getTypesVersions } from "@definitelytyped/header-parser"; +import { mangleScopedPackage } from "@definitelytyped/utils"; + +import { checkNpmVersionAndGetMatchingImplementationPackage, checkPackageJson } from "./checks"; +import { findDTRootAndPackageNameFrom, packageDirectoryNameWithVersionFromPath } from "./util"; + +async function main(): Promise { + const args = process.argv.slice(2); + + console.log(`dtslint@${require("../package.json").version}`); + if (args.length === 1 && args[0] === "types") { + console.log( + "Please provide a package name to test.\nTo test all changed packages at once, run `pnpm run test-all`.", + ); + process.exit(1); + } + + const dirPath = args.reduce((acc, arg) => joinPaths(acc, mangleScopedPackage(arg)), process.cwd()); + + console.log( + `Should ${dirPath} stay in expectedNpmVersionFailures? ${await shouldPackageStayInExpectedVersionFailures(dirPath)}`, + ); +} + +/** + * @returns Warning text - should be displayed during the run, but does not indicate failure. + */ +async function shouldPackageStayInExpectedVersionFailures(dirPath: string): Promise { + try { + await findDTRootAndPackageNameFrom(dirPath); + } catch { + return false; + } + + const packageJson = checkPackageJson(dirPath, getTypesVersions(dirPath)); + if (Array.isArray(packageJson)) { + console.error(new Error("\n\t* " + packageJson.join("\n\t* "))); + + return false; + } + + const packageDirectoryNameWithVersion = packageDirectoryNameWithVersionFromPath(dirPath); + + const { warnings } = await checkNpmVersionAndGetMatchingImplementationPackage( + packageJson, + packageDirectoryNameWithVersion, + ); + + const partialRemovableWarning = " can be removed from expectedNpmVersionFailures.txt in "; + const canBeRemovedForDirPath = warnings.some((msg) => msg.includes(partialRemovableWarning)); + + return !canBeRemovedForDirPath; +} + +if (require.main === module) { + main().catch((err) => { + console.error(err.stack); + process.exit(1); + }); +} else { + module.exports = shouldPackageStayInExpectedVersionFailures; +} diff --git a/packages/dtslint/src/index.ts b/packages/dtslint/src/index.ts index 791104c020..41bb117994 100644 --- a/packages/dtslint/src/index.ts +++ b/packages/dtslint/src/index.ts @@ -2,7 +2,7 @@ import { getTypesVersions } from "@definitelytyped/header-parser"; import { AllTypeScriptVersion, TypeScriptVersion } from "@definitelytyped/typescript-versions"; -import { deepEquals, readJson } from "@definitelytyped/utils"; +import { deepEquals, mangleScopedPackage, readJson } from "@definitelytyped/utils"; import fs from "fs"; import { basename, dirname, join as joinPaths, resolve } from "path"; import { @@ -12,7 +12,7 @@ import { runAreTheTypesWrong, } from "./checks"; import { TsVersion, lint } from "./lint"; -import { getCompilerOptions, packageDirectoryNameWithVersionFromPath, packageNameFromPath } from "./util"; +import { findDTRootAndPackageNameFrom, getCompilerOptions, packageDirectoryNameWithVersionFromPath } from "./util"; import assert = require("assert"); async function main(): Promise { @@ -74,13 +74,7 @@ async function main(): Promise { process.exit(1); } - const path = - arg.indexOf("@") === 0 && arg.indexOf("/") !== -1 - ? // we have a scoped module, e.g. @bla/foo - // which should be converted to bla__foo - arg.slice(1).replace("/", "__") - : arg; - dirPath = joinPaths(dirPath, path); + dirPath = joinPaths(dirPath, mangleScopedPackage(arg)); } } } @@ -157,16 +151,8 @@ async function runTests( npmChecks: boolean | "only", tsLocal: string | undefined, ): Promise { - // Assert that we're really on DefinitelyTyped. - const dtRoot = findDTRoot(dirPath); - const packageName = packageNameFromPath(dirPath); + const { dtRoot } = await findDTRootAndPackageNameFrom(dirPath); const packageDirectoryNameWithVersion = packageDirectoryNameWithVersionFromPath(dirPath); - assertPathIsInDefinitelyTyped(dirPath, dtRoot); - assertPathIsNotBanned(packageName); - assertPackageIsNotDeprecated( - packageName, - await fs.promises.readFile(joinPaths(dtRoot, "notNeededPackages.json"), "utf-8"), - ); const typesVersions = getTypesVersions(dirPath); const packageJson = checkPackageJson(dirPath, typesVersions); @@ -299,58 +285,6 @@ async function testTypesVersion( return { errors }; } -function findDTRoot(dirPath: string) { - let path = dirPath; - while (basename(path) !== "types" && dirname(path) !== "." && dirname(path) !== "/") { - path = dirname(path); - } - return dirname(path); -} - -function assertPathIsInDefinitelyTyped(dirPath: string, dtRoot: string): void { - // TODO: It's not clear whether this assertion makes sense, and it's broken on Azure Pipelines (perhaps because DT isn't cloned into DefinitelyTyped) - // Re-enable it later if it makes sense. - // if (basename(dtRoot) !== "DefinitelyTyped")) { - if (!fs.existsSync(joinPaths(dtRoot, "types"))) { - throw new Error( - "Since this type definition includes a header (a comment starting with `// Type definitions for`), " + - "assumed this was a DefinitelyTyped package.\n" + - "But it is not in a `DefinitelyTyped/types/xxx` directory: " + - dirPath, - ); - } -} - -/** - * Starting at some point in time, npm has banned all new packages whose names - * contain the word `download`. However, some older packages exist that still - * contain this name. - * @NOTE for contributors: The list of literal exceptions below should ONLY be - * extended with packages for which there already exists a corresponding type - * definition package in the `@types` scope. More information: - * https://github.com/microsoft/DefinitelyTyped-tools/pull/381. - */ -function assertPathIsNotBanned(packageName: string) { - if ( - /(^|\W)download($|\W)/.test(packageName) && - packageName !== "download" && - packageName !== "downloadjs" && - packageName !== "s3-download-stream" - ) { - // Since npm won't release their banned-words list, we'll have to manually add to this list. - throw new Error(`${packageName}: Contains the word 'download', which is banned by npm.`); - } -} - -export function assertPackageIsNotDeprecated(packageName: string, notNeededPackages: string) { - const unneeded = JSON.parse(notNeededPackages).packages; - if (Object.keys(unneeded).includes(packageName)) { - throw new Error(`${packageName}: notNeededPackages.json has an entry for ${packageName}. -That means ${packageName} ships its own types, and @types/${packageName} was deprecated and removed from Definitely Typed. -If you want to re-add @types/${packageName}, please remove its entry from notNeededPackages.json.`); - } -} - if (require.main === module) { main().catch((err) => { console.error(err.stack); diff --git a/packages/dtslint/src/util.ts b/packages/dtslint/src/util.ts index 60b596ffba..2b36f2dabe 100644 --- a/packages/dtslint/src/util.ts +++ b/packages/dtslint/src/util.ts @@ -1,4 +1,4 @@ -import { createGitHubStringSetGetter, joinPaths } from "@definitelytyped/utils"; +import { createGitHubStringSetGetter, joinPaths, readFile } from "@definitelytyped/utils"; import fs from "fs"; import { basename, dirname } from "path"; import stripJsonComments = require("strip-json-comments"); @@ -42,3 +42,82 @@ export const getExpectedNpmVersionFailures = createGitHubStringSetGetter( "packages/dtslint/expectedNpmVersionFailures.txt", joinPaths(root, "expectedNpmVersionFailures.txt"), ); + +export async function findDTRootAndPackageNameFrom(dirPath: string): Promise<{ dtRoot: string; packageName: string }> { + const dtRoot = findDTRoot(dirPath); + assertPathIsInDefinitelyTyped(dirPath, dtRoot); + + const packageName = packageNameFromPath(dirPath); + assertPathIsNotBanned(packageName); + + const notNeededPackagesJsonPath = joinPaths(dtRoot, "notNeededPackages.json"); + const notNeededPackagesJson = await readFile(notNeededPackagesJsonPath); + assertPackageIsNotDeprecated(packageName, notNeededPackagesJson); + + return { dtRoot, packageName }; +} + +export function findDTRoot(dirPath: string) { + let path = dirPath; + while (basename(path) !== "types" && dirname(path) !== "." && dirname(path) !== "/") { + path = dirname(path); + } + return dirname(path); +} + +export function assertPathIsInDefinitelyTyped(dirPath: string, dtRoot: string): void { + // TODO: It's not clear whether this assertion makes sense, and it's broken on Azure Pipelines (perhaps because DT isn't cloned into DefinitelyTyped) + // Re-enable it later if it makes sense. + // if (basename(dtRoot) !== "DefinitelyTyped")) { + if (!fs.existsSync(joinPaths(dtRoot, dirPath))) { + throw new Error( + [ + `The type definition for "${dirPath}" is expected to be a DefinitelyTyped package`, + `located in \`DefinitelyTyped/${dirPath}\` directory`, + ] + .map((sentence) => sentence.trim()) + .join(" "), + ); + } +} + +/** + * Starting at some point in time, npm has banned all new packages whose names + * contain the word `download`. However, some older packages exist that still + * contain this name. + * @NOTE for contributors: The list of literal exceptions below should ONLY be + * extended with packages for which there already exists a corresponding type + * definition package in the `@types` scope. More information: + * https://github.com/microsoft/DefinitelyTyped-tools/pull/381. + */ +export function assertPathIsNotBanned(packageName: string) { + if ( + /(^|\W)download($|\W)/.test(packageName) && + packageName !== "download" && + packageName !== "downloadjs" && + packageName !== "s3-download-stream" + ) { + // Since npm won't release their banned-words list, we'll have to manually add to this list. + throw new Error(`${packageName}: Contains the word 'download', which is banned by npm.`); + } +} + +export function assertPackageIsNotDeprecated(packageName: string, notNeededPackages: string) { + const unneeded = JSON.parse(notNeededPackages).packages; + if (Object.keys(unneeded).includes(packageName)) { + throw new Error( + [ + `${packageName}:`, + `notNeededPackages.json has an entry for ${packageName}.`, + + `That means ${packageName} ships its own types,`, + `and @types/${packageName} was deprecated and removed from Definitely Typed.`, + + `If you want to re-add @types/${packageName}, `, + `please remove its entry from notNeededPackages.json.`, + ] + .map((sentence) => sentence.trim()) + .join(" "), + ); + } +} diff --git a/packages/dtslint/test/index.test.ts b/packages/dtslint/test/index.test.ts index af07e11ec7..1e2c2332f6 100644 --- a/packages/dtslint/test/index.test.ts +++ b/packages/dtslint/test/index.test.ts @@ -1,6 +1,6 @@ /// import { CompilerOptionsRaw, checkTsconfig } from "../src/checks"; -import { assertPackageIsNotDeprecated } from "../src/index"; +import { assertPackageIsNotDeprecated } from "../src/util"; describe("dtslint", () => { const base: CompilerOptionsRaw = {