Quick fix for no-implicit-any errors to add explicit type annotation#14786
Quick fix for no-implicit-any errors to add explicit type annotation#14786
Conversation
|
I am delighted by this. Thank you @mhegazy! |
| }, | ||
| getApparentType, | ||
|
|
||
| getUnionType, |
There was a problem hiding this comment.
Nit: Maybe makeUnionType or createUnionType? This works too though.
| case Diagnostics.Variable_0_implicitly_has_type_1_in_some_locations_where_its_type_cannot_be_determined.code: | ||
| return getCodeActionForVariableDeclaration(<PropertyDeclaration | PropertySignature | VariableDeclaration>token.parent); | ||
| case Diagnostics.Variable_0_implicitly_has_an_1_type.code: | ||
| return getCodeActionForVariableUsage(<Identifier>token); |
There was a problem hiding this comment.
I feel like there should be assertions when you cast based on the error code you're matching on.
There was a problem hiding this comment.
We asserted a few lines earlier that it has to be an identifier, DotDotDotToken, PublicKeyword, ProtectedKeyword, or ReadonlyKeyword. since this is a variable declaration error, we know it can not be anything that is not an identifier (i.e. binding pattern).
| case Diagnostics.Variable_0_implicitly_has_an_1_type.code: | ||
| return getCodeActionForVariableUsage(<Identifier>token); | ||
|
|
||
| // Paramtert declarations |
| return typeString; | ||
| } | ||
|
|
||
| function getParameterIndexInList(parameter: ParameterDeclaration, list: NodeArray<ParameterDeclaration>) { |
There was a problem hiding this comment.
Isn't this just indexOf?
| for (const child of node.getChildren(sourcefile)) { | ||
| if (child.kind === kind) return child; | ||
| } | ||
| } |
There was a problem hiding this comment.
explicit return undefined
| return -1; | ||
| } | ||
|
|
||
| function getFirstChildOfKind(node: Node, sourcefile: SourceFile, kind: SyntaxKind) { |
There was a problem hiding this comment.
There's a findChildOfKind which is nice and slow and allocates the lamdba you were trying to avoid here.
|
|
||
| // Property declarations | ||
| case Diagnostics.Member_0_implicitly_has_an_1_type.code: | ||
| return getCodeActionForVariableDeclaration(<VariableDeclaration>token.parent); |
There was a problem hiding this comment.
Is there ever a time that you won't have a symbol that you can't call this directly? What about in the case of a computed property?
There was a problem hiding this comment.
We filter these earlier on. it can only be identifier for a var declarations
Conflicts: src/compiler/checker.ts src/compiler/diagnosticMessages.json src/compiler/types.ts src/services/tsconfig.json
| getUndefinedType: () => undefinedType, | ||
| getNullType: () => nullType, | ||
| getESSymbolType: () => esSymbolType, | ||
| getNeverType: () => neverType, |
There was a problem hiding this comment.
As a concern, we have multiple never types internally. I feel somewhat uneasy about exposing it.
There was a problem hiding this comment.
made them all them internal for now.
|
Add new lines to the end of files? |
|
@DanielRosenwasser any more comments? |
sandersn
left a comment
There was a problem hiding this comment.
A few questions about inference, plus I noticed some typos.
| case Diagnostics.Property_0_implicitly_has_type_any_because_its_set_accessor_lacks_a_parameter_type_annotation.code: | ||
| return isSetAccessor(containingFunction) ? getCodeActionForSetAccessor(containingFunction) : undefined; | ||
|
|
||
| // Property declarations |
There was a problem hiding this comment.
This looks redundant with the very first case of this switch.
| errorCodes: [ | ||
| // Variable declarations | ||
| Diagnostics.Variable_0_implicitly_has_type_1_in_some_locations_where_its_type_cannot_be_determined.code, | ||
| Diagnostics.Variable_0_implicitly_has_an_1_type.code, |
There was a problem hiding this comment.
this error maps to getCodeActionForVariableUsage, so I think it is actually should be commented with // variable uses
| return undefined; | ||
| } | ||
|
|
||
| const types = inferTypeForParametersFromUsage(containingFunction) || |
There was a problem hiding this comment.
so it's either from usage from calls or from intra-function usages, but not both? Why have inferences from call sites win?
There was a problem hiding this comment.
If we do the function body first we are guranteed to manifacture something that the makes the funciton happy, but it usually will be too general. giving usage precedence allows us to get more specific types.. e.g.:
function length(a) { return a.length; }
length([1,2,3]);inferring from function body will give a a type { length: any}. which is accurate, but rarely what the user would write manually.
inferring from the call site gives us number[] which is more like it..
There was a problem hiding this comment.
Perhaps you should infer <T extends { length }>(a: T) => ... instead. In combination with return type inference, this should yield <T extends { length }>(a: T) => T["length"], which will work correctly when applied to arrays (or indeed anything with a length property).
There was a problem hiding this comment.
I tried that.. the types get unwieldy very fast.. and you have this structural type that has length, push, splice, with an element that is has kind, instead of just Node[]. the call sites provided the more user-identifiable name.. though the first one is more correct.
| const types = inferTypeForParametersFromUsage(containingFunction) || | ||
| map(containingFunction.parameters, p => isIdentifier(p.name) && inferTypeForVariableFromUsage(p.name)); | ||
|
|
||
| if (types) { |
There was a problem hiding this comment.
it would be easier to read both nested blocks as early returns (if (!types) return undefined and if (!textChanges) return undefined.
|
|
||
| if (types) { | ||
| const textChanges = reduceLeft(containingFunction.parameters, (memo, parameter, index) => { | ||
| if (types[index] && !parameter.type && !parameter.initializer) { |
There was a problem hiding this comment.
containingFunction.parameters.length === types.length, right?
If so, I don't think we need the efficiency of reduce and lazy initialisation of memo. It would be easier to read as a zip followed by filtering out undefined:
zipWith(containingFunction.parameters, types, (param, type) => ...).filter(change => change !== undefined)
(zipWith is in core.ts so it might need to be moved into the public API?)
| } | ||
|
|
||
| if (usageContext.callContexts) { | ||
| for (const callConext of usageContext.callContexts) { |
| return checker.createSignature(/*declaration*/ undefined, /*typeParameters*/ undefined, /*thisParameter*/ undefined, parameters, returnType, /*typePredicate*/ undefined, callContext.argumentTypes.length, /*hasRestParameter*/ false, /*hasLiteralTypes*/ false); | ||
| } | ||
|
|
||
| function inferTypeFromContext(node: Expression, checker: TypeChecker, usageContext: UsageContext): void { |
There was a problem hiding this comment.
Can you move this before getTypeFromUsageContext since it's used before?
There was a problem hiding this comment.
Ok, moved get* after infer*
| return; | ||
| } | ||
| else { | ||
| const indextType = checker.getTypeAtLocation(parent); |
There was a problem hiding this comment.
probably is a typo for indexType ?
| function getSignatureFromCallContext(callContext: CallContext, checker: TypeChecker): Signature { | ||
| const parameters: Symbol[] = []; | ||
| for (let i = 0; i < callContext.argumentTypes.length; i++) { | ||
| const symbol = checker.createSymbol(SymbolFlags.FunctionScopedVariable, escapeLeadingUnderscores(`p${i}`)); |
There was a problem hiding this comment.
I would call this arg${i} to match our other names for unnamed parameters.
| else if (usageContext.properties && hasCallContext(usageContext.properties.get("push" as __String))) { | ||
| return checker.createArrayType(getParameterTypeFromCallContexts(0, usageContext.properties.get("push" as __String).callContexts, /*isRestParameter*/ false, checker)); | ||
| } | ||
| else if (usageContext.properties || usageContext.callContexts || usageContext.constructContexts || usageContext.numberIndexContext || usageContext.stringIndexContext) { |
There was a problem hiding this comment.
what happens if you combine object types made up of the synthetic properties (et al) with the candidate types? Are the candidate types strictly better than the synthetic object type?
There was a problem hiding this comment.
I would think so.. a user-defined type seems more like what you would put on a function, e.g. Node or Symbol, and not {kind: SyntaxKind} or {flags: SymbolFlags} to use an example of our code base.
Add a new quick fix for no-implicit-any errors for variables, parameters, and members. The fix tries to "infer" the type from assignments to the symbol in question, and serializes the type in an explicit type annotation addressing the no-implicit-any error.