angular/packages/language-service/ivy/test/compiler_factory_spec.ts
Keen Yee Liau 8f1317f06f fix(language-service): [Ivy] create compiler only when program changes (#39231)
This commit fixes a bug in which a new Ivy Compiler is created every time
language service receives a new request. This is not needed if the
`ts.Program` has not changed.

A new class `CompilerFactory` is created to manage Compiler lifecycle and
keep track of template changes so that it knows when to override them.
With this change, we no longer need the method `getModifiedResourceFile()`
on the adapter. Instead, we call `overrideComponentTemplate` on the
template type checker.

This commit also changes the incremental build strategy from
`PatchedIncrementalBuildStrategy` to `TrackedIncrementalBuildStrategy`.

PR Close #39231
2020-10-14 14:10:37 -07:00

79 lines
2.8 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 {APP_COMPONENT, setup, TEST_TEMPLATE} from './mock_host';
const {project, service} = setup();
/**
* The following specs do not directly test the CompilerFactory class, rather
* it asserts our understanding of the project/program/template interaction
* which forms the underlying assumption used in the CompilerFactory.
*/
describe('tsserver', () => {
beforeEach(() => {
service.reset();
});
describe('project version', () => {
it('is updated after TS changes', () => {
const v0 = project.getProjectVersion();
service.overwriteInlineTemplate(APP_COMPONENT, `{{ foo }}`);
const v1 = project.getProjectVersion();
expect(v1).not.toBe(v0);
});
it('is updated after template changes', () => {
const v0 = project.getProjectVersion();
service.overwrite(TEST_TEMPLATE, `{{ bar }}`);
const v1 = project.getProjectVersion();
expect(v1).not.toBe(v0);
});
});
describe('program', () => {
it('is updated after TS changes', () => {
const p0 = project.getLanguageService().getProgram();
expect(p0).toBeDefined();
service.overwriteInlineTemplate(APP_COMPONENT, `{{ foo }}`);
const p1 = project.getLanguageService().getProgram();
expect(p1).not.toBe(p0);
});
it('is not updated after template changes', () => {
const p0 = project.getLanguageService().getProgram();
expect(p0).toBeDefined();
service.overwrite(TEST_TEMPLATE, `{{ bar }}`);
const p1 = project.getLanguageService().getProgram();
expect(p1).toBe(p0);
});
});
describe('external template', () => {
it('should not be part of the project root', () => {
// Templates should _never_ be added to the project root, otherwise they
// will be parsed as TS files.
const scriptInfo = service.getScriptInfo(TEST_TEMPLATE);
expect(project.isRoot(scriptInfo)).toBeFalse();
});
it('should be attached to project', () => {
const scriptInfo = service.getScriptInfo(TEST_TEMPLATE);
// Script info for external template must be attached to a project because
// that's the primary mechanism used in the extension to locate the right
// language service instance. See
// https://github.com/angular/vscode-ng-language-service/blob/b048f5f6b9996c5cf113b764c7ffe13d9eeb4285/server/src/session.ts#L192
expect(scriptInfo.isAttached(project)).toBeTrue();
// Attaching a script info to a project does not mean that the project
// will contain the script info. Kinda like friendship on social media.
expect(project.containsScriptInfo(scriptInfo)).toBeFalse();
});
});
});