diff --git a/packages/language-service/ivy/test/env.ts b/packages/language-service/ivy/test/env.ts deleted file mode 100644 index 73b88e6638..0000000000 --- a/packages/language-service/ivy/test/env.ts +++ /dev/null @@ -1,226 +0,0 @@ -/** - * @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 {TmplAstNode} from '@angular/compiler'; -import {StrictTemplateOptions} from '@angular/compiler-cli/src/ngtsc/core/api'; -import {absoluteFrom, AbsoluteFsPath, FileSystem, getFileSystem, getSourceFileOrError} from '@angular/compiler-cli/src/ngtsc/file_system'; -import {MockFileSystem, TestFile} from '@angular/compiler-cli/src/ngtsc/file_system/testing'; -import {loadStandardTestFiles} from '@angular/compiler-cli/src/ngtsc/testing'; -import {OptimizeFor, TemplateTypeChecker} from '@angular/compiler-cli/src/ngtsc/typecheck/api'; -import * as ts from 'typescript/lib/tsserverlibrary'; - -import {LanguageService} from '../language_service'; - -import {MockServerHost} from './mock_host'; - -// TODO(alxhub): replace this environment with //packages/language-service/ivy/testing - -function writeTsconfig( - fs: FileSystem, entryFiles: AbsoluteFsPath[], options: TestableOptions): void { - fs.writeFile( - absoluteFrom('/tsconfig.json'), - - JSON.stringify( - { - compilerOptions: { - strict: true, - experimentalDecorators: true, - moduleResolution: 'node', - target: 'es2015', - lib: [ - 'dom', - 'es2015', - ], - }, - files: entryFiles, - angularCompilerOptions: { - strictTemplates: true, - ...options, - } - }, - null, 2)); -} - -export type TestableOptions = StrictTemplateOptions; - -export class LanguageServiceTestEnvironment { - private constructor( - readonly tsLS: ts.LanguageService, readonly ngLS: LanguageService, - readonly projectService: ts.server.ProjectService, readonly host: MockServerHost) {} - - static setup(files: TestFile[], options: TestableOptions = {}): LanguageServiceTestEnvironment { - const fs = getFileSystem(); - if (!(fs instanceof MockFileSystem)) { - throw new Error(`LanguageServiceTestEnvironment only works with a mock filesystem`); - } - fs.init(loadStandardTestFiles({ - fakeCommon: true, - })); - - const host = new MockServerHost(fs); - const tsconfigPath = absoluteFrom('/tsconfig.json'); - - const entryFiles: AbsoluteFsPath[] = []; - for (const {name, contents, isRoot} of files) { - fs.writeFile(name, contents); - if (isRoot === true) { - entryFiles.push(name); - } - } - - if (entryFiles.length === 0) { - throw new Error(`Expected at least one root TestFile.`); - } - - writeTsconfig(fs, files.filter(file => file.isRoot === true).map(file => file.name), options); - - const projectService = new ts.server.ProjectService({ - host, - logger, - cancellationToken: ts.server.nullCancellationToken, - useSingleInferredProject: true, - useInferredProjectPerProjectRoot: true, - typingsInstaller: ts.server.nullTypingsInstaller, - }); - - // Open all root files. - for (const entryFile of entryFiles) { - projectService.openClientFile(entryFile); - } - - const project = projectService.findProject(tsconfigPath); - if (project === undefined) { - throw new Error(`Failed to create project for ${tsconfigPath}`); - } - // The following operation forces a ts.Program to be created. - const tsLS = project.getLanguageService(); - - const ngLS = new LanguageService(project, tsLS); - return new LanguageServiceTestEnvironment(tsLS, ngLS, projectService, host); - } - - getClass(fileName: AbsoluteFsPath, className: string): ts.ClassDeclaration { - const program = this.tsLS.getProgram(); - if (program === undefined) { - throw new Error(`Expected to get a ts.Program`); - } - const sf = getSourceFileOrError(program, fileName); - return getClassOrError(sf, className); - } - - updateFileWithCursor(fileName: AbsoluteFsPath, contents: string): {cursor: number, text: string} { - const {cursor, text} = extractCursorInfo(contents); - this.updateFile(fileName, text); - return {cursor, text}; - } - - updateFile(fileName: AbsoluteFsPath, contents: string): void { - const normalFileName = ts.server.toNormalizedPath(fileName); - const scriptInfo = - this.projectService.getOrCreateScriptInfoForNormalizedPath(normalFileName, true, ''); - if (scriptInfo === undefined) { - throw new Error(`Could not find a file named ${fileName}`); - } - - // Get the current contents to find the length - const len = scriptInfo.getSnapshot().getLength(); - scriptInfo.editContent(0, len, contents); - } - - expectNoSourceDiagnostics(): void { - const program = this.tsLS.getProgram(); - if (program === undefined) { - throw new Error(`Expected to get a ts.Program`); - } - - const ngCompiler = this.ngLS.compilerFactory.getOrCreate(); - - for (const sf of program.getSourceFiles()) { - if (sf.isDeclarationFile || sf.fileName.endsWith('.ngtypecheck.ts')) { - continue; - } - - const syntactic = program.getSyntacticDiagnostics(sf); - expect(syntactic.map(diag => diag.messageText)).toEqual([]); - if (syntactic.length > 0) { - continue; - } - - const semantic = program.getSemanticDiagnostics(sf); - expect(semantic.map(diag => diag.messageText)).toEqual([]); - if (semantic.length > 0) { - continue; - } - - // It's more efficient to optimize for WholeProgram since we call this with every file in the - // program. - const ngDiagnostics = ngCompiler.getDiagnosticsForFile(sf, OptimizeFor.WholeProgram); - expect(ngDiagnostics.map(diag => diag.messageText)).toEqual([]); - } - - this.ngLS.compilerFactory.registerLastKnownProgram(); - } - - expectNoTemplateDiagnostics(fileName: AbsoluteFsPath, className: string): void { - const program = this.tsLS.getProgram(); - if (program === undefined) { - throw new Error(`Expected to get a ts.Program`); - } - const sf = getSourceFileOrError(program, fileName); - const component = getClassOrError(sf, className); - - const diags = this.getTemplateTypeChecker().getDiagnosticsForComponent(component); - this.ngLS.compilerFactory.registerLastKnownProgram(); - expect(diags.map(diag => diag.messageText)).toEqual([]); - } - - getTemplateTypeChecker(): TemplateTypeChecker { - return this.ngLS.compilerFactory.getOrCreate().getTemplateTypeChecker(); - } -} - -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; - }, -}; - - -function getClassOrError(sf: ts.SourceFile, name: string): ts.ClassDeclaration { - for (const stmt of sf.statements) { - if (ts.isClassDeclaration(stmt) && stmt.name !== undefined && stmt.name.text === name) { - return stmt; - } - } - throw new Error(`Class ${name} not found in file: ${sf.fileName}: ${sf.text}`); -} - -export function extractCursorInfo(textWithCursor: string): {cursor: number, text: string} { - const cursor = textWithCursor.indexOf('¦'); - if (cursor === -1 || textWithCursor.indexOf('¦', cursor + 1) !== -1) { - throw new Error(`Expected to find exactly one cursor symbol '¦'`); - } - - return { - cursor, - text: textWithCursor.substr(0, cursor) + textWithCursor.substr(cursor + 1), - }; -} diff --git a/packages/language-service/ivy/test/mock_host.ts b/packages/language-service/ivy/test/mock_host.ts deleted file mode 100644 index 6f99cf1aa5..0000000000 --- a/packages/language-service/ivy/test/mock_host.ts +++ /dev/null @@ -1,121 +0,0 @@ -/** - * @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 {MockFileSystem} from '@angular/compiler-cli/src/ngtsc/file_system/testing'; - -import * as ts from 'typescript/lib/tsserverlibrary'; - -const NOOP_FILE_WATCHER: ts.FileWatcher = { - close() {} -}; - -/** - * Adapts from the `ts.server.ServerHost` API to an in-memory filesystem. - */ -export class MockServerHost implements ts.server.ServerHost { - constructor(private fs: MockFileSystem) {} - - get newLine(): string { - return '\n'; - } - - get useCaseSensitiveFileNames(): boolean { - return this.fs.isCaseSensitive(); - } - - readFile(path: string, encoding?: string): string|undefined { - return this.fs.readFile(absoluteFrom(path)); - } - - resolvePath(path: string): string { - return this.fs.resolve(path); - } - - fileExists(path: string): boolean { - const absPath = absoluteFrom(path); - return this.fs.exists(absPath) && this.fs.lstat(absPath).isFile(); - } - - directoryExists(path: string): boolean { - const absPath = absoluteFrom(path); - return this.fs.exists(absPath) && this.fs.lstat(absPath).isDirectory(); - } - - createDirectory(path: string): void { - this.fs.ensureDir(absoluteFrom(path)); - } - - getExecutingFilePath(): string { - // This is load-bearing, as TypeScript uses the result of this call to locate the directory in - // which it expects to find .d.ts files for the "standard libraries" - DOM, ES2015, etc. - return '/node_modules/typescript/lib/tsserver.js'; - } - - getCurrentDirectory(): string { - return '/'; - } - - createHash(data: string): string { - return ts.sys.createHash!(data); - } - - get args(): string[] { - throw new Error('Property not implemented.'); - } - - watchFile( - path: string, callback: ts.FileWatcherCallback, pollingInterval?: number, - options?: ts.WatchOptions): ts.FileWatcher { - return NOOP_FILE_WATCHER; - } - - watchDirectory( - path: string, callback: ts.DirectoryWatcherCallback, recursive?: boolean, - options?: ts.WatchOptions): ts.FileWatcher { - return NOOP_FILE_WATCHER; - } - - setTimeout(callback: (...args: any[]) => void, ms: number, ...args: any[]) { - throw new Error('Method not implemented.'); - } - - clearTimeout(timeoutId: any): void { - throw new Error('Method not implemented.'); - } - - setImmediate(callback: (...args: any[]) => void, ...args: any[]) { - throw new Error('Method not implemented.'); - } - - clearImmediate(timeoutId: any): void { - throw new Error('Method not implemented.'); - } - - write(s: string): void { - throw new Error('Method not implemented.'); - } - - writeFile(path: string, data: string, writeByteOrderMark?: boolean): void { - throw new Error('Method not implemented.'); - } - - getDirectories(path: string): string[] { - throw new Error('Method not implemented.'); - } - - readDirectory( - path: string, extensions?: readonly string[], exclude?: readonly string[], - include?: readonly string[], depth?: number): string[] { - throw new Error('Method not implemented.'); - } - - exit(exitCode?: number): void { - throw new Error('Method not implemented.'); - } -} diff --git a/packages/language-service/ivy/test/test_utils.ts b/packages/language-service/ivy/test/test_utils.ts deleted file mode 100644 index 4ddb57d80a..0000000000 --- a/packages/language-service/ivy/test/test_utils.ts +++ /dev/null @@ -1,88 +0,0 @@ -/** - * @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 as _} from '@angular/compiler-cli/src/ngtsc/file_system'; -import {TestFile} from '@angular/compiler-cli/src/ngtsc/file_system/testing'; -import {LanguageServiceTestEnvironment, TestableOptions} from '@angular/language-service/ivy/test/env'; -import * as ts from 'typescript/lib/tsserverlibrary'; - - -export function getText(contents: string, textSpan: ts.TextSpan) { - return contents.substr(textSpan.start, textSpan.length); -} - -function last(array: T[]): T { - return array[array.length - 1]; -} - -function getFirstClassDeclaration(declaration: string) { - const matches = declaration.match(/(?:export class )(\w+)(?:\s|\{)/); - if (matches === null || matches.length !== 2) { - throw new Error(`Did not find exactly one exported class in: ${declaration}`); - } - return matches[1].trim(); -} - -export function createModuleWithDeclarations( - filesWithClassDeclarations: TestFile[], externalResourceFiles: TestFile[] = [], - options: TestableOptions = {}): LanguageServiceTestEnvironment { - const externalClasses = - filesWithClassDeclarations.map(file => getFirstClassDeclaration(file.contents)); - const externalImports = filesWithClassDeclarations.map(file => { - const className = getFirstClassDeclaration(file.contents); - const fileName = last(file.name.split('/')).replace('.ts', ''); - return `import {${className}} from './${fileName}';`; - }); - const contents = ` - import {NgModule} from '@angular/core'; - import {CommonModule} from '@angular/common'; - ${externalImports.join('\n')} - - @NgModule({ - declarations: [${externalClasses.join(',')}], - imports: [CommonModule], - }) - export class AppModule {} - `; - const moduleFile = {name: _('/app-module.ts'), contents, isRoot: true}; - return LanguageServiceTestEnvironment.setup( - [moduleFile, ...filesWithClassDeclarations, ...externalResourceFiles], options); -} - -export function humanizeDocumentSpanLike( - item: T, env: LanguageServiceTestEnvironment, overrides: Map = new Map()): T& - Stringy { - const fileContents = (overrides.has(item.fileName) ? overrides.get(item.fileName) : - env.host.readFile(item.fileName)) ?? - ''; - if (!fileContents) { - throw new Error(`Could not read file ${item.fileName}`); - } - return { - ...item, - textSpan: getText(fileContents, item.textSpan), - contextSpan: item.contextSpan ? getText(fileContents, item.contextSpan) : undefined, - originalTextSpan: item.originalTextSpan ? getText(fileContents, item.originalTextSpan) : - undefined, - originalContextSpan: - item.originalContextSpan ? getText(fileContents, item.originalContextSpan) : undefined, - }; -} -type Stringy = { - [P in keyof T]: string; -}; - -export function assertFileNames(refs: Array<{fileName: string}>, expectedFileNames: string[]) { - const actualPaths = refs.map(r => r.fileName); - const actualFileNames = actualPaths.map(p => last(p.split('/'))); - expect(new Set(actualFileNames)).toEqual(new Set(expectedFileNames)); -} - -export function assertTextSpans(items: Array<{textSpan: string}>, expectedTextSpans: string[]) { - const actualSpans = items.map(item => item.textSpan); - expect(new Set(actualSpans)).toEqual(new Set(expectedTextSpans)); -}