Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions packages/dtslint/src/check-package-in-attw.ts
Original file line number Diff line number Diff line change
@@ -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<void> {
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<boolean> {
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;
}
Original file line number Diff line number Diff line change
@@ -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<void> {
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<boolean> {
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;
}
74 changes: 4 additions & 70 deletions packages/dtslint/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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<void> {
Expand Down Expand Up @@ -74,13 +74,7 @@ async function main(): Promise<void> {
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));
}
}
}
Expand Down Expand Up @@ -157,16 +151,8 @@ async function runTests(
npmChecks: boolean | "only",
tsLocal: string | undefined,
): Promise<string> {
// 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);
Expand Down Expand Up @@ -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);
Expand Down
81 changes: 80 additions & 1 deletion packages/dtslint/src/util.ts
Original file line number Diff line number Diff line change
@@ -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");
Expand Down Expand Up @@ -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(" "),
);
}
}
2 changes: 1 addition & 1 deletion packages/dtslint/test/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/// <reference types="jest" />
import { CompilerOptionsRaw, checkTsconfig } from "../src/checks";
import { assertPackageIsNotDeprecated } from "../src/index";
import { assertPackageIsNotDeprecated } from "../src/util";

describe("dtslint", () => {
const base: CompilerOptionsRaw = {
Expand Down