diff --git a/src/compiler/transformers/declarations/diagnostics.ts b/src/compiler/transformers/declarations/diagnostics.ts index 53b5750a1edd0..94d6994f8d2ef 100644 --- a/src/compiler/transformers/declarations/diagnostics.ts +++ b/src/compiler/transformers/declarations/diagnostics.ts @@ -466,4 +466,4 @@ namespace ts { }; } } -} \ No newline at end of file +} diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index ccd7aa1d067b8..dc28871d1c0ea 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -2516,10 +2516,10 @@ Actual: ${stringify(fullActual)}`); * @param expectedContents The contents of the file after the fixes are applied. * @param fileName The file to check. If not supplied, the current open file is used. */ - public verifyFileAfterCodeFix(expectedContents: string, fileName?: string) { + public verifyFileAfterCodeFix(expectedContents: string, fileName?: string, index?: number) { fileName = fileName ? fileName : this.activeFile.fileName; - this.applyCodeActions(this.getCodeFixes(fileName)); + this.applyCodeActions(this.getCodeFixes(fileName), index); const actualContents: string = this.getFileContent(fileName); if (this.removeWhitespace(actualContents) !== this.removeWhitespace(expectedContents)) { @@ -4388,6 +4388,10 @@ namespace FourSlashInterface { this.state.verifyRangeAfterCodeFix(expectedText, includeWhiteSpace, errorCode, index); } + public fileAfterCodeFix(expectedContents: string, fileName?: string, index?: number) { + this.state.verifyFileAfterCodeFix(expectedContents, fileName, index); + } + public codeFixAll(options: VerifyCodeFixAllOptions): void { this.state.verifyCodeFixAll(options); } diff --git a/src/services/codefixes/inferFromUsage.ts b/src/services/codefixes/inferFromUsage.ts index a8ec514c549a1..62f73df9f80b8 100644 --- a/src/services/codefixes/inferFromUsage.ts +++ b/src/services/codefixes/inferFromUsage.ts @@ -26,9 +26,6 @@ namespace ts.codefix { errorCodes, getCodeActions(context) { const { sourceFile, program, span: { start }, errorCode, cancellationToken, host } = context; - if (isSourceFileJS(sourceFile)) { - return undefined; // TODO: GH#20113 - } const token = getTokenAtPosition(sourceFile, start); let declaration!: Declaration | undefined; @@ -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 case Diagnostics.Rest_parameter_0_implicitly_has_an_any_type.code: return Diagnostics.Infer_parameter_types_from_usage; default: @@ -59,7 +56,7 @@ namespace ts.codefix { } function doChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, token: Node, errorCode: number, program: Program, cancellationToken: CancellationToken, markSeen: NodeSeenTracker, host: LanguageServiceHost): Declaration | undefined { - if (!isParameterPropertyModifier(token.kind) && token.kind !== SyntaxKind.Identifier && token.kind !== SyntaxKind.DotDotDotToken) { + if (!isParameterPropertyModifier(token.kind) && token.kind !== SyntaxKind.Identifier && token.kind !== SyntaxKind.DotDotDotToken && token.kind !== SyntaxKind.ThisKeyword) { return undefined; } @@ -72,6 +69,14 @@ namespace ts.codefix { annotateVariableDeclaration(changes, sourceFile, parent, program, host, cancellationToken); return parent; } + if (isPropertyAccessExpression(parent)) { + const type = inferTypeForVariableFromUsage(parent.name, program, cancellationToken); + const typeNode = type && getTypeNodeIfAccessible(type, parent, program, host); + if (typeNode) { + changes.tryInsertJSDocType(sourceFile, parent, typeNode); + } + return parent; + } return undefined; case Diagnostics.Variable_0_implicitly_has_an_1_type.code: { @@ -92,7 +97,7 @@ namespace ts.codefix { switch (errorCode) { // Parameter declarations case Diagnostics.Parameter_0_implicitly_has_an_1_type.code: - if (isSetAccessor(containingFunction)) { + if (isSetAccessorDeclaration(containingFunction)) { annotateSetAccessor(changes, sourceFile, containingFunction, program, host, cancellationToken); return containingFunction; } @@ -108,7 +113,7 @@ namespace ts.codefix { // Get Accessor declarations case Diagnostics.Property_0_implicitly_has_type_any_because_its_get_accessor_lacks_a_return_type_annotation.code: case Diagnostics._0_which_lacks_return_type_annotation_implicitly_has_an_1_return_type.code: - if (isGetAccessor(containingFunction) && isIdentifier(containingFunction.name)) { + if (isGetAccessorDeclaration(containingFunction) && isIdentifier(containingFunction.name)) { annotate(changes, sourceFile, containingFunction, inferTypeForVariableFromUsage(containingFunction.name, program, cancellationToken), program, host); return containingFunction; } @@ -116,7 +121,7 @@ namespace ts.codefix { // Set Accessor declarations case Diagnostics.Property_0_implicitly_has_type_any_because_its_set_accessor_lacks_a_parameter_type_annotation.code: - if (isSetAccessor(containingFunction)) { + if (isSetAccessorDeclaration(containingFunction)) { annotateSetAccessor(changes, sourceFile, containingFunction, program, host, cancellationToken); return containingFunction; } @@ -150,18 +155,23 @@ namespace ts.codefix { return; } - const types = inferTypeForParametersFromUsage(containingFunction, sourceFile, program, cancellationToken) || - containingFunction.parameters.map(p => isIdentifier(p.name) ? inferTypeForVariableFromUsage(p.name, program, cancellationToken) : undefined); - // We didn't actually find a set of type inference positions matching each parameter position - if (!types || containingFunction.parameters.length !== types.length) { - return; - } + const parameterInferences = inferTypeForParametersFromUsage(containingFunction, sourceFile, program, cancellationToken) || + containingFunction.parameters.map(p => ({ + declaration: p, + type: isIdentifier(p.name) ? inferTypeForVariableFromUsage(p.name, program, cancellationToken) : undefined + })); + Debug.assert(containingFunction.parameters.length === parameterInferences.length); - zipWith(containingFunction.parameters, types, (parameter, type) => { - if (!parameter.type && !parameter.initializer) { - annotate(changes, sourceFile, parameter, type, program, host); + if (isInJSFile(containingFunction)) { + annotateJSDocParameters(changes, sourceFile, parameterInferences, program, host); + } + else { + for (const { declaration, type } of parameterInferences) { + if (declaration && !declaration.type && !declaration.initializer) { + annotate(changes, sourceFile, declaration, type, program, host); + } } - }); + } } function annotateSetAccessor(changes: textChanges.ChangeTracker, sourceFile: SourceFile, setAccessorDeclaration: SetAccessorDeclaration, program: Program, host: LanguageServiceHost, cancellationToken: CancellationToken): void { @@ -169,16 +179,37 @@ namespace ts.codefix { if (param && isIdentifier(setAccessorDeclaration.name) && isIdentifier(param.name)) { const type = inferTypeForVariableFromUsage(setAccessorDeclaration.name, program, cancellationToken) || inferTypeForVariableFromUsage(param.name, program, cancellationToken); - annotate(changes, sourceFile, param, type, program, host); + if (isInJSFile(setAccessorDeclaration)) { + annotateJSDocParameters(changes, sourceFile, [{ declaration: param, type }], program, host); + } + else { + annotate(changes, sourceFile, param, type, program, host); + } } } function annotate(changes: textChanges.ChangeTracker, sourceFile: SourceFile, declaration: textChanges.TypeAnnotatable, type: Type | undefined, program: Program, host: LanguageServiceHost): void { const typeNode = type && getTypeNodeIfAccessible(type, declaration, program, host); - if (typeNode) changes.tryInsertTypeAnnotation(sourceFile, declaration, typeNode); + if (typeNode) { + if (isInJSFile(sourceFile) && declaration.kind !== SyntaxKind.PropertySignature) { + changes.tryInsertJSDocType(sourceFile, declaration, typeNode); + } + else { + changes.tryInsertTypeAnnotation(sourceFile, declaration, typeNode); + } + } } - function getTypeNodeIfAccessible(type: Type, enclosingScope: Node, program: Program, host: LanguageServiceHost): TypeNode | undefined { + function annotateJSDocParameters(changes: textChanges.ChangeTracker, sourceFile: SourceFile, parameterInferences: ParameterInference[], program: Program, host: LanguageServiceHost): void { + const result = mapDefined(parameterInferences, inference => { + const param = inference.declaration; + const typeNode = inference.type && getTypeNodeIfAccessible(inference.type, param, program, host); + return typeNode && !param.initializer && !getJSDocType(param) ? { ...inference, typeNode } : undefined; + }); + changes.tryInsertJSDocParameters(sourceFile, result); + } + + function getTypeNodeIfAccessible(type: Type, enclosingScope: Node, program: Program, host: LanguageServiceHost): TypeNode | undefined { const checker = program.getTypeChecker(); let typeIsAccessible = true; const notAccessible = () => { typeIsAccessible = false; }; @@ -212,7 +243,7 @@ namespace ts.codefix { return InferFromReference.inferTypeFromReferences(getReferences(token, program, cancellationToken), program.getTypeChecker(), cancellationToken); } - function inferTypeForParametersFromUsage(containingFunction: FunctionLikeDeclaration, sourceFile: SourceFile, program: Program, cancellationToken: CancellationToken): (Type | undefined)[] | undefined { + function inferTypeForParametersFromUsage(containingFunction: FunctionLikeDeclaration, sourceFile: SourceFile, program: Program, cancellationToken: CancellationToken): ParameterInference[] | undefined { switch (containingFunction.kind) { case SyntaxKind.Constructor: case SyntaxKind.FunctionExpression: @@ -228,6 +259,13 @@ namespace ts.codefix { } } + interface ParameterInference { + declaration: ParameterDeclaration; + type?: Type; + typeNode?: TypeNode; + isOptional?: boolean; + } + namespace InferFromReference { interface CallContext { argumentTypes: Type[]; @@ -255,7 +293,7 @@ namespace ts.codefix { return getTypeFromUsageContext(usageContext, checker); } - export function inferTypeForParametersFromReferences(references: ReadonlyArray, declaration: FunctionLikeDeclaration, checker: TypeChecker, cancellationToken: CancellationToken): (Type | undefined)[] | undefined { + export function inferTypeForParametersFromReferences(references: ReadonlyArray, declaration: FunctionLikeDeclaration, checker: TypeChecker, cancellationToken: CancellationToken): ParameterInference[] | undefined { if (references.length === 0) { return undefined; } @@ -274,8 +312,10 @@ namespace ts.codefix { return callContexts && declaration.parameters.map((parameter, parameterIndex) => { const types: Type[] = []; const isRest = isRestParameter(parameter); + let isOptional = false; for (const callContext of callContexts) { if (callContext.argumentTypes.length <= parameterIndex) { + isOptional = isInJSFile(declaration); continue; } @@ -289,10 +329,14 @@ namespace ts.codefix { } } if (!types.length) { - return undefined; + return { declaration: parameter }; } const type = checker.getWidenedType(checker.getUnionType(types, UnionReduction.Subtype)); - return isRest ? checker.createArrayType(type) : type; + return { + type: isRest ? checker.createArrayType(type) : type, + isOptional: isOptional && !isRest, + declaration: parameter + }; }); } diff --git a/src/services/textChanges.ts b/src/services/textChanges.ts index abd153f0bacd7..0d45039f5b181 100644 --- a/src/services/textChanges.ts +++ b/src/services/textChanges.ts @@ -209,6 +209,12 @@ namespace ts.textChanges { export type TypeAnnotatable = SignatureDeclaration | VariableDeclaration | ParameterDeclaration | PropertyDeclaration | PropertySignature; + interface JSDocParameter { + declaration: ParameterDeclaration; + typeNode: TypeNode; + isOptional?: boolean; + } + export class ChangeTracker { private readonly changes: Change[] = []; private readonly newFiles: { readonly oldFile: SourceFile | undefined, readonly fileName: string, readonly statements: ReadonlyArray }[] = []; @@ -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); + this.insertText(sourceFile, token.getStart(sourceFile), text); + } + public replaceRangeWithText(sourceFile: SourceFile, range: TextRange, text: string) { this.changes.push({ kind: ChangeKind.Text, sourceFile, range, text }); } @@ -347,6 +359,23 @@ namespace ts.textChanges { this.replaceRangeWithText(sourceFile, createRange(pos), text); } + public tryInsertJSDocParameters(sourceFile: SourceFile, parameters: JSDocParameter[]) { + if (parameters.length === 0) { + return; + } + const parent = parameters[0].declaration.parent; + const indent = getLineAndCharacterOfPosition(sourceFile, parent.getStart()).character; + let commentText = "\n"; + for (const { declaration, typeNode, isOptional } of parameters) { + if (isIdentifier(declaration.name)) { + const printed = changesToText.getNonformattedText(typeNode, sourceFile, this.newLineCharacter).text; + commentText += this.printJSDocParameter(indent, printed, declaration.name, isOptional); + } + } + commentText += repeatString(" ", indent + 1); + this.insertCommentThenNewline(sourceFile, indent, parent.getStart(), commentText); + } + /** Prefer this over replacing a node with another that has a type annotation, as it avoids reformatting the other parts of the node. */ public tryInsertTypeAnnotation(sourceFile: SourceFile, node: TypeAnnotatable, type: TypeNode): void { let endNode: Node | undefined; @@ -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 = changesToText.getNonformattedText(type, sourceFile, this.newLineCharacter).text; + let commentText; + if (isGetAccessorDeclaration(node)) { + commentText = ` @return {${printed}} `; + } + else { + commentText = ` @type {${printed}} `; + node = node.parent; + } + this.insertCommentThenNewline(sourceFile, getLineAndCharacterOfPosition(sourceFile, node.getStart(sourceFile)).character, node.getStart(sourceFile), commentText); + } + + private printJSDocParameter(indent: number, printed: string, name: Identifier, isOptionalParameter: boolean | undefined) { + let printName = unescapeLeadingUnderscores(name.escapedText); + if (isOptionalParameter) { + printName = `[${printName}]`; + } + return repeatString(" ", indent) + ` * @param {${printed}} ${printName}\n`; + } + public insertTypeParameters(sourceFile: SourceFile, node: SignatureDeclaration, typeParameters: ReadonlyArray): void { // If no `(`, is an arrow function `x => x`, so use the pos of the first parameter const start = (findChildOfKind(node, SyntaxKind.OpenParenToken, sourceFile) || first(node.parameters)).getStart(sourceFile); @@ -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 } { const writer = new Writer(newLineCharacter); const newLine = newLineCharacter === "\n" ? NewLineKind.LineFeed : NewLineKind.CarriageReturnLineFeed; createPrinter({ newLine, neverAsciiEscape: true }, writer).writeNode(EmitHint.Unspecified, node, sourceFile, writer); diff --git a/tests/cases/fourslash/codeFixInferFromUsageCallJS.ts b/tests/cases/fourslash/codeFixInferFromUsageCallJS.ts new file mode 100644 index 0000000000000..fa7eb5ee67130 --- /dev/null +++ b/tests/cases/fourslash/codeFixInferFromUsageCallJS.ts @@ -0,0 +1,20 @@ +/// + +// @allowJs: true +// @checkJs: true +// @noImplicitAny: true +// @Filename: test.js +////function wat(b) { +//// b(); +////} + +verify.codeFixAll({ + fixId: "inferFromUsage", + fixAllDescription: "Infer all types from usage", + newFileContent: +`/** + * @param {() => void} b + */ +function wat(b) { + b(); +}`}); diff --git a/tests/cases/fourslash/codeFixInferFromUsageJS.ts b/tests/cases/fourslash/codeFixInferFromUsageJS.ts new file mode 100644 index 0000000000000..62c9d1bf90b08 --- /dev/null +++ b/tests/cases/fourslash/codeFixInferFromUsageJS.ts @@ -0,0 +1,12 @@ +/// + +// @allowJs: true +// @checkJs: true +// @noImplicitAny: true +// @Filename: test.js +////[|var foo;|] +////function f() { +//// foo += 2; +////} + +verify.rangeAfterCodeFix("/** @type {number} */\nvar foo;",/*includeWhiteSpace*/ undefined, /*errorCode*/ undefined, 2); diff --git a/tests/cases/fourslash/codeFixInferFromUsageMember2JS.ts b/tests/cases/fourslash/codeFixInferFromUsageMember2JS.ts new file mode 100644 index 0000000000000..dc52286a9063e --- /dev/null +++ b/tests/cases/fourslash/codeFixInferFromUsageMember2JS.ts @@ -0,0 +1,14 @@ +/// + +// @allowJs: true +// @checkJs: true +// @noImplicitAny: true +// @Filename: important.js + +/////** @typedef {{ [|p |]}} I */ +/////** @type {I} */ +////var i; +////i.p = 0; + + +verify.rangeAfterCodeFix("p: number", undefined, undefined, 1); diff --git a/tests/cases/fourslash/codeFixInferFromUsageMemberJS.ts b/tests/cases/fourslash/codeFixInferFromUsageMemberJS.ts new file mode 100644 index 0000000000000..cb86347cb9f1e --- /dev/null +++ b/tests/cases/fourslash/codeFixInferFromUsageMemberJS.ts @@ -0,0 +1,30 @@ +/// + +// @allowJs: true +// @checkJs: true +// @noImplicitAny: true +// @strictNullChecks: true +// @Filename: important.js +////class C { +//// constructor() { +//// [|this.p|] = undefined; +//// } +//// method() { +//// this.p.push(1) +//// } +////} + + +// Note: Should be number[] | undefined, but inference currently privileges assignments +// over usage (even when the only result is undefined) and infers only undefined. +verify.fileAfterCodeFix( +`class C { + constructor() { + /** @type {undefined} */ + this.p = undefined; + } + method() { + this.p.push(1) + } +} +`, undefined, 2); diff --git a/tests/cases/fourslash/codeFixInferFromUsageMultipleParametersJS.ts b/tests/cases/fourslash/codeFixInferFromUsageMultipleParametersJS.ts new file mode 100644 index 0000000000000..8f544df432369 --- /dev/null +++ b/tests/cases/fourslash/codeFixInferFromUsageMultipleParametersJS.ts @@ -0,0 +1,23 @@ +/// + +// @allowJs: true +// @checkJs: true +// @noImplicitAny: true +// @Filename: important.js + +//// function f([|a, b, c, d, e = 0, ...d |]) { +//// } +//// f(1, "string", { a: 1 }, {shouldNotBeHere: 2}, {shouldNotBeHere: 2}, 3, "string"); + + +verify.fileAfterCodeFix( +`/** + * @param {number} a + * @param {string} b + * @param {{ a: number; }} c + * @param {{ shouldNotBeHere: number; }} d + * @param {(string | number)[]} d + */ +function f(a, b, c, d, e = 0, ...d ) { +} +f(1, "string", { a: 1 }, {shouldNotBeHere: 2}, {shouldNotBeHere: 2}, 3, "string");`, undefined, 6); diff --git a/tests/cases/fourslash/codeFixInferFromUsageNumberIndexSignatureJS.ts b/tests/cases/fourslash/codeFixInferFromUsageNumberIndexSignatureJS.ts new file mode 100644 index 0000000000000..b6b8fe4c25fc8 --- /dev/null +++ b/tests/cases/fourslash/codeFixInferFromUsageNumberIndexSignatureJS.ts @@ -0,0 +1,17 @@ +/// +// @allowJs: true +// @checkJs: true +// @noImplicitAny: true +// @Filename: important.js + +////function f([|a|]) { +//// return a[0] + 1; +////} + +verify.fileAfterCodeFix( +`/** + * @param {number[]} a + */ +function f(a) { + return a[0] + 1; +}`, undefined, 2); diff --git a/tests/cases/fourslash/codeFixInferFromUsageOptionalParamJS.ts b/tests/cases/fourslash/codeFixInferFromUsageOptionalParamJS.ts new file mode 100644 index 0000000000000..cc00c1ddb5d75 --- /dev/null +++ b/tests/cases/fourslash/codeFixInferFromUsageOptionalParamJS.ts @@ -0,0 +1,21 @@ +/// + +// @allowJs: true +// @checkJs: true +// @noImplicitAny: true +// @Filename: important.js +////function f([|a|]){ +//// a; +////} +////f(); +////f(1); + +verify.fileAfterCodeFix( +`/** + * @param {number} [a] + */ +function f(a) { + a; +} +f(); +f(1);`, undefined, 2); diff --git a/tests/cases/fourslash/codeFixInferFromUsagePartialParameterListJS.ts b/tests/cases/fourslash/codeFixInferFromUsagePartialParameterListJS.ts new file mode 100644 index 0000000000000..67795b6a17818 --- /dev/null +++ b/tests/cases/fourslash/codeFixInferFromUsagePartialParameterListJS.ts @@ -0,0 +1,29 @@ +/// + +// @allowJs: true +// @checkJs: true +// @noImplicitAny: true +// @strictNullChecks: true +// @Filename: important.js +/////** +//// * @param {*} y +//// */ +////function f(x, y, z) { +//// return x +////} +////f(1, 2, 3) + +verify.fileAfterCodeFix( +` +/** + * @param {*} y + */ +/** + * @param {number} x + * @param {number} z + */ +function f(x, y, z) { + return x +} +f(1, 2, 3) +`, undefined, 2); diff --git a/tests/cases/fourslash/codeFixInferFromUsagePropertyAccessJS.ts b/tests/cases/fourslash/codeFixInferFromUsagePropertyAccessJS.ts new file mode 100644 index 0000000000000..a52c66312fc00 --- /dev/null +++ b/tests/cases/fourslash/codeFixInferFromUsagePropertyAccessJS.ts @@ -0,0 +1,34 @@ +/// + +// @allowJs: true +// @checkJs: true +// @noImplicitAny: true +// @Filename: important.js + +////function foo([|a, m, x|]) { +//// a.b.c; +//// +//// var numeric = 0; +//// numeric = m.n(); +//// +//// x.y.z +//// x.y.z.push(0); +//// return x.y.z +////} + +verify.fileAfterCodeFix( +`/** + * @param {{ b: { c: any; }; }} a + * @param {{ n: () => number; }} m + * @param {{ y: { z: number[]; }; }} x + */ +function foo(a, m, x) { + a.b.c; + + var numeric = 0; + numeric = m.n(); + + x.y.z + x.y.z.push(0); + return x.y.z +}`, undefined, 2); diff --git a/tests/cases/fourslash/codeFixInferFromUsageRestParam2JS.ts b/tests/cases/fourslash/codeFixInferFromUsageRestParam2JS.ts new file mode 100644 index 0000000000000..0504816c59724 --- /dev/null +++ b/tests/cases/fourslash/codeFixInferFromUsageRestParam2JS.ts @@ -0,0 +1,27 @@ +/// + +// @allowJs: true +// @checkJs: true +// @noImplicitAny: true +// @Filename: important.js +/////** @param {number} a */ +////function f(a, [|...rest|]){ +//// a; rest; +////} +////f(1); +////f(2, "s1"); +////f(3, false, "s2"); +////f(4, "s1", "s2", false, "s4"); + +verify.fileAfterCodeFix( +`/** @param {number} a */ +/** + * @param {(string | boolean)[]} rest + */ +function f(a, ...rest){ + a; rest; +} +f(1); +f(2, "s1"); +f(3, false, "s2"); +f(4, "s1", "s2", false, "s4");`, undefined, 2); diff --git a/tests/cases/fourslash/codeFixInferFromUsageRestParam3JS.ts b/tests/cases/fourslash/codeFixInferFromUsageRestParam3JS.ts new file mode 100644 index 0000000000000..bda7090ba06d3 --- /dev/null +++ b/tests/cases/fourslash/codeFixInferFromUsageRestParam3JS.ts @@ -0,0 +1,21 @@ +/// + +// @allowJs: true +// @checkJs: true +// @noImplicitAny: true +// @Filename: important.js +/////** @param {number} a */ +////function f(a, [|...rest |]){ +//// a; +//// rest.push(22); +////} + +verify.fileAfterCodeFix( +`/** @param {number} a */ +/** + * @param {number[]} rest + */ +function f(a, ...rest){ + a; + rest.push(22); +}`, undefined, 2); diff --git a/tests/cases/fourslash/codeFixInferFromUsageRestParamJS.ts b/tests/cases/fourslash/codeFixInferFromUsageRestParamJS.ts new file mode 100644 index 0000000000000..36582bfa7dbcb --- /dev/null +++ b/tests/cases/fourslash/codeFixInferFromUsageRestParamJS.ts @@ -0,0 +1,27 @@ +/// + +// @allowJs: true +// @checkJs: true +// @noImplicitAny: true +// @Filename: important.js +/////** @param {number} a */ +////function f(a: number, [|...rest|]){ +//// a; rest; +////} +////f(1); +////f(2, "s1"); +////f(3, "s1", "s2"); +////f(3, "s1", "s2", "s3", "s4"); + +verify.fileAfterCodeFix( +`/** @param {number} a */ +/** + * @param {string[]} rest + */ +function f(a: number, ...rest){ + a; rest; +} +f(1); +f(2, "s1"); +f(3, "s1", "s2"); +f(3, "s1", "s2", "s3", "s4");`, undefined, 4); diff --git a/tests/cases/fourslash/codeFixInferFromUsageSetter2.ts b/tests/cases/fourslash/codeFixInferFromUsageSetter2.ts deleted file mode 100644 index d058bc2787c7d..0000000000000 --- a/tests/cases/fourslash/codeFixInferFromUsageSetter2.ts +++ /dev/null @@ -1,11 +0,0 @@ -/// - -// @noImplicitAny: true -////class C { -//// set [|x(v)|] { -//// v; -//// } -////} -////(new C).x = 1; - -verify.rangeAfterCodeFix("x(v: number)", undefined, undefined, 1); \ No newline at end of file diff --git a/tests/cases/fourslash/codeFixInferFromUsageSetterJS.ts b/tests/cases/fourslash/codeFixInferFromUsageSetterJS.ts new file mode 100644 index 0000000000000..71621f48239d3 --- /dev/null +++ b/tests/cases/fourslash/codeFixInferFromUsageSetterJS.ts @@ -0,0 +1,24 @@ +/// + +// @allowJs: true +// @checkJs: true +// @noImplicitAny: true +// @Filename: important.js +////class C { +//// set [|x(v)|] { +//// v; +//// } +////} +////(new C).x = 1; + +verify.fileAfterCodeFix( +` +class C { + /** + * @param {number} v + */ + set x(v) { + v; + } +} +(new C).x = 1;`, undefined, 2); diff --git a/tests/cases/fourslash/codeFixInferFromUsageSetterWithInaccessibleTypeJS.ts b/tests/cases/fourslash/codeFixInferFromUsageSetterWithInaccessibleTypeJS.ts new file mode 100644 index 0000000000000..7620b969234ac --- /dev/null +++ b/tests/cases/fourslash/codeFixInferFromUsageSetterWithInaccessibleTypeJS.ts @@ -0,0 +1,36 @@ +/// + +// @allowJs: true +// @checkJs: true +// @noEmit: true +// @noImplicitAny: true + +// @Filename: /promise.d.ts +////interface Promise { +////} +////declare var Promise: Promise; + +// @Filename: /a.js +////export class D {} +////export default new D(); + +// @Filename: /b.js +////export class C { +//// set [|x|](val) { val; } +//// method() { this.x = import("./a"); } +////} + +goTo.file("/b.js"); +verify.codeFix({ + index: 2, + description: "Infer type of 'x' from usage", + newFileContent: +`export class C { + /** + * @param {Promise} val + */ + set x(val) { val; } + method() { this.x = import("./a"); } +}`, +}); + diff --git a/tests/cases/fourslash/codeFixInferFromUsageSingleLineClassJS.ts b/tests/cases/fourslash/codeFixInferFromUsageSingleLineClassJS.ts new file mode 100644 index 0000000000000..f78a70aa14abf --- /dev/null +++ b/tests/cases/fourslash/codeFixInferFromUsageSingleLineClassJS.ts @@ -0,0 +1,19 @@ +/// + +// @allowJs: true +// @checkJs: true +// @noEmit: true +// @noImplicitAny: true +// @Filename: important.js +////class C {m(x) {return x;}} +////var c = new C() +////c.m(1) + +verify.fileAfterCodeFix( +` +class C {/** + * @param {number} x + */ + m(x) {return x;}} +var c = new C() +c.m(1)`, undefined, 2); diff --git a/tests/cases/fourslash/codeFixInferFromUsageStringIndexSignatureJS.ts b/tests/cases/fourslash/codeFixInferFromUsageStringIndexSignatureJS.ts new file mode 100644 index 0000000000000..5182e80969e91 --- /dev/null +++ b/tests/cases/fourslash/codeFixInferFromUsageStringIndexSignatureJS.ts @@ -0,0 +1,23 @@ +/// + +// @allowJs: true +// @checkJs: true +// @noEmit: true +// @noImplicitAny: true +// @Filename: important.js + +////function f([|a|]) { +//// return a['hi']; +////} + +verify.codeFix({ + index: 2, + description: "Infer parameter types from usage", + newFileContent: +`/** + * @param {{ [x: string]: any; }} a + */ +function f(a) { + return a['hi']; +}`, +}); diff --git a/tests/cases/fourslash/codeFixInferFromUsageVariable2JS.ts b/tests/cases/fourslash/codeFixInferFromUsageVariable2JS.ts new file mode 100644 index 0000000000000..0e88a30b96bee --- /dev/null +++ b/tests/cases/fourslash/codeFixInferFromUsageVariable2JS.ts @@ -0,0 +1,18 @@ +/// + +// @allowJs: true +// @checkJs: true +// @noEmit: true +// @noImplicitAny: true +// @Filename: important.js +////[|var x; +////function f() { +//// x++; +////}|] + +verify.rangeAfterCodeFix(`/** @type {number} */ +var x; +function f() { + x++; +} +`, /*includeWhiteSpace*/ undefined, /*errorCode*/ undefined, 2); diff --git a/tests/cases/fourslash/codeFixInferFromUsageVariableJS.ts b/tests/cases/fourslash/codeFixInferFromUsageVariableJS.ts new file mode 100644 index 0000000000000..8e96fd0891616 --- /dev/null +++ b/tests/cases/fourslash/codeFixInferFromUsageVariableJS.ts @@ -0,0 +1,14 @@ +/// + +// @allowJs: true +// @checkJs: true +// @noEmit: true +// @noImplicitAny: true +// @Filename: important.js + +////[|var x;|] +////function f() { +//// x++; +////} + +verify.rangeAfterCodeFix("/** @type {number } */\nvar x;", /*includeWhiteSpace*/ undefined, /*errorCode*/ undefined, 2); diff --git a/tests/cases/fourslash/codeFixInferFromUsage_allJS.ts b/tests/cases/fourslash/codeFixInferFromUsage_allJS.ts new file mode 100644 index 0000000000000..00e44660a9848 --- /dev/null +++ b/tests/cases/fourslash/codeFixInferFromUsage_allJS.ts @@ -0,0 +1,48 @@ +/// + +// @allowJs: true +// @checkJs: true +// @noImplicitAny: true +// @strictNullChecks: true +// @Filename: important.js + +////function f(x, y) { +//// x += 0; +//// y += ""; +////} +//// +////function g(z) { +//// return z * 2; +////} +//// +////let x = null; +////function h() { +//// if (!x) x = 2; +////} + +verify.codeFixAll({ + fixId: "inferFromUsage", + fixAllDescription: "Infer all types from usage", + newFileContent: +`/** + * @param {number} x + * @param {string} y + */ +function f(x, y) { + x += 0; + y += ""; +} + +/** + * @param {number} z + */ +function g(z) { + return z * 2; +} + +/** @type {number | null} */ +let x = null; +function h() { + if (!x) x = 2; +}`, +});