diff --git a/packages/language-service/ivy/test/BUILD.bazel b/packages/language-service/ivy/test/BUILD.bazel new file mode 100644 index 0000000000..2c70a5e70c --- /dev/null +++ b/packages/language-service/ivy/test/BUILD.bazel @@ -0,0 +1,28 @@ +load("//tools:defaults.bzl", "jasmine_node_test", "ts_library") + +ts_library( + name = "test_lib", + testonly = True, + srcs = glob(["*.ts"]), + deps = [ + "@npm//typescript", + ], +) + +jasmine_node_test( + name = "test", + data = [ + # Note that we used to depend on the npm_package of common, core, and + # forms, but this is no longer the case. We did it for View Engine + # because we wanted to load the flat dts, which is only available in the + # npm_package. Ivy does not currently produce flat dts, so we might + # as well just depend on the outputs of ng_module. + "//packages/common", + "//packages/core", + "//packages/forms", + "//packages/language-service/test:project", + ], + deps = [ + ":test_lib", + ], +) diff --git a/packages/language-service/ivy/test/mock_host.ts b/packages/language-service/ivy/test/mock_host.ts new file mode 100644 index 0000000000..f9a39ff3c4 --- /dev/null +++ b/packages/language-service/ivy/test/mock_host.ts @@ -0,0 +1,77 @@ +/** + * @license + * Copyright Google Inc. 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 {join} from 'path'; +import * as ts from 'typescript/lib/tsserverlibrary'; + +const logger: ts.server.Logger = { + close(): void{}, + hasLevel(level: ts.server.LogLevel): boolean { + return false; + }, + loggingEnabled(): boolean { + return false; + }, + perftrc(s: string): void{}, + info(s: string): void{}, + startGroup(): void{}, + endGroup(): void{}, + msg(s: string, type?: ts.server.Msg): void{}, + getLogFileName(): string | + undefined { + return; + }, +}; + +export const TEST_SRCDIR = process.env.TEST_SRCDIR!; +export const PROJECT_DIR = + join(TEST_SRCDIR, 'angular', 'packages', 'language-service', 'test', 'project'); +export const TSCONFIG = join(PROJECT_DIR, 'tsconfig.json'); +export const APP_COMPONENT = join(PROJECT_DIR, 'app', 'app.component.ts'); +export const APP_MAIN = join(PROJECT_DIR, 'app', 'main.ts'); +export const PARSING_CASES = join(PROJECT_DIR, 'app', 'parsing-cases.ts'); + +export const host: ts.server.ServerHost = { + ...ts.sys, + readFile(absPath: string, encoding?: string): string | + undefined { + // TODO: Need to remove all annotations in templates like we do in + // MockTypescriptHost + return ts.sys.readFile(absPath, encoding); + }, + // TODO: Need to cast as never because this is not a proper ServerHost interface. + // ts.sys lacks methods like watchFile() and watchDirectory(), but these are not + // needed for now. +} as never; + +/** + * Create a ConfiguredProject and an actual program for the test project located + * in packages/language-service/test/project. Project creation exercises the + * actual code path, but a mock host is used for the filesystem to intercept + * and modify test files. + */ +export function setup() { + const projectService = new ts.server.ProjectService({ + host, + logger, + cancellationToken: ts.server.nullCancellationToken, + useSingleInferredProject: true, + useInferredProjectPerProjectRoot: true, + typingsInstaller: ts.server.nullTypingsInstaller, + }); + // Opening APP_COMPONENT forces a new ConfiguredProject to be created based + // on the tsconfig.json in the test project. + projectService.openClientFile(APP_COMPONENT); + const project = projectService.findProject(TSCONFIG); + if (!project) { + throw new Error(`Failed to create project for ${TSCONFIG}`); + } + // The following operation forces a ts.Program to be created. + const tsLS = project.getLanguageService(); + return {projectService, project, tsLS}; +} diff --git a/packages/language-service/ivy/test/mock_host_spec.ts b/packages/language-service/ivy/test/mock_host_spec.ts new file mode 100644 index 0000000000..848b0db319 --- /dev/null +++ b/packages/language-service/ivy/test/mock_host_spec.ts @@ -0,0 +1,47 @@ +/** + * @license + * Copyright Google Inc. 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 {APP_MAIN, setup, TEST_SRCDIR} from './mock_host'; + +describe('mock host', () => { + it('can load test project from Bazel runfiles', () => { + const {project, tsLS} = setup(); + expect(project).toBeInstanceOf(ts.server.ConfiguredProject); + const program = tsLS.getProgram(); + expect(program).toBeDefined(); + const sourceFiles = program!.getSourceFiles().map(sf => { + const {fileName} = sf; + if (fileName.startsWith(TEST_SRCDIR)) { + return fileName.substring(TEST_SRCDIR.length); + } + return fileName; + }); + expect(sourceFiles).toEqual(jasmine.arrayContaining([ + // This shows that module resolution works + '/angular/packages/common/src/common.d.ts', + '/angular/packages/core/src/core.d.ts', + '/angular/packages/forms/src/forms.d.ts', + // This shows that project files are present + '/angular/packages/language-service/test/project/app/app.component.ts', + '/angular/packages/language-service/test/project/app/main.ts', + '/angular/packages/language-service/test/project/app/parsing-cases.ts', + ])); + }); + + it('produces no TS error for test project', () => { + const {project, tsLS} = setup(); + const errors = project.getAllProjectErrors(); + expect(errors).toEqual([]); + const globalErrors = project.getGlobalProjectErrors(); + expect(globalErrors).toEqual([]); + const diags = tsLS.getSemanticDiagnostics(APP_MAIN); + expect(diags).toEqual([]); + }); +}); diff --git a/packages/language-service/test/project/tsconfig.json b/packages/language-service/test/project/tsconfig.json index dca7200ff1..d2a130c87a 100644 --- a/packages/language-service/test/project/tsconfig.json +++ b/packages/language-service/test/project/tsconfig.json @@ -1,12 +1,18 @@ { - "//00": "This file is used for IDE only, actual compilation options is in MockTypescriptHost in test_utils.ts", + "//00": "This file is used for both IDE and the actual Project creation in Ivy language service for testing purpose", "compilerOptions": { - "lib": ["es2015"], + "lib": ["es2015", "dom"], "strict": true, "experimentalDecorators": true, + "moduleResolution": "node", + "target": "es2015", "baseUrl": "../../../..", "paths": { "@angular/*": ["packages/*"] } + }, + "angularCompilerOptions": { + "fullTemplateTypeCheck": true, + "strictInjectionParameters": true } }