fix(compiler-cli): extend `angularCompilerOptions` in tsconfig from node (#40694)

TypeScript supports non rooted extends, we should do the same

b346f5764e/src/compiler/commandLineParser.ts (L2603-L2628)

Closes: #36715

PR Close #40694
This commit is contained in:
Alan Agius 2021-02-08 16:35:08 +01:00 committed by Joey Perrott
parent 719f9ef7ac
commit 5eb195416b
2 changed files with 86 additions and 21 deletions

View File

@ -9,7 +9,7 @@
import {isSyntaxError, Position} from '@angular/compiler';
import * as ts from 'typescript';
import {absoluteFrom, AbsoluteFsPath, getFileSystem, ReadonlyFileSystem, relative, resolve} from '../src/ngtsc/file_system';
import {absoluteFrom, AbsoluteFsPath, FileSystem, getFileSystem, ReadonlyFileSystem, relative, resolve} from '../src/ngtsc/file_system';
import {NgCompilerOptions} from './ngtsc/core/api';
import {replaceTsWithNgInErrors} from './ngtsc/diagnostics';
@ -137,6 +137,8 @@ export function readConfiguration(
project: string, existingOptions?: api.CompilerOptions,
host: ConfigurationHost = getFileSystem()): ParsedConfiguration {
try {
const fs = getFileSystem();
const readConfigFile = (configFile: string) =>
ts.readConfigFile(configFile, file => host.readFile(host.resolve(file)));
const readAngularCompilerOptions =
@ -150,20 +152,14 @@ export function readConfiguration(
// we are only interested into merging 'angularCompilerOptions' as
// other options like 'compilerOptions' are merged by TS
let existingNgCompilerOptions: NgCompilerOptions;
if (parentOptions && config.angularCompilerOptions) {
existingNgCompilerOptions = {...config.angularCompilerOptions, ...parentOptions};
} else {
existingNgCompilerOptions = parentOptions || config.angularCompilerOptions;
}
const existingNgCompilerOptions = {...config.angularCompilerOptions, ...parentOptions};
if (config.extends) {
let extendedConfigPath = host.resolve(host.dirname(configFile), config.extends);
extendedConfigPath = host.extname(extendedConfigPath) ?
extendedConfigPath :
absoluteFrom(`${extendedConfigPath}.json`);
if (config.extends && typeof config.extends === 'string') {
const extendedConfigPath = getExtendedConfigPath(
configFile, config.extends, host, fs,
);
if (host.exists(extendedConfigPath)) {
if (extendedConfigPath !== null) {
// Call readAngularCompilerOptions recursively to merge NG Compiler options
return readAngularCompilerOptions(extendedConfigPath, existingNgCompilerOptions);
}
@ -172,13 +168,6 @@ export function readConfiguration(
return existingNgCompilerOptions;
};
const parseConfigHost = {
useCaseSensitiveFileNames: true,
fileExists: host.exists.bind(host),
readDirectory: ts.sys.readDirectory,
readFile: ts.sys.readFile
};
const {projectFile, basePath} = calcProjectFileAndBasePath(project, host);
const configFileName = host.resolve(host.pwd(), projectFile);
const {config, error} = readConfigFile(projectFile);
@ -191,13 +180,14 @@ export function readConfiguration(
emitFlags: api.EmitFlags.Default
};
}
const existingCompilerOptions = {
const existingCompilerOptions: api.CompilerOptions = {
genDir: basePath,
basePath,
...readAngularCompilerOptions(configFileName),
...existingOptions,
};
const parseConfigHost = createParseConfigHost(host, fs);
const {options, errors, fileNames: rootNames, projectReferences} =
ts.parseJsonConfigFileContent(
config, parseConfigHost, basePath, existingCompilerOptions, configFileName);
@ -227,6 +217,47 @@ export function readConfiguration(
}
}
function createParseConfigHost(host: ConfigurationHost, fs = getFileSystem()): ts.ParseConfigHost {
return {
fileExists: host.exists.bind(host),
readDirectory: ts.sys.readDirectory,
readFile: host.readFile.bind(host),
useCaseSensitiveFileNames: fs.isCaseSensitive(),
};
}
function getExtendedConfigPath(
configFile: string, extendsValue: string, host: ConfigurationHost,
fs: FileSystem): AbsoluteFsPath|null {
let extendedConfigPath: AbsoluteFsPath|null = null;
if (extendsValue.startsWith('.') || fs.isRooted(extendsValue)) {
extendedConfigPath = host.resolve(host.dirname(configFile), extendsValue);
extendedConfigPath = host.extname(extendedConfigPath) ?
extendedConfigPath :
absoluteFrom(`${extendedConfigPath}.json`);
} else {
const parseConfigHost = createParseConfigHost(host, fs);
// Path isn't a rooted or relative path, resolve like a module.
const {
resolvedModule,
} =
ts.nodeModuleNameResolver(
extendsValue, configFile,
{moduleResolution: ts.ModuleResolutionKind.NodeJs, resolveJsonModule: true},
parseConfigHost);
if (resolvedModule) {
extendedConfigPath = absoluteFrom(resolvedModule.resolvedFileName);
}
}
if (extendedConfigPath !== null && host.exists(extendedConfigPath)) {
return extendedConfigPath;
}
return null;
}
export interface PerformCompilationResult {
diagnostics: Diagnostics;
program?: api.Program;

View File

@ -102,4 +102,38 @@ describe('perform_compile', () => {
annotateForClosureCompiler: false,
}));
});
it('should merge tsconfig "angularCompilerOptions" when extends point to node package', () => {
support.writeFiles({
'tsconfig-level-1.json': `{
"extends": "@angular-ru/tsconfig",
"angularCompilerOptions": {
"enableIvy": false
}
}
`,
'node_modules/@angular-ru/tsconfig/tsconfig.json': `{
"compilerOptions": {
"strict": true
},
"angularCompilerOptions": {
"skipMetadataEmit": true
}
}
`,
'node_modules/@angular-ru/tsconfig/package.json': `{
"name": "@angular-ru/tsconfig",
"version": "0.0.0",
"main": "./tsconfig.json"
}
`,
});
const {options} = readConfiguration(path.resolve(basePath, 'tsconfig-level-1.json'));
expect(options).toEqual(jasmine.objectContaining({
strict: true,
skipMetadataEmit: true,
enableIvy: false,
}));
});
});