Skip to content

Commit 5a6d886

Browse files
kbrillaatscott
authored andcommitted
feat(language-service): add angular template inlay hints support
Add Angular template inlay hints end-to-end across language-service and VS Code extension server/client wiring, including inlay-specific configuration mapping, request guards, and refresh behavior.
1 parent aaf6e3e commit 5a6d886

File tree

11 files changed

+3010
-2
lines changed

11 files changed

+3010
-2
lines changed

packages/language-service/api.ts

Lines changed: 279 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,265 @@ export interface LinkedEditingRanges {
9999
wordPattern?: string;
100100
}
101101

102+
/**
103+
* A display part for interactive inlay hints.
104+
* When clicked, can navigate to the definition of the type/parameter.
105+
*/
106+
export interface InlayHintDisplayPart {
107+
/** The text to display */
108+
text: string;
109+
/** Optional navigation target span */
110+
span?: {
111+
/** Start offset in the target file */
112+
start: number;
113+
/** Length of the span */
114+
length: number;
115+
};
116+
/** Optional target file path for navigation */
117+
file?: string;
118+
}
119+
120+
/**
121+
* Represents an Angular-specific inlay hint to be displayed in the editor.
122+
*/
123+
export interface AngularInlayHint {
124+
/** Offset position where the hint should appear */
125+
position: number;
126+
/**
127+
* The text to display.
128+
* For non-interactive hints, this contains the full hint text.
129+
* For interactive hints, this may be empty and displayParts is used instead.
130+
*/
131+
text: string;
132+
/** Kind of hint: 'Type' for type annotations, 'Parameter' for parameter names */
133+
kind: 'Type' | 'Parameter';
134+
/** Whether to add padding before the hint */
135+
paddingLeft?: boolean;
136+
/** Whether to add padding after the hint */
137+
paddingRight?: boolean;
138+
/** Optional tooltip documentation */
139+
tooltip?: string;
140+
/**
141+
* Display parts for interactive hints.
142+
* When present, these parts can be clicked to navigate to type/parameter definitions.
143+
* Used when interactiveInlayHints is enabled.
144+
*/
145+
displayParts?: InlayHintDisplayPart[];
146+
}
147+
148+
/**
149+
* Configuration for which Angular inlay hints to show.
150+
* These options are designed to align with TypeScript's inlay hints configuration
151+
* where applicable to Angular templates.
152+
*/
153+
export interface InlayHintsConfig {
154+
// ═══════════════════════════════════════════════════════════════════════════
155+
// VARIABLE TYPE HINTS - equivalent to TypeScript's includeInlayVariableTypeHints
156+
// ═══════════════════════════════════════════════════════════════════════════
157+
158+
/** Show type hints for @for loop variables: `@for (item: Item of items)` */
159+
forLoopVariableTypes?: boolean;
160+
161+
/**
162+
* Show type hints for @if alias variables: `@if (data; as result: Data)`
163+
*
164+
* Can be a boolean to enable/disable all @if alias hints, or an object for fine-grained control:
165+
* - `simpleExpressions`: Show hints for simple variable references like `@if (data; as result)`
166+
* - `complexExpressions`: Show hints for complex expressions like `@if (data == 2; as b)` or `@if (data.prop; as p)`
167+
*
168+
* When set to true, shows hints for all @if aliases.
169+
* When set to 'complex', only shows hints for complex expressions (property access, comparisons, calls, etc.)
170+
*/
171+
ifAliasTypes?:
172+
| boolean
173+
| 'complex'
174+
| {
175+
/** Show hints for simple variable references: @if (data; as result). Default: true */
176+
simpleExpressions?: boolean;
177+
/** Show hints for complex expressions: @if (data.prop; as p), @if (a == b; as c). Default: true */
178+
complexExpressions?: boolean;
179+
};
180+
181+
/** Show type hints for @let declarations: `@let count: number = items.length` */
182+
letDeclarationTypes?: boolean;
183+
184+
/** Show type hints for template reference variables: `#ref: HTMLInputElement` */
185+
referenceVariableTypes?: boolean;
186+
187+
/**
188+
* Suppress hints when variable name matches the type name (case-insensitive).
189+
* Equivalent to TypeScript's `includeInlayVariableTypeHintsWhenTypeMatchesName`.
190+
* When false, skips hints like `@let user: User = getUser()` where name matches type.
191+
* @default true
192+
*/
193+
variableTypeHintsWhenTypeMatchesName?: boolean;
194+
195+
// ═══════════════════════════════════════════════════════════════════════════
196+
// ARROW FUNCTION TYPE HINTS - equivalent to TypeScript's function type hints
197+
// ═══════════════════════════════════════════════════════════════════════════
198+
199+
/**
200+
* Show type hints for arrow function parameters in templates.
201+
* Equivalent to TypeScript's `includeInlayFunctionParameterTypeHints`.
202+
*
203+
* When enabled, shows: `(a: number, b: string) => a + b` where types are inferred.
204+
* @default true
205+
*/
206+
arrowFunctionParameterTypes?: boolean;
207+
208+
/**
209+
* Show return type hints for arrow functions in templates.
210+
* Equivalent to TypeScript's `includeInlayFunctionLikeReturnTypeHints`.
211+
*
212+
* When enabled, shows: `(a, b): number => a + b` where return type is inferred.
213+
* @default true
214+
*/
215+
arrowFunctionReturnTypes?: boolean;
216+
217+
// ═══════════════════════════════════════════════════════════════════════════
218+
// PARAMETER NAME HINTS - equivalent to TypeScript's includeInlayParameterNameHints
219+
// ═══════════════════════════════════════════════════════════════════════════
220+
221+
/**
222+
* Show parameter name hints for function/method arguments in expressions.
223+
* Equivalent to TypeScript's `includeInlayParameterNameHints`.
224+
*
225+
* When enabled, shows: `handleClick(event: $event)` where 'event' is the parameter name.
226+
* Options:
227+
* - 'none': No parameter name hints
228+
* - 'literals': Only for literal arguments (strings, numbers, booleans)
229+
* - 'all': For all arguments
230+
* @default 'all'
231+
*/
232+
parameterNameHints?: 'none' | 'literals' | 'all';
233+
234+
/**
235+
* Show parameter hints even when argument name matches parameter name.
236+
* Equivalent to TypeScript's `includeInlayParameterNameHintsWhenArgumentMatchesName`.
237+
* When false, skips hints like `onClick(event)` where arg name matches param name.
238+
* @default false
239+
*/
240+
parameterNameHintsWhenArgumentMatchesName?: boolean;
241+
242+
// ═══════════════════════════════════════════════════════════════════════════
243+
// EVENT TYPE HINTS - Angular-specific for event bindings
244+
// ═══════════════════════════════════════════════════════════════════════════
245+
246+
/**
247+
* Show type hints for event binding $event parameter.
248+
* Works with @Output() EventEmitter<T>, output() OutputEmitterRef<T>, and model() changes.
249+
* Example: `(click: MouseEvent)`, `(valueChange: string)`
250+
*
251+
* Can be a boolean to enable/disable all event hints, or an object for fine-grained control:
252+
* - `nativeEvents`: Show hints for native HTML events (click, input, etc.)
253+
* - `componentEvents`: Show hints for custom component/directive events
254+
* - `animationEvents`: Show hints for animation events (@trigger.done)
255+
*/
256+
eventParameterTypes?:
257+
| boolean
258+
| {
259+
/** Show hints for native HTML events (click, input, keydown, etc.). Default: true */
260+
nativeEvents?: boolean;
261+
/** Show hints for custom component/directive output events. Default: true */
262+
componentEvents?: boolean;
263+
/** Show hints for animation events (@trigger.start, @trigger.done). Default: true */
264+
animationEvents?: boolean;
265+
};
266+
267+
// ═══════════════════════════════════════════════════════════════════════════
268+
// PIPE AND BINDING TYPE HINTS - Angular-specific
269+
// ═══════════════════════════════════════════════════════════════════════════
270+
271+
/** Show type hints for pipe output types: `{{ value | async: Observable<T> }}` */
272+
pipeOutputTypes?: boolean;
273+
274+
/**
275+
* Show type hints for property/input binding types.
276+
* Works with @Input(), input(), input.required(), and model() inputs.
277+
* Example: `[disabled: boolean]="isDisabled"`
278+
*
279+
* Can be a boolean to enable/disable all property hints, or an object for fine-grained control:
280+
* - `nativeProperties`: Show hints for native DOM properties ([disabled], [hidden], etc.)
281+
* - `componentInputs`: Show hints for component/directive @Input() and input() bindings
282+
*/
283+
propertyBindingTypes?:
284+
| boolean
285+
| {
286+
/** Show hints for native DOM properties. Default: true */
287+
nativeProperties?: boolean;
288+
/** Show hints for component/directive inputs. Default: true */
289+
componentInputs?: boolean;
290+
};
291+
292+
/**
293+
* Show WritableSignal<T> type for two-way bindings with model() signals.
294+
* When true: `[(checked: WritableSignal<boolean>)]="checkboxSignal"`
295+
* When false: `[(checked: boolean)]="checkboxSignal"`
296+
* @default true
297+
*/
298+
twoWayBindingSignalTypes?: boolean;
299+
300+
// ═══════════════════════════════════════════════════════════════════════════
301+
// VISUAL DIFFERENTIATION OPTIONS
302+
// ═══════════════════════════════════════════════════════════════════════════
303+
304+
/**
305+
* Add visual indicator for input.required() bindings.
306+
* Options:
307+
* - 'none': No special indicator
308+
* - 'asterisk': Add asterisk suffix `[user*: User]`
309+
* - 'exclamation': Add exclamation suffix `[user: User!]`
310+
* @default 'none'
311+
*/
312+
requiredInputIndicator?: 'none' | 'asterisk' | 'exclamation';
313+
314+
// ═══════════════════════════════════════════════════════════════════════════
315+
// INTERACTIVE HINTS - equivalent to TypeScript's interactiveInlayHints
316+
// ═══════════════════════════════════════════════════════════════════════════
317+
318+
/**
319+
* Enable clickable hints that navigate to type/parameter definitions.
320+
* Equivalent to TypeScript's `interactiveInlayHints`.
321+
* @default false
322+
*/
323+
interactiveInlayHints?: boolean;
324+
325+
// ═══════════════════════════════════════════════════════════════════════════
326+
// HOST LISTENER ARGUMENT TYPE HINTS - Angular-specific for @HostListener
327+
// ═══════════════════════════════════════════════════════════════════════════
328+
329+
/**
330+
* Show type hints for @HostListener argument expressions.
331+
* Example: `@HostListener('click', ['$event.target: EventTarget | null', '$event.clientX: number'])`
332+
*
333+
* When enabled, shows the inferred type for each expression passed in the decorator arguments.
334+
* @default true
335+
*/
336+
hostListenerArgumentTypes?: boolean;
337+
338+
// ═══════════════════════════════════════════════════════════════════════════
339+
// CONTROL FLOW BLOCK TYPE HINTS - Angular-specific for new control flow syntax
340+
// ═══════════════════════════════════════════════════════════════════════════
341+
342+
/**
343+
* Show type hints for @switch block expressions.
344+
* Example: `@switch (status: Status) { @case (Status.Active) { ... } }`
345+
*
346+
* When enabled, shows the inferred type of the switch expression value.
347+
* @default true
348+
*/
349+
switchExpressionTypes?: boolean;
350+
351+
/**
352+
* Show type hints for @defer block trigger expressions.
353+
* Example: `@defer (when isVisible: boolean) { ... }`
354+
*
355+
* When enabled, shows the inferred type for 'when' trigger conditions.
356+
* @default true
357+
*/
358+
deferTriggerTypes?: boolean;
359+
}
360+
102361
/**
103362
* `NgLanguageService` describes an instance of an Angular language service,
104363
* whose API surface is a strict superset of TypeScript's language service.
@@ -136,6 +395,26 @@ export interface NgLanguageService extends ts.LanguageService {
136395
): GetTemplateLocationForComponentResponse;
137396
getTypescriptLanguageService(): ts.LanguageService;
138397

398+
/**
399+
* Provide Angular-specific inlay hints for templates.
400+
*
401+
* Returns hints for:
402+
* - @for loop variable types: `@for (user: User of users)`
403+
* - @if alias types: `@if (data; as result: ApiResult)`
404+
* - Event parameter types: `(click)="onClick($event: MouseEvent)"`
405+
* - Pipe output types
406+
* - @let declaration types
407+
*
408+
* @param fileName The file to get inlay hints for
409+
* @param span The text span to get hints within
410+
* @param config Optional configuration for which hints to show
411+
*/
412+
getAngularInlayHints(
413+
fileName: string,
414+
span: ts.TextSpan,
415+
config?: InlayHintsConfig,
416+
): AngularInlayHint[];
417+
139418
applyRefactoring(
140419
fileName: string,
141420
positionOrRange: number | ts.TextRange,

0 commit comments

Comments
 (0)