Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support multiple tsconfig files in expect #973

Merged
merged 16 commits into from
Mar 8, 2024
9 changes: 9 additions & 0 deletions .changeset/serious-eels-think.md
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Loading
Loading