Skip to content

Commit

Permalink
Support multiple tsconfig files in expect (#973)
Browse files Browse the repository at this point in the history
  • Loading branch information
jakebailey committed Mar 8, 2024
1 parent 3393312 commit adfd769
Show file tree
Hide file tree
Showing 20 changed files with 341 additions and 144 deletions.
9 changes: 9 additions & 0 deletions .changeset/serious-eels-think.md
@@ -0,0 +1,9 @@
---
"@definitelytyped/definitions-parser": patch
"@definitelytyped/eslint-plugin": patch
"@definitelytyped/header-parser": patch
"@definitelytyped/publisher": patch
"@definitelytyped/dtslint": patch
---

Allow packages to test multiple tsconfigs by specifying list of tsconfigs in package.json
1 change: 1 addition & 0 deletions packages/definitions-parser/test/utils.ts
Expand Up @@ -23,6 +23,7 @@ export function createTypingsVersionRaw(
minimumTypeScriptVersion: "2.3",
nonNpm: false,
projects: ["zombo.com"],
tsconfigs: ["tsconfig.json"],
},
typesVersions: [],
license: License.MIT,
Expand Down
6 changes: 3 additions & 3 deletions packages/dtslint/src/checks.ts
Expand Up @@ -33,7 +33,7 @@ interface Tsconfig {
exclude?: string[];
}

export function checkTsconfig(dirPath: string, config: Tsconfig): string[] {
export function checkTsconfig(config: Tsconfig): string[] {
const errors = [];
const mustHave = {
noEmit: true,
Expand Down Expand Up @@ -140,13 +140,13 @@ export function checkTsconfig(dirPath: string, config: Tsconfig): string[] {
if (options.paths) {
for (const key in options.paths) {
if (options.paths[key].length !== 1) {
errors.push(`${dirPath}/tsconfig.json: "paths" must map each module specifier to only one file.`);
errors.push(`"paths" must map each module specifier to only one file.`);
}
const [target] = options.paths[key];
if (target !== "./index.d.ts") {
const m = target.match(/^(?:..\/)+([^\/]+)\/(?:v\d+\.?\d*\/)?index.d.ts$/);
if (!m || m[1] !== key) {
errors.push(`${dirPath}/tsconfig.json: "paths" must map '${key}' to ${key}'s index.d.ts.`);
errors.push(`"paths" must map '${key}' to ${key}'s index.d.ts.`);
}
}
}
Expand Down
23 changes: 19 additions & 4 deletions packages/dtslint/src/index.ts
Expand Up @@ -195,6 +195,7 @@ async function runTests(
const tsVersion = tsLocal ? "local" : TypeScriptVersion.latest;
const testTypesResult = await testTypesVersion(
dirPath,
packageJson.tsconfigs,
tsVersion,
tsVersion,
expectOnly,
Expand Down Expand Up @@ -222,7 +223,15 @@ async function runTests(
if (lows.length > 1) {
console.log("testing from", low, "to", hi, "in", versionPath);
}
const testTypesResult = await testTypesVersion(versionPath, low, hi, expectOnly, undefined, isLatest);
const testTypesResult = await testTypesVersion(
versionPath,
packageJson.tsconfigs,
low,
hi,
expectOnly,
undefined,
isLatest,
);
errors.push(...testTypesResult.errors);
}
}
Expand Down Expand Up @@ -264,6 +273,7 @@ function next(v: TypeScriptVersion): TypeScriptVersion {

async function testTypesVersion(
dirPath: string,
tsconfigs: readonly string[],
lowVersion: TsVersion,
hiVersion: TsVersion,
expectOnly: boolean,
Expand All @@ -273,10 +283,15 @@ async function testTypesVersion(
const errors = [];
const checkExpectedFilesResult = checkExpectedFiles(dirPath, isLatest);
errors.push(...checkExpectedFilesResult.errors);
const tsconfigErrors = checkTsconfig(dirPath, getCompilerOptions(dirPath));
if (tsconfigErrors.length > 0) {
errors.push("\n\t* " + tsconfigErrors.join("\n\t* "));

for (const tsconfig of tsconfigs) {
const tsconfigPath = joinPaths(dirPath, tsconfig);
const tsconfigErrors = checkTsconfig(getCompilerOptions(tsconfigPath));
if (tsconfigErrors.length > 0) {
errors.push("\n\t* " + tsconfigPath + ":\n\t* " + tsconfigErrors.join("\n\t* "));
}
}

const err = await lint(dirPath, lowVersion, hiVersion, isLatest, expectOnly, tsLocal);
if (err) {
errors.push(err);
Expand Down
7 changes: 3 additions & 4 deletions packages/dtslint/src/util.ts
@@ -1,6 +1,6 @@
import { createGitHubStringSetGetter, joinPaths } from "@definitelytyped/utils";
import fs from "fs";
import { basename, dirname, join } from "path";
import { basename, dirname } from "path";
import stripJsonComments = require("strip-json-comments");
import * as ts from "typescript";

Expand All @@ -19,15 +19,14 @@ export function readJson(path: string) {
return JSON.parse(stripJsonComments(text));
}

export function getCompilerOptions(dirPath: string): {
export function getCompilerOptions(tsconfigPath: string): {
compilerOptions: ts.CompilerOptions;
files?: string[];
includes?: string[];
excludes?: string[];
} {
const tsconfigPath = join(dirPath, "tsconfig.json");
if (!fs.existsSync(tsconfigPath)) {
throw new Error(`Need a 'tsconfig.json' file in ${dirPath}`);
throw new Error(`${tsconfigPath} does not exist`);
}
return readJson(tsconfigPath) as {
compilerOptions: ts.CompilerOptions;
Expand Down
95 changes: 42 additions & 53 deletions packages/dtslint/test/index.test.ts
Expand Up @@ -20,132 +20,121 @@ describe("dtslint", () => {
describe("checks", () => {
describe("checkTsconfig", () => {
it("disallows unknown compiler options", () => {
expect(checkTsconfig("test", based({ completelyInvented: true }))).toEqual([
expect(checkTsconfig(based({ completelyInvented: true }))).toEqual([
"Unexpected compiler option completelyInvented",
]);
});
it("allows exactOptionalPropertyTypes: true", () => {
expect(checkTsconfig("test", based({ exactOptionalPropertyTypes: true }))).toEqual([]);
expect(checkTsconfig(based({ exactOptionalPropertyTypes: true }))).toEqual([]);
});
it("allows module: node16", () => {
expect(checkTsconfig("test", based({ module: "node16" }))).toEqual([]);
expect(checkTsconfig(based({ module: "node16" }))).toEqual([]);
});
it("allows `paths`", () => {
expect(checkTsconfig("test", based({ paths: { boom: ["../boom/index.d.ts"] } }))).toEqual([]);
expect(checkTsconfig(based({ paths: { boom: ["../boom/index.d.ts"] } }))).toEqual([]);
});
it("disallows missing `module`", () => {
const compilerOptions = { ...base };
delete compilerOptions.module;
expect(checkTsconfig("test", { compilerOptions, files: ["index.d.ts", "base.test.ts"] })).toEqual([
expect(checkTsconfig({ compilerOptions, files: ["index.d.ts", "base.test.ts"] })).toEqual([
'Must specify "module" to `"module": "commonjs"` or `"module": "node16"`.',
]);
});
it("disallows exactOptionalPropertyTypes: false", () => {
expect(checkTsconfig("test", based({ exactOptionalPropertyTypes: false }))).toEqual([
expect(checkTsconfig(based({ exactOptionalPropertyTypes: false }))).toEqual([
'When "exactOptionalPropertyTypes" is present, it must be set to `true`.',
]);
});
it("allows paths: self-reference", () => {
expect(checkTsconfig("react-native", based({ paths: { "react-native": ["./index.d.ts"] } }))).toEqual([]);
expect(checkTsconfig(based({ paths: { "react-native": ["./index.d.ts"] } }))).toEqual([]);
});
it("allows paths: matching ../reference/index.d.ts", () => {
expect(
checkTsconfig("reactive-dep", based({ paths: { "react-native": ["../react-native/index.d.ts"] } })),
).toEqual([]);
expect(checkTsconfig(based({ paths: { "react-native": ["../react-native/index.d.ts"] } }))).toEqual([]);
expect(
checkTsconfig(
"reactive-dep",
based({ paths: { "react-native": ["../react-native/index.d.ts"], react: ["../react/v16/index.d.ts"] } }),
),
).toEqual([]);
});
it("forbids paths: mapping to multiple things", () => {
expect(
checkTsconfig(
"reactive-dep",
based({ paths: { "react-native": ["./index.d.ts", "../react-native/v0.68/index.d.ts"] } }),
),
).toEqual([`reactive-dep/tsconfig.json: "paths" must map each module specifier to only one file.`]);
checkTsconfig(based({ paths: { "react-native": ["./index.d.ts", "../react-native/v0.68/index.d.ts"] } })),
).toEqual([`"paths" must map each module specifier to only one file.`]);
});
it("allows paths: matching ../reference/version/index.d.ts", () => {
expect(checkTsconfig("reactive-dep", based({ paths: { react: ["../react/v16/index.d.ts"] } }))).toEqual([]);
expect(
checkTsconfig("reactive-dep", based({ paths: { "react-native": ["../react-native/v0.69/index.d.ts"] } })),
).toEqual([]);
expect(
checkTsconfig(
"reactive-dep/v1",
based({ paths: { "react-native": ["../../react-native/v0.69/index.d.ts"] } }),
),
).toEqual([]);
expect(checkTsconfig(based({ paths: { react: ["../react/v16/index.d.ts"] } }))).toEqual([]);
expect(checkTsconfig(based({ paths: { "react-native": ["../react-native/v0.69/index.d.ts"] } }))).toEqual([]);
expect(checkTsconfig(based({ paths: { "react-native": ["../../react-native/v0.69/index.d.ts"] } }))).toEqual(
[],
);
});
it("forbids paths: mapping to self-contained file", () => {
expect(checkTsconfig("rrrr", based({ paths: { "react-native": ["./other.d.ts"] } }))).toEqual([
`rrrr/tsconfig.json: "paths" must map 'react-native' to react-native's index.d.ts.`,
expect(checkTsconfig(based({ paths: { "react-native": ["./other.d.ts"] } }))).toEqual([
`"paths" must map 'react-native' to react-native's index.d.ts.`,
]);
});
it("forbids paths: mismatching ../NOT/index.d.ts", () => {
expect(checkTsconfig("rrrr", based({ paths: { "react-native": ["../cocoa/index.d.ts"] } }))).toEqual([
`rrrr/tsconfig.json: "paths" must map 'react-native' to react-native's index.d.ts.`,
expect(checkTsconfig(based({ paths: { "react-native": ["../cocoa/index.d.ts"] } }))).toEqual([
`"paths" must map 'react-native' to react-native's index.d.ts.`,
]);
});
it("forbids paths: mismatching ../react-native/NOT.d.ts", () => {
expect(checkTsconfig("rrrr", based({ paths: { "react-native": ["../react-native/other.d.ts"] } }))).toEqual([
`rrrr/tsconfig.json: "paths" must map 'react-native' to react-native's index.d.ts.`,
expect(checkTsconfig(based({ paths: { "react-native": ["../react-native/other.d.ts"] } }))).toEqual([
`"paths" must map 'react-native' to react-native's index.d.ts.`,
]);
});
it("forbids paths: mismatching ../react-native/NOT/index.d.ts", () => {
expect(
checkTsconfig("rrrr", based({ paths: { "react-native": ["../react-native/deep/index.d.ts"] } })),
).toEqual([`rrrr/tsconfig.json: "paths" must map 'react-native' to react-native's index.d.ts.`]);
expect(checkTsconfig(based({ paths: { "react-native": ["../react-native/deep/index.d.ts"] } }))).toEqual([
`"paths" must map 'react-native' to react-native's index.d.ts.`,
]);
});
it("forbids paths: mismatching ../react-native/version/NOT/index.d.ts", () => {
expect(
checkTsconfig("rrrr", based({ paths: { "react-native": ["../react-native/v0.68/deep/index.d.ts"] } })),
).toEqual([`rrrr/tsconfig.json: "paths" must map 'react-native' to react-native's index.d.ts.`]);
expect(checkTsconfig(based({ paths: { "react-native": ["../react-native/v0.68/deep/index.d.ts"] } }))).toEqual([
`"paths" must map 'react-native' to react-native's index.d.ts.`,
]);
});
it("forbids paths: mismatching ../react-native/version/NOT.d.ts", () => {
expect(
checkTsconfig("rrrr", based({ paths: { "react-native": ["../react-native/v0.70/other.d.ts"] } })),
).toEqual([`rrrr/tsconfig.json: "paths" must map 'react-native' to react-native's index.d.ts.`]);
expect(checkTsconfig(based({ paths: { "react-native": ["../react-native/v0.70/other.d.ts"] } }))).toEqual([
`"paths" must map 'react-native' to react-native's index.d.ts.`,
]);
});
it("Forbids exclude", () => {
expect(checkTsconfig("exclude", { compilerOptions: base, exclude: ["**/node_modules"] })).toEqual([
expect(checkTsconfig({ compilerOptions: base, exclude: ["**/node_modules"] })).toEqual([
`Use "files" instead of "exclude".`,
]);
});
it("Forbids include", () => {
expect(checkTsconfig("include", { compilerOptions: base, include: ["**/node_modules"] })).toEqual([
expect(checkTsconfig({ compilerOptions: base, include: ["**/node_modules"] })).toEqual([
`Use "files" instead of "include".`,
]);
});
it("Requires files", () => {
expect(checkTsconfig("include", { compilerOptions: base })).toEqual([`Must specify "files".`]);
expect(checkTsconfig({ compilerOptions: base })).toEqual([`Must specify "files".`]);
});
it("Requires files to contain index.d.ts", () => {
expect(
checkTsconfig("include", { compilerOptions: base, files: ["package-name.d.ts", "package-name.test.ts"] }),
).toEqual([`"files" list must include "index.d.ts".`]);
expect(checkTsconfig({ compilerOptions: base, files: ["package-name.d.ts", "package-name.test.ts"] })).toEqual([
`"files" list must include "index.d.ts".`,
]);
});
// it("Requires files to contain .[mc]ts file", () => {
// expect(checkTsconfig("include", { compilerOptions: base, files: ["index.d.ts"] })).toEqual([
// expect(checkTsconfig({ compilerOptions: base, files: ["index.d.ts"] })).toEqual([
// `"files" list must include at least one ".ts", ".tsx", ".mts" or ".cts" file for testing.`,
// ]);
// });
it("Allows files to contain index.d.ts plus a .tsx", () => {
expect(checkTsconfig("include", { compilerOptions: base, files: ["index.d.ts", "tests.tsx"] })).toEqual([]);
expect(checkTsconfig({ compilerOptions: base, files: ["index.d.ts", "tests.tsx"] })).toEqual([]);
});
it("Allows files to contain index.d.ts plus a .mts", () => {
expect(checkTsconfig("include", { compilerOptions: base, files: ["index.d.ts", "tests.mts"] })).toEqual([]);
expect(checkTsconfig({ compilerOptions: base, files: ["index.d.ts", "tests.mts"] })).toEqual([]);
});
it("Allows files to contain index.d.ts plus a .cts", () => {
expect(checkTsconfig("include", { compilerOptions: base, files: ["index.d.ts", "tests.cts"] })).toEqual([]);
expect(checkTsconfig({ compilerOptions: base, files: ["index.d.ts", "tests.cts"] })).toEqual([]);
});
it("Allows files to contain ./index.d.ts plus a ./.tsx", () => {
expect(checkTsconfig("include", { compilerOptions: base, files: ["./index.d.ts", "./tests.tsx"] })).toEqual([]);
expect(checkTsconfig({ compilerOptions: base, files: ["./index.d.ts", "./tests.tsx"] })).toEqual([]);
});
it("Issues both errors on empty files list", () => {
expect(checkTsconfig("include", { compilerOptions: base, files: [] })).toEqual([
expect(checkTsconfig({ compilerOptions: base, files: [] })).toEqual([
`"files" list must include "index.d.ts".`,
// `"files" list must include at least one ".ts", ".tsx", ".mts" or ".cts" file for testing.`,
]);
Expand Down

0 comments on commit adfd769

Please sign in to comment.