Skip to content
This repository has been archived by the owner on Nov 16, 2023. It is now read-only.

Commit

Permalink
Support typesVersions (ts3.1 directories)
Browse files Browse the repository at this point in the history
  • Loading branch information
Andy Hanson committed Oct 9, 2018
1 parent a64779a commit 5013cbf
Show file tree
Hide file tree
Showing 10 changed files with 212 additions and 74 deletions.
17 changes: 16 additions & 1 deletion docs/dt-header.md
Expand Up @@ -61,6 +61,16 @@ export { f } from "./subModule";
export function f(): number;
```

`foo/ts3.1/index.d.ts`:
```ts
// Type definitions for abs 1.2
// Project: https://github.com/foo/foo
// Definitions by: My Name <https://github.com/myname>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
export function f(): number;
```


**Good**:

`foo/index.d.ts`: Same
Expand All @@ -70,4 +80,9 @@ export function f(): number;
export function f(): number;
```

Don't use a header twice -- only do it in the index.
`foo/ts3.1/index.d.ts`:
```ts
export function f(): number;
```

Don't repeat the header -- only do it in the index of the root.
22 changes: 11 additions & 11 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

44 changes: 35 additions & 9 deletions src/checks.ts
@@ -1,44 +1,70 @@
import assert = require("assert");
import { makeTypesVersionsForPackageJson, TypeScriptVersion } from "definitelytyped-header-parser";
import { pathExists } from "fs-extra";
import * as path from "path";
import { join as joinPaths } from "path";

import { getCompilerOptions, readJson } from "./util";

export async function checkPackageJson(dirPath: string): Promise<void> {
const pkgJsonPath = path.join(dirPath, "package.json");
export async function checkPackageJson(
dirPath: string,
typesVersions: ReadonlyArray<TypeScriptVersion>,
): Promise<void> {
const pkgJsonPath = joinPaths(dirPath, "package.json");
const needsTypesVersions = typesVersions.length !== 0;
if (!await pathExists(pkgJsonPath)) {
if (needsTypesVersions) {
throw new Error(`${dirPath}: Must have 'package.json' for "typesVersions"`);
}
return;
}
const pkgJson = await readJson(pkgJsonPath) as {};

if ((pkgJson as any).private !== true) {
throw new Error(`${pkgJsonPath} should set \`"private": true\``);
}

if (needsTypesVersions) {
assert.strictEqual((pkgJson as any).types, "index", `"types" in '${pkgJsonPath}' should be "index".`);
const expected = makeTypesVersionsForPackageJson(typesVersions);
assert.deepEqual((pkgJson as any).typesVersions, expected,
`"typesVersions" in '${pkgJsonPath}' is not set right. Should be: ${JSON.stringify(expected, undefined, 4)}`);
}

for (const key in pkgJson) { // tslint:disable-line forin
switch (key) {
case "private":
case "dependencies":
case "license":
// "private" checked above, "dependencies" / "license" checked by types-publisher
// "private"/"typesVersions"/"types" checked above, "dependencies" / "license" checked by types-publisher,
break;
case "typesVersions":
case "types":
if (!needsTypesVersions) {
throw new Error(`${pkgJsonPath} doesn't need to set "${key}" when no 'ts3.x' directories exist.`);
}
break;
default:
throw new Error(`${pkgJsonPath} should not include field ${key}`);
}
}
}

export async function checkTsconfig(dirPath: string, dt: boolean): Promise<void> {
export interface DefinitelyTypedInfo {
/** "../" or "../../" or "../../../" */
readonly relativeBaseUrl: string;
}
export async function checkTsconfig(dirPath: string, dt: DefinitelyTypedInfo | undefined): Promise<void> {
const options = await getCompilerOptions(dirPath);

if (dt) {
const isOlderVersion = /^v\d+$/.test(path.basename(dirPath));
const baseUrl = isOlderVersion ? "../../" : "../";
const { relativeBaseUrl } = dt;

const mustHave = {
module: "commonjs",
noEmit: true,
forceConsistentCasingInFileNames: true,
baseUrl,
typeRoots: [baseUrl],
baseUrl: relativeBaseUrl,
typeRoots: [relativeBaseUrl],
types: [],
};

Expand Down
83 changes: 71 additions & 12 deletions src/index.ts
@@ -1,11 +1,12 @@
#!/usr/bin/env node
import { parseTypeScriptVersionLine, TypeScriptVersion } from "definitelytyped-header-parser";
import { readFile } from "fs-extra";
import { isTypeScriptVersion, parseTypeScriptVersionLine, TypeScriptVersion } from "definitelytyped-header-parser";
import { readdir, readFile } from "fs-extra";
import { basename, dirname, join as joinPaths } from "path";

import { checkPackageJson, checkTsconfig } from "./checks";
import { cleanInstalls, installAll } from "./installer";
import { checkTslintJson, lint } from "./lint";
import { checkTslintJson, lint, TsVersion } from "./lint";
import { assertDefined, last, mapDefined, withoutPrefix } from "./util";

async function main(): Promise<void> {
const args = process.argv.slice(2);
Expand Down Expand Up @@ -82,22 +83,80 @@ function listen(dirPath: string): void {
}

async function runTests(dirPath: string, onlyTestTsNext: boolean): Promise<void> {
const text = await readFile(joinPaths(dirPath, "index.d.ts"), "utf-8");
const isOlderVersion = /^v\d+$/.test(basename(dirPath));

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.
const dt = text.includes("// Type definitions for");
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.
assertPathIsInDefinitelyTyped(dirPath);
}
const minVersion = getTypeScriptVersion(text);

await checkTslintJson(dirPath, dt);
const typesVersions = mapDefined(await readdir(dirPath), name => {
if (name === "tsconfig.json" || name === "tslint.json") { return undefined; }
const version = withoutPrefix(name, "ts");
if (version === undefined) { return undefined; }
if (!isTypeScriptVersion(version)) {
throw new Error(`There is an entry named ${name}, but ${version} is not a valid TypeScript version.`);
}
if (!TypeScriptVersion.isRedirectable(version)) {
throw new Error(`At ${dirPath}/${name}: TypeScript version directories only available starting with ts3.1.`);
}
return version;
});

if (dt) {
await checkPackageJson(dirPath);
await checkPackageJson(dirPath, typesVersions);
}
await checkTsconfig(dirPath, dt);
const err = await lint(dirPath, minVersion, onlyTestTsNext);

if (onlyTestTsNext) {
if (typesVersions.length === 0) {
await testTypesVersion(dirPath, "next", "next", isOlderVersion, dt, indexText);
} else {
const latestTypesVersion = last(typesVersions);
const versionPath = joinPaths(dirPath, `ts${latestTypesVersion}`);
const versionIndexText = await readFile(joinPaths(versionPath, "index.d.ts"), "utf-8");
await testTypesVersion(versionPath, "next", "next", isOlderVersion, dt, versionIndexText);
}
} else {
await testTypesVersion(dirPath, undefined, getTsVersion(0), isOlderVersion, dt, indexText);
for (let i = 0; i < typesVersions.length; i++) {
const version = typesVersions[i];
const versionPath = joinPaths(dirPath, `ts${version}`);
const versionIndexText = await readFile(joinPaths(versionPath, "index.d.ts"), "utf-8");
await testTypesVersion(versionPath, version, getTsVersion(i + 1), isOlderVersion, dt, versionIndexText);
}

function getTsVersion(i: number): TsVersion {
return i === typesVersions.length ? "next" : assertDefined(TypeScriptVersion.previous(typesVersions[i]));
}
}
}

async function testTypesVersion(
dirPath: string,
lowVersion: TsVersion | undefined,
maxVersion: TsVersion,
isOlderVersion: boolean,
dt: boolean,
indexText: string,
): Promise<void> {
const minVersionFromComment = getTypeScriptVersionFromComment(indexText);
if (minVersionFromComment !== undefined && lowVersion !== undefined) {
throw new Error(`Already in the \`ts${lowVersion}\` directory, don't need \`// TypeScript Version\`.`);
}
if (minVersionFromComment !== undefined && TypeScriptVersion.isRedirectable(minVersionFromComment)) {
throw new Error(`Don't use \`// TypeScript Version\` for newer TS versions, use typesVerisons instead.`);
}
const minVersion = lowVersion || minVersionFromComment || TypeScriptVersion.lowest;

await checkTslintJson(dirPath, dt);
await checkTsconfig(dirPath, dt
? { relativeBaseUrl: joinPaths("..", isOlderVersion ? ".." : "", lowVersion !== undefined ? ".." : "") + "/" }
: undefined);
const err = await lint(dirPath, minVersion, maxVersion);
if (err) {
throw new Error(err);
}
Expand All @@ -114,11 +173,11 @@ function assertPathIsInDefinitelyTyped(dirPath: string): void {
}
}

function getTypeScriptVersion(text: string): TypeScriptVersion {
function getTypeScriptVersionFromComment(text: string): TypeScriptVersion | undefined {
const searchString = "// TypeScript Version: ";
const x = text.indexOf(searchString);
if (x === -1) {
return "2.0";
return undefined;
}

let line = text.slice(x, text.indexOf("\n", x));
Expand Down
9 changes: 5 additions & 4 deletions src/installer.ts
Expand Up @@ -2,6 +2,7 @@ import { exec } from "child_process";
import { TypeScriptVersion } from "definitelytyped-header-parser";
import * as fs from "fs-extra";
import * as path from "path";
import { TsVersion } from "./lint";

const installsDir = path.join(__dirname, "..", "typescript-installs");

Expand All @@ -12,7 +13,7 @@ export async function installAll() {
await install("next");
}

async function install(version: TypeScriptVersion | "next"): Promise<void> {
async function install(version: TsVersion): Promise<void> {
const dir = installDir(version);
if (!await fs.pathExists(dir)) {
console.log(`Installing to ${dir}...`);
Expand All @@ -27,11 +28,11 @@ export function cleanInstalls(): Promise<void> {
return fs.remove(installsDir);
}

export function typeScriptPath(version: TypeScriptVersion | "next"): string {
export function typeScriptPath(version: TsVersion): string {
return path.join(installDir(version), "node_modules", "typescript");
}

function installDir(version: TypeScriptVersion | "next"): string {
function installDir(version: TsVersion): string {
return path.join(installsDir, version);
}

Expand All @@ -49,7 +50,7 @@ async function execAndThrowErrors(cmd: string, cwd?: string): Promise<void> {
});
}

function packageJson(version: TypeScriptVersion | "next"): {} {
function packageJson(version: TsVersion): {} {
return {
description: `Installs typescript@${version}`,
repository: "N/A",
Expand Down
42 changes: 27 additions & 15 deletions src/lint.ts
@@ -1,3 +1,4 @@
import assert = require("assert");
import { TypeScriptVersion } from "definitelytyped-header-parser";
import { pathExists, readFile } from "fs-extra";
import { join as joinPaths } from "path";
Expand All @@ -10,11 +11,7 @@ import { Options as ExpectOptions } from "./rules/expectRule";
import { typeScriptPath } from "./installer";
import { readJson } from "./util";

export async function lint(
dirPath: string,
minVersion: TypeScriptVersion,
onlyTestTsNext: boolean,
): Promise<string | undefined> {
export async function lint(dirPath: string, minVersion: TsVersion, maxVersion: TsVersion): Promise<string | undefined> {
const lintConfigPath = getConfigPath(dirPath);
const tsconfigPath = joinPaths(dirPath, "tsconfig.json");
const program = Linter.createProgram(tsconfigPath);
Expand All @@ -24,7 +21,7 @@ export async function lint(
formatter: "stylish",
};
const linter = new Linter(lintOptions, program);
const config = await getLintConfig(lintConfigPath, tsconfigPath, minVersion, onlyTestTsNext);
const config = await getLintConfig(lintConfigPath, tsconfigPath, minVersion, maxVersion);

for (const filename of program.getRootFileNames()) {
const contents = await readFile(filename, "utf-8");
Expand Down Expand Up @@ -91,8 +88,8 @@ function getConfigPath(dirPath: string): string {
async function getLintConfig(
expectedConfigPath: string,
tsconfigPath: string,
minVersion: TypeScriptVersion,
onlyTestTsNext: boolean,
minVersion: TsVersion,
maxVersion: TsVersion,
): Promise<IConfigurationFile> {
const configExists = await pathExists(expectedConfigPath);
const configPath = configExists ? expectedConfigPath : joinPaths(__dirname, "..", "dtslint.json");
Expand All @@ -104,14 +101,29 @@ async function getLintConfig(

const expectRule = config.rules.get("expect");
if (expectRule) {
const expectOptions: ExpectOptions = {
tsconfigPath,
tsNextPath: typeScriptPath("next"),
olderInstalls: TypeScriptVersion.range(minVersion).map(versionName =>
({ versionName, path: typeScriptPath(versionName) })),
onlyTestTsNext,
};
const versionsToTest = range(minVersion, maxVersion).map(versionName =>
({ versionName, path: typeScriptPath(versionName) }));
const expectOptions: ExpectOptions = { tsconfigPath, versionsToTest };
expectRule.ruleArguments = [expectOptions];
}
return config;
}

function range(minVersion: TsVersion, maxVersion: TsVersion): ReadonlyArray<TsVersion> {
if (minVersion === "next") {
assert(maxVersion === "next");
return ["next"];
}

const minIdx = TypeScriptVersion.all.indexOf(minVersion);
assert(minIdx >= 0);
if (maxVersion === "next") {
return [...TypeScriptVersion.all.slice(minIdx), "next"];
}

const maxIdx = TypeScriptVersion.all.indexOf(maxVersion);
assert(maxIdx >= minIdx);
return TypeScriptVersion.all.slice(minIdx, maxIdx + 1);
}

export type TsVersion = TypeScriptVersion | "next";

0 comments on commit 5013cbf

Please sign in to comment.