fix(language-service): Support completions of two-way bindings (#40185)
This commit adds special handling to the completion builder by detecting a two way binding context and ensuring that we filter out any `Input`s that do not support two way binding. PR Close #40185
This commit is contained in:
parent
ebb7ac5979
commit
7d74853a1d
|
@ -31,6 +31,7 @@ export enum CompletionNodeContext {
|
|||
ElementAttributeKey,
|
||||
ElementAttributeValue,
|
||||
EventValue,
|
||||
TwoWayBinding,
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -415,7 +416,8 @@ export class CompletionBuilder<N extends TmplAstNode|AST> {
|
|||
}
|
||||
|
||||
private isElementAttributeCompletion(): this is ElementAttributeCompletionBuilder {
|
||||
return this.nodeContext === CompletionNodeContext.ElementAttributeKey &&
|
||||
return (this.nodeContext === CompletionNodeContext.ElementAttributeKey ||
|
||||
this.nodeContext === CompletionNodeContext.TwoWayBinding) &&
|
||||
(this.node instanceof TmplAstElement || this.node instanceof TmplAstBoundAttribute ||
|
||||
this.node instanceof TmplAstTextAttribute || this.node instanceof TmplAstBoundEvent);
|
||||
}
|
||||
|
@ -460,6 +462,10 @@ export class CompletionBuilder<N extends TmplAstNode|AST> {
|
|||
if (this.node instanceof TmplAstBoundEvent) {
|
||||
continue;
|
||||
}
|
||||
if (!completion.twoWayBindingSupported &&
|
||||
this.nodeContext === CompletionNodeContext.TwoWayBinding) {
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
case AttributeCompletionKind.DirectiveOutput:
|
||||
if (this.node instanceof TmplAstBoundAttribute) {
|
||||
|
|
|
@ -133,6 +133,8 @@ export class LanguageService {
|
|||
return null;
|
||||
}
|
||||
|
||||
// For two-way bindings, we actually only need to be concerned with the bound attribute because
|
||||
// the bindings in the template are written with the attribute name, not the event name.
|
||||
const node = positionDetails.context.kind === TargetNodeKind.TwoWayBindingContext ?
|
||||
positionDetails.context.nodes[0] :
|
||||
positionDetails.context.node;
|
||||
|
@ -272,6 +274,8 @@ function nodeContextFromTarget(target: TargetContext): CompletionNodeContext {
|
|||
case TargetNodeKind.ElementInBodyContext:
|
||||
// Completions in element bodies are for new attributes.
|
||||
return CompletionNodeContext.ElementAttributeKey;
|
||||
case TargetNodeKind.TwoWayBindingContext:
|
||||
return CompletionNodeContext.TwoWayBinding;
|
||||
case TargetNodeKind.AttributeInKeyContext:
|
||||
return CompletionNodeContext.ElementAttributeKey;
|
||||
case TargetNodeKind.AttributeInValueContext:
|
||||
|
|
|
@ -39,6 +39,22 @@ const DIR_WITH_OUTPUT = {
|
|||
`
|
||||
};
|
||||
|
||||
const DIR_WITH_TWO_WAY_BINDING = {
|
||||
'Dir': `
|
||||
@Directive({
|
||||
selector: '[dir]',
|
||||
inputs: ['model', 'otherInput'],
|
||||
outputs: ['modelChange', 'otherOutput'],
|
||||
})
|
||||
export class Dir {
|
||||
model!: any;
|
||||
modelChange!: any;
|
||||
otherInput!: any;
|
||||
otherOutput!: any;
|
||||
}
|
||||
`
|
||||
};
|
||||
|
||||
const NG_FOR_DIR = {
|
||||
'NgFor': `
|
||||
@Directive({
|
||||
|
@ -519,6 +535,27 @@ describe('completions', () => {
|
|||
['myOutput']);
|
||||
expectReplacementText(completions, text, 'my');
|
||||
});
|
||||
|
||||
it('should return completions inside an LHS of a partially complete two-way binding', () => {
|
||||
const {ngLS, fileName, cursor, text} =
|
||||
setup(`<h1 dir [(mod¦)]></h1>`, ``, DIR_WITH_TWO_WAY_BINDING);
|
||||
const completions =
|
||||
ngLS.getCompletionsAtPosition(fileName, cursor, /* options */ undefined);
|
||||
expectReplacementText(completions, text, 'mod');
|
||||
|
||||
expectContain(completions, ts.ScriptElementKind.memberVariableElement, ['model']);
|
||||
|
||||
// The completions should not include the events (because the 'Change' suffix is not used in
|
||||
// the two way binding) or inputs that do not have a corresponding name+'Change' output.
|
||||
expectDoesNotContain(
|
||||
completions, unsafeCastDisplayInfoKindToScriptElementKind(DisplayInfoKind.EVENT),
|
||||
['modelChange']);
|
||||
expectDoesNotContain(
|
||||
completions, ts.ScriptElementKind.memberVariableElement, ['otherInput']);
|
||||
expectDoesNotContain(
|
||||
completions, unsafeCastDisplayInfoKindToScriptElementKind(DisplayInfoKind.EVENT),
|
||||
['otherOutput']);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
Loading…
Reference in New Issue