diff --git a/packages/language-service/ivy/references.ts b/packages/language-service/ivy/references.ts index 204d4185cc..964297d3d7 100644 --- a/packages/language-service/ivy/references.ts +++ b/packages/language-service/ivy/references.ts @@ -392,6 +392,11 @@ export class ReferencesAndRenameBuilder { ...shimDocumentSpan, fileName: templateUrl, textSpan: toTextSpan(span), + // Specifically clear other text span values because we do not have enough knowledge to + // convert these to spans in the template. + contextSpan: undefined, + originalContextSpan: undefined, + originalTextSpan: undefined, }; } } diff --git a/packages/language-service/ivy/test/references_spec.ts b/packages/language-service/ivy/test/references_spec.ts index d2f08df74e..70295cac80 100644 --- a/packages/language-service/ivy/test/references_spec.ts +++ b/packages/language-service/ivy/test/references_spec.ts @@ -6,14 +6,12 @@ * found in the LICENSE file at https://angular.io/license */ -import {absoluteFrom, absoluteFrom as _} from '@angular/compiler-cli/src/ngtsc/file_system'; -import {initMockFileSystem, TestFile} from '@angular/compiler-cli/src/ngtsc/file_system/testing'; +import {initMockFileSystem} from '@angular/compiler-cli/src/ngtsc/file_system/testing'; -import {extractCursorInfo, LanguageServiceTestEnvironment} from './env'; -import {assertFileNames, assertTextSpans, createModuleWithDeclarations, humanizeDocumentSpanLike} from './test_utils'; +import {assertFileNames, assertTextSpans, createModuleAndProjectWithDeclarations, humanizeDocumentSpanLike, LanguageServiceTestEnv, OpenBuffer} from '../testing'; describe('find references and rename locations', () => { - let env: LanguageServiceTestEnvironment; + let env: LanguageServiceTestEnv; beforeEach(() => { initMockFileSystem('Native'); @@ -25,31 +23,33 @@ describe('find references and rename locations', () => { }); describe('cursor is on binding in component class', () => { - let cursor: number; + let appFile: OpenBuffer; beforeEach(() => { - const cursorInfo = extractCursorInfo(` - import {Component} from '@angular/core'; + const files = { + 'app.ts': `import {Component} from '@angular/core'; @Component({templateUrl: './app.html'}) export class AppCmp { - myP¦rop!: string; - }`); - cursor = cursorInfo.cursor; - const appFile = {name: _('/app.ts'), contents: cursorInfo.text}; - const templateFile = {name: _('/app.html'), contents: '{{myProp}}'}; - env = createModuleWithDeclarations([appFile], [templateFile]); + myProp!: string; + }`, + 'app.html': '{{myProp}}' + }; + env = LanguageServiceTestEnv.setup(); + const project = createModuleAndProjectWithDeclarations(env, 'test', files); + appFile = project.openFile('app.ts'); + appFile.moveCursorToText('myP¦rop'); }); it('gets component member references from TS file and external template', () => { - const refs = getReferencesAtPosition(_('/app.ts'), cursor)!; + const refs = getReferencesAtPosition(appFile)!; expect(refs.length).toBe(2); assertFileNames(refs, ['app.html', 'app.ts']); assertTextSpans(refs, ['myProp']); }); it('gets rename locations from TS file and external template', () => { - const renameLocations = getRenameLocationsAtPosition(_('/app.ts'), cursor)!; + const renameLocations = getRenameLocationsAtPosition(appFile)!; expect(renameLocations.length).toBe(2); assertFileNames(renameLocations, ['app.html', 'app.ts']); assertTextSpans(renameLocations, ['myProp']); @@ -57,34 +57,34 @@ describe('find references and rename locations', () => { }); describe('when cursor is on binding in an external template', () => { - let cursor: number; + let templateFile: OpenBuffer; beforeEach(() => { - const appFile = { - name: _('/app.ts'), - contents: ` + const files = { + 'app.ts': ` import {Component} from '@angular/core'; @Component({templateUrl: './app.html'}) export class AppCmp { myProp = ''; }`, + 'app.html': '{{myProp}}' }; - const cursorInfo = extractCursorInfo('{{myP¦rop}}'); - cursor = cursorInfo.cursor; - const templateFile = {name: _('/app.html'), contents: cursorInfo.text}; - env = createModuleWithDeclarations([appFile], [templateFile]); + env = LanguageServiceTestEnv.setup(); + const project = createModuleAndProjectWithDeclarations(env, 'test', files); + templateFile = project.openFile('app.html'); + templateFile.moveCursorToText('myP¦rop'); }); it('gets references', () => { - const refs = getReferencesAtPosition(_('/app.html'), cursor)!; + const refs = getReferencesAtPosition(templateFile)!; expect(refs.length).toBe(2); assertFileNames(refs, ['app.html', 'app.ts']); assertTextSpans(refs, ['myProp']); }); it('gets rename locations', () => { - const renameLocations = getRenameLocationsAtPosition(_('/app.html'), cursor)!; + const renameLocations = getRenameLocationsAtPosition(templateFile)!; expect(renameLocations.length).toBe(2); assertFileNames(renameLocations, ['app.html', 'app.ts']); assertTextSpans(renameLocations, ['myProp']); @@ -92,23 +92,26 @@ describe('find references and rename locations', () => { }); describe('when cursor is on function call in external template', () => { - let cursor: number; + let appFile: OpenBuffer; beforeEach(() => { - const cursorInfo = extractCursorInfo(` + const files = { + 'app.ts': ` import {Component} from '@angular/core'; - @Component({template: '
'}) + @Component({template: '
'}) export class AppCmp { setTitle(s: number) {} - }`); - cursor = cursorInfo.cursor; - const appFile = {name: _('/app.ts'), contents: cursorInfo.text}; - env = createModuleWithDeclarations([appFile]); + }` + }; + env = LanguageServiceTestEnv.setup(); + const project = createModuleAndProjectWithDeclarations(env, 'test', files); + appFile = project.openFile('app.ts'); + appFile.moveCursorToText('setTi¦tle(2)'); }); it('gets component member reference in ts file', () => { - const refs = getReferencesAtPosition(_('/app.ts'), cursor)!; + const refs = getReferencesAtPosition(appFile)!; expect(refs.length).toBe(2); assertFileNames(refs, ['app.ts']); @@ -116,7 +119,7 @@ describe('find references and rename locations', () => { }); it('gets rename location in ts file', () => { - const renameLocations = getRenameLocationsAtPosition(_('/app.ts'), cursor)!; + const renameLocations = getRenameLocationsAtPosition(appFile)!; expect(renameLocations.length).toBe(2); assertFileNames(renameLocations, ['app.ts']); @@ -125,31 +128,34 @@ describe('find references and rename locations', () => { }); describe('when cursor in on argument to a function call in an external template', () => { - let cursor: number; + let appFile: OpenBuffer; beforeEach(() => { - const cursorInfo = extractCursorInfo(` + const files = { + 'app.ts': ` import {Component} from '@angular/core'; - @Component({template: '
'}) + @Component({template: '
'}) export class AppCmp { title = ''; setTitle(s: string) {} - }`); - cursor = cursorInfo.cursor; - const appFile = {name: _('/app.ts'), contents: cursorInfo.text}; - env = createModuleWithDeclarations([appFile]); + }` + }; + env = LanguageServiceTestEnv.setup(); + const project = createModuleAndProjectWithDeclarations(env, 'test', files); + appFile = project.openFile('app.ts'); + appFile.moveCursorToText('(ti¦tle)'); }); it('gets member reference in ts file', () => { - const refs = getReferencesAtPosition(_('/app.ts'), cursor)!; + const refs = getReferencesAtPosition(appFile)!; expect(refs.length).toBe(2); assertTextSpans(refs, ['title']); }); it('finds rename location in ts file', () => { - const refs = getRenameLocationsAtPosition(_('/app.ts'), cursor)!; + const refs = getRenameLocationsAtPosition(appFile)!; expect(refs.length).toBe(2); assertTextSpans(refs, ['title']); @@ -157,56 +163,58 @@ describe('find references and rename locations', () => { }); describe('when cursor is on $event in method call arguments', () => { - let cursor: number; + let file: OpenBuffer; beforeEach(() => { - const cursorInfo = extractCursorInfo(` + const files = { + 'app.ts': ` import {Component} from '@angular/core'; - @Component({template: '
'}) + @Component({template: '
'}) export class AppCmp { setTitle(s: any) {} - }`); - cursor = cursorInfo.cursor; - const appFile = {name: _('/app.ts'), contents: cursorInfo.text}; - env = createModuleWithDeclarations([appFile]); + }` + }; + env = LanguageServiceTestEnv.setup(); + const project = createModuleAndProjectWithDeclarations(env, 'test', files); + file = project.openFile('app.ts'); + file.moveCursorToText('($even¦t)'); }); it('find references', () => { - const refs = getReferencesAtPosition(_('/app.ts'), cursor)!; + const refs = getReferencesAtPosition(file)!; expect(refs.length).toBe(1); assertTextSpans(refs, ['$event']); }); it('gets no rename locations', () => { - const renameLocations = getRenameLocationsAtPosition(_('/app.ts'), cursor)!; + const renameLocations = getRenameLocationsAtPosition(file)!; expect(renameLocations).toBeUndefined(); }); }); describe('when cursor in on LHS of property write in external template', () => { - let cursor: number; + let file: OpenBuffer; beforeEach(() => { - const appFile = { - name: _('/app.ts'), - contents: ` + const files = { + 'app.ts': ` import {Component} from '@angular/core'; @Component({templateUrl: './app.html' }) export class AppCmp { title = ''; }`, + 'app.html': `
` }; - const templateFileWithCursor = `
`; - const cursorInfo = extractCursorInfo(templateFileWithCursor); - cursor = cursorInfo.cursor; - const templateFile = {name: _('/app.html'), contents: cursorInfo.text}; - env = createModuleWithDeclarations([appFile], [templateFile]); + env = LanguageServiceTestEnv.setup(); + const project = createModuleAndProjectWithDeclarations(env, 'test', files); + file = project.openFile('app.html'); + file.moveCursorToText('ti¦tle = '); }); it('gets member reference in ts file', () => { - const refs = getReferencesAtPosition(_('/app.html'), cursor)!; + const refs = getReferencesAtPosition(file)!; expect(refs.length).toBe(2); assertFileNames(refs, ['app.ts', 'app.html']); @@ -214,7 +222,7 @@ describe('find references and rename locations', () => { }); it('gets rename location in ts file', () => { - const renameLocations = getRenameLocationsAtPosition(_('/app.html'), cursor)!; + const renameLocations = getRenameLocationsAtPosition(file)!; expect(renameLocations.length).toBe(2); assertFileNames(renameLocations, ['app.ts', 'app.html']); @@ -223,27 +231,28 @@ describe('find references and rename locations', () => { }); describe('when cursor in on RHS of property write in external template', () => { - let cursor: number; + let file: OpenBuffer; beforeEach(() => { - const cursorInfo = extractCursorInfo(` + const files = { + 'app.ts': ` import {Component} from '@angular/core'; - @Component({template: '
' }) + @Component({template: '
' }) export class AppCmp { title = ''; otherTitle = ''; - }`); - cursor = cursorInfo.cursor; - const appFile = { - name: _('/app.ts'), - contents: cursorInfo.text, + }` }; - env = createModuleWithDeclarations([appFile]); + + env = LanguageServiceTestEnv.setup(); + const project = createModuleAndProjectWithDeclarations(env, 'test', files); + file = project.openFile('app.ts'); + file.moveCursorToText('= otherT¦itle">'); }); it('get reference to member in ts file', () => { - const refs = getReferencesAtPosition(_('/app.ts'), cursor)!; + const refs = getReferencesAtPosition(file)!; expect(refs.length).toBe(2); assertFileNames(refs, ['app.ts']); @@ -251,7 +260,7 @@ describe('find references and rename locations', () => { }); it('finds rename location in ts file', () => { - const renameLocations = getRenameLocationsAtPosition(_('/app.ts'), cursor)!; + const renameLocations = getRenameLocationsAtPosition(file)!; expect(renameLocations.length).toBe(2); assertFileNames(renameLocations, ['app.ts']); @@ -260,26 +269,26 @@ describe('find references and rename locations', () => { }); describe('when cursor in on a keyed read', () => { - let cursor: number; + let file: OpenBuffer; beforeEach(() => { - const cursorInfo = extractCursorInfo(` + const files = { + 'app.ts': ` import {Component} from '@angular/core'; - @Component({template: '{{hero["na¦me"]}}' }) + @Component({template: '{{hero["name"]}}' }) export class AppCmp { hero: {name: string} = {name: 'Superman'}; - }`); - cursor = cursorInfo.cursor; - const appFile = { - name: _('/app.ts'), - contents: cursorInfo.text, + }` }; - env = createModuleWithDeclarations([appFile]); + env = LanguageServiceTestEnv.setup(); + const project = createModuleAndProjectWithDeclarations(env, 'test', files); + file = project.openFile('app.ts'); + file.moveCursorToText('{{hero["na¦me"]}}'); }); it('gets reference to member type definition and initialization in component class', () => { - const refs = getReferencesAtPosition(_('/app.ts'), cursor)!; + const refs = getReferencesAtPosition(file)!; // 3 references: the type definition, the value assignment, and the read in the template expect(refs.length).toBe(3); @@ -293,7 +302,7 @@ describe('find references and rename locations', () => { }); it('gets rename locations in component class', () => { - const renameLocations = getRenameLocationsAtPosition(_('/app.ts'), cursor)!; + const renameLocations = getRenameLocationsAtPosition(file)!; expect(renameLocations).toBeUndefined(); // TODO(atscott): We should handle this case. The fix requires us to fix the result span as @@ -307,12 +316,11 @@ describe('find references and rename locations', () => { }); describe('when cursor in on RHS of keyed write in a template', () => { - let cursor: number; + let file: OpenBuffer; beforeEach(() => { - const appFile = { - name: _('/app.ts'), - contents: ` + const files = { + 'app.ts': ` import {Component} from '@angular/core'; @Component({templateUrl: './app.html' }) @@ -320,16 +328,16 @@ describe('find references and rename locations', () => { hero: {name: string} = {name: 'Superman'}; batman = 'batman'; }`, + 'app.html': `
` }; - const templateFileWithCursor = `
`; - const cursorInfo = extractCursorInfo(templateFileWithCursor); - cursor = cursorInfo.cursor; - const templateFile = {name: _('/app.html'), contents: cursorInfo.text}; - env = createModuleWithDeclarations([appFile], [templateFile]); + env = LanguageServiceTestEnv.setup(); + const project = createModuleAndProjectWithDeclarations(env, 'test', files); + file = project.openFile('app.html'); + file.moveCursorToText('bat¦man'); }); it('get references in ts file', () => { - const refs = getReferencesAtPosition(_('/app.html'), cursor)!; + const refs = getReferencesAtPosition(file)!; expect(refs.length).toBe(2); assertFileNames(refs, ['app.ts', 'app.html']); @@ -337,7 +345,7 @@ describe('find references and rename locations', () => { }); it('finds rename location in ts file', () => { - const renameLocations = getRenameLocationsAtPosition(_('/app.html'), cursor)!; + const renameLocations = getRenameLocationsAtPosition(file)!; expect(renameLocations.length).toBe(2); assertFileNames(renameLocations, ['app.ts', 'app.html']); @@ -346,23 +354,26 @@ describe('find references and rename locations', () => { }); describe('when cursor in on an element reference', () => { - let cursor: number; + let file: OpenBuffer; beforeEach(() => { - const cursorInfo = extractCursorInfo(` + const files = { + 'app.ts': ` import {Component} from '@angular/core'; - @Component({template: ' {{ myIn¦put.value }}'}) + @Component({template: ' {{ myInput.value }}'}) export class AppCmp { title = ''; - }`); - cursor = cursorInfo.cursor; - const appFile = {name: _('/app.ts'), contents: cursorInfo.text}; - env = createModuleWithDeclarations([appFile]); + }` + }; + env = LanguageServiceTestEnv.setup(); + const project = createModuleAndProjectWithDeclarations(env, 'test', files); + file = project.openFile('app.ts'); + file.moveCursorToText('myInp¦ut.value'); }); it('get reference to declaration in template', () => { - const refs = getReferencesAtPosition(_('/app.ts'), cursor)!; + const refs = getReferencesAtPosition(file)!; expect(refs.length).toBe(2); assertTextSpans(refs, ['myInput']); @@ -373,7 +384,7 @@ describe('find references and rename locations', () => { }); it('finds rename location in template', () => { - const renameLocations = getRenameLocationsAtPosition(_('/app.ts'), cursor)!; + const renameLocations = getRenameLocationsAtPosition(file)!; expect(renameLocations.length).toBe(2); assertTextSpans(renameLocations, ['myInput']); @@ -381,30 +392,29 @@ describe('find references and rename locations', () => { }); describe('when cursor in on a template reference', () => { - let cursor: number; + let file: OpenBuffer; beforeEach(() => { - const templateWithCursor = ` - bla - `; - const appFile = { - name: _('/app.ts'), - contents: ` + const files = { + 'app.ts': ` import {Component} from '@angular/core'; @Component({templateUrl: './app.html'}) export class AppCmp { title = ''; }`, + 'app.html': ` + bla + ` }; - const cursorInfo = extractCursorInfo(templateWithCursor); - cursor = cursorInfo.cursor; - const templateFile = {name: _('/app.html'), contents: cursorInfo.text}; - env = createModuleWithDeclarations([appFile], [templateFile]); + env = LanguageServiceTestEnv.setup(); + const project = createModuleAndProjectWithDeclarations(env, 'test', files); + file = project.openFile('app.html'); + file.moveCursorToText('#myTem¦plate'); }); it('gets reference to declaration', () => { - const refs = getReferencesAtPosition(_('/app.html'), cursor)!; + const refs = getReferencesAtPosition(file)!; expect(refs.length).toBe(2); assertTextSpans(refs, ['myTemplate']); assertFileNames(refs, ['app.html']); @@ -415,7 +425,7 @@ describe('find references and rename locations', () => { }); it('finds rename location in template', () => { - const renameLocations = getRenameLocationsAtPosition(_('/app.html'), cursor)!; + const renameLocations = getRenameLocationsAtPosition(file)!; expect(renameLocations.length).toBe(2); assertTextSpans(renameLocations, ['myTemplate']); assertFileNames(renameLocations, ['app.html']); @@ -424,11 +434,7 @@ describe('find references and rename locations', () => { describe('template references', () => { describe('directives', () => { - let appFile: TestFile; - let dirFile: TestFile; - - beforeEach(() => { - const dirFileContents = ` + const dirFileContents = ` import {Directive} from '@angular/core'; @Directive({selector: '[dir]', exportAs: 'myDir'}) @@ -436,34 +442,35 @@ describe('find references and rename locations', () => { dirValue!: string; doSomething() {} }`; - const appFileContents = ` + const appFileContents = ` import {Component} from '@angular/core'; @Component({templateUrl: './app.html'}) export class AppCmp {}`; - appFile = {name: _('/app.ts'), contents: appFileContents}; - dirFile = {name: _('/dir.ts'), contents: dirFileContents}; - }); describe('when cursor is on usage of template reference', () => { - let cursor: number; + let file: OpenBuffer; beforeEach(() => { - const templateWithCursor = '
{{ dirR¦ef }}'; - const cursorInfo = extractCursorInfo(templateWithCursor); - cursor = cursorInfo.cursor; - const templateFile = {name: _('/app.html'), contents: cursorInfo.text}; - env = createModuleWithDeclarations([appFile, dirFile], [templateFile]); + const files = { + 'app.ts': appFileContents, + 'dir.ts': dirFileContents, + 'app.html': '
{{ dirRef }}' + }; + env = LanguageServiceTestEnv.setup(); + const project = createModuleAndProjectWithDeclarations(env, 'test', files); + file = project.openFile('app.html'); + file.moveCursorToText('#dirR¦ef'); }); it('should get references', () => { - const refs = getReferencesAtPosition(_('/app.html'), cursor)!; + const refs = getReferencesAtPosition(file)!; expect(refs.length).toBe(2); assertFileNames(refs, ['app.html']); assertTextSpans(refs, ['dirRef']); }); it('should find rename locations', () => { - const renameLocations = getRenameLocationsAtPosition(_('/app.html'), cursor)!; + const renameLocations = getRenameLocationsAtPosition(file)!; expect(renameLocations.length).toBe(2); assertFileNames(renameLocations, ['app.html']); assertTextSpans(renameLocations, ['dirRef']); @@ -471,24 +478,28 @@ describe('find references and rename locations', () => { }); describe('when cursor is on a property read of directive reference', () => { - let cursor: number; + let file: OpenBuffer; beforeEach(() => { - const fileWithCursor = '
{{ dirRef.dirV¦alue }}'; - const cursorInfo = extractCursorInfo(fileWithCursor); - cursor = cursorInfo.cursor; - const templateFile = {name: _('/app.html'), contents: cursorInfo.text}; - env = createModuleWithDeclarations([appFile, dirFile], [templateFile]); + const files = { + 'app.ts': appFileContents, + 'dir.ts': dirFileContents, + 'app.html': '
{{ dirRef.dirValue }}' + }; + env = LanguageServiceTestEnv.setup(); + const project = createModuleAndProjectWithDeclarations(env, 'test', files); + file = project.openFile('app.html'); + file.moveCursorToText('dirRef.dirV¦alue'); }); it('should get references', () => { - const refs = getReferencesAtPosition(_('/app.html'), cursor)!; + const refs = getReferencesAtPosition(file)!; expect(refs.length).toBe(2); assertFileNames(refs, ['dir.ts', 'app.html']); assertTextSpans(refs, ['dirValue']); }); it('should find rename locations', () => { - const renameLocations = getRenameLocationsAtPosition(_('/app.html'), cursor)!; + const renameLocations = getRenameLocationsAtPosition(file)!; expect(renameLocations.length).toBe(2); assertFileNames(renameLocations, ['dir.ts', 'app.html']); assertTextSpans(renameLocations, ['dirValue']); @@ -496,25 +507,29 @@ describe('find references and rename locations', () => { }); describe('when cursor is on a safe prop read', () => { - let cursor: number; + let file: OpenBuffer; beforeEach(() => { - const fileWithCursor = '
{{ dirRef?.dirV¦alue }}'; - const cursorInfo = extractCursorInfo(fileWithCursor); - cursor = cursorInfo.cursor; - const templateFile = {name: _('/app.html'), contents: cursorInfo.text}; - env = createModuleWithDeclarations([appFile, dirFile], [templateFile]); + const files = { + 'app.ts': appFileContents, + 'dir.ts': dirFileContents, + 'app.html': '
{{ dirRef?.dirValue }}' + }; + env = LanguageServiceTestEnv.setup(); + const project = createModuleAndProjectWithDeclarations(env, 'test', files); + file = project.openFile('app.html'); + file.moveCursorToText('dirRef?.dirV¦alue'); }); it('should get references', () => { - const refs = getReferencesAtPosition(_('/app.html'), cursor)!; + const refs = getReferencesAtPosition(file)!; expect(refs.length).toBe(2); assertFileNames(refs, ['dir.ts', 'app.html']); assertTextSpans(refs, ['dirValue']); }); it('should find rename locations', () => { - const renameLocations = getRenameLocationsAtPosition(_('/app.html'), cursor)!; + const renameLocations = getRenameLocationsAtPosition(file)!; expect(renameLocations.length).toBe(2); assertFileNames(renameLocations, ['dir.ts', 'app.html']); assertTextSpans(renameLocations, ['dirValue']); @@ -522,25 +537,29 @@ describe('find references and rename locations', () => { }); describe('when cursor is on safe method call', () => { - let cursor: number; + let file: OpenBuffer; beforeEach(() => { - const fileWithCursor = '
{{ dirRef?.doSometh¦ing() }}'; - const cursorInfo = extractCursorInfo(fileWithCursor); - cursor = cursorInfo.cursor; - const templateFile = {name: _('/app.html'), contents: cursorInfo.text}; - env = createModuleWithDeclarations([appFile, dirFile], [templateFile]); + const files = { + 'app.ts': appFileContents, + 'dir.ts': dirFileContents, + 'app.html': '
{{ dirRef?.doSomething() }}' + }; + env = LanguageServiceTestEnv.setup(); + const project = createModuleAndProjectWithDeclarations(env, 'test', files); + file = project.openFile('app.html'); + file.moveCursorToText('dirRef?.doSometh¦ing()'); }); it('should get references', () => { - const refs = getReferencesAtPosition(_('/app.html'), cursor)!; + const refs = getReferencesAtPosition(file)!; expect(refs.length).toBe(2); assertFileNames(refs, ['dir.ts', 'app.html']); assertTextSpans(refs, ['doSomething']); }); it('should find rename locations', () => { - const renameLocations = getRenameLocationsAtPosition(_('/app.html'), cursor)!; + const renameLocations = getRenameLocationsAtPosition(file)!; expect(renameLocations.length).toBe(2); assertFileNames(renameLocations, ['dir.ts', 'app.html']); assertTextSpans(renameLocations, ['doSomething']); @@ -551,44 +570,43 @@ describe('find references and rename locations', () => { describe('template variables', () => { describe('when cursor is on variable which was initialized implicitly', () => { - let cursor: number; + let file: OpenBuffer; beforeEach(() => { - const cursorInfo = extractCursorInfo(` -
- - {{her¦o}} - -
- `); - cursor = cursorInfo.cursor; - const appFile = { - name: _('/app.ts'), - contents: ` + const files = { + 'app.ts': ` import {Component} from '@angular/core'; @Component({templateUrl: './template.ng.html'}) export class AppCmp { heroes: string[] = []; - }` + }`, + 'template.ng.html': ` +
+ + {{hero}} + +
+ ` }; - const templateFile = {name: _('/template.ng.html'), contents: cursorInfo.text}; - env = createModuleWithDeclarations([appFile], [templateFile]); + env = LanguageServiceTestEnv.setup(); + const project = createModuleAndProjectWithDeclarations(env, 'test', files); + file = project.openFile('template.ng.html'); + file.moveCursorToText('{{her¦o}}'); }); it('should find references', () => { - const refs = getReferencesAtPosition(_('/template.ng.html'), cursor)!; + const refs = getReferencesAtPosition(file)!; expect(refs.length).toBe(3); assertFileNames(refs, ['template.ng.html']); assertTextSpans(refs, ['hero']); - const originalRefs = env.ngLS.getReferencesAtPosition(_('/template.ng.html'), cursor)!; // Get the declaration by finding the reference that appears first in the template - originalRefs.sort((a, b) => a.textSpan.start - b.textSpan.start); - expect(originalRefs[0].isDefinition).toBe(true); + refs.sort((a, b) => a.textSpan.start - b.textSpan.start); + expect(refs[0].isDefinition).toBe(true); }); it('should find rename locations', () => { - const renameLocations = getRenameLocationsAtPosition(_('/template.ng.html'), cursor)!; + const renameLocations = getRenameLocationsAtPosition(file)!; expect(renameLocations.length).toBe(3); assertFileNames(renameLocations, ['template.ng.html']); assertTextSpans(renameLocations, ['hero']); @@ -596,34 +614,37 @@ describe('find references and rename locations', () => { }); describe('when cursor is on renamed variable', () => { - let cursor: number; + let file: OpenBuffer; beforeEach(() => { - const cursorInfo = extractCursorInfo(` + const files = { + 'app.ts': ` import {Component} from '@angular/core'; - @Component({template: '
{{iR¦ef}}
'}) + @Component({template: '
{{iRef}}
'}) export class AppCmp { heroes: string[] = []; - }`); - cursor = cursorInfo.cursor; - const appFile = {name: _('/app.ts'), contents: cursorInfo.text}; - env = createModuleWithDeclarations([appFile]); + }` + }; + + env = LanguageServiceTestEnv.setup(); + const project = createModuleAndProjectWithDeclarations(env, 'test', files); + file = project.openFile('app.ts'); + file.moveCursorToText('{{iR¦ef}}'); }); it('should find references', () => { - const refs = getReferencesAtPosition(_('/app.ts'), cursor)!; + const refs = getReferencesAtPosition(file)!; expect(refs.length).toBe(2); assertFileNames(refs, ['app.ts']); assertTextSpans(refs, ['iRef']); - const originalRefs = env.ngLS.getReferencesAtPosition(_('/app.ts'), cursor)!; // Get the declaration by finding the reference that appears first in the template - originalRefs.sort((a, b) => a.textSpan.start - b.textSpan.start); - expect(originalRefs[0].isDefinition).toBe(true); + refs.sort((a, b) => a.textSpan.start - b.textSpan.start); + expect(refs[0].isDefinition).toBe(true); }); it('should find rename locations', () => { - const renameLocations = getRenameLocationsAtPosition(_('/app.ts'), cursor)!; + const renameLocations = getRenameLocationsAtPosition(file)!; expect(renameLocations.length).toBe(2); assertFileNames(renameLocations, ['app.ts']); assertTextSpans(renameLocations, ['iRef']); @@ -631,9 +652,10 @@ describe('find references and rename locations', () => { }); describe('when cursor is on initializer of variable', () => { - let cursor: number; + let file: OpenBuffer; beforeEach(() => { - const dirFile = ` + const files = { + 'example-directive.ts': ` import {Directive, Input} from '@angular/core'; export class ExampleContext { @@ -647,37 +669,37 @@ describe('find references and rename locations', () => { ctx is ExampleContext { return true; } - }`; - const fileWithCursor = ` + }`, + 'app.ts': ` import {Component, NgModule} from '@angular/core'; import {ExampleDirective} from './example-directive'; - @Component({template: '
{{id}}
'}) + @Component({template: '
{{id}}
'}) export class AppCmp { state = {}; } @NgModule({declarations: [AppCmp, ExampleDirective]}) - export class AppModule {}`; - const cursorInfo = extractCursorInfo(fileWithCursor); - cursor = cursorInfo.cursor; - env = LanguageServiceTestEnvironment.setup([ - {name: _('/app.ts'), contents: cursorInfo.text, isRoot: true}, - {name: _('/example-directive.ts'), contents: dirFile}, - ]); + export class AppModule {}` + }; + + env = LanguageServiceTestEnv.setup(); + const project = env.addProject('test', files); + file = project.openFile('app.ts'); + file.moveCursorToText('identif¦ier'); env.expectNoSourceDiagnostics(); - env.expectNoTemplateDiagnostics(absoluteFrom('/app.ts'), 'AppCmp'); + project.expectNoTemplateDiagnostics('app.ts', 'AppCmp'); }); it('should find references', () => { - const refs = getReferencesAtPosition(_('/app.ts'), cursor)!; + const refs = getReferencesAtPosition(file)!; expect(refs.length).toBe(2); assertFileNames(refs, ['app.ts', 'example-directive.ts']); assertTextSpans(refs, ['identifier']); }); it('should find rename locations', () => { - const renameLocations = getRenameLocationsAtPosition(_('/app.ts'), cursor)!; + const renameLocations = getRenameLocationsAtPosition(file)!; expect(renameLocations.length).toBe(2); assertFileNames(renameLocations, ['app.ts', 'example-directive.ts']); assertTextSpans(renameLocations, ['identifier']); @@ -685,29 +707,33 @@ describe('find references and rename locations', () => { }); describe('when cursor is on property read of variable', () => { - let cursor: number; + let file: OpenBuffer; beforeEach(() => { - const cursorInfo = extractCursorInfo(` + const files = { + 'app.ts': ` import {Component} from '@angular/core'; - @Component({template: '
{{hero.na¦me}}
'}) + @Component({template: '
{{hero.name}}
'}) export class AppCmp { heroes: Array<{name: string}> = []; - }`); - cursor = cursorInfo.cursor; - const appFile = {name: _('/app.ts'), contents: cursorInfo.text}; - env = createModuleWithDeclarations([appFile]); + }` + }; + + env = LanguageServiceTestEnv.setup(); + const project = createModuleAndProjectWithDeclarations(env, 'test', files); + file = project.openFile('app.ts'); + file.moveCursorToText('hero.na¦me'); }); it('should find references', () => { - const refs = getReferencesAtPosition(_('/app.ts'), cursor)!; + const refs = getReferencesAtPosition(file)!; expect(refs.length).toBe(2); assertFileNames(refs, ['app.ts']); assertTextSpans(refs, ['name']); }); it('should find rename locations', () => { - const renameLocations = getRenameLocationsAtPosition(_('/app.ts'), cursor)!; + const renameLocations = getRenameLocationsAtPosition(file)!; expect(renameLocations.length).toBe(2); assertFileNames(renameLocations, ['app.ts']); assertTextSpans(renameLocations, ['name']); @@ -716,9 +742,7 @@ describe('find references and rename locations', () => { }); describe('pipes', () => { - let prefixPipeFile: TestFile; - beforeEach(() => { - const prefixPipe = ` + const prefixPipe = ` import {Pipe, PipeTransform} from '@angular/core'; @Pipe({ name: 'prefixPipe' }) @@ -729,35 +753,37 @@ describe('find references and rename locations', () => { return ''; } }`; - prefixPipeFile = {name: _('/prefix-pipe.ts'), contents: prefixPipe}; - }); describe('when cursor is on pipe name', () => { - let cursor: number; + let file: OpenBuffer; beforeEach(() => { - const appContentsWithCursor = ` + const files = { + 'app.ts': ` import {Component} from '@angular/core'; - @Component({template: '{{birthday | prefi¦xPipe: "MM/dd/yy"}}'}) + @Component({template: '{{birthday | prefixPipe: "MM/dd/yy"}}'}) export class AppCmp { birthday = ''; } - `; - const cursorInfo = extractCursorInfo(appContentsWithCursor); - cursor = cursorInfo.cursor; - const appFile = {name: _('/app.ts'), contents: cursorInfo.text}; - env = createModuleWithDeclarations([appFile, prefixPipeFile]); + `, + 'prefix-pipe.ts': prefixPipe + }; + + env = LanguageServiceTestEnv.setup(); + const project = createModuleAndProjectWithDeclarations(env, 'test', files); + file = project.openFile('app.ts'); + file.moveCursorToText('prefi¦xPipe:'); }); it('should find references', () => { - const refs = getReferencesAtPosition(_('/app.ts'), cursor)!; + const refs = getReferencesAtPosition(file)!; expect(refs.length).toBe(5); assertFileNames(refs, ['index.d.ts', 'prefix-pipe.ts', 'app.ts']); assertTextSpans(refs, ['transform', 'prefixPipe']); }); it('should find rename locations', () => { - const renameLocations = getRenameLocationsAtPosition(_('/app.ts'), cursor)!; + const renameLocations = getRenameLocationsAtPosition(file)!; expect(renameLocations).toBeUndefined(); // TODO(atscott): Add support for renaming the pipe 'name' @@ -768,32 +794,36 @@ describe('find references and rename locations', () => { }); describe('when cursor is on pipe argument', () => { - let cursor: number; + let file: OpenBuffer; beforeEach(() => { - const appContentsWithCursor = ` + const files = { + 'prefix-pipe.ts': prefixPipe, + 'app.ts': ` import {Component} from '@angular/core'; - @Component({template: '{{birthday | prefixPipe: pr¦efix}}'}) + @Component({template: '{{birthday | prefixPipe: prefix}}'}) export class AppCmp { birthday = ''; prefix = ''; } - `; - const cursorInfo = extractCursorInfo(appContentsWithCursor); - cursor = cursorInfo.cursor; - const appFile = {name: _('/app.ts'), contents: cursorInfo.text}; - env = createModuleWithDeclarations([appFile, prefixPipeFile]); + ` + }; + + env = LanguageServiceTestEnv.setup(); + const project = createModuleAndProjectWithDeclarations(env, 'test', files); + file = project.openFile('app.ts'); + file.moveCursorToText('prefixPipe: pr¦efix'); }); it('should find references', () => { - const refs = getReferencesAtPosition(_('/app.ts'), cursor)!; + const refs = getReferencesAtPosition(file)!; expect(refs.length).toBe(2); assertFileNames(refs, ['app.ts']); assertTextSpans(refs, ['prefix']); }); it('should find rename locations', () => { - const renameLocations = getRenameLocationsAtPosition(_('/app.ts'), cursor)!; + const renameLocations = getRenameLocationsAtPosition(file)!; expect(renameLocations.length).toBe(2); assertFileNames(renameLocations, ['app.ts']); assertTextSpans(renameLocations, ['prefix']); @@ -811,30 +841,34 @@ describe('find references and rename locations', () => { @Input('alias') aliasedModel!: string; }`; describe('when cursor is on the input in the template', () => { - let cursor: number; + let file: OpenBuffer; beforeEach(() => { - const stringModelTestFile = {name: _('/string-model.ts'), contents: dirFileContents}; - const cursorInfo = extractCursorInfo(` + const files = { + 'string-model.ts': dirFileContents, + 'app.ts': ` import {Component} from '@angular/core'; - @Component({template: '
'}) + @Component({template: '
'}) export class AppCmp { title = 'title'; - }`); - cursor = cursorInfo.cursor; - const appFile = {name: _('/app.ts'), contents: cursorInfo.text}; - env = createModuleWithDeclarations([appFile, stringModelTestFile]); + }` + }; + + env = LanguageServiceTestEnv.setup(); + const project = createModuleAndProjectWithDeclarations(env, 'test', files); + file = project.openFile('app.ts'); + file.moveCursorToText('[mod¦el]'); }); it('should find references', () => { - const refs = getReferencesAtPosition(_('/app.ts'), cursor)!; + const refs = getReferencesAtPosition(file)!; expect(refs.length).toEqual(2); assertFileNames(refs, ['string-model.ts', 'app.ts']); assertTextSpans(refs, ['model']); }); it('should find rename locations', () => { - const renameLocations = getRenameLocationsAtPosition(_('/app.ts'), cursor)!; + const renameLocations = getRenameLocationsAtPosition(file)!; expect(renameLocations.length).toEqual(2); assertFileNames(renameLocations, ['string-model.ts', 'app.ts']); assertTextSpans(renameLocations, ['model']); @@ -842,36 +876,37 @@ describe('find references and rename locations', () => { }); describe('when cursor is on an input that maps to multiple directives', () => { - let cursor: number; + let file: OpenBuffer; beforeEach(() => { - const otherDirFile = { - name: _('/other-dir.ts'), - contents: ` + const files = { + 'other-dir.ts': ` import {Directive, Input} from '@angular/core'; @Directive({selector: '[string-model]'}) export class OtherDir { @Input('model') model!: any; } - ` - }; - const stringModelTestFile = {name: _('/string-model.ts'), contents: dirFileContents}; - const cursorInfo = extractCursorInfo(` + `, + 'string-model.ts': dirFileContents, + 'app.ts': ` import {Component} from '@angular/core'; - @Component({template: '
'}) + @Component({template: '
'}) export class AppCmp { title = 'title'; - }`); - cursor = cursorInfo.cursor; - const appFile = {name: _('/app.ts'), contents: cursorInfo.text}; - env = createModuleWithDeclarations([appFile, stringModelTestFile, otherDirFile]); + }` + }; + + env = LanguageServiceTestEnv.setup(); + const project = createModuleAndProjectWithDeclarations(env, 'test', files); + file = project.openFile('app.ts'); + file.moveCursorToText('[mod¦el]'); }); // TODO(atscott): This test does not pass because the template symbol builder only returns one // binding. xit('should find references', () => { - const refs = getReferencesAtPosition(_('/app.ts'), cursor)!; + const refs = getReferencesAtPosition(file)!; expect(refs.length).toEqual(3); assertFileNames(refs, ['string-model.ts', 'app.ts', 'other-dir']); assertTextSpans(refs, ['model', 'otherDirAliasedInput']); @@ -881,7 +916,7 @@ describe('find references and rename locations', () => { // The result is that rather than returning `undefined` because we don't handle alias inputs, // we return the rename locations for the first binding. xit('should find rename locations', () => { - const renameLocations = getRenameLocationsAtPosition(_('/app.ts'), cursor)!; + const renameLocations = getRenameLocationsAtPosition(file)!; expect(renameLocations).toBeUndefined(); // TODO(atscott): // expect(renameLocations.length).toEqual(3); @@ -891,30 +926,34 @@ describe('find references and rename locations', () => { }); describe('should work when cursor is on text attribute input', () => { - let cursor: number; + let file: OpenBuffer; beforeEach(() => { - const stringModelTestFile = {name: _('/string-model.ts'), contents: dirFileContents}; - const cursorInfo = extractCursorInfo(` + const files = { + 'string-model.ts': dirFileContents, + 'app.ts': ` import {Component} from '@angular/core'; - @Component({template: '
'}) + @Component({template: '
'}) export class AppCmp { title = 'title'; - }`); - cursor = cursorInfo.cursor; - const appFile = {name: _('/app.ts'), contents: cursorInfo.text}; - env = createModuleWithDeclarations([appFile, stringModelTestFile]); + }` + }; + + env = LanguageServiceTestEnv.setup(); + const project = createModuleAndProjectWithDeclarations(env, 'test', files); + file = project.openFile('app.ts'); + file.moveCursorToText('mod¦el="title"'); }); it('should work for text attributes', () => { - const refs = getReferencesAtPosition(_('/app.ts'), cursor)!; + const refs = getReferencesAtPosition(file)!; expect(refs.length).toEqual(2); assertFileNames(refs, ['string-model.ts', 'app.ts']); assertTextSpans(refs, ['model']); }); it('should find rename locations', () => { - const renameLocations = getRenameLocationsAtPosition(_('/app.ts'), cursor)!; + const renameLocations = getRenameLocationsAtPosition(file)!; expect(renameLocations.length).toEqual(2); assertFileNames(renameLocations, ['string-model.ts', 'app.ts']); assertTextSpans(renameLocations, ['model']); @@ -922,40 +961,40 @@ describe('find references and rename locations', () => { }); describe('when cursor is on the class member input', () => { - let cursor: number; + let file: OpenBuffer; beforeEach(() => { - const dirFileWithCursor = ` + const files = { + 'string-model.ts': ` import {Directive, Input} from '@angular/core'; @Directive({selector: '[string-model]'}) export class StringModel { - @Input() mod¦el!: string; - }`; - const cursorInfo = extractCursorInfo(dirFileWithCursor); - cursor = cursorInfo.cursor; - const stringModelTestFile = {name: _('/string-model.ts'), contents: cursorInfo.text}; - const appFile = { - name: _('/app.ts'), - contents: ` + @Input() model!: string; + }`, + 'app.ts': ` import {Component} from '@angular/core'; @Component({template: '
'}) export class AppCmp { title = 'title'; - }`, + }` }; - env = createModuleWithDeclarations([appFile, stringModelTestFile]); + + env = LanguageServiceTestEnv.setup(); + const project = createModuleAndProjectWithDeclarations(env, 'test', files); + file = project.openFile('string-model.ts'); + file.moveCursorToText('@Input() mod¦el!'); }); it('should work from the TS input declaration', () => { - const refs = getReferencesAtPosition(_('/string-model.ts'), cursor)!; + const refs = getReferencesAtPosition(file)!; expect(refs.length).toEqual(2); assertFileNames(refs, ['app.ts', 'string-model.ts']); assertTextSpans(refs, ['model']); }); it('should find rename locations', () => { - const renameLocations = getRenameLocationsAtPosition(_('/string-model.ts'), cursor)!; + const renameLocations = getRenameLocationsAtPosition(file)!; expect(renameLocations.length).toEqual(2); assertFileNames(renameLocations, ['app.ts', 'string-model.ts']); assertTextSpans(renameLocations, ['model']); @@ -963,9 +1002,10 @@ describe('find references and rename locations', () => { }); describe('when cursor is on input referenced somewhere in the class functions', () => { - let cursor: number; + let file: OpenBuffer; beforeEach(() => { - const otherDirContents = ` + const files = { + 'other-dir.ts': ` import {Directive, Input} from '@angular/core'; import {StringModel} from './string-model'; @@ -974,44 +1014,40 @@ describe('find references and rename locations', () => { @Input() stringModelRef!: StringModel; doSomething() { - console.log(this.stringModelRef.mod¦el); + console.log(this.stringModelRef.model); } - }`; - const cursorInfo = extractCursorInfo(otherDirContents); - cursor = cursorInfo.cursor; - const otherDirFile = {name: _('/other-dir.ts'), contents: cursorInfo.text}; - const stringModelTestFile = { - name: _('/string-model.ts'), - contents: ` + }`, + 'string-model.ts': ` import {Directive, Input} from '@angular/core'; @Directive({selector: '[string-model]'}) export class StringModel { @Input() model!: string; }`, - }; - const appFile = { - name: _('/app.ts'), - contents: ` + 'app.ts': ` import {Component} from '@angular/core'; @Component({template: '
'}) export class AppCmp { title = 'title'; - }`, + }` }; - env = createModuleWithDeclarations([appFile, stringModelTestFile, otherDirFile]); + + env = LanguageServiceTestEnv.setup(); + const project = createModuleAndProjectWithDeclarations(env, 'test', files); + file = project.openFile('other-dir.ts'); + file.moveCursorToText('this.stringModelRef.mod¦el'); }); it('should find references', () => { - const refs = getReferencesAtPosition(_('/other-dir.ts'), cursor)!; + const refs = getReferencesAtPosition(file)!; expect(refs.length).toEqual(3); assertFileNames(refs, ['app.ts', 'string-model.ts', 'other-dir.ts']); assertTextSpans(refs, ['model']); }); it('should find rename locations', () => { - const renameLocations = getRenameLocationsAtPosition(_('/other-dir.ts'), cursor)!; + const renameLocations = getRenameLocationsAtPosition(file)!; expect(renameLocations.length).toEqual(3); assertFileNames(renameLocations, ['app.ts', 'string-model.ts', 'other-dir.ts']); assertTextSpans(renameLocations, ['model']); @@ -1019,30 +1055,34 @@ describe('find references and rename locations', () => { }); describe('when cursor is on an aliased input', () => { - let cursor: number; + let file: OpenBuffer; beforeEach(() => { - const stringModelTestFile = {name: _('/string-model.ts'), contents: dirFileContents}; - const cursorInfo = extractCursorInfo(` + const files = { + 'string-model.ts': dirFileContents, + 'app.ts': ` import {Component} from '@angular/core'; - @Component({template: '
'}) + @Component({template: '
'}) export class AppCmp { title = 'title'; - }`); - cursor = cursorInfo.cursor; - const appFile = {name: _('/app.ts'), contents: cursorInfo.text}; - env = createModuleWithDeclarations([appFile, stringModelTestFile]); + }` + }; + + env = LanguageServiceTestEnv.setup(); + const project = createModuleAndProjectWithDeclarations(env, 'test', files); + file = project.openFile('app.ts'); + file.moveCursorToText('[al¦ias]'); }); it('should find references', () => { - const refs = getReferencesAtPosition(_('/app.ts'), cursor)!; + const refs = getReferencesAtPosition(file)!; expect(refs.length).toEqual(2); assertFileNames(refs, ['string-model.ts', 'app.ts']); assertTextSpans(refs, ['aliasedModel', 'alias']); }); it('should find rename locations', () => { - const renameLocations = getRenameLocationsAtPosition(_('/app.ts'), cursor)!; + const renameLocations = getRenameLocationsAtPosition(file)!; expect(renameLocations).toBeUndefined(); // TODO(atscott): add support for renaming alias outputs // expect(renameLocations.length).toEqual(2); @@ -1077,50 +1117,49 @@ describe('find references and rename locations', () => { } describe('when cursor is on output key in template', () => { - let cursor: number; + let file: OpenBuffer; beforeEach(() => { - const cursorInfo = extractCursorInfo( - generateAppFile(`
`)); - cursor = cursorInfo.cursor; - env = LanguageServiceTestEnvironment.setup([ - {name: _('/app.ts'), contents: cursorInfo.text, isRoot: true}, - {name: _('/string-model.ts'), contents: dirFile}, - ]); + const appFile = + generateAppFile(`
`); + + env = LanguageServiceTestEnv.setup(); + const project = env.addProject('test', {'app.ts': appFile, 'string-model.ts': dirFile}); + file = project.openFile('app.ts'); + file.moveCursorToText('(mod¦elChange)'); }); it('should find references', () => { - const refs = getReferencesAtPosition(_('/app.ts'), cursor)!; + const refs = getReferencesAtPosition(file)!; expect(refs.length).toEqual(2); assertTextSpans(refs, ['modelChange']); }); it('should find rename locations', () => { - const renameLocations = getRenameLocationsAtPosition(_('/app.ts'), cursor)!; + const renameLocations = getRenameLocationsAtPosition(file)!; expect(renameLocations.length).toEqual(2); assertTextSpans(renameLocations, ['modelChange']); }); }); describe('when cursor is on alias output key', () => { - let cursor: number; + let file: OpenBuffer; beforeEach(() => { - const cursorInfo = extractCursorInfo( - generateAppFile(`
`)); - cursor = cursorInfo.cursor; - env = LanguageServiceTestEnvironment.setup([ - {name: _('/app.ts'), contents: cursorInfo.text, isRoot: true}, - {name: _('/string-model.ts'), contents: dirFile}, - ]); + const appFile = generateAppFile(`
`); + + env = LanguageServiceTestEnv.setup(); + const project = env.addProject('test', {'app.ts': appFile, 'string-model.ts': dirFile}); + file = project.openFile('app.ts'); + file.moveCursorToText('(a¦lias)'); }); it('should find references', () => { - const refs = getReferencesAtPosition(_('/app.ts'), cursor)!; + const refs = getReferencesAtPosition(file)!; expect(refs.length).toEqual(2); assertTextSpans(refs, ['aliasedModelChange', 'alias']); }); it('should find rename locations', () => { - const renameLocations = getRenameLocationsAtPosition(_('/app.ts'), cursor)!; + const renameLocations = getRenameLocationsAtPosition(file)!; expect(renameLocations).toBeUndefined(); // TODO(atscott): add support for renaming alias outputs // expect(renameLocations.length).toEqual(2); @@ -1130,28 +1169,31 @@ describe('find references and rename locations', () => { }); it('should get references to both input and output for two-way binding', () => { - const dirFile = { - name: _('/dir.ts'), - contents: ` + const files = { + 'dir.ts': ` import {Directive, Input, Output} from '@angular/core'; @Directive({selector: '[string-model]'}) export class StringModel { @Input() model!: any; @Output() modelChange!: any; + }`, + 'app.ts': ` + import {Component} from '@angular/core'; + + @Component({template: '
'}) + export class AppCmp { + title = 'title'; }` + }; - const {text, cursor} = extractCursorInfo(` - import {Component} from '@angular/core'; - @Component({template: '
'}) - export class AppCmp { - title = 'title'; - }`); - const appFile = {name: _('/app.ts'), contents: text}; - env = createModuleWithDeclarations([appFile, dirFile]); + env = LanguageServiceTestEnv.setup(); + const project = createModuleAndProjectWithDeclarations(env, 'test', files); + const file = project.openFile('app.ts'); + file.moveCursorToText('[(mod¦el)]'); - const refs = getReferencesAtPosition(_('/app.ts'), cursor)!; + const refs = getReferencesAtPosition(file)!; // Note that this includes the 'model` twice from the template. As with other potential // duplicates (like if another plugin returns the same span), we expect the LS clients to filter // these out themselves. @@ -1162,15 +1204,15 @@ describe('find references and rename locations', () => { describe('directives', () => { describe('when cursor is on the directive class', () => { - let cursor: number; + let file: OpenBuffer; beforeEach(() => { - const cursorInfo = extractCursorInfo(` + const files = { + 'dir.ts': ` import {Directive} from '@angular/core'; @Directive({selector: '[dir]'}) - export class Di¦r {}`); - cursor = cursorInfo.cursor; - const appFile = ` + export class Dir {}`, + 'app.ts': ` import {Component, NgModule} from '@angular/core'; import {Dir} from './dir'; @@ -1180,15 +1222,17 @@ describe('find references and rename locations', () => { @NgModule({declarations: [AppCmp, Dir]}) export class AppModule {} - `; - env = LanguageServiceTestEnvironment.setup([ - {name: _('/app.ts'), contents: appFile, isRoot: true}, - {name: _('/dir.ts'), contents: cursorInfo.text}, - ]); + ` + }; + + env = LanguageServiceTestEnv.setup(); + const project = env.addProject('test', files); + file = project.openFile('dir.ts'); + file.moveCursorToText('export class Di¦r {}'); }); it('should find references', () => { - const refs = getReferencesAtPosition(_('/dir.ts'), cursor)!; + const refs = getReferencesAtPosition(file)!; // 4 references are: class declaration, template usage, app import and use in declarations // list. expect(refs.length).toBe(4); @@ -1197,7 +1241,7 @@ describe('find references and rename locations', () => { }); it('should find rename locations', () => { - const renameLocations = getRenameLocationsAtPosition(_('/dir.ts'), cursor)!; + const renameLocations = getRenameLocationsAtPosition(file)!; expect(renameLocations).toBeUndefined(); // TODO(atscott): We should handle this case, but exclude the template results // expect(renameLocations.length).toBe(3); @@ -1207,7 +1251,7 @@ describe('find references and rename locations', () => { }); describe('when cursor is on an attribute', () => { - let cursor: number; + let file: OpenBuffer; beforeEach(() => { const dirFile = ` import {Directive} from '@angular/core'; @@ -1219,35 +1263,34 @@ describe('find references and rename locations', () => { @Directive({selector: '[dir]'}) export class Dir2 {}`; - const cursorInfo = extractCursorInfo(` + const appFile = ` import {Component, NgModule} from '@angular/core'; import {Dir} from './dir'; import {Dir2} from './dir2'; - @Component({template: '
'}) + @Component({template: '
'}) export class AppCmp { } @NgModule({declarations: [AppCmp, Dir, Dir2]}) export class AppModule {} - `); - cursor = cursorInfo.cursor; - env = LanguageServiceTestEnvironment.setup([ - {name: _('/app.ts'), contents: cursorInfo.text, isRoot: true}, - {name: _('/dir.ts'), contents: dirFile}, - {name: _('/dir2.ts'), contents: dirFile2}, - ]); + `; + env = LanguageServiceTestEnv.setup(); + const project = + env.addProject('test', {'app.ts': appFile, 'dir.ts': dirFile, 'dir2.ts': dirFile2}); + file = project.openFile('app.ts'); + file.moveCursorToText('
'); }); it('gets references to all matching directives', () => { - const refs = getReferencesAtPosition(_('/app.ts'), cursor)!; + const refs = getReferencesAtPosition(file)!; expect(refs.length).toBe(8); assertTextSpans(refs, ['
', 'Dir', 'Dir2']); assertFileNames(refs, ['app.ts', 'dir.ts', 'dir2.ts']); }); it('finds rename locations for all matching directives', () => { - const renameLocations = getRenameLocationsAtPosition(_('/app.ts'), cursor)!; + const renameLocations = getRenameLocationsAtPosition(file)!; expect(renameLocations).toBeUndefined(); // TODO(atscott): We could consider supporting rename for directive selectors in the future // expect(renameLocations.length).toBe(3); @@ -1257,30 +1300,34 @@ describe('find references and rename locations', () => { }); describe('when cursor is on generic directive selector in template', () => { - let cursor: number; + let file: OpenBuffer; beforeEach(() => { - const cursorInfo = extractCursorInfo(` + const files = { + 'app.ts': ` import {Component, NgModule} from '@angular/core'; - @Component({template: '
'}) + @Component({template: '
'}) export class AppCmp { items = []; } - `); - cursor = cursorInfo.cursor; - const appFile = {name: _('/app.ts'), contents: cursorInfo.text}; - env = createModuleWithDeclarations([appFile]); + ` + }; + + env = LanguageServiceTestEnv.setup(); + const project = createModuleAndProjectWithDeclarations(env, 'test', files); + file = project.openFile('app.ts'); + file.moveCursorToText('*ngF¦or'); }); it('should be able to request references', () => { - const refs = getReferencesAtPosition(_('/app.ts'), cursor)!; + const refs = getReferencesAtPosition(file)!; expect(refs.length).toBe(6); assertTextSpans(refs, ['
', 'NgForOf']); assertFileNames(refs, ['index.d.ts', 'app.ts']); }); it('should not support rename if directive is in a dts file', () => { - const renameLocations = getRenameLocationsAtPosition(_('/app.ts'), cursor); + const renameLocations = getRenameLocationsAtPosition(file); expect(renameLocations).toBeUndefined(); }); }); @@ -1288,13 +1335,13 @@ describe('find references and rename locations', () => { describe('components', () => { describe('when cursor is on component class', () => { - let cursor: number; + let file: OpenBuffer; beforeEach(() => { - const cursorInfo = extractCursorInfo(` + const myComp = ` import {Component} from '@angular/core'; @Component({selector: 'my-comp', template: ''}) - export class MyCo¦mp {}`); + export class MyComp {}`; const appFile = ` import {Component, NgModule} from '@angular/core'; import {MyComp} from './comp'; @@ -1306,15 +1353,14 @@ describe('find references and rename locations', () => { @NgModule({declarations: [AppCmp, MyComp]}) export class AppModule {} `; - cursor = cursorInfo.cursor; - env = LanguageServiceTestEnvironment.setup([ - {name: _('/app.ts'), contents: appFile, isRoot: true}, - {name: _('/comp.ts'), contents: cursorInfo.text}, - ]); + env = LanguageServiceTestEnv.setup(); + const project = env.addProject('test', {'comp.ts': myComp, 'app.ts': appFile}); + file = project.openFile('comp.ts'); + file.moveCursorToText('MyCo¦mp'); }); it('finds references', () => { - const refs = getReferencesAtPosition(_('/comp.ts'), cursor)!; + const refs = getReferencesAtPosition(file)!; // 4 references are: class declaration, template usage, app import and use in declarations // list. expect(refs.length).toBe(4); @@ -1323,7 +1369,7 @@ describe('find references and rename locations', () => { }); it('gets rename locations', () => { - const renameLocations = getRenameLocationsAtPosition(_('/comp.ts'), cursor)!; + const renameLocations = getRenameLocationsAtPosition(file)!; expect(renameLocations).toBeUndefined(); // TODO(atscott): If we register as an exclusive provider for TS, we may need to return // results here and should exclude the template results. @@ -1334,33 +1380,32 @@ describe('find references and rename locations', () => { }); describe('when cursor is on the element tag', () => { - let cursor: number; + let file: OpenBuffer; beforeEach(() => { const compFile = ` import {Component} from '@angular/core'; @Component({selector: 'my-comp', template: ''}) export class MyComp {}`; - const cursorInfo = extractCursorInfo(` + const app = ` import {Component, NgModule} from '@angular/core'; import {MyComp} from './comp'; - @Component({template: ''}) + @Component({template: ''}) export class AppCmp { } @NgModule({declarations: [AppCmp, MyComp]}) export class AppModule {} - `); - cursor = cursorInfo.cursor; - env = LanguageServiceTestEnvironment.setup([ - {name: _('/app.ts'), contents: cursorInfo.text, isRoot: true}, - {name: _('/comp.ts'), contents: compFile}, - ]); + `; + env = LanguageServiceTestEnv.setup(); + const project = env.addProject('test', {'app.ts': app, 'comp.ts': compFile}); + file = project.openFile('app.ts'); + file.moveCursorToText(''); }); it('gets references', () => { - const refs = getReferencesAtPosition(_('/app.ts'), cursor)!; + const refs = getReferencesAtPosition(file)!; // 4 references are: class declaration, template usage, app import and use in declarations // list. expect(refs.length).toBe(4); @@ -1369,7 +1414,7 @@ describe('find references and rename locations', () => { }); it('finds rename locations', () => { - const renameLocations = getRenameLocationsAtPosition(_('/app.ts'), cursor)!; + const renameLocations = getRenameLocationsAtPosition(file)!; expect(renameLocations).toBeUndefined(); // TODO(atscott): We may consider supporting rename of component selector in the future // expect(renameLocations.length).toBe(2); @@ -1382,46 +1427,58 @@ describe('find references and rename locations', () => { describe('get rename info', () => { it('indicates inability to rename when cursor is outside template and in a string literal', () => { - const {cursor, text} = extractCursorInfo(` + const comp = ` import {Component} from '@angular/core'; @Component({selector: 'my-comp', template: ''}) export class MyComp { - myProp = 'cannot rena¦me me'; - }`); - env = createModuleWithDeclarations([{name: _('/my-comp.ts'), contents: text}]); + myProp = 'cannot rename me'; + }`; + env = LanguageServiceTestEnv.setup(); + const project = createModuleAndProjectWithDeclarations(env, 'test', {'my-comp.ts': comp}); env.expectNoSourceDiagnostics(); - const result = env.ngLS.getRenameInfo(_('/my-comp.ts'), cursor); + + const file = project.openFile('my-comp.ts'); + file.moveCursorToText('cannot rena¦me me'); + const result = file.getRenameInfo(); expect(result.canRename).toEqual(false); }); it('gets rename info when cursor is outside template', () => { - const {cursor, text} = extractCursorInfo(` + const comp = ` import {Component, Input} from '@angular/core'; @Component({name: 'my-comp', template: ''}) export class MyComp { - @Input() m¦yProp!: string; - }`); - env = createModuleWithDeclarations([{name: _('/my-comp.ts'), contents: text}]); + @Input() myProp!: string; + }`; + env = LanguageServiceTestEnv.setup(); + const project = createModuleAndProjectWithDeclarations(env, 'test', {'my-comp.ts': comp}); env.expectNoSourceDiagnostics(); - const result = env.ngLS.getRenameInfo(_('/my-comp.ts'), cursor) as ts.RenameInfoSuccess; + + const file = project.openFile('my-comp.ts'); + file.moveCursorToText('m¦yProp!'); + const result = file.getRenameInfo() as ts.RenameInfoSuccess; expect(result.canRename).toEqual(true); expect(result.displayName).toEqual('myProp'); expect(result.kind).toEqual('property'); }); it('gets rename info on keyed read', () => { - const {cursor, text} = extractCursorInfo(` + const text = ` import {Component} from '@angular/core'; - @Component({name: 'my-comp', template: '{{ myObj["my¦Prop"] }}'}) + @Component({name: 'my-comp', template: '{{ myObj["myProp"] }}'}) export class MyComp { readonly myObj = {'myProp': 'hello world'}; - }`); - env = createModuleWithDeclarations([{name: _('/my-comp.ts'), contents: text}]); + }`; + env = LanguageServiceTestEnv.setup(); + const project = createModuleAndProjectWithDeclarations(env, 'test', {'my-comp.ts': text}); env.expectNoSourceDiagnostics(); - const result = env.ngLS.getRenameInfo(_('/my-comp.ts'), cursor) as ts.RenameInfoSuccess; + const file = project.openFile('my-comp.ts'); + file.moveCursorToText('myObj["my¦Prop"]'); + + const result = file.getRenameInfo() as ts.RenameInfoSuccess; expect(result.canRename).toEqual(true); expect(result.displayName).toEqual('myProp'); expect(result.kind).toEqual('property'); @@ -1429,48 +1486,51 @@ describe('find references and rename locations', () => { result.triggerSpan.start, result.triggerSpan.start + result.triggerSpan.length)) .toBe('myProp'); // re-queries also work - const {triggerSpan, displayName} = - env.ngLS.getRenameInfo(_('/my-comp.ts'), cursor) as ts.RenameInfoSuccess; + const {triggerSpan, displayName} = file.getRenameInfo() as ts.RenameInfoSuccess; expect(displayName).toEqual('myProp'); expect(text.substring(triggerSpan.start, triggerSpan.start + triggerSpan.length)) .toBe('myProp'); }); it('gets rename info when cursor is on a directive input in a template', () => { - const dirFile = { - name: _('/dir.ts'), - contents: ` + const files = { + 'dir.ts': ` import {Directive, Input} from '@angular/core'; @Directive({selector: '[dir]'}) export class MyDir { @Input() dir!: any; - }` - }; - const {cursor, text} = extractCursorInfo(` + }`, + 'my-comp.ts': ` import {Component, Input} from '@angular/core'; - @Component({name: 'my-comp', template: '
'}) + @Component({name: 'my-comp', template: '
'}) export class MyComp { @Input() myProp!: string; - }`); - env = createModuleWithDeclarations([{name: _('/my-comp.ts'), contents: text}, dirFile]); + }` + }; + + env = LanguageServiceTestEnv.setup(); + const project = createModuleAndProjectWithDeclarations(env, 'test', files); env.expectNoSourceDiagnostics(); - const result = env.ngLS.getRenameInfo(_('/my-comp.ts'), cursor) as ts.RenameInfoSuccess; + + const file = project.openFile('my-comp.ts'); + file.moveCursorToText('di¦r="something"'); + const result = file.getRenameInfo() as ts.RenameInfoSuccess; expect(result.canRename).toEqual(true); expect(result.displayName).toEqual('dir'); expect(result.kind).toEqual('property'); }); }); - function getReferencesAtPosition(fileName: string, position: number) { + function getReferencesAtPosition(file: OpenBuffer) { env.expectNoSourceDiagnostics(); - const result = env.ngLS.getReferencesAtPosition(fileName, position); + const result = file.getReferencesAtPosition(); return result?.map((item) => humanizeDocumentSpanLike(item, env)); } - function getRenameLocationsAtPosition(fileName: string, position: number) { + function getRenameLocationsAtPosition(file: OpenBuffer) { env.expectNoSourceDiagnostics(); - const result = env.ngLS.findRenameLocations(fileName, position); + const result = file.fineRenameLocations(); return result?.map((item) => humanizeDocumentSpanLike(item, env)); } }); diff --git a/packages/language-service/ivy/testing/src/buffer.ts b/packages/language-service/ivy/testing/src/buffer.ts index b17c869c8d..aa7a49eb9e 100644 --- a/packages/language-service/ivy/testing/src/buffer.ts +++ b/packages/language-service/ivy/testing/src/buffer.ts @@ -88,4 +88,16 @@ export class OpenBuffer { getTypeDefinitionAtPosition() { return this.ngLS.getTypeDefinitionAtPosition(this.scriptInfo.fileName, this._cursor); } + + getReferencesAtPosition() { + return this.ngLS.getReferencesAtPosition(this.scriptInfo.fileName, this._cursor); + } + + fineRenameLocations() { + return this.ngLS.findRenameLocations(this.scriptInfo.fileName, this._cursor); + } + + getRenameInfo() { + return this.ngLS.getRenameInfo(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 b79d903916..8838b5b229 100644 --- a/packages/language-service/ivy/testing/src/project.ts +++ b/packages/language-service/ivy/testing/src/project.ts @@ -98,9 +98,14 @@ export class Project { openFile(projectFileName: string): OpenBuffer { if (!this.buffers.has(projectFileName)) { const fileName = absoluteFrom(`/${this.name}/${projectFileName}`); + let scriptInfo = this.tsProject.getScriptInfo(fileName); this.projectService.openClientFile(fileName); + // Mark the project as dirty because the act of opening a file may result in the version + // changing since TypeScript will `switchToScriptVersionCache` when a file is opened. + // Note that this emulates what we have to do in the server/extension as well. + this.tsProject.markAsDirty(); - const scriptInfo = this.tsProject.getScriptInfo(fileName); + scriptInfo = this.tsProject.getScriptInfo(fileName); if (scriptInfo === undefined) { throw new Error( `Unable to open ScriptInfo for ${projectFileName} in project ${this.tsConfigPath}`); diff --git a/packages/language-service/ivy/testing/src/util.ts b/packages/language-service/ivy/testing/src/util.ts index 50208c58f5..780ad53853 100644 --- a/packages/language-service/ivy/testing/src/util.ts +++ b/packages/language-service/ivy/testing/src/util.ts @@ -45,6 +45,11 @@ export function assertFileNames(refs: Array<{fileName: string}>, expectedFileNam 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)); +} + /** * Returns whether the given `ts.Diagnostic` is of a type only produced by the Angular compiler (as * opposed to being an upstream TypeScript diagnostic).