-
Notifications
You must be signed in to change notification settings - Fork 24.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Autocompletion in expression contexts #39727
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,10 +7,11 @@ | |
*/ | ||
|
||
import {TmplAstReference, TmplAstTemplate} from '@angular/compiler'; | ||
import {MethodCall, PropertyRead, PropertyWrite, SafeMethodCall, SafePropertyRead} from '@angular/compiler/src/compiler'; | ||
import * as ts from 'typescript'; | ||
|
||
import {AbsoluteFsPath} from '../../file_system'; | ||
import {CompletionKind, GlobalCompletion, ReferenceCompletion, VariableCompletion} from '../api'; | ||
import {CompletionKind, GlobalCompletion, ReferenceCompletion, ShimLocation, VariableCompletion} from '../api'; | ||
|
||
import {ExpressionIdentifier, findFirstMatchingNode} from './comments'; | ||
import {TemplateData} from './context'; | ||
|
@@ -28,6 +29,9 @@ export class CompletionEngine { | |
*/ | ||
private globalCompletionCache = new Map<TmplAstTemplate|null, GlobalCompletion>(); | ||
|
||
private expressionCompletionCache = | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you have any concerns about memory "leak" since we are not invalidating cache entries? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, because the entire |
||
new Map<PropertyRead|SafePropertyRead|MethodCall|SafeMethodCall, ShimLocation>(); | ||
|
||
constructor(private tcb: ts.Node, private data: TemplateData, private shimPath: AbsoluteFsPath) {} | ||
|
||
/** | ||
|
@@ -79,4 +83,52 @@ export class CompletionEngine { | |
this.globalCompletionCache.set(context, completion); | ||
return completion; | ||
} | ||
|
||
getExpressionCompletionLocation(expr: PropertyRead|PropertyWrite|MethodCall| | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm actually a bit curious about this method in general. Shouldn't Edit: Ah, I see that you use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think there's any nuance with We could add to |
||
SafeMethodCall): ShimLocation|null { | ||
if (this.expressionCompletionCache.has(expr)) { | ||
return this.expressionCompletionCache.get(expr)!; | ||
} | ||
|
||
// Completion works inside property reads and method calls. | ||
let tsExpr: ts.PropertyAccessExpression|null = null; | ||
if (expr instanceof PropertyRead || expr instanceof MethodCall || | ||
expr instanceof PropertyWrite) { | ||
// Non-safe navigation operations are trivial: `foo.bar` or `foo.bar()` | ||
tsExpr = findFirstMatchingNode(this.tcb, { | ||
filter: ts.isPropertyAccessExpression, | ||
withSpan: expr.nameSpan, | ||
}); | ||
} else if (expr instanceof SafePropertyRead || expr instanceof SafeMethodCall) { | ||
// Safe navigation operations are a little more complex, and involve a ternary. Completion | ||
// happens in the "true" case of the ternary. | ||
const ternaryExpr = findFirstMatchingNode(this.tcb, { | ||
filter: ts.isParenthesizedExpression, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's a little weird that you're using parenthesized expression as the filter here. I think this should actually be There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
withSpan: expr.sourceSpan, | ||
}); | ||
if (ternaryExpr === null || !ts.isConditionalExpression(ternaryExpr.expression)) { | ||
return null; | ||
} | ||
const whenTrue = ternaryExpr.expression.whenTrue; | ||
|
||
if (expr instanceof SafePropertyRead && ts.isPropertyAccessExpression(whenTrue)) { | ||
tsExpr = whenTrue; | ||
} else if ( | ||
expr instanceof SafeMethodCall && ts.isCallExpression(whenTrue) && | ||
ts.isPropertyAccessExpression(whenTrue.expression)) { | ||
tsExpr = whenTrue.expression; | ||
} | ||
} | ||
|
||
if (tsExpr === null) { | ||
return null; | ||
} | ||
|
||
const res: ShimLocation = { | ||
shimPath: this.shimPath, | ||
positionInShimFile: tsExpr.name.getEnd(), | ||
}; | ||
this.expressionCompletionCache.set(expr, res); | ||
return res; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about
KeyedRead
/KeyedWrite
/PropertyWrite
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
KeyedRead
andKeyedWrite
are non-trivial - the key is itself an expression, meaning we won't target theKeyedRead
but rather aLiteralPrimitive
(if a string literal key is used) within theKeyedRead
. It's possible to support these forms, but I want to get the 99% case working first.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added support / a test for
PropertyWrite
, since it's nearly identical toPropertyRead
.