feat(test runner): support tsconfig.extends array (#22975)

Fixes #22151.
This commit is contained in:
Dmitry Gozman 2023-05-11 19:18:13 -07:00 committed by GitHub
parent 59b9a39740
commit 9ffe33fae8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 63 additions and 18 deletions

View File

@ -39,6 +39,7 @@ interface Tsconfig {
strict?: boolean; strict?: boolean;
allowJs?: boolean; allowJs?: boolean;
}; };
references?: any[];
} }
export interface TsConfigLoaderResult { export interface TsConfigLoaderResult {
@ -123,20 +124,19 @@ export function walkForTsConfig(
function loadTsconfig( function loadTsconfig(
configFilePath: string, configFilePath: string,
existsSync: (path: string) => boolean = fs.existsSync,
readFileSync: (filename: string) => string = (filename: string) =>
fs.readFileSync(filename, "utf8")
): Tsconfig | undefined { ): Tsconfig | undefined {
if (!existsSync(configFilePath)) { if (!fs.existsSync(configFilePath)) {
return undefined; return undefined;
} }
const configString = readFileSync(configFilePath); const configString = fs.readFileSync(configFilePath, 'utf-8');
const cleanedJson = StripBom(configString); const cleanedJson = StripBom(configString);
let config: Tsconfig = json5.parse(cleanedJson); const parsedConfig: Tsconfig = json5.parse(cleanedJson);
let extendedConfig = config.extends;
if (extendedConfig) { let config: Tsconfig = {};
const extendsArray = Array.isArray(parsedConfig.extends) ? parsedConfig.extends : (parsedConfig.extends ? [parsedConfig.extends] : []);
for (let extendedConfig of extendsArray) {
if ( if (
typeof extendedConfig === "string" && typeof extendedConfig === "string" &&
extendedConfig.indexOf(".json") === -1 extendedConfig.indexOf(".json") === -1
@ -148,7 +148,7 @@ function loadTsconfig(
if ( if (
extendedConfig.indexOf("/") !== -1 && extendedConfig.indexOf("/") !== -1 &&
extendedConfig.indexOf(".") !== -1 && extendedConfig.indexOf(".") !== -1 &&
!existsSync(extendedConfigPath) !fs.existsSync(extendedConfigPath)
) { ) {
extendedConfigPath = path.join( extendedConfigPath = path.join(
currentDir, currentDir,
@ -158,7 +158,7 @@ function loadTsconfig(
} }
const base = const base =
loadTsconfig(extendedConfigPath, existsSync, readFileSync) || {}; loadTsconfig(extendedConfigPath) || {};
// baseUrl should be interpreted as relative to the base tsconfig, // baseUrl should be interpreted as relative to the base tsconfig,
// but we need to update it so it is relative to the original tsconfig being loaded // but we need to update it so it is relative to the original tsconfig being loaded
@ -170,16 +170,14 @@ function loadTsconfig(
); );
} }
config = { config = mergeConfigs(config, base);
...base,
...config,
compilerOptions: {
...base.compilerOptions,
...config.compilerOptions,
},
};
} }
config = mergeConfigs(config, parsedConfig);
// The only top-level property that is excluded from inheritance is "references".
// https://www.typescriptlang.org/tsconfig#extends
config.references = parsedConfig.references;
if (path.basename(configFilePath) === 'jsconfig.json' && config.compilerOptions?.allowJs === undefined) { if (path.basename(configFilePath) === 'jsconfig.json' && config.compilerOptions?.allowJs === undefined) {
config.compilerOptions = config.compilerOptions || {}; config.compilerOptions = config.compilerOptions || {};
config.compilerOptions.allowJs = true; config.compilerOptions.allowJs = true;
@ -188,6 +186,17 @@ function loadTsconfig(
return config; return config;
} }
function mergeConfigs(base: Tsconfig, override: Tsconfig): Tsconfig {
return {
...base,
...override,
compilerOptions: {
...base.compilerOptions,
...override.compilerOptions,
},
};
}
function StripBom(string: string) { function StripBom(string: string) {
if (typeof string !== 'string') { if (typeof string !== 'string') {
throw new TypeError(`Expected a string, got ${typeof string}`); throw new TypeError(`Expected a string, got ${typeof string}`);

View File

@ -469,3 +469,39 @@ test('should respect path resolver for JS and TS files from jsconfig.json', asyn
expect(result.passed).toBe(2); expect(result.passed).toBe(2);
expect(result.exitCode).toBe(0); expect(result.exitCode).toBe(0);
}); });
test('should support extends in tsconfig.json', async ({ runInlineTest }) => {
const result = await runInlineTest({
'tsconfig.json': `{
"extends": ["./tsconfig.base1.json", "./tsconfig.base2.json"],
}`,
'tsconfig.base1.json': `{
"extends": "./tsconfig.base.json",
}`,
'tsconfig.base2.json': `{
"compilerOptions": {
"baseUrl": "dir",
},
}`,
'tsconfig.base.json': `{
"compilerOptions": {
"paths": {
"util/*": ["./foo/bar/util/*"],
},
},
}`,
'a.test.ts': `
const { foo } = require('util/file');
import { test, expect } from '@playwright/test';
test('test', ({}, testInfo) => {
expect(foo).toBe('foo');
});
`,
'dir/foo/bar/util/file.ts': `
module.exports = { foo: 'foo' };
`,
});
expect(result.passed).toBe(1);
expect(result.exitCode).toBe(0);
});