132 lines
5.0 KiB
TypeScript
132 lines
5.0 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright Google LLC All Rights Reserved.
|
|
*
|
|
* Use of this source code is governed by an MIT-style license that can be
|
|
* found in the LICENSE file at https://angular.io/license
|
|
*/
|
|
|
|
import {absoluteFrom} from '@angular/compiler-cli/src/ngtsc/file_system';
|
|
import {initMockFileSystem} from '@angular/compiler-cli/src/ngtsc/file_system/testing';
|
|
|
|
import {LanguageServiceTestEnvironment} from './env';
|
|
|
|
describe('language-service/compiler integration', () => {
|
|
beforeEach(() => {
|
|
initMockFileSystem('Native');
|
|
});
|
|
|
|
it('should show type-checking errors from components with poisoned scopes', () => {
|
|
// Normally, the Angular compiler suppresses errors from components that belong to NgModules
|
|
// which themselves have errors (such scopes are considered "poisoned"), to avoid overwhelming
|
|
// the user with secondary errors that stem from a primary root cause. However, this prevents
|
|
// the generation of type check blocks and other metadata within the compiler which drive the
|
|
// Language Service's understanding of components. Therefore in the Language Service, the
|
|
// compiler is configured to make use of such data even if it's "poisoned". This test verifies
|
|
// that a component declared in an NgModule with a faulty import still generates template
|
|
// diagnostics.
|
|
|
|
const file = absoluteFrom('/test.ts');
|
|
const env = LanguageServiceTestEnvironment.setup([{
|
|
name: file,
|
|
contents: `
|
|
import {Component, Directive, Input, NgModule} from '@angular/core';
|
|
|
|
@Component({
|
|
selector: 'test-cmp',
|
|
template: '<div [dir]="3"></div>',
|
|
})
|
|
export class Cmp {}
|
|
|
|
@Directive({
|
|
selector: '[dir]',
|
|
})
|
|
export class Dir {
|
|
@Input() dir!: string;
|
|
}
|
|
|
|
export class NotAModule {}
|
|
|
|
@NgModule({
|
|
declarations: [Cmp, Dir],
|
|
imports: [NotAModule],
|
|
})
|
|
export class Mod {}
|
|
`,
|
|
isRoot: true,
|
|
}]);
|
|
|
|
const diags = env.ngLS.getSemanticDiagnostics(file);
|
|
expect(diags.map(diag => diag.messageText))
|
|
.toContain(`Type 'number' is not assignable to type 'string'.`);
|
|
});
|
|
|
|
it('should handle broken imports during incremental build steps', () => {
|
|
// This test validates that the compiler's incremental APIs correctly handle a broken import
|
|
// when invoked via the Language Service. Testing this via the LS is important as only the LS
|
|
// requests Angular analysis in the presence of TypeScript-level errors. In the case of broken
|
|
// imports this distinction is especially important: Angular's incremental analysis is
|
|
// built on the the compiler's dependency graph, and this graph must be able to function even
|
|
// with broken imports.
|
|
//
|
|
// The test works by creating a component/module pair where the module imports and declares a
|
|
// component from a separate file. That component is initially not exported, meaning the
|
|
// module's import is broken. Angular will correctly complain that the NgModule is declaring a
|
|
// value which is not statically analyzable.
|
|
//
|
|
// Then, the component file is fixed to properly export the component class, and an incremental
|
|
// build step is performed. The compiler should recognize that the module's previous analysis
|
|
// is stale, even though it was not able to fully understand the import during the first pass.
|
|
|
|
const moduleFile = absoluteFrom('/mod.ts');
|
|
const componentFile = absoluteFrom('/cmp.ts');
|
|
|
|
const componentSource = (isExported: boolean): string => `
|
|
import {Component} from '@angular/core';
|
|
|
|
@Component({
|
|
selector: 'some-cmp',
|
|
template: 'Not important',
|
|
})
|
|
${isExported ? 'export' : ''} class Cmp {}
|
|
`;
|
|
|
|
const env = LanguageServiceTestEnvironment.setup([
|
|
{
|
|
name: moduleFile,
|
|
contents: `
|
|
import {NgModule} from '@angular/core';
|
|
|
|
import {Cmp} from './cmp';
|
|
|
|
@NgModule({
|
|
declarations: [Cmp],
|
|
})
|
|
export class Mod {}
|
|
`,
|
|
isRoot: true,
|
|
},
|
|
{
|
|
name: componentFile,
|
|
contents: componentSource(/* start with component not exported */ false),
|
|
isRoot: true,
|
|
}
|
|
]);
|
|
|
|
// Angular should be complaining about the module not being understandable.
|
|
const programBefore = env.tsLS.getProgram()!;
|
|
const moduleSfBefore = programBefore.getSourceFile(moduleFile)!;
|
|
const ngDiagsBefore = env.ngLS.compilerFactory.getOrCreate().getDiagnostics(moduleSfBefore);
|
|
expect(ngDiagsBefore.length).toBe(1);
|
|
|
|
// Fix the import.
|
|
env.updateFile(componentFile, componentSource(/* properly export the component */ true));
|
|
|
|
// Angular should stop complaining about the NgModule.
|
|
const programAfter = env.tsLS.getProgram()!;
|
|
const moduleSfAfter = programAfter.getSourceFile(moduleFile)!;
|
|
const ngDiagsAfter = env.ngLS.compilerFactory.getOrCreate().getDiagnostics(moduleSfAfter);
|
|
expect(ngDiagsAfter.length).toBe(0);
|
|
});
|
|
});
|