-
Notifications
You must be signed in to change notification settings - Fork 12.6k
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
inferFromUsage codefix now emits JSDoc in JS files #27610
Conversation
Probably everything else, but not as well.
It's still bad, but at least it's not wrong.
Also, remove redundant is(Set|Get)Accessor functions.
By surrounding the parameter name with brackets. It is super, super ugly right now.
More to come, though. annotate is only called in annotateVariableDeclaration where we don't know whether we're in JS or not.
@@ -50,7 +47,7 @@ namespace ts.codefix { | |||
function getDiagnostic(errorCode: number, token: Node): DiagnosticMessage { | |||
switch (errorCode) { | |||
case Diagnostics.Parameter_0_implicitly_has_an_1_type.code: | |||
return isSetAccessor(getContainingFunction(token)!) ? Diagnostics.Infer_type_of_0_from_usage : Diagnostics.Infer_parameter_types_from_usage; // TODO: GH#18217 | |||
return isSetAccessorDeclaration(getContainingFunction(token)!) ? Diagnostics.Infer_type_of_0_from_usage : Diagnostics.Infer_parameter_types_from_usage; // TODO: GH#18217 |
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.
Why do we have both of these -- isSetAccessor
and isSetAccessorDeclaration
?
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.
No reason, but removing one would change the public API, so I decided not do it in this PR.
|
||
// @allowJs: true | ||
// @checkJs: true | ||
// @noImplicitAny: true |
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 think this shouldn't be necessary because we should make it a suggestion diagnostic anyway, if we don't already.
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.
Either noImplicitAny isn't triggered as a suggestion (perhaps just for JS?), or the test is only seeing codefixes from errors. Any idea which? I don't know how to tell what gets triggered as a suggestion, do you?
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.
verify.getSuggestionDiagnostics
should allow you to test that.
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.
There is one suggestion, but it's "'numeric' is declared but its value is never read.". I don't think noImplicitAny is leading to a infer-from-usage suggestion. It probably should someday, but I'd like to improve the precision and recall of it 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.
Filed #27624
|
||
verify.fileAfterCodeFix( | ||
`/** @param {number} a */ | ||
/** |
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.
Could you file this as a bug? We should combine comments, not add a new one.
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'll file it after merging the PR since the bug depends on being able to emit JSDoc in the first place.
I personally think the current approach is a good compromise and that we'll rarely have to generate partial JSDoc. The bigger lack is untyped JSDoc:
/** @param x Just a description, no type */
function f(x) {
return x * x
}
newFileContent: | ||
`export class C { | ||
/** | ||
* @param {Promise<typeof import("/a")>} val |
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 think this should be "./a"
when we synthesize the import. Doesn't look so bad in this example but in a real example this might import from /home/myname/projects/typescript/someproject/src/components/a
.
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.
Wow, nice catch. I asked Wesley and he said that getTypeNodeIfAccessible
needs to provide more members to the symbol tracker host (from Program and LanguageServiceHost). It sounds fairly simple, but it's not related to this change, so I'll put it in a separate PR.
Fixes #20113 |
src/services/textChanges.ts
Outdated
@@ -365,6 +394,27 @@ namespace ts.textChanges { | |||
this.insertNodeAt(sourceFile, endNode.end, type, { prefix: ": " }); | |||
} | |||
|
|||
public tryInsertJSDocType(sourceFile: SourceFile, node: Node, type: TypeNode): void { | |||
const printed = createPrinter().printNode(EmitHint.Unspecified, type, sourceFile); |
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.
Think this could also use getNonformattedText
.
@@ -339,6 +345,12 @@ namespace ts.textChanges { | |||
this.insertText(sourceFile, token.getStart(sourceFile), text); | |||
} | |||
|
|||
public insertCommentThenNewline(sourceFile: SourceFile, character: number, position: number, commentText: string): void { | |||
const token = getTouchingToken(sourceFile, position); | |||
const text = "/**" + commentText + "*/" + this.newLineCharacter + repeatString(" ", character); |
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.
" " [](start = 91, length = 3)
Will the formatting done after the fix is applied convert this to tabs if that's what the user prefers?
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 we do for other fixes is to copy exactly the indentation string that they were using -- if their preferred indentation is tab, space, space, tab, space, space, we should copy that. I'll make a new PR unifying our comment code since #27565 has its own separate jsdoc-adding helpers.
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.
There are cases where we need to make to new indentation, though, as in the single-line test case that needs to annotate class C {m(x) {return x;}}
commentText += this.printJSDocParameter(indent, printed, declaration.name, isOptional); | ||
} | ||
} | ||
commentText += repeatString(" ", indent + 1); |
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.
commentText += repeatString(" ", indent + 1); [](start = 12, length = 45)
This seems like it belongs in insertCommentThenNewline
(which would then either need to check for newlines in the text or accept a flag indicating whether or not the */
should be indented).
if (isOptionalParameter) { | ||
printName = `[${printName}]`; | ||
} | ||
return repeatString(" ", indent) + ` * @param {${printed}} ${printName}\n`; |
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.
repeatString(" ", indent) [](start = 19, length = 25)
Having these scattered through the code seems fragile. What about passing an array of lines to insertCommentThenNewline
and letting it handle breaks and indentation?
@@ -806,7 +856,7 @@ namespace ts.textChanges { | |||
} | |||
|
|||
/** Note: output node may be mutated input node. */ | |||
function getNonformattedText(node: Node, sourceFile: SourceFile | undefined, newLineCharacter: string): { text: string, node: Node } { | |||
export function getNonformattedText(node: Node, sourceFile: SourceFile | undefined, newLineCharacter: string): { text: string, node: Node } { |
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.
export [](start = 8, length = 6)
Why is this exported?
@@ -365,6 +394,27 @@ namespace ts.textChanges { | |||
this.insertNodeAt(sourceFile, endNode.end, type, { prefix: ": " }); | |||
} | |||
|
|||
public tryInsertJSDocType(sourceFile: SourceFile, node: Node, type: TypeNode): void { |
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.
tryInsertJSDocType [](start = 15, length = 18)
I feel like I'm missing something obvious, but why "try"?
changes.tryInsertJSDocParameters(sourceFile, result); | ||
} | ||
|
||
function getTypeNodeIfAccessible(type: Type, enclosingScope: Node, program: Program, host: LanguageServiceHost): TypeNode | undefined { |
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.
[](start = 0, length = 3)
This looks unintentional.
@@ -274,8 +312,10 @@ namespace ts.codefix { | |||
return callContexts && declaration.parameters.map((parameter, parameterIndex) => { | |||
const types: Type[] = []; | |||
const isRest = isRestParameter(parameter); | |||
let isOptional = false; |
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.
isOptional [](start = 20, length = 10)
I don't understand this flag. I'll drop by.
for (const callContext of callContexts) { | ||
if (callContext.argumentTypes.length <= parameterIndex) { | ||
isOptional = isInJSFile(declaration); |
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.
isInJSFile [](start = 37, length = 10)
I assume isInJSFile
is very cheap but I would habitually have either pulled it out of the loop or checked isOptional ||
.
inferFromUsage now emits JSDoc. It emits
@type
by default, but@param
for parameters and@return
for get accessors. It does not emit@return
type annotations because the codefix itself does not. (It probably should, but I'll address that in a followup PR.)For example:
Gives the annotation
Currently, only
@param
blocks are multi-line.Note that I generate strings for JSDoc. I don't know enough about printing trivia to know whether it's worthwhile to generate a structure and then print it.