Switch from index.d.ts header to package.json

Lots of code churn and probable bugs, but no real functionality change
right now. In the future this makes it much easier to mimic real ES
module layouts.
This commit is contained in:
Nathan Shively-Sanders
2023-04-14 16:36:08 -07:00
parent 39d5fe7d16
commit 72c06336e7
43 changed files with 917 additions and 1114 deletions

31
.vscode/launch.json vendored
View File

@@ -15,6 +15,37 @@
"--path", "../DefinitelyTyped", "--selection", "all", "--localTypeScriptPath", "../../ts/built/local",
],
"type": "node"
},
{
"name": "jest",
"program": "${workspaceFolder}/node_modules/.bin/jest",
"request": "launch",
"skipFiles": [
"<node_internals>/**"
],
"args": [ "definitions-parser"],
"type": "node"
},
{
"name": "dts-critic test",
"program": "${workspaceFolder}/packages/dts-critic/dist/index.js",
"request": "launch",
"skipFiles": [
"<node_internals>/**"
],
"args": [ "--dts", "packages/dts-critic/testsource/tslib/index.d.ts", ],
"type": "node"
},
{
"name": "dtslint aframe",
"program": "${workspaceFolder}/packages/dtslint/dist/index.js",
"request": "launch",
"skipFiles": [
"<node_internals>/**"
],
"args": [ "../DefinitelyTyped/types/aframe", ],
"type": "node"
},
]

View File

@@ -8,7 +8,7 @@ import {
formatDependencyVersion,
removeTypesScope,
} from "./packages";
import { parsePackageSemver } from './lib/definition-parser';
import { tryParsePackageVersion } from './lib/definition-parser';
export interface Affected {
readonly changedPackages: readonly TypingsData[];
@@ -85,7 +85,7 @@ function getReverseDependencies(
}
for (const typing of allPackages.allTypings()) {
for (const [ name, version ] of typing.allPackageJsonDependencies()) {
const dependencies = map.get(packageIdToKey(allPackages.tryResolve({ name: removeTypesScope(name), version: parsePackageSemver(version) })));
const dependencies = map.get(packageIdToKey(allPackages.tryResolve({ name: removeTypesScope(name), version: tryParsePackageVersion(version) })));
if (dependencies) {
dependencies[1].add(typing.id);
}

View File

@@ -1,5 +1,5 @@
import * as ts from "typescript";
import { parseHeaderOrFail } from "@definitelytyped/header-parser";
import { validatePackageJson } from "@definitelytyped/header-parser";
import { allReferencedFiles, createSourceFile, getDeclaredGlobals } from "./module-info";
import {
DependencyVersion,
@@ -10,7 +10,6 @@ import {
DirectoryParsedTypingVersion,
getMangledNameForScopedPackage,
} from "../packages";
import * as semver from "semver";
import { getAllowedPackageJsonDependencies } from "./settings";
import {
FS,
@@ -25,6 +24,7 @@ import {
flatMap,
unique,
createModuleResolutionHost,
parsePackageSemver,
} from "@definitelytyped/utils";
import { TypeScriptVersion } from "@definitelytyped/typescript-versions";
import { slicePrefixes } from "./utils";
@@ -100,12 +100,11 @@ export async function getTypingInfo(packageName: string, dt: FS): Promise<Typing
if (!matchesVersion(data, directoryVersion, considerLibraryMinorVersion)) {
if (considerLibraryMinorVersion) {
throw new Error(
`Directory ${directoryName} indicates major.minor version ${directoryVersion.major}.${directoryVersion.minor}, ` +
`but header indicates major.minor version ${data.libraryMajorVersion}.${data.libraryMinorVersion}`
);
`Directory ${directoryName} indicates major.minor version ${directoryVersion.major}.${directoryVersion.minor ?? "*"}, ` +
`but package.json indicates major.minor version ${data.libraryMajorVersion}.${data.libraryMinorVersion}`);
}
throw new Error(
`Directory ${directoryName} indicates major version ${directoryVersion.major}, but header indicates major version ` +
`Directory ${directoryName} indicates major version ${directoryVersion.major}, but package.json indicates major version ` +
data.libraryMajorVersion.toString()
);
}
@@ -171,42 +170,11 @@ export function parseVersionFromDirectoryName(
}
/**
* Like `parseVersionFromDirectoryName`, but the leading 'v' is optional,
* and falls back to '*' if the input format is not parseable.
* TODO: Uses of this should very likely be using semver. For now I'll parse with semver and convert to major & minor | *
* falls back to '*' if the input format is not parseable.
*/
export function tryParsePackageVersion(versionString: string | undefined): DependencyVersion {
const match = /^v?(\d+)(\.(\d+))?$/.exec(versionString!);
if (match === null) {
return "*";
}
return {
major: Number(match[1]),
minor: match[3] !== undefined ? Number(match[3]) : undefined, // tslint:disable-line strict-type-predicates (false positive)
};
}
/**
* Like `tryParsePackageVersion`, but throws if the input format is not parseable.
*/
export function parsePackageVersion(versionString: string): DirectoryParsedTypingVersion {
const version = tryParsePackageVersion(versionString);
if (version === "*") {
throw new Error(`Version string '${versionString}' is not a valid format.`);
}
return version;
}
export function parsePackageSemver(version: string): DependencyVersion {
if (version === "workspace:.") {
return "*"
}
const start = new semver.Range(version).set[0][0].semver
if (start === (semver.Comparator as any).ANY) {
return "*"
}
else {
return { major: start.major, minor: start.minor }
}
return versionString != null ? parsePackageSemver(versionString) : "*";
}
async function combineDataForAllTypesVersions(
@@ -228,7 +196,10 @@ async function combineDataForAllTypesVersions(
: {};
const packageJsonType = checkPackageJsonType(packageJson.type, packageJsonName);
// Every typesVersion has an index.d.ts, but only the root index.d.ts should have a header.
const packageJsonResult = validatePackageJson(typingsPackageName, packageJsonName, packageJson, typesVersions);
if (Array.isArray(packageJsonResult)) {
throw Error(packageJsonResult.join("\n"));
}
const {
contributors,
libraryMajorVersion,
@@ -236,7 +207,7 @@ async function combineDataForAllTypesVersions(
typeScriptVersion: minTsVersion,
libraryName,
projects,
} = parseHeaderOrFail(readFileAndThrowOnBOM("index.d.ts", fs));
} = packageJsonResult
const dataForRoot = getTypingDataForSingleTypesVersion(
undefined,

View File

@@ -1,4 +1,4 @@
import { parseHeaderOrFail } from "@definitelytyped/header-parser";
import { validatePackageJson } from "@definitelytyped/header-parser";
import { Dir, FS, InMemoryFS, mangleScopedPackage } from "@definitelytyped/utils";
import * as semver from "semver";
@@ -39,14 +39,17 @@ export class DTMock {
* @param packageName The package of which an old version is to be added.
* @param olderVersion The older version that's to be added.
*/
public addOldVersionOfPackage(packageName: string, olderVersion: `${number}`) {
public addOldVersionOfPackage(packageName: string, olderVersion: `${number}`, fullVersion: string) {
const latestDir = this.pkgDir(mangleScopedPackage(packageName));
const index = latestDir.get("index.d.ts") as string;
if (latestDir.get("package.json") === undefined) {
throw new Error(`Package ${packageName} does not have a package.json`);
}
const packageJson = JSON.parse(latestDir.get("package.json") as string);
const latestHeader = parseHeaderOrFail(index);
const latestHeader = validatePackageJson(mangleScopedPackage(packageName), "package.json", packageJson, []);
if (Array.isArray(latestHeader)) {
throw new Error(latestHeader.join("\n"));
}
const latestVersion = `${latestHeader.libraryMajorVersion}.${latestHeader.libraryMinorVersion}`;
const olderVersionParsed = semver.coerce(olderVersion)!;
@@ -66,13 +69,14 @@ export class DTMock {
},
})
);
oldDir.set("package.json", JSON.stringify({...packageJson, version: olderVersion }));
oldDir.set("package.json", JSON.stringify({...packageJson, version: fullVersion }));
latestDir.forEach((content, entry) => {
if (
content !== oldDir &&
entry !== "index.d.ts" &&
entry !== "tsconfig.json" &&
entry !== "package.json" &&
!(content instanceof Dir && /^v\d+(\.\d+)?$/.test(entry))
) {
oldDir.set(entry, content);
@@ -89,11 +93,7 @@ export function createMockDT() {
const boring = dt.pkgDir("boring");
boring.set(
"index.d.ts",
`// Type definitions for boring 1.0
// Project: https://boring.com
// Definitions by: Some Guy From Space <https://github.com/goodspaceguy420>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
`
import * as React from 'react';
export const drills: number;
`
@@ -163,17 +163,13 @@ untested.d.ts
`
);
boring.set("tsconfig.json", tsconfig(["boring-tests.ts"]));
boring.set("package.json", packageJson("boring", "1.0", {"@types/react": "*", "@types/react-default": "*", "@types/things": "*", "@types/vorticon": "*", "@types/manual": "*", "@types/super-big-fun-hus": "*"}));
boring.set("package.json", packageJson("boring", "1.0",
{"@types/react": "*", "@types/react-default": "*", "@types/things": "*", "@types/vorticon": "*", "@types/manual": "*", "@types/super-big-fun-hus": "*"}));
const globby = dt.pkgDir("globby");
globby.set(
"index.d.ts",
`// Type definitions for globby 0.2
// Project: https://globby-gloopy.com
// Definitions by: The Dragon Quest Slime <https://github.com/gloopyslime>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
/// <reference path="./sneaky.d.ts" />
`/// <reference path="./sneaky.d.ts" />
/// <reference types="andere/snee" />
declare var x: number
`
@@ -199,30 +195,21 @@ var z = y;
`
);
globby.set("tsconfig.json", tsconfig(["globby-tests.ts", "test/other-tests.ts"]));
globby.set("package.json", packageJson("globby", "1.0", { "@types/andere": "*" }))
const hasDependency = dt.pkgDir("has-dependency");
hasDependency.set(
"index.d.ts",
`// Type definitions for has-dependency 3.3
// Project: https://www.microsoft.com
// Definitions by: Andrew Branch <https://github.com/andrewbranch>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
// TypeScript Version: 3.1
export * from "moment"`
`export * from "moment"`
);
hasDependency.set("has-dependency-tests.ts", "");
hasDependency.set("tsconfig.json", tsconfig(["has-dependency-tests.ts"]));
hasDependency.set("package.json", `{ "private": true, "dependencies": { "moment": "*" } }`);
hasDependency.set("package.json", packageJson("has-dependency", "1.0", { "moment": "*" }));
const hasOlderTestDependency = dt.pkgDir("has-older-test-dependency");
hasOlderTestDependency.set(
"index.d.ts",
`// Type definitions for has-older-test-dependency
// Project: https://github.com/baz/foo
// Definitions by: My Self <https://github.com/me>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
`
``
);
hasOlderTestDependency.set(
"has-older-test-dependency-tests.ts",
@@ -248,13 +235,7 @@ declare var jQuery: 1;
);
jquery.set(
"index.d.ts",
`// Type definitions for jquery 3.3
// Project: https://jquery.com
// Definitions by: Leonard Thieu <https://github.com/leonard-thieu>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
// TypeScript Version: 2.3
/// <reference path="JQuery.d.ts" />
`/// <reference path="JQuery.d.ts" />
export = jQuery;
`
@@ -271,12 +252,10 @@ console.log(jQuery);
const scoped = dt.pkgDir("wordpress__plugins");
scoped.set(
"index.d.ts",
`// Type definitions for @wordpress/plguins
// Project: https://github.com/WordPress/gutenberg/tree/master/packages/plugins/README.md
// Definitions by: Derek Sifford <https://github.com/dsifford>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
`
`
);
scoped.set("package.json", packageJson("wordpress__plugins", "1.0", {}));
scoped.set(
"tsconfig.json",
JSON.stringify({
@@ -324,7 +303,13 @@ function packageJson(packageName: string, version: string, dependencies: Record<
return `{
"private": true,
"name": "@types/${packageName}",
"version": "${version}.0",
"version": "${version}.0",
"projects": ["https://project"],
"contributors": [{
"name": "The Dragon Quest Slime",
"url": "https://github.com/slime",
"githubUsername": "slime"
}],
"dependencies": {
${Object.entries(dependencies).map(([name, version]) => ` "${name}": "${version}"`).join(",\n")}
},

View File

@@ -1,11 +1,11 @@
import assert = require("assert");
import { Author } from "@definitelytyped/header-parser";
import { FS, mapValues, assertSorted, assertDefined, unique } from "@definitelytyped/utils";
import { FS, mapValues, assertSorted, assertDefined, unique, parsePackageSemver } from "@definitelytyped/utils";
import { AllTypeScriptVersion, TypeScriptVersion } from "@definitelytyped/typescript-versions";
import * as semver from "semver";
import { readDataFile } from "./data-file";
import { scopeName, typesDirectoryName } from "./lib/settings";
import { parseVersionFromDirectoryName, parsePackageSemver } from "./lib/definition-parser";
import { parseVersionFromDirectoryName } from "./lib/definition-parser";
import { slicePrefixes } from "./lib/utils";
export class AllPackages {
@@ -141,11 +141,14 @@ export class AllPackages {
* I have NO idea why it's an iterator. Surely not for efficiency. */
*allDependencyTypings(pkg: TypingsData): Iterable<TypingsData> {
for (const [ name, version ] of pkg.allPackageJsonDependencies()) {
// TODO: chart.js@3 has types; @types/chart.js@2.9 is the last version on DT.
// It shouldn't be an error to depend on chart.js@3 but it's currently ambiguous with @types/chart.js.
if (!name.startsWith(`@${scopeName}/`)) continue
const dtName = removeTypesScope(name)
if (pkg.name === dtName) continue
const versions = this.data.get(dtName);
if (versions) {
yield versions.get(parsePackageSemver(version), pkg.libraryName);
yield versions.get(parsePackageSemver(version), pkg.name + ":" + JSON.stringify((versions as any).versions));
}
}
}
@@ -474,7 +477,7 @@ export class TypingsVersions {
getAll(): Iterable<TypingsData> {
return this.map.values();
}
// TODO: Need to use Semver instead of DependencyVersion, and getLatestMatch should use Semver ranges
get(version: DependencyVersion, errorMessage?: string): TypingsData {
return version === "*" ? this.getLatest() : this.getLatestMatch(version, errorMessage);
}

View File

@@ -4,7 +4,7 @@ import { getTypingInfo } from "./lib/definition-parser";
import { definitionParserWorkerFilename } from "./lib/definition-parser-worker";
import { AllPackages, readNotNeededPackages, typesDataFilename, TypingsVersionsRaw } from "./packages";
export { tryParsePackageVersion, parsePackageVersion } from "./lib/definition-parser";
export { tryParsePackageVersion } from "./lib/definition-parser";
export interface ParallelOptions {
readonly nProcesses: number;

View File

@@ -6,8 +6,8 @@ import { getTypingInfo } from "../src/lib/definition-parser";
describe(getTypingInfo, () => {
it("keys data by major.minor version", async () => {
const dt = createMockDT();
dt.addOldVersionOfPackage("jquery", "1.42");
dt.addOldVersionOfPackage("jquery", "2");
dt.addOldVersionOfPackage("jquery", "1.42", "1.42.0");
dt.addOldVersionOfPackage("jquery", "2", "2.0.0");
const info = await getTypingInfo("jquery", dt.fs);
expect(Object.keys(info).sort()).toEqual(["1.42", "2.0", "3.3"]);
@@ -24,12 +24,7 @@ describe(getTypingInfo, () => {
const d = dt.pkgDir("example");
d.set(
"index.d.ts",
`// Type definitions for example 1.0
// Project: https://github.com/example/com
// Definitions by: My Self <https://github.com/ñ>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
;;`
`;;`
);
d.set(
@@ -39,6 +34,24 @@ describe(getTypingInfo, () => {
compilerOptions: {},
})
);
d.set("package.json", JSON.stringify({
"private": true,
"name": "@types/example",
"version": "25.0.0",
"projects": [
"https://github.com/ckeditor/ckeditor5/tree/master/packages/ckeditor5-engine"
],
"contributors": [
{
"name": "Example",
"url": "https://github.com/example",
"githubUsername": "example"
}
],
"devDependencies": {
"@types/example": "workspace:."
}
}));
const info = await getTypingInfo("example", dt.fs);
expect(info).toBeDefined();
@@ -48,12 +61,7 @@ describe(getTypingInfo, () => {
const scopedWithOlderScopedDependency = dt.pkgDir("ckeditor__ckeditor5-engine");
scopedWithOlderScopedDependency.set(
"index.d.ts",
`// Type definitions for @ckeditor/ckeditor-engine 25.0
// Project: https://github.com/ckeditor/ckeditor5/tree/master/packages/ckeditor5-engine
// Definitions by: My Self <https://github.com/ñ>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
import * as utils from '@ckeditor/ckeditor5-utils';`
`import * as utils from '@ckeditor/ckeditor5-utils';`
);
scopedWithOlderScopedDependency.set(
@@ -68,23 +76,30 @@ import * as utils from '@ckeditor/ckeditor5-utils';`
"package.json",
JSON.stringify({
"private": true,
"name": "@types/ckeditor__ckeditor-engine",
"name": "@types/ckeditor__ckeditor5-engine",
"version": "25.0.0",
"projects": [
"https://github.com/ckeditor/ckeditor5/tree/master/packages/ckeditor5-engine"
],
"contributors": [
{
"name": "Example",
"url": "https://github.com/ñ",
"githubUsername": "ñ"
}
],
"dependencies": {
"@types/ckeditor__ckeditor5-utils": "10.0.0",
},
"devDependencies": {
"@types/ckeditor__ckeditor-engine": "workspace:."
"@types/ckeditor__ckeditor5-engine": "workspace:."
}
}))
const olderScopedPackage = dt.pkgDir("ckeditor__ckeditor5-utils");
olderScopedPackage.set(
"index.d.ts",
`// Type definitions for @ckeditor/ckeditor5-utils 25.0
// Project: https://github.com/ckeditor/ckeditor5/tree/master/packages/ckeditor5-utils
// Definitions by: My Self <https://github.com/ñ>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
`
export function myFunction(arg:string): string;
`
);
@@ -102,15 +117,25 @@ export function myFunction(arg:string): string;
"private": true,
"name": "@types/ckeditor__ckeditor5-utils",
"version": "25.0.0",
"projects": [
"https://github.com/ckeditor/ckeditor5/tree/master/packages/ckeditor5-utils"
],
"contributors": [
{
"name": "Example",
"url": "https://github.com/ñ",
"githubUsername": "ñ"
}
],
"dependencies": {
},
"devDependencies": {
"@types/ckeditor__ckeditor5-utils": "workspace:."
}
}))
dt.addOldVersionOfPackage("@ckeditor/ckeditor5-utils", "10");
dt.addOldVersionOfPackage("@ckeditor/ckeditor5-utils", "10", "10.0.0");
const info = await getTypingInfo("@ckeditor/ckeditor5-engine", dt.fs);
const info = await getTypingInfo("ckeditor__ckeditor5-engine", dt.fs);
expect(info).toBeDefined();
});
@@ -120,11 +145,7 @@ export function myFunction(arg:string): string;
const safer = dt.pkgDir("safer");
safer.set(
"index.d.ts",
`// Type definitions for safer 1.0
// Project: https://github.com/safer/safer
// Definitions by: Noone <https://github.com/noone>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
`
/// <reference types="node" />
export * from 'buffer';
`
@@ -166,6 +187,16 @@ export * from 'buffer';
"private": true,
"name": "@types/safer",
"version": "1.0.0",
"projects": [
"https://github.com/safer/safer"
],
"contributors": [
{
"name": "Noone",
"url": "https://github.com/noone",
"githubUsername": "noone"
}
],
"dependencies": {
"@types/node": "*"
},
@@ -237,6 +268,24 @@ const a = new webpack.AutomaticPrefetchPlugin();
]
}`
);
webpack.set("package.json",JSON.stringify({
"private": true,
"name": "@types/webpack",
"version": "5.2.0",
"projects": [
"https://github.com/webpack/webpack"
],
"contributors": [
{
"name": "Qubo",
"url": "https://github.com/tkqubo",
"githubUsername": "tkqubo"
}
],
"devDependencies": {
"@types/webpack": "workspace:."
}
}));
const info = await getTypingInfo("webpack", dt.fs);
expect(info).toBeDefined();
@@ -255,12 +304,7 @@ const a = new webpack.AutomaticPrefetchPlugin();
const ember = dt.pkgDir("ember");
ember.set(
"index.d.ts",
`// Type definitions for ember 2.8
// Project: https://github.com/ember/ember
// Definitions by: Chris Krycho <https://github.com/chriskrycho>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
/// <reference types="jquery" />
`/// <reference types="jquery" />
declare module '@ember/routing/route' {
}
declare module '@ember/routing/rotorooter' {
@@ -303,17 +347,28 @@ import route = require('@ember/routing/route');
`{
"private": true,
"name": "@types/ember",
"version": "5.1.0",
"version": "2.8.0",
"dependencies": {
"@types/ember__routing": "*"
},
"devDependencies": {
}
"@types/ember": "workspace:."
},
"projects": [
"https://github.com/ember"
],
"contributors": [
{
"name": "Chris Krycho",
"url": "https://github.com/chriskrycho",
"githubUsername": "chriskrycho"
}
]
}`
)
const info = await getTypingInfo("ember", dt.fs);
expect(info["2.8"].packageJsonDevDependencies).toEqual({});
expect(info["2.8"].packageJsonDevDependencies).toEqual({ "@types/ember": "workspace:." });
});
it("doesn't omit dependencies if only some deep modules are declared", async () => {
@@ -336,8 +391,8 @@ import route = require('@ember/routing/route');
describe("concerning multiple versions", () => {
it("records what the version directory looks like on disk", async () => {
const dt = createMockDT();
dt.addOldVersionOfPackage("jquery", "2");
dt.addOldVersionOfPackage("jquery", "1.5");
dt.addOldVersionOfPackage("jquery", "2", "2.0.0");
dt.addOldVersionOfPackage("jquery", "1.5", "1.5.0");
const info = await getTypingInfo("jquery", dt.fs);
expect(info).toEqual({
@@ -357,7 +412,7 @@ import route = require('@ember/routing/route');
describe("validation thereof", () => {
it("throws if a directory exists for the latest major version", () => {
const dt = createMockDT();
dt.addOldVersionOfPackage("jquery", "3");
dt.addOldVersionOfPackage("jquery", "3", "3.0.0");
return expect(getTypingInfo("jquery", dt.fs)).rejects.toThrow(
"The latest version of the 'jquery' package is 3.3, so the subdirectory 'v3' is not allowed; " +
@@ -367,7 +422,7 @@ import route = require('@ember/routing/route');
it("throws if a directory exists for the latest minor version", () => {
const dt = createMockDT();
dt.addOldVersionOfPackage("jquery", "3.3");
dt.addOldVersionOfPackage("jquery", "3.3", "3.3.0");
return expect(getTypingInfo("jquery", dt.fs)).rejects.toThrow(
"The latest version of the 'jquery' package is 3.3, so the subdirectory 'v3.3' is not allowed."
@@ -376,7 +431,7 @@ import route = require('@ember/routing/route');
it("does not throw when a minor version is older than the latest", () => {
const dt = createMockDT();
dt.addOldVersionOfPackage("jquery", "3.0");
dt.addOldVersionOfPackage("jquery", "3.0", "3.0.0");
return expect(getTypingInfo("jquery", dt.fs)).resolves.toBeDefined();
});

View File

@@ -1,6 +1 @@
// Type definitions for fail 1.0
// Project: https://youtube.com/typeref-fails
// Definitions by: Type Ref Fails <https://github.com/typeref-fails>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
/// <reference types="fail/v3" />

View File

@@ -0,0 +1,18 @@
{
"private": true,
"name": "@types/fail",
"version": "1.0.0",
"projects": [
"https://youtube.com/typeref-fails"
],
"devDependencies": {
"@types/fail": "workspace:."
},
"contributors": [
{
"name": "Type Ref Fails",
"url": "https://github.com/typeref-fails",
"githubUsername": "typeref-fails"
}
]
}

View File

@@ -1,8 +1,3 @@
// Type definitions for styled-components-react-native 5.1
// Project: https://github.com/styled-components/styled-components
// Definitions by: Nathan Bierema <https://github.com/Methuselah96>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
declare module "styled-components/native" {
import {} from "styled-components";
}

View File

@@ -2,10 +2,20 @@
"private": true,
"name": "@types/styled-components-react-native",
"version": "5.1.0",
"projects": [
"https://github.com/styled-components/styled-components"
],
"dependencies": {
"@types/styled-components": "*"
},
"devDependencies": {
"@types/styled-components-react-native": "workspace:."
}
},
"contributors": [
{
"name": "Nathan Bierema",
"url": "https://github.com/Methuselah96",
"githubUsername": "Methuselah96"
}
]
}

View File

@@ -1,6 +1 @@
// Type definitions for referenced 1.0
// Project: https://youtube.com/s-fails
// Definitions by: Type Ref Fails <https://github.com/typeref-fails>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
export interface Referenced {}

View File

@@ -0,0 +1,18 @@
{
"private": true,
"name": "@types/referenced",
"version": "1.0.0",
"projects": [
"https://youtube.com/s-fails"
],
"devDependencies": {
"@types/referenced": "workspace:."
},
"contributors": [
{
"name": "Typescript Bot",
"url": "https://github.com/typescript-bot",
"githubUsername": "typescript-bot"
}
]
}

View File

@@ -1,8 +1,3 @@
// Type definitions for referencing 1.0
// Project: https://youtube.com/s-fails
// Definitions by: Type Ref Fails <https://github.com/typeref-fails>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
import { Referenced } from "../referenced";
export interface Referencing extends Referenced {}

View File

@@ -0,0 +1,18 @@
{
"private": true,
"name": "@types/referencing",
"version": "1.0.0",
"projects": [
"https://youtube.com/s-fails"
],
"devDependencies": {
"@types/referencing": "workspace:."
},
"contributors": [
{
"name": "Typescript Bot",
"url": "https://github.com/typescript-bot",
"githubUsername": "typescript-bot"
}
]
}

View File

@@ -38,6 +38,7 @@ testo({
expect(changedPackages).toEqual([]);
},
olderVersion() {
debugger;
const { changedPackages, dependentPackages } = getAffectedPackages(allPackages, [
{ name: "jquery", version: { major: 1 } },
]);

View File

@@ -20,7 +20,7 @@ describe(AllPackages, () => {
beforeAll(async () => {
const dt = createMockDT();
dt.addOldVersionOfPackage("jquery", "1");
dt.addOldVersionOfPackage("jquery", "1", "1.0.0");
const [log] = quietLoggerWithErrors();
allPackages = await parseDefinitions(dt.fs, undefined, log);
});
@@ -63,9 +63,9 @@ describe(TypingsVersions, () => {
beforeAll(async () => {
const dt = createMockDT();
dt.addOldVersionOfPackage("jquery", "1");
dt.addOldVersionOfPackage("jquery", "2");
dt.addOldVersionOfPackage("jquery", "2.5");
dt.addOldVersionOfPackage("jquery", "1", "1.0.0");
dt.addOldVersionOfPackage("jquery", "2", "2.0.0");
dt.addOldVersionOfPackage("jquery", "2.5", "2.5.0");
versions = new TypingsVersions(await getTypingInfo("jquery", dt.fs));
});

View File

@@ -110,15 +110,10 @@ function getNonNpm(args: { dtPath: string }): void {
const dtTypesPath = getDtTypesPath(args.dtPath);
const isNpmJson = getAllIsNpm(args.dtPath);
for (const item of fs.readdirSync(dtTypesPath)) {
const entry = path.join(dtTypesPath, item);
const dts = fs.readFileSync(entry + "/index.d.ts", "utf8");
let header;
try {
header = headerParser.parseHeaderOrFail(dts);
} catch (e) {
header = undefined;
}
if (!isNpmPackage(item, header, isNpmJson)) {
const packageJsonPath = path.join(dtTypesPath, item, "package.json");
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
let header= headerParser.validatePackageJson(item, packageJsonPath, packageJson, [])
if (!isNpmPackage(item, Array.isArray(header) ? undefined : header, isNpmJson)) {
nonNpm.push(item);
}
}

View File

@@ -268,7 +268,7 @@ Add -browser to the end of your name to make sure it doesn't conflict with exist
]);
},
noMatchingNpmVersion() {
expect(dtsCritic(testsource("typescript.d.ts"))).toEqual([
expect(dtsCritic(testsource("typescript/index.d.ts"))).toEqual([
{
kind: ErrorKind.NoMatchingNpmVersion,
message: expect.stringContaining(`The types for 'typescript' must match a version that exists on npm.
@@ -277,7 +277,7 @@ You should copy the major and minor version from the package on npm.`),
]);
},
nonNpmHasMatchingPackage() {
expect(dtsCritic(testsource("tslib.d.ts"))).toEqual([
expect(dtsCritic(testsource("tslib/index.d.ts"))).toEqual([
{
kind: ErrorKind.NonNpmHasMatchingPackage,
message: `The non-npm package 'tslib' conflicts with the existing npm package 'tslib'.

View File

@@ -77,10 +77,10 @@ export function dtsCritic(
);
}
const dts = fs.readFileSync(dtsPath, "utf-8");
const header = parseDtHeader(dts);
const name = findDtsName(dtsPath);
const packageJsonPath = path.join(path.dirname(path.resolve(dtsPath)), "package.json");
const header = parseDtHeader(name, JSON.parse(fs.readFileSync(packageJsonPath, "utf-8")));
const npmInfo = getNpmInfo(name);
if (isNonNpm(header)) {
@@ -128,12 +128,9 @@ If you want to check the declaration against the JavaScript source code, you mus
}
}
function parseDtHeader(dts: string): headerParser.Header | undefined {
try {
return headerParser.parseHeaderOrFail(dts);
} catch (e) {
return undefined;
}
function parseDtHeader(packageName: string, packageJson: Record<string, unknown>): headerParser.Header | undefined {
const result = headerParser.validatePackageJson(packageName, "package.json", packageJson, []);
return Array.isArray(result) ? undefined : result;
}
function isNonNpm(header: headerParser.Header | undefined): boolean {

View File

@@ -0,0 +1,18 @@
{
"private": true,
"name": "@types/deimos",
"version": "0.0.0",
"projects": [
"https://github.com/microsoft/TypeScript"
],
"devDependencies": {
"@types/typescript": "workspace:."
},
"contributors": [
{
"name": "Typescript Bot",
"url": "https://github.com/typescript-bot",
"githubUsername": "typescript-bot"
}
]
}

View File

@@ -1,4 +0,0 @@
// Type definitions for non-npm package tslib
// Project: https://github.com/microsoft/TypeScript
// Definitions by: TypeScript Bot <https://github.com/typescript-bot>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped

View File

View File

@@ -0,0 +1,20 @@
{
"private": true,
"name": "@types/tslib",
"nonNpm": true,
"nonNpmDescription": "tslib",
"version": "1.0.0",
"projects": [
"https://github.com/microsoft/TypeScript"
],
"devDependencies": {
"@types/tslib": "workspace:."
},
"contributors": [
{
"name": "Typescript Bot",
"url": "https://github.com/typescript-bot",
"githubUsername": "typescript-bot"
}
]
}

View File

@@ -1,4 +0,0 @@
// Type definitions for typescript 1200000.5
// Project: https://github.com/microsoft/TypeScript
// Definitions by: TypeScript Bot <https://github.com/typescript-bot>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped

View File

@@ -0,0 +1,18 @@
{
"private": true,
"name": "@types/typescript",
"version": "1200000.5.0",
"projects": [
"https://github.com/microsoft/TypeScript"
],
"devDependencies": {
"@types/typescript": "workspace:."
},
"contributors": [
{
"name": "Typescript Bot",
"url": "https://github.com/typescript-bot",
"githubUsername": "typescript-bot"
}
]
}

View File

@@ -1,88 +1,21 @@
import { makeTypesVersionsForPackageJson } from "@definitelytyped/header-parser";
import { TypeScriptVersion } from "@definitelytyped/typescript-versions";
import assert = require("assert");
import * as header from "@definitelytyped/header-parser";
import { AllTypeScriptVersion } from "@definitelytyped/typescript-versions";
import { pathExistsSync } from "fs-extra";
import { join as joinPaths } from "path";
import { CompilerOptions } from "typescript";
import { deepEquals } from "@definitelytyped/utils";
import { readJson, packageNameFromPath } from "./util";
export function checkPackageJson(dirPath: string, typesVersions: readonly TypeScriptVersion[]): string[] {
// TODO: forbid triple-slash types references like "package/v1"
export function checkPackageJson(dirPath: string, typesVersions: readonly AllTypeScriptVersion[]): header.Header | string[] {
// TODO: Don't allow package.json except in the root dir of a package and of /v* folders one below the root.
// (this used to be in dt-header rule, but probably also elsewhere)
const pkgJsonPath = joinPaths(dirPath, "package.json");
if (!pathExistsSync(pkgJsonPath)) {
throw new Error(`${dirPath}: Missing 'package.json'`);
}
return checkPackageJsonContents(dirPath, readJson(pkgJsonPath), typesVersions);
return header.validatePackageJson(packageNameFromPath(dirPath), joinPaths(dirPath, "package.json"), readJson(pkgJsonPath), typesVersions);
}
// TODO: forbid triple-slash types references like "package/v1"
export function checkPackageJsonContents(dirPath: string, pkgJson: Record<string, unknown>, typesVersions: readonly TypeScriptVersion[]): string[] {
const errors = []
const pkgJsonPath = joinPaths(dirPath, "package.json");
const packageName = packageNameFromPath(dirPath);
const needsTypesVersions = typesVersions.length !== 0;
if (pkgJson.private !== true) {
errors.push(`${pkgJsonPath} should have \`"private": true\``)
}
if (pkgJson.name !== "@types/" + packageName) {
errors.push(`${pkgJsonPath} should have \`"name": "@types/${packageName}"\``);
}
if (typeof pkgJson.devDependencies !== "object"
|| pkgJson.devDependencies === null
|| (pkgJson.devDependencies as any)["@types/" + packageName] !== "workspace:.") {
errors.push(`In ${pkgJsonPath}, devDependencies must include \`"@types/${packageName}": "workspace:."\``);
}
// TODO: Dependencies are not allowed in devDependencies, to avoid redundancy (although this is VERY linty)
if (!pkgJson.version || typeof pkgJson.version !== "string") {
errors.push(`${pkgJsonPath} should have \`"version"\` matching the version of the implementation package.`);
}
else if (!/\d+\.\d+\.\d+/.exec(pkgJson.version)) {
errors.push(`${pkgJsonPath} has bad "version": should look like "NN.NN.0"`);
}
else if (!pkgJson.version.endsWith(".0")) {
errors.push(`${pkgJsonPath} has bad "version": must end with ".0"`);
}
if (needsTypesVersions) {
assert.strictEqual(pkgJson.types, "index", `"types" in '${pkgJsonPath}' should be "index".`);
const expected = makeTypesVersionsForPackageJson(typesVersions) as Record<string, object>;
if (!deepEquals(pkgJson.typesVersions, expected)) {
errors.push(`"typesVersions" in '${pkgJsonPath}' is not set right. Should be: ${JSON.stringify(expected, undefined, 4)}`)
}
}
// NOTE: I had to install eslint-plugin-import in DT bceause DT-tools hasn't shipped a new version
// DONE: normal package: 3box
// DONE: scoped package: adeira__js
// DONE: old-version package: gulp/v3
// DONE: old-TS-version package: har-format
// DONE: react
// DONE: node
for (const key in pkgJson) {
switch (key) {
case "private":
case "dependencies":
case "license":
case "imports":
case "exports":
case "type":
case "name":
case "version":
case "devDependencies":
// "private"/"typesVersions"/"types"/"name"/"version" checked above, "dependencies" / "license" checked by types-publisher,
// TODO: asserts for above in types-publisher
break;
case "typesVersions":
case "types":
if (!needsTypesVersions) {
errors.push(`${pkgJsonPath} doesn't need to set "${key}" when no 'ts3.x' directories exist.`);
}
break;
default:
errors.push(`${pkgJsonPath} should not include field ${key}`);
}
}
return errors
}
/**
* numbers in `CompilerOptions` might be enum values mapped from strings
*/
@@ -97,52 +30,50 @@ export interface DefinitelyTypedInfo {
readonly relativeBaseUrl: string;
}
// TODO: Maybe check ALL of tsconfig, not just compilerOptions
export function checkTsconfig(options: CompilerOptionsRaw, dt: boolean): string[] {
export function checkTsconfig(options: CompilerOptionsRaw): string[] {
const errors = []
if (dt) {
const mustHave = {
noEmit: true,
forceConsistentCasingInFileNames: true,
types: [],
};
const mustHave = {
noEmit: true,
forceConsistentCasingInFileNames: true,
types: [],
};
for (const key of Object.getOwnPropertyNames(mustHave) as (keyof typeof mustHave)[]) {
const expected = mustHave[key];
const actual = options[key];
if (!deepEquals(expected, actual)) {
errors.push(
`Expected compilerOptions[${JSON.stringify(key)}] === ${JSON.stringify(expected)}, but got ${JSON.stringify(
actual
)}`
);
}
for (const key of Object.getOwnPropertyNames(mustHave) as (keyof typeof mustHave)[]) {
const expected = mustHave[key];
const actual = options[key];
if (!deepEquals(expected, actual)) {
errors.push(
`Expected compilerOptions[${JSON.stringify(key)}] === ${JSON.stringify(expected)}, but got ${JSON.stringify(
actual
)}`
);
}
}
for (const key in options) {
switch (key) {
case "lib":
case "noImplicitAny":
case "noImplicitThis":
case "strict":
case "strictNullChecks":
case "noUncheckedIndexedAccess":
case "strictFunctionTypes":
case "esModuleInterop":
case "allowSyntheticDefaultImports":
case "target":
case "jsx":
case "jsxFactory":
case "experimentalDecorators":
case "noUnusedLocals":
case "noUnusedParameters":
case "exactOptionalPropertyTypes":
case "module":
break;
default:
if (!(key in mustHave)) {
errors.push(`Unexpected compiler option ${key}`);
}
}
for (const key in options) {
switch (key) {
case "lib":
case "noImplicitAny":
case "noImplicitThis":
case "strict":
case "strictNullChecks":
case "noUncheckedIndexedAccess":
case "strictFunctionTypes":
case "esModuleInterop":
case "allowSyntheticDefaultImports":
case "target":
case "jsx":
case "jsxFactory":
case "experimentalDecorators":
case "noUnusedLocals":
case "noUnusedParameters":
case "exactOptionalPropertyTypes":
case "module":
break;
default:
if (!(key in mustHave)) {
errors.push(`Unexpected compiler option ${key}`);
}
}
}
if (!("lib" in options)) {
@@ -190,20 +121,3 @@ export function checkTsconfig(options: CompilerOptionsRaw, dt: boolean): string[
}
return errors;
}
function deepEquals(expected: unknown, actual: unknown): boolean {
if (expected instanceof Array) {
return (
actual instanceof Array && actual.length === expected.length && expected.every((e, i) => deepEquals(e, actual[i]))
);
} else if (typeof expected === "object") {
for (const k in expected) {
if (!deepEquals((expected as any)[k], (actual as any)[k])) {
return false;
}
}
return true
}else {
return expected === actual;
}
}

View File

@@ -1,6 +1,5 @@
#!/usr/bin/env node
import { parseTypeScriptVersionLine } from "@definitelytyped/header-parser";
import { AllTypeScriptVersion, TypeScriptVersion } from "@definitelytyped/typescript-versions";
import assert = require("assert");
import { readdir, readFile, stat, existsSync } from "fs-extra";
@@ -130,19 +129,12 @@ async function runTests(
expectOnly: boolean,
tsLocal: string | undefined
): Promise<void> {
const indexText = await readFile(joinPaths(dirPath, "index.d.ts"), "utf-8");
// If this *is* on DefinitelyTyped, types-publisher will fail if it can't parse the header.
// TODO: We really shouldn't be running off DT anymore.
const dt = indexText.includes("// Type definitions for");
if (dt) {
// Someone may have copied text from DefinitelyTyped to their type definition and included a header,
// so assert that we're really on DefinitelyTyped.
const dtRoot = findDTRoot(dirPath);
const packageName = packageNameFromPath(dirPath);
assertPathIsInDefinitelyTyped(dirPath, dtRoot);
assertPathIsNotBanned(packageName);
assertPackageIsNotDeprecated(packageName, await readFile(joinPaths(dtRoot, "notNeededPackages.json"), "utf-8"));
}
// Assert that we're really on DefinitelyTyped.
const dtRoot = findDTRoot(dirPath);
const packageName = packageNameFromPath(dirPath);
assertPathIsInDefinitelyTyped(dirPath, dtRoot);
assertPathIsNotBanned(packageName);
assertPackageIsNotDeprecated(packageName, await readFile(joinPaths(dtRoot, "notNeededPackages.json"), "utf-8"));
const typesVersions = await mapDefinedAsync(await readdir(dirPath), async (name) => {
if (name === "tsconfig.json" || name === "tslint.json" || name === "tsutils") {
@@ -159,23 +151,21 @@ async function runTests(
if (!TypeScriptVersion.isRedirectable(version)) {
throw new Error(`At ${dirPath}/${name}: TypeScript version directories only available starting with ts3.1.`);
}
if (!TypeScriptVersion.isSupported(version)) {
throw new Error("At ${dirPath}/${name}: TypeScript version ${version} is not supported on Definitely Typed.");
}
return version;
});
if (dt) {
const packageJsonErrors = checkPackageJson(dirPath, typesVersions);
if (packageJsonErrors.length > 0) {
throw new Error("\n\t* " + packageJsonErrors.join("\n\t* "))
}
const packageJson = checkPackageJson(dirPath, typesVersions);
if (Array.isArray(packageJson)) {
throw new Error("\n\t* " + packageJson.join("\n\t* "))
}
const minVersion = maxVersion(
getMinimumTypeScriptVersionFromComment(indexText),
TypeScriptVersion.lowest
) as TypeScriptVersion;
const minVersion = maxVersion(packageJson.typeScriptVersion, TypeScriptVersion.lowest);
if (onlyTestTsNext || tsLocal) {
const tsVersion = tsLocal ? "local" : TypeScriptVersion.latest;
await testTypesVersion(dirPath, tsVersion, tsVersion, dt, expectOnly, tsLocal, /*isLatest*/ true);
await testTypesVersion(dirPath, tsVersion, tsVersion, expectOnly, tsLocal, /*isLatest*/ true);
} else {
// For example, typesVersions of [3.2, 3.5, 3.6] will have
// associated ts3.2, ts3.5, ts3.6 directories, for
@@ -196,18 +186,15 @@ async function runTests(
if (lows.length > 1) {
console.log("testing from", low, "to", hi, "in", versionPath);
}
await testTypesVersion(versionPath, low, hi, dt, expectOnly, undefined, isLatest);
await testTypesVersion(versionPath, low, hi, expectOnly, undefined, isLatest);
}
}
}
function maxVersion(v1: TypeScriptVersion | undefined, v2: TypeScriptVersion): TypeScriptVersion;
function maxVersion(v1: AllTypeScriptVersion | undefined, v2: AllTypeScriptVersion): AllTypeScriptVersion;
function maxVersion(v1: AllTypeScriptVersion | undefined, v2: AllTypeScriptVersion) {
if (!v1) return v2;
if (!v2) return v1;
if (parseFloat(v1) >= parseFloat(v2)) return v1;
return v2;
function maxVersion(v1: TypeScriptVersion, v2: TypeScriptVersion): TypeScriptVersion;
function maxVersion(v1: AllTypeScriptVersion, v2: TypeScriptVersion): TypeScriptVersion;
function maxVersion(v1: AllTypeScriptVersion, v2: TypeScriptVersion) {
return parseFloat(v1) >= parseFloat(v2) ? v1 : v2;
}
function next(v: TypeScriptVersion): TypeScriptVersion {
@@ -221,13 +208,12 @@ async function testTypesVersion(
dirPath: string,
lowVersion: TsVersion,
hiVersion: TsVersion,
dt: boolean,
expectOnly: boolean,
tsLocal: string | undefined,
isLatest: boolean
): Promise<void> {
checkTslintJson(dirPath, dt);
const tsconfigErrors = checkTsconfig(getCompilerOptions(dirPath), dt);
checkTslintJson(dirPath);
const tsconfigErrors = checkTsconfig(getCompilerOptions(dirPath));
if (tsconfigErrors.length > 0) {
throw new Error("\n\t* " + tsconfigErrors.join("\n\t* "))
}
@@ -289,19 +275,6 @@ If you want to re-add @types/${packageName}, please remove its entry from notNee
}
}
function getMinimumTypeScriptVersionFromComment(text: string): AllTypeScriptVersion | undefined {
const match = text.match(/\/\/ (?:Minimum )?TypeScript Version: /);
if (!match) {
return undefined;
}
let line = text.slice(match.index, text.indexOf("\n", match.index));
if (line.endsWith("\r")) {
line = line.slice(0, line.length - 1);
}
return parseTypeScriptVersionLine(line);
}
if (!module.parent) {
main().catch((err) => {
console.error(err.stack);

View File

@@ -190,25 +190,14 @@ function testNoLintDisables(disabler: "tslint:disable" | "eslint-disable", text:
}
}
export function checkTslintJson(dirPath: string, dt: boolean): void {
export function checkTslintJson(dirPath: string): void {
const configPath = getConfigPath(dirPath);
const shouldExtend = `@definitelytyped/dtslint/${dt ? "dt" : "dtslint"}.json`;
const validateExtends = (extend: string | string[]) =>
extend === shouldExtend || (!dt && Array.isArray(extend) && extend.some((val) => val === shouldExtend));
const shouldExtend = "@definitelytyped/dtslint/dt.json";
if (!pathExistsSync(configPath)) {
if (dt) {
throw new Error(
`On DefinitelyTyped, must include \`tslint.json\` containing \`{ "extends": "${shouldExtend}" }\`.\n` +
"This was inferred as a DefinitelyTyped package because it contains a `// Type definitions for` header."
);
}
return;
throw new Error(`Missing \`tslint.json\` that contains \`{ "extends": "${shouldExtend}" }\`.`);
}
const tslintJson = readJson(configPath);
if (!validateExtends(tslintJson.extends as any)) {
throw new Error(`If 'tslint.json' is present, it should extend "${shouldExtend}"`);
if (readJson(configPath).extends !== shouldExtend) {
throw new Error(`'tslint.json' must extend "${shouldExtend}"`);
}
}

View File

@@ -1,74 +0,0 @@
import { renderExpected, validate } from "@definitelytyped/header-parser";
import { createRule, isMainFile } from "../util";
type MessageId =
| "definitionsBy"
| "minimumTypeScriptVersion"
| "parseError"
| "typeDefinitionsFor"
| "typescriptVersion";
const rule = createRule({
name: "dt-header",
defaultOptions: [],
meta: {
type: "problem",
docs: {
description: "Ensure consistency of DefinitelyTyped headers.",
recommended: "error",
},
messages: {
definitionsBy: "Author name should be your name, not the default.",
minimumTypeScriptVersion: "TypeScript version should be specified under header in `index.d.ts`.",
parseError: "Error parsing header. Expected: {{expected}}",
typeDefinitionsFor: "Header should only be in `index.d.ts` of the root.",
typescriptVersion: "Minimum TypeScript version should be specified under header in `index.d.ts`.",
},
schema: [],
},
create(context) {
const sourceCode = context.getSourceCode();
const { lines, text } = sourceCode;
const lookFor = (search: string, messageId: MessageId) => {
for (let i = 0; i < lines.length; i += 1) {
if (lines[i].startsWith(search)) {
context.report({
loc: {
end: { line: i + 1, column: search.length },
start: { line: i + 1, column: 0 },
},
messageId,
});
}
}
};
if (!isMainFile(context.getFilename(), /*allowNested*/ true)) {
lookFor("// Type definitions for", "typeDefinitionsFor");
lookFor("// TypeScript Version", "typescriptVersion");
lookFor("// Minimum TypeScript Version", "minimumTypeScriptVersion");
return {};
}
lookFor("// Definitions by: My Self", "definitionsBy");
const error = validate(text);
if (error) {
context.report({
data: {
expected: renderExpected(error.expected),
},
loc: {
column: error.column,
line: error.line,
},
messageId: "parseError",
});
}
return {};
},
});
export = rule;

View File

@@ -1,189 +0,0 @@
import { ESLintUtils } from "@typescript-eslint/utils";
import * as dtHeader from "../src/rules/dt-header";
const ruleTester = new ESLintUtils.RuleTester({
parser: "@typescript-eslint/parser",
});
ruleTester.run("dt-header", dtHeader, {
invalid: [
{
code: ``,
errors: [
{
column: 2,
data: {
expected: "/\\/\\/ Type definitions for (non-npm package )?/",
},
line: 1,
messageId: "parseError",
},
],
filename: "types/blank/index.d.ts",
},
{
code: `
// ...
`,
errors: [
{
column: 1,
data: {
expected: "/\\/\\/ Type definitions for (non-npm package )?/",
},
line: 2,
messageId: "parseError",
},
],
filename: "types/only-comment/index.d.ts",
},
{
code: `
// Type definitions for dt-header 0.75
// Project: https://github.com/bobby-headers/dt-header
// Definitions by: Jane Doe <https://github.com/janedoe>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
// Minimum TypeScript Version: 3.1
`,
errors: [
{
column: 1,
data: {
expected: "/\\/\\/ Type definitions for (non-npm package )?/",
},
line: 2,
messageId: "parseError",
},
],
filename: "types/start-with-whitespace/index.d.ts",
},
{
code: `// Type definitions for dt-header 1.0
// Project: https://github.com/bobby-headers/dt-header
// Definitions by: Jane Doe <https://github.ORG/janedoe>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
`,
errors: [
{
column: 30,
data: {
expected: "/\\<https\\:\\/\\/github\\.com\\/([a-zA-Z\\d\\-]+)\\/?\\>/",
},
line: 3,
messageId: "parseError",
},
],
filename: "types/bad-url-github-org/index.d.ts",
},
{
code: `// Type definitions for dt-header 1.0
// Project: https://github.com/bobby-headers/dt-header
// Definitions by: Jane Doe <https://github.com/jane doe>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
`,
errors: [
{
column: 30,
data: {
expected: "/\\<https\\:\\/\\/github\\.com\\/([a-zA-Z\\d\\-]+)\\/?\\>/",
},
line: 3,
messageId: "parseError",
},
],
filename: "types/bad-url-space/index.d.ts",
},
{
code: `// Type definitions for dt-header 1.0
// Project: https://github.com/not/important
// Definitions by: My Self <https://github.com/me>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
`,
errors: [
{
column: 1,
endColumn: 27,
line: 3,
messageId: "definitionsBy",
},
],
filename: "types/bad-username/index.d.ts",
},
{
code: `// Type definitions for dt-header v1.0.3
// Project: https://github.com/bobby-headers/dt-header
// Definitions by: Jane Doe <https://github.com/janedoe>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
`,
errors: [
{
column: 26,
data: {
expected: "foo MAJOR.MINOR (patch version not allowed)",
},
line: 1,
messageId: "parseError",
},
],
filename: "types/foo/index.d.ts",
},
{
code: `// Type definitions for
`,
errors: [
{
column: 1,
endColumn: 24,
line: 1,
messageId: "typeDefinitionsFor",
},
],
filename: "types/foo/notIndex.d.ts",
},
],
valid: [
``,
{
code: `// This isn't the main index
`,
filename: "types/foo/bar/index.d.ts",
},
{
code: `// Type definitions for dt-header 0.75
// Project: https://github.com/bobby-headers/dt-header
// Definitions by: Jane Doe <https://github.com/janedoe>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
// Minimum TypeScript Version: 3.1
`,
filename: "types/foo/v0.75/index.d.ts",
},
{
code: `// Type definitions for dt-header 1.0
// Project: https://github.com/bobby-headers/dt-header
// Definitions by: Jane Doe <https://github.com/janedoe>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
// Minimum TypeScript Version: 3.1
`,
filename: "types/foo/v1/index.d.ts",
},
{
code: `// Type definitions for dt-header 2.0
// Project: https://github.com/bobby-headers/dt-header
// Definitions by: Jane Doe <https://github.com/janedoe>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
// Minimum TypeScript Version: 3.1
`,
filename: "types/foo/index.d.ts",
},
{
code: ``,
filename: "types/foo/notIndex.d.ts",
},
],
});

View File

@@ -45,6 +45,11 @@ describe("dtslint", () => {
"private": true,
"name": "@types/hapi",
"version": "18.0.0",
"projects": [
"https://github.com/hapijs/hapi",
"https://hapijs.com"
],
"typeScriptVersion": "4.2",
"dependencies": {
"@types/boom": "*",
"@types/catbox": "*",
@@ -57,96 +62,63 @@ describe("dtslint", () => {
},
"devDependencies": {
"@types/hapi": "workspace:."
}
},
"contributors": [
{
"name": "Rafael Souza Fijalkowski",
"url": "https://github.com/rafaelsouzaf",
"githubUsername": "rafaelsouzaf"
},
{
"name": "Justin Simms",
"url": "https://github.com/jhsimms",
"githubUsername": "jhsimms"
},
{
"name": "Simon Schick",
"url": "https://github.com/SimonSchick",
"githubUsername": "SimonSchick"
},
{
"name": "Rodrigo Saboya",
"url": "https://github.com/saboya",
"githubUsername": "saboya"
}
]
}
describe("checks", () => {
describe("checkTsconfig", () => {
it("disallows unknown compiler options", () => {
expect(checkTsconfig({ ...base, completelyInvented: true }, true)).toEqual([
expect(checkTsconfig({ ...base, completelyInvented: true })).toEqual([
"Unexpected compiler option completelyInvented"
]);
});
it("allows exactOptionalPropertyTypes: true", () => {
expect(checkTsconfig({ ...base, exactOptionalPropertyTypes: true }, true)).toEqual([]);
expect(checkTsconfig({ ...base, exactOptionalPropertyTypes: true })).toEqual([]);
});
it("allows module: node16", () => {
expect(checkTsconfig({ ...base, module: "node16" }, true)).toEqual([]);
expect(checkTsconfig({ ...base, module: "node16" })).toEqual([]);
});
it("disallows missing `module`", () => {
const options = { ...base };
delete options.module;
expect(checkTsconfig(options, true)).toEqual([
expect(checkTsconfig(options)).toEqual([
'Must specify "module" to `"module": "commonjs"` or `"module": "node16"`.'
]);
});
it("disallows exactOptionalPropertyTypes: false", () => {
expect(checkTsconfig({ ...base, exactOptionalPropertyTypes: false }, true)).toEqual([
expect(checkTsconfig({ ...base, exactOptionalPropertyTypes: false })).toEqual([
'When "exactOptionalPropertyTypes" is present, it must be set to `true`.'
]);
});
it("disallows `paths`", () => {
expect(checkTsconfig({ ...base, paths: { "c": ['.'] } }, true)).toEqual([
expect(checkTsconfig({ ...base, paths: { "c": ['.'] } })).toEqual([
'Unexpected compiler option paths'
]);
});
});
describe("checkPackageJson", () => {
it("requires private: true", () => {
const pkg = { ...pkgJson };
delete pkg.private;
expect(checkPackageJsonContents("cort-start/hapi", pkg, [])).toEqual([
"cort-start/hapi/package.json should have `\"private\": true`"
]);
});
it("requires name", () => {
const pkg = { ...pkgJson };
delete pkg.name;
expect(checkPackageJsonContents("cort-start/hapi", pkg, [])).toEqual([
"cort-start/hapi/package.json should have `\"name\": \"@types/hapi\"`"
]);
});
it("requires name to match", () => {
expect(checkPackageJsonContents("cort-start/hapi", { ...pkgJson, name: "@types/sad" }, [])).toEqual([
"cort-start/hapi/package.json should have `\"name\": \"@types/hapi\"`"
]);
});
it("requires devDependencies", () => {
const pkg = { ...pkgJson };
delete pkg.devDependencies;
expect(checkPackageJsonContents("cort-start/hapi", pkg, [])).toEqual([
`In cort-start/hapi/package.json, devDependencies must include \`"@types/hapi": "workspace:."\``
]);
});
it("requires devDependencies to contain self-package", () => {
expect(checkPackageJsonContents("cort-start/hapi", { ...pkgJson, devDependencies: { } }, [])).toEqual([
`In cort-start/hapi/package.json, devDependencies must include \`"@types/hapi": "workspace:."\``
]);
});
it("requires devDependencies to contain self-package version 'workspace:.'", () => {
expect(checkPackageJsonContents("cort-start/hapi", { ...pkgJson, devDependencies: { "@types/hapi": "*" } }, [])).toEqual([
`In cort-start/hapi/package.json, devDependencies must include \`"@types/hapi": "workspace:."\``
]);
});
it("requires version", () => {
const pkg = { ...pkgJson };
delete pkg.version;
expect(checkPackageJsonContents("cort-start/hapi", pkg, [])).toEqual([
`cort-start/hapi/package.json should have \`"version"\` matching the version of the implementation package.`
]);
});
it("requires version to be NN.NN.NN", () => {
expect(checkPackageJsonContents("cort-start/hapi", { ...pkgJson, version: "hi there" }, [])).toEqual([
`cort-start/hapi/package.json has bad "version": should look like "NN.NN.0"`
]);
});
it("requires version to end with .0", () => {
expect(checkPackageJsonContents("cort-start/hapi", { ...pkgJson, version: "1.2.3" }, [])).toEqual([
`cort-start/hapi/package.json has bad "version": must end with ".0"`
]);
});
it("works with old-version packages", () => {
expect(checkPackageJsonContents("cort-start/hapi/v16", { ...pkgJson, version: "16.6.0" }, [])).toEqual([])
});
;
});
describe("assertPackageIsNotDeprecated", () => {
it("disallows packages that are in notNeededPackages.json", () => {

View File

@@ -1,6 +1 @@
// Type definitions for package dts-critic 1.0
// Project: https://https://github.com/DefinitelyTyped/dts-critic
// Definitions by: Jane Doe <https://github.com/janedoe>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
export default dtsCritic();

View File

@@ -0,0 +1,18 @@
{
"private": true,
"name": "@types/dts-critic",
"version": "1.0.0",
"projects": [
"https://github.com/microsoft/TypeScript"
],
"devDependencies": {
"@types/dts-critic": "workspace:."
},
"contributors": [
{
"name": "Jane Doe",
"url": "https://github.com/janedoe",
"githubUsername": "janedoe"
}
]
}

View File

@@ -1,4 +0,0 @@
// Type definitions for package wenceslas 1.0
// Project: https://github.com/bobby-headers/dt-header
// Definitions by: Jane Doe <https://github.com/janedoe>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped

View File

@@ -0,0 +1,18 @@
{
"private": true,
"name": "@types/wenceslas",
"version": "1.0.0",
"projects": [
"https://github.com/bobby-headers/dt-header"
],
"devDependencies": {
"@types/wenceslas": "workspace:."
},
"contributors": [
{
"name": "Jane Doe",
"url": "https://github.com/janedoe",
"githubUsername": "janedoe"
}
]
}

View File

@@ -1,10 +1,10 @@
import pm = require("parsimmon");
import { AllTypeScriptVersion, TypeScriptVersion } from "@definitelytyped/typescript-versions";
import assert = require("assert");
import { deepEquals, parsePackageSemver } from "@definitelytyped/utils";
// TODO: This is obsolete now.
// 1. Delete this package.
// 2. Port checks to checking package.json.
// 3. Port tests to package.json too.
// TODO:
// 1. Convert this package into a packageJson checker
// 2. Move checks from dt-header into dtslint/checks.ts and remove the rule.
// 4. Add test for header in dtslint that forbids it.
// 5. Update dts-gen and DT README and ??? -- rest of ecosystem.
/*
@@ -18,7 +18,7 @@ import { AllTypeScriptVersion, TypeScriptVersion } from "@definitelytyped/typesc
// TypeScript Version: 2.1
*/
// used in dts-critic
export interface Header {
readonly nonNpm: boolean;
readonly libraryName: string;
@@ -28,13 +28,13 @@ export interface Header {
readonly projects: readonly string[];
readonly contributors: readonly Author[];
}
// used in definitions-parser
export interface Author {
readonly name: string;
readonly url: string;
readonly githubUsername: string | undefined;
}
// used locally
export interface ParseError {
readonly index: number;
readonly line: number;
@@ -42,7 +42,7 @@ export interface ParseError {
readonly expected: readonly string[];
}
export function makeTypesVersionsForPackageJson(typesVersions: readonly TypeScriptVersion[]): unknown {
export function makeTypesVersionsForPackageJson(typesVersions: readonly AllTypeScriptVersion[]): unknown {
if (typesVersions.length === 0) {
return undefined;
}
@@ -56,230 +56,279 @@ export function makeTypesVersionsForPackageJson(typesVersions: readonly TypeScri
return out;
}
export function parseHeaderOrFail(mainFileContent: string): Header {
const header = parseHeader(mainFileContent, /*strict*/ false);
if (isParseError(header)) {
throw new Error(renderParseError(header));
export function validatePackageJson(packageName: string, packageJsonPath: string, packageJson: Record<string, unknown>, typesVersions: readonly AllTypeScriptVersion[]): Header | string[] {
const errors = []
const needsTypesVersions = typesVersions.length !== 0;
// NOTE: I had to install eslint-plugin-import in DT because DT-tools hasn't shipped a new version
// DONE: normal package: 3box
// DONE: scoped package: adeira__js
// DONE: old-version package: gulp/v3
// DONE: old-TS-version package: har-format
// DONE: react
// DONE: node
for (const key in packageJson) {
switch (key) {
case "private":
case "dependencies":
case "license":
case "imports":
case "exports":
case "type":
case "name":
case "version":
case "devDependencies":
case "projects":
case "typeScriptVersion":
case "contributors":
case "nonNpm":
case "nonNpmDescription":
// "dependencies" / "license" checked by types-publisher,
// TODO: asserts for other fields in types-publisher
break;
case "typesVersions":
case "types":
if (!needsTypesVersions) {
errors.push(`${packageJsonPath} doesn't need to set "${key}" when no 'ts4.x' directories exist.`);
}
break;
default:
errors.push(`${packageJsonPath} should not include property ${key}`);
}
}
return header;
}
// private
if (packageJson.private !== true) {
errors.push(`${packageJsonPath} has bad "private": must be \`"private": true\``)
}
// devDependencies
if (typeof packageJson.devDependencies !== "object"
|| packageJson.devDependencies === null
|| (packageJson.devDependencies as any)["@types/" + packageName] !== "workspace:.") {
errors.push(`${packageJsonPath} has bad "devDependencies": must include \`"@types/${packageName}": "workspace:."\``);
}
// TODO: disallow devDeps from containing dependencies (although this is VERY linty)
// TODO: Check that devDeps are NOT used in .d.ts files
export function validate(mainFileContent: string): ParseError | undefined {
const h = parseHeader(mainFileContent, /*strict*/ true);
return isParseError(h) ? h : undefined;
}
// typesVersions
if (needsTypesVersions) {
assert.strictEqual(packageJson.types, "index", `"types" in '${packageJsonPath}' should be "index".`);
const expected = makeTypesVersionsForPackageJson(typesVersions) as Record<string, object>;
if (!deepEquals(packageJson.typesVersions, expected)) {
errors.push(`'${packageJsonPath}' has bad "typesVersions". Should be: ${JSON.stringify(expected, undefined, 4)}`)
}
}
export function renderExpected(expected: readonly string[]): string {
return expected.length === 1 ? expected[0] : `one of\n\t${expected.join("\n\t")}`;
}
function renderParseError({ line, column, expected }: ParseError): string {
return `At ${line}:${column} : Expected ${renderExpected(expected)}`;
}
function isParseError(x: {}): x is ParseError {
// tslint:disable-next-line strict-type-predicates
return (x as ParseError).expected !== undefined;
}
/** @param strict If true, we allow fewer things to be parsed. Turned on by linting. */
function parseHeader(text: string, strict: boolean): Header | ParseError {
const res = headerParser(strict).parse(text);
return res.status
? res.value
: { index: res.index.offset, line: res.index.line, column: res.index.column, expected: res.expected };
}
function headerParser(strict: boolean): pm.Parser<Header> {
return pm.seqMap(
pm.regex(/\/\/ Type definitions for (non-npm package )?/),
parseLabel(strict),
pm.string("// Project: "),
projectParser,
pm.regexp(/\r?\n\/\/ Definitions by: /),
contributorsParser(strict),
definitionsParser,
typeScriptVersionParser,
pm.all, // Don't care about the rest of the file
// tslint:disable-next-line:variable-name
(str, label, _project, projects, _defsBy, contributors, _definitions, typeScriptVersion) => ({
libraryName: label.name,
libraryMajorVersion: label.major,
libraryMinorVersion: label.minor,
nonNpm: str.endsWith("non-npm package "),
// building the header object uses a monadic error pattern based on the one in the old header parser
// It's verbose and repetitive, but I didn't feel like writing a monadic `seq` to be used in only one place.
let libraryName = "ERROR"
let libraryMajorVersion = 0
let libraryMinorVersion = 0
let nonNpm = false
let typeScriptVersion: AllTypeScriptVersion = TypeScriptVersion.lowest
let projects: string[] = []
let contributors: Author[] = []
const nameResult = validateName()
const versionResult = validateVersion()
const nonNpmResult = validateNonNpm()
const typeScriptVersionResult = validateTypeScriptVersion()
const projectsResult = validateProjects()
const contributorsResult = validateContributors()
if (typeof nameResult === "object") {
errors.push(...nameResult.errors)
}
else {
libraryName = packageName;
}
if ('errors' in versionResult) {
errors.push(...versionResult.errors)
}
else {
libraryMajorVersion = versionResult.major
libraryMinorVersion = versionResult.minor
}
if (typeof nonNpmResult === "object") {
errors.push(...nonNpmResult.errors)
}
else {
nonNpm = nonNpmResult
}
if (typeof typeScriptVersionResult === "object") {
errors.push(...typeScriptVersionResult.errors)
}
else {
typeScriptVersion = typeScriptVersionResult
}
if ('errors' in projectsResult) {
errors.push(...projectsResult.errors)
}
else {
projects = projectsResult
}
if ('errors' in contributorsResult) {
errors.push(...contributorsResult.errors)
}
else {
contributors = contributorsResult
}
if (errors.length) {
return errors
}
else {
return {
libraryName,
libraryMajorVersion,
libraryMinorVersion,
nonNpm,
typeScriptVersion,
projects,
contributors,
typeScriptVersion,
})
);
}
interface Label {
readonly name: string;
readonly major: number;
readonly minor: number;
}
/*
Allow any of the following:
// Project: https://foo.com
// https://bar.com
// Project: https://foo.com,
// https://bar.com
// Project: https://foo.com, https://bar.com
Use `\s\s+` to ensure at least 2 spaces, to disambiguate from the next line being `// Definitions by:`.
*/
const separator: pm.Parser<string> = pm.regexp(/(, )|(,?\r?\n\/\/\s\s+)/);
const projectParser: pm.Parser<readonly string[]> = pm.sepBy1(pm.regexp(/[^,\r\n]+/), separator);
function contributorsParser(strict: boolean): pm.Parser<readonly Author[]> {
const contributor: pm.Parser<Author> = strict
? pm.seqMap(
pm.regexp(/([^<]+) /, 1),
pm.regexp(/\<https\:\/\/github\.com\/([a-zA-Z\d\-]+)\/?\>/, 1),
(name, githubUsername) => ({ name, url: `https://github.com/${githubUsername}`, githubUsername })
)
: // In non-strict mode, allows arbitrary URL, and trailing whitespace.
pm.seqMap(pm.regexp(/([^<]+) /, 1), pm.regexp(/<([^>]+)> */, 1), (name, url) => {
const rgx = /^https\:\/\/github.com\/([a-zA-Z\d\-]+)\/?$/;
const match = rgx.exec(url);
const githubUsername = match === null ? undefined : match[1];
// tslint:disable-next-line no-null-keyword
return { name, url: githubUsername ? `https://github.com/${githubUsername}` : url, githubUsername };
});
return pm.sepBy1(contributor, separator);
}
const definitionsParser = pm.regexp(/\r?\n\/\/ Definitions: [^\r\n]+/);
function parseLabel(strict: boolean): pm.Parser<Label> {
return pm.Parser((input, index) => {
// Take all until the first newline.
const endIndex = regexpIndexOf(input, /\r|\n/, index);
if (endIndex === -1) {
return fail("EOF");
}
// Index past the end of the newline.
const end = input[endIndex] === "\r" ? endIndex + 2 : endIndex + 1;
const tilNewline = input.slice(index, endIndex);
}
// Parse in reverse. Once we've stripped off the version, the rest is the libary name.
const reversed = reverse(tilNewline);
// Last digit is allowed to be "x", which acts like "0"
const rgx = /((\d+|x)\.(\d+)(\.\d+)?(v)? )?(.+)/;
const match = rgx.exec(reversed);
if (match === null) {
// tslint:disable-line no-null-keyword
return fail();
function validateName(): string | { errors: string[] } {
if (packageJson.name !== "@types/" + packageName) {
return { errors: [ `${packageJsonPath} should have \`"name": "@types/${packageName}"\``] };
}
const [, version, a, b, c, v, nameReverse] = match;
let majorReverse: string;
let minorReverse: string;
if (version !== undefined) {
// tslint:disable-line strict-type-predicates
if (c !== undefined) {
// tslint:disable-line strict-type-predicates
// There is a patch version
majorReverse = c;
minorReverse = b;
if (strict) {
return fail("patch version not allowed");
else {
return packageName;
}
}
function validateVersion(): { major: number, minor: number } | {errors: string[]} {
const errors = []
if (!packageJson.version || typeof packageJson.version !== "string") {
errors.push(`${packageJsonPath} should have \`"version"\` matching the version of the implementation package.`);
}
else if (!/\d+\.\d+\.\d+/.exec(packageJson.version)) {
errors.push(`${packageJsonPath} has bad "version": should look like "NN.NN.0"`);
}
else if (!packageJson.version.endsWith(".0")) {
errors.push(`${packageJsonPath} has bad "version": must end with ".0"`);
}
else {
let version: "*" | { major: number, minor?: number } = "*"
try {
version = parsePackageSemver(packageJson.version)
if (version === "*") {
errors.push("Failed to parse version")
}
} else {
majorReverse = b;
minorReverse = a;
else {
// TODO: parseSemverPackage will eventually return a real semver, which always has minor filled in
return { major: version.major, minor: version.minor ?? 0 }
}
} catch (e: any) {
errors.push(`'${packageJsonPath}' has bad "version": Semver parsing failed with '${e.message}'`);
}
if (v !== undefined && strict) {
// tslint:disable-line strict-type-predicates
return fail("'v' not allowed");
}
return { errors }
}
function validateNonNpm(): boolean | {errors: string[] } {
const errors = []
if (packageJson.nonNpm != undefined) {
if (packageJson.nonNpm !== true) {
errors.push(`${packageJsonPath} has bad "nonNpm": must be true if present.`);
}
else if (!packageJson.nonNpmDescription) {
errors.push(`${packageJsonPath} has missing "nonNpmDescription", which is required with "nonNpm": true.`);
}
else if (typeof packageJson.nonNpmDescription !== "string") {
errors.push(`${packageJsonPath} has bad "nonNpmDescription": must be a string if present.`);
}
else {
return true;
}
return { errors }
}
else if (packageJson.nonNpmDescription !== undefined) {
errors.push(`${packageJsonPath} has "nonNpmDescription" without "nonNpm": true.`);
}
if (errors.length) {
return { errors }
} else {
if (strict) {
return fail("Needs MAJOR.MINOR");
}
majorReverse = "0";
minorReverse = "0";
return false
}
const [name, major, minor] = [reverse(nameReverse), reverse(majorReverse), reverse(minorReverse)];
return pm.makeSuccess<Label>(end, {
name,
major: intOfString(major),
minor: minor === "x" ? 0 : intOfString(minor),
});
function fail(msg?: string): pm.Reply<Label> {
let expected = "foo MAJOR.MINOR";
if (msg !== undefined) {
expected += ` (${msg})`;
}
function validateTypeScriptVersion(): AllTypeScriptVersion | { errors: string[] } {
if (packageJson.typeScriptVersion) {
if ((typeof packageJson.typeScriptVersion !== "string" || !TypeScriptVersion.isTypeScriptVersion(packageJson.typeScriptVersion))) {
return { errors: [`${packageJsonPath} has bad "typeScriptVersion": if present, must be a MAJOR.MINOR semver string up to "${TypeScriptVersion.latest}".
(Defaults to "${TypeScriptVersion.lowest}" if not provided.)`] };
}
else {
return packageJson.typeScriptVersion
}
return pm.makeFailure(index, expected);
}
});
}
const typeScriptVersionLineParser: pm.Parser<AllTypeScriptVersion> = pm
.regexp(/\/\/ (?:Minimum )?TypeScript Version: (\d.(\d))/, 1)
.chain<TypeScriptVersion>((v) =>
TypeScriptVersion.all.includes(v as TypeScriptVersion)
? pm.succeed(v as TypeScriptVersion)
: pm.fail(`TypeScript ${v} is not yet supported.`)
);
const typeScriptVersionParser: pm.Parser<AllTypeScriptVersion> = pm
.regexp(/\r?\n/)
.then(typeScriptVersionLineParser)
.fallback<TypeScriptVersion>(TypeScriptVersion.shipped[0]);
export function parseTypeScriptVersionLine(line: string): AllTypeScriptVersion {
const result = typeScriptVersionLineParser.parse(line);
if (!result.status) {
throw new Error(`Could not parse version: line is '${line}'`);
return TypeScriptVersion.lowest
}
return result.value;
}
function reverse(s: string): string {
let out = "";
for (let i = s.length - 1; i >= 0; i--) {
out += s[i];
function validateProjects(): string[] | { errors: string[] } {
const errors = []
if (!packageJson.projects || !Array.isArray(packageJson.projects) || !packageJson.projects.every(p => typeof p === "string")) {
errors.push(`${packageJsonPath} has bad "projects": must be an array of strings that point to the project web site(s).`);
}
else if (packageJson.projects.length === 0) {
errors.push(`${packageJsonPath} has bad "projects": must have at least one project URL.`);
}
else {
return packageJson.projects
}
return { errors }
}
return out;
}
function regexpIndexOf(s: string, rgx: RegExp, start: number): number {
const index = s.slice(start).search(rgx);
return index === -1 ? index : index + start;
}
declare module "parsimmon" {
// tslint:disable-next-line no-unnecessary-qualifier
type Pr<T> = pm.Parser<T>; // https://github.com/Microsoft/TypeScript/issues/14121
export function seqMap<T, U, V, W, X, Y, Z, A, B, C>(
p1: Pr<T>,
p2: Pr<U>,
p3: Pr<V>,
p4: Pr<W>,
p5: Pr<X>,
p6: Pr<Y>,
p7: Pr<Z>,
p8: Pr<A>,
p9: Pr<B>,
cb: (a1: T, a2: U, a3: V, a4: W, a5: X, a6: Y, a7: Z, a8: A, a9: B) => C
): Pr<C>;
}
function intOfString(str: string): number {
const n = Number.parseInt(str, 10);
if (Number.isNaN(n)) {
throw new Error(`Error in parseInt(${JSON.stringify(str)})`);
function validateContributors(): Author[] | { errors: string[] } {
const errors: string[] = []
if (!packageJson.contributors || !Array.isArray(packageJson.contributors)) {
errors.push(`${packageJsonPath} has bad "contributors": must be an array of type Array<{ name: string, url: string, githubUsername: string}>.`);
}
else if (packageJson.contributors.length === 0) {
errors.push(`${packageJsonPath} has bad "contributors": must have at least one contributor.`);
}
else {
const es = checkPackageJsonContributors(packageJsonPath, packageJson.contributors);
if (es.length) {
errors.push(...es)
}
else {
return packageJson.contributors as Author[]
}
}
return { errors }
}
return n;
}
function checkPackageJsonContributors(packageJsonPath: string, packageJsonContributors: readonly unknown[]) {
const errors: string[] = []
for (const c of packageJsonContributors) {
if (typeof c !== "object" || c === null) {
errors.push(`${packageJsonPath} has bad "contributors": must be an array of type Array<{ name: string, url: string, githubUsername: string}>.`)
continue
}
if (!("name" in c) || typeof c.name !== "string") {
errors.push(`${packageJsonPath} has bad "name" in contributor ${JSON.stringify(c)}
Must be an object of type { name: string, url: string, githubUsername: string }.`)
}
else if (c.name === "My Self") {
errors.push(`${packageJsonPath} has bad "name" in contributor ${JSON.stringify(c)}
Author name should be your name, not the default.`)
}
if (!("githubUsername" in c) || typeof c.githubUsername !== "string") {
errors.push(`${packageJsonPath} has bad "githubUsername" in contributor ${JSON.stringify(c)}
Must be an object of type { name: string, url: string, githubUsername: string }.`)
}
else if (!("url" in c) || typeof c.url !== "string") {
errors.push(`${packageJsonPath} has bad "url" in contributor ${JSON.stringify(c)}
Must be an object of type { name: string, url: string, githubUsername: string }.`)
}
else if (c.url !== "https://github.com/" + c.githubUsername) {
errors.push(`${packageJsonPath} has bad "url" in contributor ${JSON.stringify(c)}
Must be "https://github.com/${c.githubUsername}".`)
}
for (const key in c) {
switch (key) {
case "name":
case "url":
case "githubUsername":
break;
default:
errors.push(`${packageJsonPath} has bad contributor ${JSON.stringify(c)}: should not include property ${key}`);
}
}
}
return errors
}

View File

@@ -1,192 +1,108 @@
import { TypeScriptVersion } from "@definitelytyped/typescript-versions";
import { parseHeaderOrFail, parseTypeScriptVersionLine, makeTypesVersionsForPackageJson } from "../src";
import { validatePackageJson, makeTypesVersionsForPackageJson } from "../src";
describe("parse", () => {
it("works", () => {
const src = dedent`
// Type definitions for foo 1.2
// Project: https://github.com/foo/foo, https://foo.com
// Definitions by: My Self <https://github.com/me>, Some Other Guy <https://github.com/otherguy>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
// TypeScript Version: 2.2
...file content...`;
expect(parseHeaderOrFail(src)).toStrictEqual({
libraryName: "foo",
libraryMajorVersion: 1,
libraryMinorVersion: 2,
typeScriptVersion: "2.2",
nonNpm: false,
projects: ["https://github.com/foo/foo", "https://foo.com"],
contributors: [
{ name: "My Self", url: "https://github.com/me", githubUsername: "me" },
{ name: "Some Other Guy", url: "https://github.com/otherguy", githubUsername: "otherguy" },
],
});
});
it("works with spacing", () => {
const src = dedent`
// Type definitions for foo 1.2
// Project: https://github.com/foo/foo,
// https://foo.com
// Definitions by: My Self <https://github.com/me>,
// Some Other Guy <https://github.com/otherguy>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
...file content...`;
expect(parseHeaderOrFail(src)).toStrictEqual({
libraryName: "foo",
libraryMajorVersion: 1,
libraryMinorVersion: 2,
typeScriptVersion: "4.3",
nonNpm: false,
projects: ["https://github.com/foo/foo", "https://foo.com"],
contributors: [
{ name: "My Self", url: "https://github.com/me", githubUsername: "me" },
{ name: "Some Other Guy", url: "https://github.com/otherguy", githubUsername: "otherguy" },
],
});
});
it("works with slash end", () => {
const src = dedent`
// Type definitions for foo 1.2
// Project: https://github.com/foo/foo,
// https://foo.com
// Definitions by: My Self <https://github.com/me/>,
// Some Other Guy <https://github.com/otherguy/>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
...file content...`;
expect(parseHeaderOrFail(src)).toStrictEqual({
libraryName: "foo",
libraryMajorVersion: 1,
libraryMinorVersion: 2,
typeScriptVersion: "4.3",
nonNpm: false,
projects: ["https://github.com/foo/foo", "https://foo.com"],
contributors: [
{ name: "My Self", url: "https://github.com/me", githubUsername: "me" },
{ name: "Some Other Guy", url: "https://github.com/otherguy", githubUsername: "otherguy" },
],
});
});
it("works with bad url", () => {
const src = dedent`
// Type definitions for foo 1.2
// Project: https://github.com/foo/foo
// Definitions by: Bad Url <sptth://hubgit.moc/em>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped`;
expect(parseHeaderOrFail(src).contributors).toStrictEqual([
{ name: "Bad Url", url: "sptth://hubgit.moc/em", githubUsername: undefined },
describe("validatePackageJson", () => {
const pkgJson: Record<string, unknown> = {
"private": true,
"name": "@types/hapi",
"version": "18.0.0",
"projects": [
"https://github.com/hapijs/hapi",
"https://hapijs.com"
],
"typeScriptVersion": "4.2",
"dependencies": {
"@types/boom": "*",
"@types/catbox": "*",
"@types/iron": "*",
"@types/mimos": "*",
"@types/node": "*",
"@types/podium": "*",
"@types/shot": "*",
"joi": "^17.3.0"
},
"devDependencies": {
"@types/hapi": "workspace:."
},
"contributors": [
{
"name": "Rafael Souza Fijalkowski",
"url": "https://github.com/rafaelsouzaf",
"githubUsername": "rafaelsouzaf"
},
{
"name": "Justin Simms",
"url": "https://github.com/jhsimms",
"githubUsername": "jhsimms"
},
{
"name": "Simon Schick",
"url": "https://github.com/SimonSchick",
"githubUsername": "SimonSchick"
},
{
"name": "Rodrigo Saboya",
"url": "https://github.com/saboya",
"githubUsername": "saboya"
}
]
};
it("requires private: true", () => {
const pkg = { ...pkgJson };
delete pkg.private;
expect(validatePackageJson("hapi", "cort-start/hapi/package.json", pkg, [])).toEqual([
`cort-start/hapi/package.json has bad "private": must be \`"private": true\``
]);
});
it("allows 'non-npm' on Type definitions line", () => {
const src = dedent`
// Type definitions for non-npm package foo 1.2
// Project: https://github.com/foo/foo, https://foo.com
// Definitions by: My Self <https://github.com/me>, Some Other Guy <https://github.com/otherguy>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
// TypeScript Version: 2.2
...file content...`;
expect(parseHeaderOrFail(src).nonNpm).toBe(true);
it("requires name", () => {
const pkg = { ...pkgJson };
delete pkg.name;
expect(validatePackageJson("hapi", "cort-start/hapi/package.json", pkg, [])).toEqual([
"cort-start/hapi/package.json should have `\"name\": \"@types/hapi\"`"
]);
});
});
describe("parseTypeScriptVersionLine", () => {
it("works", () => {
const src = "// TypeScript Version: 2.1";
expect(parseTypeScriptVersionLine(src)).toBe("2.1");
const minimum = "// Minimum TypeScript Version: 2.8";
expect(parseTypeScriptVersionLine(minimum)).toBe("2.8");
const wrong = "// TypeScript Version: 3.14";
expect(() => parseTypeScriptVersionLine(wrong)).toThrow();
it("requires name to match", () => {
expect(validatePackageJson("hapi", "cort-start/hapi/package.json", { ...pkgJson, name: "@types/sad" }, [])).toEqual([
"cort-start/hapi/package.json should have `\"name\": \"@types/hapi\"`"
]);
});
it("allows typescript 2.3", () => {
const src = "// TypeScript Version: 2.3";
expect(parseTypeScriptVersionLine(src)).toBe("2.3");
it("requires devDependencies", () => {
const pkg = { ...pkgJson };
delete pkg.devDependencies;
expect(validatePackageJson("hapi", "cort-start/hapi/package.json", pkg, [])).toEqual([
`cort-start/hapi/package.json has bad "devDependencies": must include \`"@types/hapi": "workspace:."\``
]);
});
it("allows post 3 version tags", () => {
const src = "// TypeScript Version: 3.0";
expect(parseTypeScriptVersionLine(src)).toBe("3.0");
it("requires devDependencies to contain self-package", () => {
expect(validatePackageJson("hapi", "cort-start/hapi/package.json", { ...pkgJson, devDependencies: { } }, [])).toEqual([
`cort-start/hapi/package.json has bad "devDependencies": must include \`"@types/hapi": "workspace:."\``
]);
});
it("does not allow unallowed version tags", () => {
const src = "// TypeScript Version: 5.7";
expect(() => parseTypeScriptVersionLine(src)).toThrow(`Could not parse version: line is '${src}'`);
it("requires devDependencies to contain self-package version 'workspace:.'", () => {
expect(validatePackageJson("hapi", "cort-start/hapi/package.json", { ...pkgJson, devDependencies: { "@types/hapi": "*" } }, [])).toEqual([
`cort-start/hapi/package.json has bad "devDependencies": must include \`"@types/hapi": "workspace:."\``
]);
});
});
describe("unsupported", () => {
it("contains at least 2.9", () => {
expect(TypeScriptVersion.unsupported.includes("2.9")).toBeTruthy();
it("requires version", () => {
const pkg = { ...pkgJson };
delete pkg.version;
expect(validatePackageJson("hapi", "cort-start/hapi/package.json", pkg, [])).toEqual([
`cort-start/hapi/package.json should have \`"version"\` matching the version of the implementation package.`
]);
});
});
describe("all", () => {
it("doesn't have any holes", () => {
let prev = TypeScriptVersion.all[0];
for (const version of TypeScriptVersion.all.slice(1)) {
expect(+version * 10 - +prev * 10).toEqual(1);
prev = version;
}
it("requires version to be NN.NN.NN", () => {
expect(validatePackageJson("hapi", "cort-start/hapi/package.json", { ...pkgJson, version: "hi there" }, [])).toEqual([
`cort-start/hapi/package.json has bad "version": should look like "NN.NN.0"`
]);
});
});
describe("isSupported", () => {
it("works", () => {
expect(TypeScriptVersion.isSupported("4.5")).toBeTruthy();
it("requires version to end with .0", () => {
expect(validatePackageJson("hapi", "cort-start/hapi/package.json", { ...pkgJson, version: "1.2.3" }, [])).toEqual([
`cort-start/hapi/package.json has bad "version": must end with ".0"`
]);
});
it("supports 4.3", () => {
expect(TypeScriptVersion.isSupported("4.3")).toBeTruthy();
});
it("does not support 4.2", () => {
expect(!TypeScriptVersion.isSupported("4.2")).toBeTruthy();
});
});
describe("isTypeScriptVersion", () => {
it("accepts in-range", () => {
expect(TypeScriptVersion.isTypeScriptVersion("4.5")).toBeTruthy();
});
it("rejects out-of-range", () => {
expect(TypeScriptVersion.isTypeScriptVersion("101.1")).toBeFalsy();
});
it("rejects garbage", () => {
expect(TypeScriptVersion.isTypeScriptVersion("it'sa me, luigi")).toBeFalsy();
});
});
describe("range", () => {
it("works", () => {
expect(TypeScriptVersion.range("4.7")).toEqual(["4.7", "4.8", "4.9", "5.0", "5.1"]);
});
it("includes 4.3 onwards", () => {
expect(TypeScriptVersion.range("4.3")).toEqual(TypeScriptVersion.supported);
});
});
describe("tagsToUpdate", () => {
it("works", () => {
expect(TypeScriptVersion.tagsToUpdate("5.0")).toEqual(["ts5.0", "ts5.1", "latest"]);
});
it("allows 4.2 onwards", () => {
expect(TypeScriptVersion.tagsToUpdate("4.3")).toEqual(
TypeScriptVersion.supported.map((s) => "ts" + s).concat("latest")
);
});
});
it("works with old-version packages", () => {
expect(Array.isArray(validatePackageJson("hapi", "cort-start/hapi/package.json", { ...pkgJson, version: "16.6.0" }, []))).toBeFalsy();
})
})
describe("makeTypesVersionsForPackageJson", () => {
it("is undefined for empty versions", () => {
expect(makeTypesVersionsForPackageJson([])).toBeUndefined();

View File

@@ -91,6 +91,8 @@ export namespace TypeScriptVersion {
"4.1",
"4.2",
];
// TODO: All the tests for this are in header-parser, but should be here
// because I'm going to delete header-parser
export const all: readonly AllTypeScriptVersion[] = [...unsupported, ...supported];
export const lowest = supported[0];
/** Latest version that may be specified in a `// TypeScript Version:` header. */
@@ -126,11 +128,11 @@ export namespace TypeScriptVersion {
return index === supported.length - 1 ? undefined : supported[index + 1];
}
export function isRedirectable(v: TypeScriptVersion): boolean {
export function isRedirectable(v: AllTypeScriptVersion): boolean {
return all.indexOf(v) >= all.indexOf("3.1");
}
export function isTypeScriptVersion(str: string): str is TypeScriptVersion {
export function isTypeScriptVersion(str: string): str is AllTypeScriptVersion {
return all.includes(str as TypeScriptVersion);
}
}

View File

@@ -0,0 +1,62 @@
import { TypeScriptVersion } from "@definitelytyped/typescript-versions";
describe("unsupported", () => {
it("contains at least 2.9", () => {
expect(TypeScriptVersion.unsupported.includes("2.9")).toBeTruthy();
});
});
describe("all", () => {
it("doesn't have any holes", () => {
let prev = TypeScriptVersion.all[0];
for (const version of TypeScriptVersion.all.slice(1)) {
expect(+version * 10 - +prev * 10).toEqual(1);
prev = version;
}
});
});
describe("isSupported", () => {
it("works", () => {
expect(TypeScriptVersion.isSupported("4.5")).toBeTruthy();
});
it("supports 4.3", () => {
expect(TypeScriptVersion.isSupported("4.3")).toBeTruthy();
});
it("does not support 4.2", () => {
expect(!TypeScriptVersion.isSupported("4.2")).toBeTruthy();
});
});
describe("isTypeScriptVersion", () => {
it("accepts in-range", () => {
expect(TypeScriptVersion.isTypeScriptVersion("4.5")).toBeTruthy();
});
it("rejects out-of-range", () => {
expect(TypeScriptVersion.isTypeScriptVersion("101.1")).toBeFalsy();
});
it("rejects garbage", () => {
expect(TypeScriptVersion.isTypeScriptVersion("it'sa me, luigi")).toBeFalsy();
});
});
describe("range", () => {
it("works", () => {
expect(TypeScriptVersion.range("4.7")).toEqual(["4.7", "4.8", "4.9", "5.0", "5.1"]);
});
it("includes 4.3 onwards", () => {
expect(TypeScriptVersion.range("4.3")).toEqual(TypeScriptVersion.supported);
});
});
describe("tagsToUpdate", () => {
it("works", () => {
expect(TypeScriptVersion.tagsToUpdate("5.0")).toEqual(["ts5.0", "ts5.1", "latest"]);
});
it("allows 4.2 onwards", () => {
expect(TypeScriptVersion.tagsToUpdate("4.3")).toEqual(
TypeScriptVersion.supported.map((s) => "ts" + s).concat("latest")
);
});
});

View File

@@ -34,3 +34,20 @@ export function assertSorted<T>(
}
return a;
}
export function deepEquals(expected: unknown, actual: unknown): boolean {
if (expected instanceof Array) {
return (
actual instanceof Array && actual.length === expected.length && expected.every((e, i) => deepEquals(e, actual[i]))
);
} else if (typeof expected === "object") {
for (const k in expected) {
if (!deepEquals((expected as any)[k], (actual as any)[k])) {
return false;
}
}
return true
}else {
return expected === actual;
}
}

View File

@@ -1,4 +1,19 @@
import crypto from "crypto";
import * as semver from "semver";
export function parsePackageSemver(version: string): { major: number, minor?: number } | "*" {
if (version === "workspace:.") {
return "*"
}
const start = new semver.Range(version).set[0][0].semver
if (start === (semver.Comparator as any).ANY) {
return "*"
}
else {
// TODO: we should still use real semver but for now minor===0 => minor=undefined for compatibility
return { major: start.major, minor: start.minor === 0 ? undefined : start.minor }
}
}
export function tryParseJson<T>(text: string): unknown;
export function tryParseJson<T>(text: string, predicate: (parsed: unknown) => parsed is T): T | undefined;