refactor(core): undecorated-classes-with-di migration should ignore referenced resources (#32953)

Currently the undecorated-classes-with-di migration leverages NGC in order
to work with metadata resolution. Since NGC by default tries to resolve referenced
resources on initialization of the underlying TS program, it can result in unexpected
migration failures due to missing resource files.

This is especially an issue since the CLI wraps the `AngularCompilerProgram` with
special logic (i.e. to support SCSS preprocessing etc.). We don't have all of this since
we instantiate a vanilla NGC program.

The solution to the problem is to simply treat resource requests as valid, and returning
a fake content. The migration is not dependent on templates or stylesheets.. so it's the
simplest and most robust solution.

Fixes #32826

PR Close #32953
This commit is contained in:
Paul Gschwendtner 2019-10-02 11:28:23 +02:00 committed by atscott
parent 90dda5873a
commit 6f5f481fda
2 changed files with 34 additions and 15 deletions

View File

@ -6,13 +6,13 @@
* found in the LICENSE file at https://angular.io/license
*/
import {AotCompiler, CompileStylesheetMetadata} from '@angular/compiler';
import {createProgram, readConfiguration} from '@angular/compiler-cli';
import {AotCompiler} from '@angular/compiler';
import {CompilerHost, createProgram, readConfiguration} from '@angular/compiler-cli';
import * as ts from 'typescript';
/** Creates an NGC program that can be used to read and parse metadata for files. */
export function createNgcProgram(
createHost: (options: ts.CompilerOptions) => ts.CompilerHost, tsconfigPath: string) {
createHost: (options: ts.CompilerOptions) => CompilerHost, tsconfigPath: string) {
const {rootNames, options} = readConfiguration(tsconfigPath);
// https://github.com/angular/angular/commit/ec4381dd401f03bded652665b047b6b90f2b425f made Ivy
@ -21,24 +21,21 @@ export function createNgcProgram(
options.enableIvy = false;
const host = createHost(options);
// For this migration, we never need to read resources and can just return
// an empty string for requested resources. We need to handle requested resources
// because our created NGC compiler program does not know about special resolutions
// which are set up by the Angular CLI. i.e. resolving stylesheets through "tilde".
host.readResource = () => '';
host.resourceNameToFileName = () => '$fake-file$';
const ngcProgram = createProgram({rootNames, options, host});
const program = ngcProgram.getTsProgram();
// The "AngularCompilerProgram" does not expose the "AotCompiler" instance, nor does it
// expose the logic that is necessary to analyze the determined modules. We work around
// this by just accessing the necessary private properties using the bracket notation.
const compiler: AotCompiler = (ngcProgram as any)['compiler'];
const metadataResolver = compiler['_metadataResolver'];
// Modify the "DirectiveNormalizer" to not normalize any referenced external stylesheets.
// This is necessary because in CLI projects preprocessor files are commonly referenced
// and we don't want to parse them in order to extract relative style references. This
// breaks the analysis of the project because we instantiate a standalone AOT compiler
// program which does not contain the custom logic by the Angular CLI Webpack compiler plugin.
const directiveNormalizer = metadataResolver !['_directiveNormalizer'];
directiveNormalizer['_normalizeStylesheet'] = function(metadata: CompileStylesheetMetadata) {
return new CompileStylesheetMetadata(
{styles: metadata.styles, styleUrls: [], moduleUrl: metadata.moduleUrl !});
};
const program = ngcProgram.getTsProgram();
return {host, ngcProgram, program, compiler};
}

View File

@ -1457,5 +1457,27 @@ describe('Undecorated classes with DI migration', () => {
expect(errorOutput.length).toBe(1);
expect(errorOutput[0]).toMatch(/error TS1005: 'from' expected/);
});
it('should not throw if resources could not be read', async() => {
writeFile('/index.ts', `
import {Component, NgModule} from '@angular/core';
@Component({
templateUrl: './my-template.pug',
styleUrls: ["./test.scss", "./some-special-file.custom"],
})
export class TestComp {}
@NgModule({declarations: [TestComp]})
export class MyModule {}
`);
writeFile('/test.scss', `@import '~theme.scss';`);
await runMigration();
expect(warnOutput.length).toBe(0);
expect(errorOutput.length).toBe(0);
});
});
});