Skip to content

Commit 48a800f

Browse files
authored
fix(extension): Allow requests to reach the language server when in component decorator (#2067)
This commit updates the early-return logic on the client-side to allow requests to go to the server whenever the cursor is inside the component decorator. Prior to this change, some requests would attempt to return early if the position was not inside the inline template region. However, we were never able to get the tokenizing working correctly and this has led to issues whenever there are multiple template strings (JS template strings with \`). This approach should give us the needed benefits of early-returns in non-Angular contexts (`@Component` decorator should be pretty rare) while being more permissive and correct. fixes #2064
1 parent a18c617 commit 48a800f

File tree

5 files changed

+46
-76
lines changed

5 files changed

+46
-76
lines changed

client/src/client.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import {OpenOutputChannel, ProjectLoadingFinish, ProjectLoadingStart, SuggestStr
1515
import {GetComponentsWithTemplateFile, GetTcbRequest, GetTemplateLocationForComponent, IsInAngularProject} from '../../common/requests';
1616
import {NodeModule, resolve} from '../../common/resolver';
1717

18-
import {isInsideComponentDecorator, isInsideInlineTemplateRegion, isInsideStringLiteral} from './embedded_support';
18+
import {isInsideStringLiteral, isNotTypescriptOrInsideComponentDecorator} from './embedded_support';
1919

2020
interface GetTcbResponse {
2121
uri: vscode.Uri;
@@ -67,8 +67,8 @@ export class AngularLanguageClient implements vscode.Disposable {
6767
document: vscode.TextDocument, range: vscode.Range, context: vscode.CodeActionContext,
6868
token: vscode.CancellationToken, next: lsp.ProvideCodeActionsSignature) => {
6969
if (await this.isInAngularProject(document) &&
70-
isInsideInlineTemplateRegion(document, range.start) &&
71-
isInsideInlineTemplateRegion(document, range.end)) {
70+
isNotTypescriptOrInsideComponentDecorator(document, range.start) &&
71+
isNotTypescriptOrInsideComponentDecorator(document, range.end)) {
7272
return next(document, range, context, token);
7373
}
7474
},
@@ -92,23 +92,23 @@ export class AngularLanguageClient implements vscode.Disposable {
9292
document: vscode.TextDocument, position: vscode.Position,
9393
token: vscode.CancellationToken, next: lsp.ProvideDefinitionSignature) => {
9494
if (await this.isInAngularProject(document) &&
95-
isInsideComponentDecorator(document, position)) {
95+
isNotTypescriptOrInsideComponentDecorator(document, position)) {
9696
return next(document, position, token);
9797
}
9898
},
9999
provideTypeDefinition: async (
100100
document: vscode.TextDocument, position: vscode.Position,
101101
token: vscode.CancellationToken, next) => {
102102
if (await this.isInAngularProject(document) &&
103-
isInsideInlineTemplateRegion(document, position)) {
103+
isNotTypescriptOrInsideComponentDecorator(document, position)) {
104104
return next(document, position, token);
105105
}
106106
},
107107
provideHover: async (
108108
document: vscode.TextDocument, position: vscode.Position,
109109
token: vscode.CancellationToken, next: lsp.ProvideHoverSignature) => {
110110
if (!(await this.isInAngularProject(document)) ||
111-
!isInsideInlineTemplateRegion(document, position)) {
111+
!isNotTypescriptOrInsideComponentDecorator(document, position)) {
112112
return;
113113
}
114114

@@ -132,7 +132,7 @@ export class AngularLanguageClient implements vscode.Disposable {
132132
context: vscode.SignatureHelpContext, token: vscode.CancellationToken,
133133
next: lsp.ProvideSignatureHelpSignature) => {
134134
if (await this.isInAngularProject(document) &&
135-
isInsideInlineTemplateRegion(document, position)) {
135+
isNotTypescriptOrInsideComponentDecorator(document, position)) {
136136
return next(document, position, context, token);
137137
}
138138
},
@@ -142,7 +142,7 @@ export class AngularLanguageClient implements vscode.Disposable {
142142
next: lsp.ProvideCompletionItemsSignature) => {
143143
// If not in inline template, do not perform request forwarding
144144
if (!(await this.isInAngularProject(document)) ||
145-
!isInsideInlineTemplateRegion(document, position)) {
145+
!isNotTypescriptOrInsideComponentDecorator(document, position)) {
146146
return;
147147
}
148148
const angularCompletionsPromise = next(document, position, context, token) as

client/src/embedded_support.ts

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,8 @@
88
import * as ts from 'typescript';
99
import * as vscode from 'vscode';
1010

11-
/** Determines if the position is inside an inline template. */
12-
export function isInsideInlineTemplateRegion(
13-
document: vscode.TextDocument, position: vscode.Position): boolean {
14-
if (document.languageId !== 'typescript') {
15-
return true;
16-
}
17-
return isPropertyAssignmentToStringOrStringInArray(
18-
document.getText(), document.offsetAt(position), ['template']);
19-
}
20-
2111
/** Determines if the position is inside an inline template, templateUrl, or string in styleUrls. */
22-
export function isInsideComponentDecorator(
12+
export function isNotTypescriptOrInsideComponentDecorator(
2313
document: vscode.TextDocument, position: vscode.Position): boolean {
2414
if (document.languageId !== 'typescript') {
2515
return true;

client/src/tests/embedded_support_spec.ts

Lines changed: 26 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -8,96 +8,66 @@
88
import * as vscode from 'vscode';
99
import {DocumentUri, TextDocument} from 'vscode-languageserver-textdocument';
1010

11-
import {isInsideComponentDecorator, isInsideInlineTemplateRegion} from '../embedded_support';
11+
import {isNotTypescriptOrInsideComponentDecorator} from '../embedded_support';
1212

1313
describe('embedded language support', () => {
14-
describe('isInsideInlineTemplateRegion', () => {
15-
it('empty file', () => {
16-
test('¦', isInsideInlineTemplateRegion, false);
17-
});
18-
19-
it('just after template', () => {
20-
test(`const foo = {template: '<div></div>'¦}`, isInsideInlineTemplateRegion, false);
21-
});
22-
23-
it('just before template', () => {
24-
// Note that while it seems that this should be `false`, we should still consider this inside
25-
// the string because the visual mode of vim appears to have a position on top of the open
26-
// quote while the cursor position is before it.
27-
test(`const foo = {template: ¦'<div></div>'}`, isInsideInlineTemplateRegion, true);
28-
});
29-
30-
it('two spaces before template', () => {
31-
test(`const foo = {template:¦ '<div></div>'}`, isInsideInlineTemplateRegion, false);
32-
});
33-
34-
it('at beginning of template', () => {
35-
test(`const foo = {template: '¦<div></div>'}`, isInsideInlineTemplateRegion, true);
36-
});
37-
38-
it('at end of template', () => {
39-
test(`const foo = {template: '<div></div>¦'}`, isInsideInlineTemplateRegion, true);
40-
});
41-
42-
xit('works for inline templates after a template string', () => {
43-
test(
44-
'const x = `${""}`;\n' +
45-
'const foo = {template: `hello ¦world`}',
46-
isInsideInlineTemplateRegion, true);
47-
});
48-
49-
it('works for inline templates after a tagged template string inside tagged template string',
50-
() => {
51-
test(
52-
'const x = `${`${""}`}`;\n' +
53-
'const foo = {template: `hello ¦world`}',
54-
isInsideInlineTemplateRegion, true);
55-
});
56-
});
57-
5814
describe('isInsideAngularContext', () => {
5915
it('empty file', () => {
60-
test('¦', isInsideComponentDecorator, false);
16+
test('¦', isNotTypescriptOrInsideComponentDecorator, false);
6117
});
6218

6319
it('just after template', () => {
64-
test(`const foo = {template: '<div></div>'¦}`, isInsideComponentDecorator, false);
20+
test(
21+
`const foo = {template: '<div></div>'¦}`, isNotTypescriptOrInsideComponentDecorator,
22+
false);
6523
});
6624

6725
it('inside template', () => {
68-
test(`const foo = {template: '<div>¦</div>'}`, isInsideComponentDecorator, true);
26+
test(
27+
`const foo = {template: '<div>¦</div>'}`, isNotTypescriptOrInsideComponentDecorator,
28+
true);
6929
});
7030

7131
it('just after templateUrl', () => {
72-
test(`const foo = {templateUrl: './abc.html'¦}`, isInsideComponentDecorator, false);
32+
test(
33+
`const foo = {templateUrl: './abc.html'¦}`, isNotTypescriptOrInsideComponentDecorator,
34+
false);
7335
});
7436

7537
it('inside templateUrl', () => {
76-
test(`const foo = {templateUrl: './abc¦.html'}`, isInsideComponentDecorator, true);
38+
test(
39+
`const foo = {templateUrl: './abc¦.html'}`, isNotTypescriptOrInsideComponentDecorator,
40+
true);
7741
});
7842

7943
it('just after styleUrls', () => {
80-
test(`const foo = {styleUrls: ['./abc.css']¦}`, isInsideComponentDecorator, false);
44+
test(
45+
`const foo = {styleUrls: ['./abc.css']¦}`, isNotTypescriptOrInsideComponentDecorator,
46+
false);
8147
});
8248

8349
it('inside first item of styleUrls', () => {
84-
test(`const foo = {styleUrls: ['./abc.c¦ss', 'def.css']}`, isInsideComponentDecorator, true);
50+
test(
51+
`const foo = {styleUrls: ['./abc.c¦ss', 'def.css']}`,
52+
isNotTypescriptOrInsideComponentDecorator, true);
8553
});
8654

8755
it('inside second item of styleUrls', () => {
88-
test(`const foo = {styleUrls: ['./abc.css', 'def¦.css']}`, isInsideComponentDecorator, true);
56+
test(
57+
`const foo = {styleUrls: ['./abc.css', 'def¦.css']}`,
58+
isNotTypescriptOrInsideComponentDecorator, true);
8959
});
9060

9161
it('inside second item of styleUrls, when first is complicated function', () => {
9262
test(
9363
`const foo = {styleUrls: [getCss({strict: true, dirs: ['apple', 'banana']}), 'def¦.css']}`,
94-
isInsideComponentDecorator, true);
64+
isNotTypescriptOrInsideComponentDecorator, true);
9565
});
9666

9767
it('inside non-string item of styleUrls', () => {
9868
test(
9969
`const foo = {styleUrls: [getCss({strict: true¦, dirs: ['apple', 'banana']}), 'def.css']}`,
100-
isInsideComponentDecorator, false);
70+
isNotTypescriptOrInsideComponentDecorator, false);
10171
});
10272
});
10373
});
@@ -129,4 +99,4 @@ function extractCursorInfo(textWithCursor: string): {cursor: number, text: strin
12999
cursor,
130100
text: textWithCursor.substr(0, cursor) + textWithCursor.substr(cursor + 1),
131101
};
132-
}
102+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
{{title | uppercase}}
22
<span class="subtitle">
33
subtitle
4+
{{sig()}}
5+
{{x.sig()}}
46
</span>
57
<lib-post></lib-post>
Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,16 @@
1-
import { Component } from '@angular/core';
1+
import { Component, signal } from '@angular/core';
22

33
@Component({
44
templateUrl: 'foo.component.html',
55
})
66
export class FooComponent {
77
title = 'Foo Component';
8+
sig = signal(1);
9+
x = {
10+
sig: signal(1),
11+
}
12+
/** returns 1 */
13+
method() {
14+
return 1;
15+
}
816
}

0 commit comments

Comments
 (0)