From b358495a6c46dedae3a5edd2dcbcc80fc73739e8 Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Fri, 10 Jul 2020 16:42:37 +0100 Subject: [PATCH] fix(ngcc): report a warning if ngcc tries to use a solution-style tsconfig (#38003) In CLI v10 there was a move to use the new solution-style tsconfig which became available in TS 3.9. The result of this is that the standard tsconfig.json no longer contains important information such as "paths" mappings, which ngcc might need to correctly compute dependencies. ngcc (and ngc and tsc) infer the path to tsconfig.json if not given an explicit tsconfig file-path. But now that means it infers the solution tsconfig rather than one that contains the useful information it used to get. This commit logs a warning in this case to inform the developer that they might not have meant to load this tsconfig and offer alternative options. Fixes #36386 PR Close #38003 --- .../compiler-cli/ngcc/src/ngcc_options.ts | 21 ++++++++++ .../ngcc/test/ngcc_options_spec.ts | 42 +++++++++++++++++++ packages/compiler-cli/src/perform_compile.ts | 11 ++++- 3 files changed, 73 insertions(+), 1 deletion(-) diff --git a/packages/compiler-cli/ngcc/src/ngcc_options.ts b/packages/compiler-cli/ngcc/src/ngcc_options.ts index abb3578ef6..7ef38ec650 100644 --- a/packages/compiler-cli/ngcc/src/ngcc_options.ts +++ b/packages/compiler-cli/ngcc/src/ngcc_options.ts @@ -186,6 +186,8 @@ export function getSharedSetup(options: NgccOptions): SharedSetup&RequiredNgccOp errorOnFailedEntryPoint = true; } + checkForSolutionStyleTsConfig(fileSystem, logger, projectPath, options.tsConfigPath, tsConfig); + return { basePath, targetEntryPointPath, @@ -233,3 +235,22 @@ export function clearTsConfigCache() { tsConfigPathCache = null; tsConfigCache = null; } + +function checkForSolutionStyleTsConfig( + fileSystem: FileSystem, logger: Logger, projectPath: AbsoluteFsPath, + tsConfigPath: string|null|undefined, tsConfig: ParsedConfiguration|null): void { + if (tsConfigPath !== null && !tsConfigPath && tsConfig !== null && + tsConfig.rootNames.length === 0 && tsConfig.projectReferences !== undefined && + tsConfig.projectReferences.length > 0) { + logger.warn( + `The inferred tsconfig file "${tsConfig.project}" appears to be "solution-style" ` + + `since it contains no root files but does contain project references.\n` + + `This is probably not wanted, since ngcc is unable to infer settings like "paths" mappings from such a file.\n` + + `Perhaps you should have explicitly specified one of the referenced projects using the --tsconfig option. For example:\n\n` + + tsConfig.projectReferences.map(ref => ` ngcc ... --tsconfig "${ref.originalPath}"\n`) + .join('') + + `\nFind out more about solution-style tsconfig at https://devblogs.microsoft.com/typescript/announcing-typescript-3-9/#solution-style-tsconfig.\n` + + `If you did intend to use this file, then you can hide this warning by providing it explicitly:\n\n` + + ` ngcc ... --tsconfig "${fileSystem.relative(projectPath, tsConfig.project)}"`); + } +} diff --git a/packages/compiler-cli/ngcc/test/ngcc_options_spec.ts b/packages/compiler-cli/ngcc/test/ngcc_options_spec.ts index f044799d9a..56fa85b7eb 100644 --- a/packages/compiler-cli/ngcc/test/ngcc_options_spec.ts +++ b/packages/compiler-cli/ngcc/test/ngcc_options_spec.ts @@ -56,6 +56,48 @@ runInEachFileSystem(() => { expect(setup.tsConfigPath).toBe(null); expect(setup.tsConfig).toBe(null); }); + + it('should warn about a solution-style tsconfig if the tsConfigPath is inferred', () => { + fs.writeFile(fs.resolve(projectPath, 'tsconfig.app.json'), '{"files": ["src/index.ts"]}'); + fs.writeFile(fs.resolve(projectPath, 'tsconfig.test.json'), '{"files": ["src/test.ts"]}'); + fs.writeFile(pathToProjectTsConfig, JSON.stringify({ + 'files': [], + 'references': [ + {'path': 'tsconfig.app.json'}, + {'path': 'tsconfig.test.json'}, + ] + })); + const setup = getSharedSetup({...createOptions()}); + expect(setup.tsConfigPath).toBeUndefined(); + expect(setup.tsConfig?.rootNames).toEqual([]); + expect((setup.logger as MockLogger).logs.warn).toEqual([[ + `The inferred tsconfig file "${ + pathToProjectTsConfig}" appears to be "solution-style" since it contains no root files but does contain project references.\n` + + `This is probably not wanted, since ngcc is unable to infer settings like "paths" mappings from such a file.\n` + + `Perhaps you should have explicitly specified one of the referenced projects using the --tsconfig option. For example:\n\n` + + ` ngcc ... --tsconfig "tsconfig.app.json"\n` + + ` ngcc ... --tsconfig "tsconfig.test.json"\n` + + `\nFind out more about solution-style tsconfig at https://devblogs.microsoft.com/typescript/announcing-typescript-3-9/#solution-style-tsconfig.\n` + + `If you did intend to use this file, then you can hide this warning by providing it explicitly:\n\n` + + ` ngcc ... --tsconfig "tsconfig.json"` + ]]); + }); + + it('should not warn about a solution-style tsconfig if the tsConfigPath is explicit', () => { + fs.writeFile(fs.resolve(projectPath, 'tsconfig.app.json'), '{"files": ["src/index.ts"]}'); + fs.writeFile(fs.resolve(projectPath, 'tsconfig.test.json'), '{"files": ["src/test.ts"]}'); + fs.writeFile(pathToProjectTsConfig, JSON.stringify({ + 'files': [], + 'references': [ + {'path': 'tsconfig.app.json'}, + {'path': 'tsconfig.test.json'}, + ] + })); + const setup = getSharedSetup({...createOptions(), tsConfigPath: pathToProjectTsConfig}); + expect(setup.tsConfigPath).toEqual(pathToProjectTsConfig); + expect(setup.tsConfig?.rootNames).toEqual([]); + expect((setup.logger as MockLogger).logs.warn).toEqual([]); + }); }); /** diff --git a/packages/compiler-cli/src/perform_compile.ts b/packages/compiler-cli/src/perform_compile.ts index 754ca7f2e3..1fc1a42c97 100644 --- a/packages/compiler-cli/src/perform_compile.ts +++ b/packages/compiler-cli/src/perform_compile.ts @@ -112,6 +112,7 @@ export interface ParsedConfiguration { project: string; options: api.CompilerOptions; rootNames: string[]; + projectReferences?: readonly ts.ProjectReference[]|undefined; emitFlags: api.EmitFlags; errors: Diagnostics; } @@ -197,6 +198,7 @@ export function readConfiguration( const parsed = ts.parseJsonConfigFileContent( config, parseConfigHost, basePath, existingOptions, configFileName); const rootNames = parsed.fileNames; + const projectReferences = parsed.projectReferences; const options = createNgCompilerOptions(basePath, config, parsed.options); let emitFlags = api.EmitFlags.Default; @@ -206,7 +208,14 @@ export function readConfiguration( if (options.skipTemplateCodegen) { emitFlags = emitFlags & ~api.EmitFlags.Codegen; } - return {project: projectFile, rootNames, options, errors: parsed.errors, emitFlags}; + return { + project: projectFile, + rootNames, + projectReferences, + options, + errors: parsed.errors, + emitFlags + }; } catch (e) { const errors: Diagnostics = [{ category: ts.DiagnosticCategory.Error,