Keen Yee Liau 63624a2d46 feat(language-service): [Ivy] getSemanticDiagnostics for external templates (#39065)
This PR enables `getSemanticDiagnostics()` to be called on external templates.

Several changes are needed to land this feature:

1. The adapter needs to implement two additional methods:
   a. `readResource()`
       Load the template from snapshot instead of reading from disk
   b. `getModifiedResourceFiles()`
       Inform the compiler that external templates have changed so that the
       loader could invalidate its internal cache.
2. Create `ScriptInfo` for external templates in MockHost.
   Prior to this, MockHost only track changes in TypeScript files. Now it
   needs to create `ScriptInfo` for external templates as well.

For (1), in order to make sure we don't reload the template if it hasn't
changed, we need to keep track of its version. Since the complexity has
increased, the adapter is refactored into its own class.

PR Close #39065
2020-10-08 08:45:54 -07:00

70 lines
2.7 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 * as ts from 'typescript/lib/tsserverlibrary';
import {LanguageService} from '../language_service';
import {APP_COMPONENT, setup, TEST_TEMPLATE} from './mock_host';
describe('getSemanticDiagnostics', () => {
const {project, service, tsLS} = setup();
const ngLS = new LanguageService(project, tsLS);
beforeEach(() => {
service.reset();
});
it('should not produce error for AppComponent', () => {
const diags = ngLS.getSemanticDiagnostics(APP_COMPONENT);
expect(diags).toEqual([]);
});
it('should report member does not exist', () => {
const {text} = service.overwriteInlineTemplate(APP_COMPONENT, '{{ nope }}');
const diags = ngLS.getSemanticDiagnostics(APP_COMPONENT);
expect(diags.length).toBe(1);
const {category, file, start, length, messageText} = diags[0];
expect(category).toBe(ts.DiagnosticCategory.Error);
expect(file?.fileName).toBe(APP_COMPONENT);
expect(text.substring(start!, start! + length!)).toBe('nope');
expect(messageText).toBe(`Property 'nope' does not exist on type 'AppComponent'.`);
});
it('should process external template', () => {
const diags = ngLS.getSemanticDiagnostics(TEST_TEMPLATE);
expect(diags).toEqual([]);
});
it('should report member does not exist in external template', () => {
const {text} = service.overwrite(TEST_TEMPLATE, `{{ nope }}`);
const diags = ngLS.getSemanticDiagnostics(TEST_TEMPLATE);
expect(diags.length).toBe(1);
const {category, file, start, length, messageText} = diags[0];
expect(category).toBe(ts.DiagnosticCategory.Error);
expect(file?.fileName).toBe(TEST_TEMPLATE);
expect(text.substring(start!, start! + length!)).toBe('nope');
expect(messageText).toBe(`Property 'nope' does not exist on type 'TemplateReference'.`);
});
it('should retrieve external template from latest snapshot', () => {
// This test is to make sure we are reading from snapshot instead of disk
// if content from snapshot is newer. It also makes sure the internal cache
// of the resource loader is invalidated on content change.
service.overwrite(TEST_TEMPLATE, `{{ foo }}`);
const d1 = ngLS.getSemanticDiagnostics(TEST_TEMPLATE);
expect(d1.length).toBe(1);
expect(d1[0].messageText).toBe(`Property 'foo' does not exist on type 'TemplateReference'.`);
service.overwrite(TEST_TEMPLATE, `{{ bar }}`);
const d2 = ngLS.getSemanticDiagnostics(TEST_TEMPLATE);
expect(d2.length).toBe(1);
expect(d2[0].messageText).toBe(`Property 'bar' does not exist on type 'TemplateReference'.`);
});
});