Skip to content

Commit b7c4d07

Browse files
alan-agius4josephperrott
authored andcommitted
fix(compiler-cli): readConfiguration existing options should override options in tsconfig (#40694)
At the moment, when passing an Angular Compiler option in the `existingOptions` it doesn't override the defined in the TSConfig. PR Close #40694
1 parent dfc9f36 commit b7c4d07

File tree

3 files changed

+65
-47
lines changed

3 files changed

+65
-47
lines changed

packages/compiler-cli/src/perform_compile.ts

Lines changed: 39 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {isSyntaxError, Position} from '@angular/compiler';
1010
import * as ts from 'typescript';
1111

1212
import {absoluteFrom, AbsoluteFsPath, getFileSystem, ReadonlyFileSystem, relative, resolve} from '../src/ngtsc/file_system';
13+
import {NgCompilerOptions} from './ngtsc/core/api';
1314

1415
import {replaceTsWithNgInErrors} from './ngtsc/diagnostics';
1516
import * as api from './transformers/api';
@@ -132,39 +133,28 @@ export function calcProjectFileAndBasePath(
132133
return {projectFile, basePath};
133134
}
134135

135-
export function createNgCompilerOptions(
136-
basePath: string, config: any, tsOptions: ts.CompilerOptions): api.CompilerOptions {
137-
// enableIvy `ngtsc` is an alias for `true`.
138-
const {angularCompilerOptions = {}} = config;
139-
const {enableIvy} = angularCompilerOptions;
140-
angularCompilerOptions.enableIvy = enableIvy !== false && enableIvy !== 'tsc';
141-
142-
return {...tsOptions, ...angularCompilerOptions, genDir: basePath, basePath};
143-
}
144-
145136
export function readConfiguration(
146-
project: string, existingOptions?: ts.CompilerOptions,
137+
project: string, existingOptions?: api.CompilerOptions,
147138
host: ConfigurationHost = getFileSystem()): ParsedConfiguration {
148139
try {
149-
const {projectFile, basePath} = calcProjectFileAndBasePath(project, host);
150-
151-
const readExtendedConfigFile =
152-
(configFile: string, existingConfig?: any): {config?: any, error?: ts.Diagnostic} => {
153-
const {config, error} =
154-
ts.readConfigFile(configFile, file => host.readFile(host.resolve(file)));
140+
const readConfigFile = (configFile: string) =>
141+
ts.readConfigFile(configFile, file => host.readFile(host.resolve(file)));
142+
const readAngularCompilerOptions =
143+
(configFile: string, parentOptions: NgCompilerOptions = {}): NgCompilerOptions => {
144+
const {config, error} = readConfigFile(configFile);
155145

156146
if (error) {
157-
return {error};
147+
// Errors are handled later on by 'parseJsonConfigFileContent'
148+
return parentOptions;
158149
}
159150

160151
// we are only interested into merging 'angularCompilerOptions' as
161152
// other options like 'compilerOptions' are merged by TS
162-
const baseConfig = existingConfig || config;
163-
if (existingConfig) {
164-
baseConfig.angularCompilerOptions = {
165-
...config.angularCompilerOptions,
166-
...baseConfig.angularCompilerOptions
167-
};
153+
let existingNgCompilerOptions: NgCompilerOptions;
154+
if (parentOptions && config.angularCompilerOptions) {
155+
existingNgCompilerOptions = {...config.angularCompilerOptions, ...parentOptions};
156+
} else {
157+
existingNgCompilerOptions = parentOptions || config.angularCompilerOptions;
168158
}
169159

170160
if (config.extends) {
@@ -174,16 +164,24 @@ export function readConfiguration(
174164
absoluteFrom(`${extendedConfigPath}.json`);
175165

176166
if (host.exists(extendedConfigPath)) {
177-
// Call read config recursively as TypeScript only merges CompilerOptions
178-
return readExtendedConfigFile(extendedConfigPath, baseConfig);
167+
// Call readAngularCompilerOptions recursively to merge NG Compiler options
168+
return readAngularCompilerOptions(extendedConfigPath, existingNgCompilerOptions);
179169
}
180170
}
181171

182-
return {config: baseConfig};
172+
return existingNgCompilerOptions;
183173
};
184174

185-
const {config, error} = readExtendedConfigFile(projectFile);
175+
const parseConfigHost = {
176+
useCaseSensitiveFileNames: true,
177+
fileExists: host.exists.bind(host),
178+
readDirectory: ts.sys.readDirectory,
179+
readFile: ts.sys.readFile
180+
};
186181

182+
const {projectFile, basePath} = calcProjectFileAndBasePath(project, host);
183+
const configFileName = host.resolve(host.pwd(), projectFile);
184+
const {config, error} = readConfigFile(projectFile);
187185
if (error) {
188186
return {
189187
project,
@@ -193,34 +191,28 @@ export function readConfiguration(
193191
emitFlags: api.EmitFlags.Default
194192
};
195193
}
196-
const parseConfigHost = {
197-
useCaseSensitiveFileNames: true,
198-
fileExists: host.exists.bind(host),
199-
readDirectory: ts.sys.readDirectory,
200-
readFile: ts.sys.readFile
194+
const existingCompilerOptions = {
195+
genDir: basePath,
196+
basePath,
197+
...readAngularCompilerOptions(configFileName),
198+
...existingOptions,
201199
};
202-
const configFileName = host.resolve(host.pwd(), projectFile);
203-
const parsed = ts.parseJsonConfigFileContent(
204-
config, parseConfigHost, basePath, existingOptions, configFileName);
205-
const rootNames = parsed.fileNames;
206-
const projectReferences = parsed.projectReferences;
207200

208-
const options = createNgCompilerOptions(basePath, config, parsed.options);
201+
const {options, errors, fileNames: rootNames, projectReferences} =
202+
ts.parseJsonConfigFileContent(
203+
config, parseConfigHost, basePath, existingCompilerOptions, configFileName);
204+
205+
// Coerce to boolean as `enableIvy` can be `ngtsc|true|false|undefined` here.
206+
options.enableIvy = !!(options.enableIvy ?? true);
207+
209208
let emitFlags = api.EmitFlags.Default;
210209
if (!(options.skipMetadataEmit || options.flatModuleOutFile)) {
211210
emitFlags |= api.EmitFlags.Metadata;
212211
}
213212
if (options.skipTemplateCodegen) {
214213
emitFlags = emitFlags & ~api.EmitFlags.Codegen;
215214
}
216-
return {
217-
project: projectFile,
218-
rootNames,
219-
projectReferences,
220-
options,
221-
errors: parsed.errors,
222-
emitFlags
223-
};
215+
return {project: projectFile, rootNames, projectReferences, options, errors, emitFlags};
224216
} catch (e) {
225217
const errors: ts.Diagnostic[] = [{
226218
category: ts.DiagnosticCategory.Error,

packages/compiler-cli/test/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ ts_library(
135135
":test_utils",
136136
"//packages/compiler",
137137
"//packages/compiler-cli",
138+
"@npm//typescript",
138139
],
139140
)
140141

packages/compiler-cli/test/perform_compile_spec.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
*/
88

99
import * as path from 'path';
10+
import * as ts from 'typescript';
1011

1112
import {readConfiguration} from '../src/perform_compile';
1213

@@ -77,4 +78,28 @@ describe('perform_compile', () => {
7778
const {options} = readConfiguration(path.resolve(basePath, 'tsconfig-level-1.json'));
7879
expect(options.enableIvy).toBe(false);
7980
});
81+
82+
it('should override options defined in tsconfig with those defined in `existingOptions`', () => {
83+
support.writeFiles({
84+
'tsconfig-level-1.json': `{
85+
"compilerOptions": {
86+
"target": "es2020"
87+
},
88+
"angularCompilerOptions": {
89+
"annotateForClosureCompiler": true
90+
}
91+
}
92+
`
93+
});
94+
95+
const {options} = readConfiguration(
96+
path.resolve(basePath, 'tsconfig-level-1.json'),
97+
{annotateForClosureCompiler: false, target: ts.ScriptTarget.ES2015, enableIvy: false});
98+
99+
expect(options).toEqual(jasmine.objectContaining({
100+
enableIvy: false,
101+
target: ts.ScriptTarget.ES2015,
102+
annotateForClosureCompiler: false,
103+
}));
104+
});
80105
});

0 commit comments

Comments
 (0)