From 53c65f468f6f7270a00b2b606a4a41e91be8a0c2 Mon Sep 17 00:00:00 2001 From: Alex Rickabaugh Date: Tue, 2 Feb 2021 14:20:40 -0800 Subject: [PATCH] test(language-service): update compiler_spec to use the new testing env (#40679) This commit updates compiler_spec.ts in the Ivy LS suite to utilize the new testing environment which was introduced in the previous commit. Eventually all specs should be converted, but converting one right now helps ensure that the new testing env is working properly and able to support real tests. PR Close #40679 --- .../language-service/ivy/test/BUILD.bazel | 1 + .../ivy/test/compiler_spec.ts | 185 +++++++----------- .../ivy/testing/src/buffer.ts | 11 +- .../ivy/testing/src/project.ts | 12 +- .../language-service/ivy/testing/src/util.ts | 3 + 5 files changed, 89 insertions(+), 123 deletions(-) diff --git a/packages/language-service/ivy/test/BUILD.bazel b/packages/language-service/ivy/test/BUILD.bazel index 5a23ac2fdf..2ffce0d65a 100644 --- a/packages/language-service/ivy/test/BUILD.bazel +++ b/packages/language-service/ivy/test/BUILD.bazel @@ -12,6 +12,7 @@ ts_library( "//packages/compiler-cli/src/ngtsc/testing", "//packages/compiler-cli/src/ngtsc/typecheck/api", "//packages/language-service/ivy", + "//packages/language-service/ivy/testing", "@npm//typescript", ], ) diff --git a/packages/language-service/ivy/test/compiler_spec.ts b/packages/language-service/ivy/test/compiler_spec.ts index 87883fafc7..4838441e0c 100644 --- a/packages/language-service/ivy/test/compiler_spec.ts +++ b/packages/language-service/ivy/test/compiler_spec.ts @@ -6,10 +6,10 @@ * found in the LICENSE file at https://angular.io/license */ -import {absoluteFrom, getSourceFileOrError} from '@angular/compiler-cli/src/ngtsc/file_system'; +import {absoluteFrom} from '@angular/compiler-cli/src/ngtsc/file_system'; import {initMockFileSystem} from '@angular/compiler-cli/src/ngtsc/file_system/testing'; -import {OptimizeFor} from '@angular/compiler-cli/src/ngtsc/typecheck/api'; +import {isNgSpecificDiagnostic, LanguageServiceTestEnv} from '../testing'; import {LanguageServiceTestEnvironment} from './env'; describe('language-service/compiler integration', () => { @@ -50,66 +50,43 @@ describe('language-service/compiler integration', () => { }); it('should not produce errors from inline test declarations mixing with those of the app', () => { - const appCmpFile = absoluteFrom('/test.cmp.ts'); - const appModuleFile = absoluteFrom('/test.mod.ts'); - const testFile = absoluteFrom('/test_spec.ts'); + const env = LanguageServiceTestEnv.setup(); + const project = env.addProject('test', { + 'cmp.ts': ` + import {Component} from '@angular/core'; - const env = LanguageServiceTestEnvironment.setup([ - { - name: appCmpFile, - contents: ` - import {Component} from '@angular/core'; + @Component({ + selector: 'app-cmp', + template: 'Some template', + }) + export class AppCmp {} + `, + 'mod.ts': ` + import {NgModule} from '@angular/core'; + import {AppCmp} from './cmp'; - @Component({ - selector: 'app-cmp', - template: 'Some template', - }) - export class AppCmp {} - `, - isRoot: true, - }, - { - name: appModuleFile, - contents: ` - import {NgModule} from '@angular/core'; - import {AppCmp} from './test.cmp'; + @NgModule({ + declarations: [AppCmp], + }) + export class AppModule {} + `, + 'test_spec.ts': ` + import {NgModule} from '@angular/core'; + import {AppCmp} from './cmp'; + export function test(): void { @NgModule({ declarations: [AppCmp], }) - export class AppModule {} - `, - isRoot: true, - }, - { - name: testFile, - contents: ` - import {NgModule} from '@angular/core'; - import {AppCmp} from './test.cmp'; - - export function test(): void { - @NgModule({ - declarations: [AppCmp], - }) - class TestModule {} - } - `, - isRoot: true, - } - ]); + class TestModule {} + } + `, + }); // Expect that this program is clean diagnostically. - const ngCompiler = env.ngLS.compilerFactory.getOrCreate(); - const program = ngCompiler.getNextProgram(); - expect(ngCompiler.getDiagnosticsForFile( - getSourceFileOrError(program, appCmpFile), OptimizeFor.WholeProgram)) - .toEqual([]); - expect(ngCompiler.getDiagnosticsForFile( - getSourceFileOrError(program, appModuleFile), OptimizeFor.WholeProgram)) - .toEqual([]); - expect(ngCompiler.getDiagnosticsForFile( - getSourceFileOrError(program, testFile), OptimizeFor.WholeProgram)) - .toEqual([]); + expect(project.getDiagnosticsForFile('cmp.ts')).toEqual([]); + expect(project.getDiagnosticsForFile('mod.ts')).toEqual([]); + expect(project.getDiagnosticsForFile('test_spec.ts')).toEqual([]); }); it('should show type-checking errors from components with poisoned scopes', () => { @@ -122,39 +99,36 @@ describe('language-service/compiler integration', () => { // 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'; + const env = LanguageServiceTestEnv.setup(); + const project = env.addProject('test', { + 'test.ts': ` + import {Component, Directive, Input, NgModule} from '@angular/core'; - @Component({ - selector: 'test-cmp', - template: '
', - }) - export class Cmp {} + @Component({ + selector: 'test-cmp', + template: '
', + }) + export class Cmp {} - @Directive({ - selector: '[dir]', - }) - export class Dir { - @Input() dir!: string; - } + @Directive({ + selector: '[dir]', + }) + export class Dir { + @Input() dir!: string; + } - export class NotAModule {} + export class NotAModule {} - @NgModule({ - declarations: [Cmp, Dir], - imports: [NotAModule], - }) - export class Mod {} - `, - isRoot: true, - }]); + @NgModule({ + declarations: [Cmp, Dir], + imports: [NotAModule], + }) + export class Mod {} + `, + }); - const diags = env.ngLS.getSemanticDiagnostics(file); - expect(diags.map(diag => diag.messageText)) - .toContain(`Type 'number' is not assignable to type 'string'.`); + const diags = project.getDiagnosticsForFile('test.ts').map(diag => diag.messageText); + expect(diags).toContain(`Type 'number' is not assignable to type 'string'.`); }); it('should handle broken imports during incremental build steps', () => { @@ -174,9 +148,6 @@ describe('language-service/compiler integration', () => { // 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'; @@ -187,43 +158,31 @@ describe('language-service/compiler integration', () => { ${isExported ? 'export' : ''} class Cmp {} `; - const env = LanguageServiceTestEnvironment.setup([ - { - name: moduleFile, - contents: ` - import {NgModule} from '@angular/core'; + const env = LanguageServiceTestEnv.setup(); + const project = env.addProject('test', { + 'mod.ts': ` + import {NgModule} from '@angular/core'; - import {Cmp} from './cmp'; + import {Cmp} from './cmp'; - @NgModule({ - declarations: [Cmp], - }) - export class Mod {} - `, - isRoot: true, - }, - { - name: componentFile, - contents: componentSource(/* start with component not exported */ false), - isRoot: true, - } - ]); + @NgModule({ + declarations: [Cmp], + }) + export class Mod {} + `, + 'cmp.ts': componentSource(/* start with component not exported */ false), + }); // 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().getDiagnosticsForFile( - moduleSfBefore, OptimizeFor.SingleFile); + const ngDiagsBefore = project.getDiagnosticsForFile('mod.ts').filter(isNgSpecificDiagnostic); expect(ngDiagsBefore.length).toBe(1); // Fix the import. - env.updateFile(componentFile, componentSource(/* properly export the component */ true)); + const file = project.openFile('cmp.ts'); + file.contents = 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().getDiagnosticsForFile( - moduleSfAfter, OptimizeFor.SingleFile); + const ngDiagsAfter = project.getDiagnosticsForFile('mod.ts').filter(isNgSpecificDiagnostic); expect(ngDiagsAfter.length).toBe(0); }); }); diff --git a/packages/language-service/ivy/testing/src/buffer.ts b/packages/language-service/ivy/testing/src/buffer.ts index 2e3cc7302b..442ee374fc 100644 --- a/packages/language-service/ivy/testing/src/buffer.ts +++ b/packages/language-service/ivy/testing/src/buffer.ts @@ -7,8 +7,8 @@ */ import * as ts from 'typescript/lib/tsserverlibrary'; +import {LanguageService} from '../../language_service'; -import {Project} from './project'; import {extractCursorInfo} from './util'; /** @@ -18,7 +18,7 @@ export class OpenBuffer { private _cursor: number = 0; constructor( - private project: Project, private projectFileName: string, + private ngLS: LanguageService, private projectFileName: string, private scriptInfo: ts.server.ScriptInfo) {} get cursor(): number { @@ -49,7 +49,10 @@ export class OpenBuffer { const {text: snippet, cursor} = extractCursorInfo(snippetWithCursor); const snippetIndex = this.contents.indexOf(snippet); if (snippetIndex === -1) { - throw new Error(`Snippet ${snippet} not found in ${this.projectFileName}`); + throw new Error(`Snippet '${snippet}' not found in ${this.projectFileName}`); + } + if (this.contents.indexOf(snippet, snippetIndex + 1) !== -1) { + throw new Error(`Snippet '${snippet}' is not unique within ${this.projectFileName}`); } this._cursor = snippetIndex + cursor; } @@ -59,6 +62,6 @@ export class OpenBuffer { * location in this buffer. */ getDefinitionAndBoundSpan(): ts.DefinitionInfoAndBoundSpan|undefined { - return this.project.ngLS.getDefinitionAndBoundSpan(this.scriptInfo.fileName, this._cursor); + return this.ngLS.getDefinitionAndBoundSpan(this.scriptInfo.fileName, this._cursor); } } diff --git a/packages/language-service/ivy/testing/src/project.ts b/packages/language-service/ivy/testing/src/project.ts index 0a40ed6a47..e080c280ca 100644 --- a/packages/language-service/ivy/testing/src/project.ts +++ b/packages/language-service/ivy/testing/src/project.ts @@ -50,15 +50,15 @@ export class Project { readonly ngLS: LanguageService; private buffers = new Map(); - static initialize(name: string, projectService: ts.server.ProjectService, files: ProjectFiles): - Project { + static initialize( + projectName: string, projectService: ts.server.ProjectService, files: ProjectFiles): Project { const fs = getFileSystem(); - const tsConfigPath = absoluteFrom(`/${name}/tsconfig.json`); + const tsConfigPath = absoluteFrom(`/${projectName}/tsconfig.json`); const entryFiles: AbsoluteFsPath[] = []; for (const projectFilePath of Object.keys(files)) { const contents = files[projectFilePath]; - const filePath = absoluteFrom(`/${name}/${projectFilePath}`); + const filePath = absoluteFrom(`/${projectName}/${projectFilePath}`); const dirPath = fs.dirname(filePath); fs.ensureDir(dirPath); fs.writeFile(filePath, contents); @@ -73,7 +73,7 @@ export class Project { projectService.openClientFile(entryFiles[0]); projectService.closeClientFile(entryFiles[0]); - return new Project(name, projectService, tsConfigPath); + return new Project(projectName, projectService, tsConfigPath); } constructor( @@ -102,7 +102,7 @@ export class Project { throw new Error( `Unable to open ScriptInfo for ${projectFileName} in project ${this.tsConfigPath}`); } - this.buffers.set(projectFileName, new OpenBuffer(this, projectFileName, scriptInfo)); + this.buffers.set(projectFileName, new OpenBuffer(this.ngLS, projectFileName, scriptInfo)); } return this.buffers.get(projectFileName)!; diff --git a/packages/language-service/ivy/testing/src/util.ts b/packages/language-service/ivy/testing/src/util.ts index e8990cfe02..8b1bd99cd2 100644 --- a/packages/language-service/ivy/testing/src/util.ts +++ b/packages/language-service/ivy/testing/src/util.ts @@ -23,6 +23,9 @@ export function extractCursorInfo(textWithCursor: string): {cursor: number, text } function last(array: T[]): T { + if (array.length === 0) { + throw new Error(`last() called on empty array`); + } return array[array.length - 1]; }