diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index bf83f697d7..7a44ac6622 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -523,6 +523,9 @@ namespace ts { }, getApparentType, getUnionType, + isTypeAssignableTo: (source, target) => { + return isTypeAssignableTo(source, target); + }, createAnonymousType, createSignature, createSymbol, diff --git a/src/compiler/types.ts b/src/compiler/types.ts index b115aa811e..b5baf7cf27 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -3315,6 +3315,7 @@ namespace ts { /* @internal */ getElementTypeOfArrayType(arrayType: Type): Type | undefined; /* @internal */ createPromiseType(type: Type): Type; + /* @internal */ isTypeAssignableTo(source: Type, target: Type): boolean; /* @internal */ createAnonymousType(symbol: Symbol, members: SymbolTable, callSignatures: Signature[], constructSignatures: Signature[], stringIndexInfo: IndexInfo | undefined, numberIndexInfo: IndexInfo | undefined): Type; /* @internal */ createSignature( declaration: SignatureDeclaration, diff --git a/src/services/codefixes/inferFromUsage.ts b/src/services/codefixes/inferFromUsage.ts index 60cd06101d..5dd3478288 100644 --- a/src/services/codefixes/inferFromUsage.ts +++ b/src/services/codefixes/inferFromUsage.ts @@ -393,6 +393,19 @@ namespace ts.codefix { function inferTypeFromReferences(program: Program, references: readonly Identifier[], cancellationToken: CancellationToken) { const checker = program.getTypeChecker(); + const builtinConstructors: { [s: string]: (t: Type) => Type } = { + string: () => checker.getStringType(), + number: () => checker.getNumberType(), + Array: t => checker.createArrayType(t), + Promise: t => checker.createPromiseType(t), + }; + const builtins = [ + checker.getStringType(), + checker.getNumberType(), + checker.createArrayType(checker.getAnyType()), + checker.createPromiseType(checker.getAnyType()), + ]; + return { single, parameters, @@ -401,26 +414,74 @@ namespace ts.codefix { interface CallUsage { argumentTypes: Type[]; - returnType: Usage; + return_: Usage; } interface Usage { - isNumber?: boolean; - isString?: boolean; + isNumber: boolean | undefined; + isString: boolean | undefined; /** Used ambiguously, eg x + ___ or object[___]; results in string | number if no other evidence exists */ - isNumberOrString?: boolean; + isNumberOrString: boolean | undefined; - candidateTypes?: Type[]; - properties?: UnderscoreEscapedMap; - calls?: CallUsage[]; - constructs?: CallUsage[]; - numberIndex?: Usage; - stringIndex?: Usage; - candidateThisTypes?: Type[]; + candidateTypes: Type[] | undefined; + properties: UnderscoreEscapedMap | undefined; + calls: CallUsage[] | undefined; + constructs: CallUsage[] | undefined; + numberIndex: Usage | undefined; + stringIndex: Usage | undefined; + candidateThisTypes: Type[] | undefined; + inferredTypes: Type[] | undefined; + } + + function createEmptyUsage(): Usage { + return { + isNumber: undefined, + isString: undefined, + isNumberOrString: undefined, + candidateTypes: undefined, + properties: undefined, + calls: undefined, + constructs: undefined, + numberIndex: undefined, + stringIndex: undefined, + candidateThisTypes: undefined, + inferredTypes: undefined, + }; + } + + function combineUsages(usages: Usage[]): Usage { + const combinedProperties = createUnderscoreEscapedMap(); + for (const u of usages) { + if (u.properties) { + u.properties.forEach((p, name) => { + if (!combinedProperties.has(name)) { + combinedProperties.set(name, []); + } + combinedProperties.get(name)!.push(p); + }); + } + } + const properties = createUnderscoreEscapedMap(); + combinedProperties.forEach((ps, name) => { + properties.set(name, combineUsages(ps)); + }); + return { + isNumber: usages.some(u => u.isNumber), + isString: usages.some(u => u.isString), + isNumberOrString: usages.some(u => u.isNumberOrString), + candidateTypes: flatMap(usages, u => u.candidateTypes) as Type[], + properties, + calls: flatMap(usages, u => u.calls) as CallUsage[], + constructs: flatMap(usages, u => u.constructs) as CallUsage[], + numberIndex: forEach(usages, u => u.numberIndex), + stringIndex: forEach(usages, u => u.stringIndex), + candidateThisTypes: flatMap(usages, u => u.candidateThisTypes) as Type[], + inferredTypes: undefined, // clear type cache + }; } function single(): Type { - return unifyFromUsage(inferTypesFromReferencesSingle(references)); + return combineTypes(inferTypesFromReferencesSingle(references)); } function parameters(declaration: FunctionLike): ParameterInference[] | undefined { @@ -428,7 +489,7 @@ namespace ts.codefix { return undefined; } - const usage: Usage = {}; + const usage = createEmptyUsage(); for (const reference of references) { cancellationToken.throwIfCancellationRequested(); calculateUsageOfNode(reference, usage); @@ -456,7 +517,7 @@ namespace ts.codefix { const inferred = inferTypesFromReferencesSingle(getReferences(parameter.name, program, cancellationToken)); types.push(...(isRest ? mapDefined(inferred, checker.getElementTypeOfArrayType) : inferred)); } - const type = unifyFromUsage(types); + const type = combineTypes(types); return { type: isRest ? checker.createArrayType(type) : type, isOptional: isOptional && !isRest, @@ -466,22 +527,22 @@ namespace ts.codefix { } function thisParameter() { - const usage: Usage = {}; + const usage = createEmptyUsage(); for (const reference of references) { cancellationToken.throwIfCancellationRequested(); calculateUsageOfNode(reference, usage); } - return unifyFromUsage(usage.candidateThisTypes || emptyArray); + return combineTypes(usage.candidateThisTypes || emptyArray); } function inferTypesFromReferencesSingle(references: readonly Identifier[]): Type[] { - const usage: Usage = {}; + const usage: Usage = createEmptyUsage(); for (const reference of references) { cancellationToken.throwIfCancellationRequested(); calculateUsageOfNode(reference, usage); } - return inferFromUsage(usage); + return inferTypes(usage); } function calculateUsageOfNode(node: Expression, usage: Usage): void { @@ -490,6 +551,9 @@ namespace ts.codefix { } switch (node.parent.kind) { + case SyntaxKind.ExpressionStatement: + addCandidateType(usage, checker.getVoidType()); + break; case SyntaxKind.PostfixUnaryExpression: usage.isNumber = true; break; @@ -632,6 +696,9 @@ namespace ts.codefix { else if (otherOperandType.flags & TypeFlags.StringLike) { usage.isString = true; } + else if (otherOperandType.flags & TypeFlags.Any) { + // do nothing, maybe we'll learn something elsewhere + } else { usage.isNumberOrString = true; } @@ -677,7 +744,7 @@ namespace ts.codefix { function inferTypeFromCallExpression(parent: CallExpression | NewExpression, usage: Usage): void { const call: CallUsage = { argumentTypes: [], - returnType: {} + return_: createEmptyUsage() }; if (parent.arguments) { @@ -686,7 +753,7 @@ namespace ts.codefix { } } - calculateUsageOfNode(parent, call.returnType); + calculateUsageOfNode(parent, call.return_); if (parent.kind === SyntaxKind.CallExpression) { (usage.calls || (usage.calls = [])).push(call); } @@ -700,7 +767,7 @@ namespace ts.codefix { if (!usage.properties) { usage.properties = createUnderscoreEscapedMap(); } - const propertyUsage = usage.properties.get(name) || { }; + const propertyUsage = usage.properties.get(name) || createEmptyUsage(); calculateUsageOfNode(parent, propertyUsage); usage.properties.set(name, propertyUsage); } @@ -712,7 +779,7 @@ namespace ts.codefix { } else { const indexType = checker.getTypeAtLocation(parent.argumentExpression); - const indexUsage = {}; + const indexUsage = createEmptyUsage(); calculateUsageOfNode(parent, indexUsage); if (indexType.flags & TypeFlags.NumberLike) { usage.numberIndex = indexUsage; @@ -752,8 +819,12 @@ namespace ts.codefix { return inferences.filter(i => toRemove.every(f => !f(i))); } - function unifyFromUsage(inferences: readonly Type[], fallback = checker.getAnyType()): Type { - if (!inferences.length) return fallback; + function combineFromUsage(usage: Usage) { + return combineTypes(inferTypes(usage)); + } + + function combineTypes(inferences: readonly Type[]): Type { + if (!inferences.length) return checker.getAnyType(); // 1. string or number individually override string | number // 2. non-any, non-void overrides any or void @@ -776,12 +847,12 @@ namespace ts.codefix { const anons = good.filter(i => checker.getObjectFlags(i) & ObjectFlags.Anonymous) as AnonymousType[]; if (anons.length) { good = good.filter(i => !(checker.getObjectFlags(i) & ObjectFlags.Anonymous)); - good.push(unifyAnonymousTypes(anons)); + good.push(combineAnonymousTypes(anons)); } - return checker.getWidenedType(checker.getUnionType(good)); + return checker.getWidenedType(checker.getUnionType(good.map(checker.getBaseTypeOfLiteralType), UnionReduction.Subtype)); } - function unifyAnonymousTypes(anons: AnonymousType[]) { + function combineAnonymousTypes(anons: AnonymousType[]) { if (anons.length === 1) { return anons[0]; } @@ -822,7 +893,7 @@ namespace ts.codefix { numberIndices.length ? checker.createIndexInfo(checker.getUnionType(numberIndices), numberIndexReadonly) : undefined); } - function inferFromUsage(usage: Usage) { + function inferTypes(usage: Usage): Type[] { const types = []; if (usage.isNumber) { @@ -834,92 +905,155 @@ namespace ts.codefix { if (usage.isNumberOrString) { types.push(checker.getUnionType([checker.getStringType(), checker.getNumberType()])); } + if (usage.numberIndex) { + types.push(checker.createArrayType(combineFromUsage(usage.numberIndex))); + } + if (usage.properties && usage.properties.size + || usage.calls && usage.calls.length + || usage.constructs && usage.constructs.length + || usage.stringIndex) { + types.push(inferStructuralType(usage)); + } types.push(...(usage.candidateTypes || []).map(t => checker.getBaseTypeOfLiteralType(t))); + types.push(...inferNamedTypesFromProperties(usage)); - if (usage.properties && hasCalls(usage.properties.get("then" as __String))) { - const paramType = getParameterTypeFromCalls(0, usage.properties.get("then" as __String)!.calls!, /*isRestParameter*/ false)!; // TODO: GH#18217 - const types = paramType.getCallSignatures().map(sig => sig.getReturnType()); - types.push(checker.createPromiseType(types.length ? checker.getUnionType(types, UnionReduction.Subtype) : checker.getAnyType())); - } - else if (usage.properties && hasCalls(usage.properties.get("push" as __String))) { - types.push(checker.createArrayType(getParameterTypeFromCalls(0, usage.properties.get("push" as __String)!.calls!, /*isRestParameter*/ false)!)); - } - - if (usage.numberIndex) { - types.push(checker.createArrayType(recur(usage.numberIndex))); - } - else if (usage.properties || usage.calls || usage.constructs || usage.stringIndex) { - const members = createUnderscoreEscapedMap(); - const callSignatures: Signature[] = []; - const constructSignatures: Signature[] = []; - let stringIndexInfo: IndexInfo | undefined; - - if (usage.properties) { - usage.properties.forEach((u, name) => { - const symbol = checker.createSymbol(SymbolFlags.Property, name); - symbol.type = recur(u); - members.set(name, symbol); - }); - } - - if (usage.calls) { - for (const call of usage.calls) { - callSignatures.push(getSignatureFromCall(call)); - } - } - - if (usage.constructs) { - for (const construct of usage.constructs) { - constructSignatures.push(getSignatureFromCall(construct)); - } - } - - if (usage.stringIndex) { - stringIndexInfo = checker.createIndexInfo(recur(usage.stringIndex), /*isReadonly*/ false); - } - - types.push(checker.createAnonymousType(/*symbol*/ undefined!, members, callSignatures, constructSignatures, stringIndexInfo, /*numberIndexInfo*/ undefined)); // TODO: GH#18217 - } return types; - - function recur(innerUsage: Usage): Type { - return unifyFromUsage(inferFromUsage(innerUsage)); - } } - function getParameterTypeFromCalls(parameterIndex: number, calls: CallUsage[], isRestParameter: boolean) { - let types: Type[] = []; - if (calls) { - for (const call of calls) { - if (call.argumentTypes.length > parameterIndex) { - if (isRestParameter) { - types = concatenate(types, map(call.argumentTypes.slice(parameterIndex), a => checker.getBaseTypeOfLiteralType(a))); - } - else { - types.push(checker.getBaseTypeOfLiteralType(call.argumentTypes[parameterIndex])); + function inferStructuralType(usage: Usage) { + const members = createUnderscoreEscapedMap(); + if (usage.properties) { + usage.properties.forEach((u, name) => { + const symbol = checker.createSymbol(SymbolFlags.Property, name); + symbol.type = combineFromUsage(u); + members.set(name, symbol); + }); + } + const callSignatures: Signature[] = usage.calls ? [getSignatureFromCalls(usage.calls)] : []; + const constructSignatures: Signature[] = usage.constructs ? [getSignatureFromCalls(usage.constructs)] : []; + const stringIndexInfo = usage.stringIndex && checker.createIndexInfo(combineFromUsage(usage.stringIndex), /*isReadonly*/ false); + return checker.createAnonymousType(/*symbol*/ undefined!, members, callSignatures, constructSignatures, stringIndexInfo, /*numberIndexInfo*/ undefined); // TODO: GH#18217 + } + + function inferNamedTypesFromProperties(usage: Usage): Type[] { + if (!usage.properties || !usage.properties.size) return []; + const types = builtins.filter(t => allPropertiesAreAssignableToUsage(t, usage)); + if (0 < types.length && types.length < 3) { + return types.map(t => inferInstantiationFromUsage(t, usage)); + } + return []; + } + + function allPropertiesAreAssignableToUsage(type: Type, usage: Usage) { + if (!usage.properties) return false; + return !forEachEntry(usage.properties, (propUsage, name) => { + const source = checker.getTypeOfPropertyOfType(type, name as string); + if (!source) { + return true; + } + if (propUsage.calls) { + const sigs = checker.getSignaturesOfType(source, SignatureKind.Call); + return !sigs.length || !checker.isTypeAssignableTo(source, getFunctionFromCalls(propUsage.calls)); + } + else { + return !checker.isTypeAssignableTo(source, combineFromUsage(propUsage)); + } + }); + } + + /** + * inference is limited to + * 1. generic types with a single parameter + * 2. inference to/from calls with a single signature + */ + function inferInstantiationFromUsage(type: Type, usage: Usage) { + if (!(getObjectFlags(type) & ObjectFlags.Reference) || !usage.properties) { + return type; + } + const generic = (type as TypeReference).target; + const singleTypeParameter = singleOrUndefined(generic.typeParameters); + if (!singleTypeParameter) return type; + + const types: Type[] = []; + usage.properties.forEach((propUsage, name) => { + const genericPropertyType = checker.getTypeOfPropertyOfType(generic, name as string); + Debug.assert(!!genericPropertyType, "generic should have all the properties of its reference."); + types.push(...inferTypeParameters(genericPropertyType!, combineFromUsage(propUsage), singleTypeParameter)); + }); + return builtinConstructors[type.symbol.escapedName as string](combineTypes(types)); + } + + function inferTypeParameters(genericType: Type, usageType: Type, typeParameter: Type): readonly Type[] { + if (genericType === typeParameter) { + return [usageType]; + } + else if (genericType.flags & TypeFlags.UnionOrIntersection) { + return flatMap((genericType as UnionOrIntersectionType).types, t => inferTypeParameters(t, usageType, typeParameter)); + } + else if (getObjectFlags(genericType) & ObjectFlags.Reference && getObjectFlags(usageType) & ObjectFlags.Reference) { + // this is wrong because we need a reference to the targetType to, so we can check that it's also a reference + const genericArgs = (genericType as TypeReference).typeArguments; + const usageArgs = (usageType as TypeReference).typeArguments; + const types = []; + if (genericArgs && usageArgs) { + for (let i = 0; i < genericArgs.length; i++) { + if (usageArgs[i]) { + types.push(...inferTypeParameters(genericArgs[i], usageArgs[i], typeParameter)); } } } + return types; } - - if (types.length) { - const type = checker.getWidenedType(checker.getUnionType(types, UnionReduction.Subtype)); - return isRestParameter ? checker.createArrayType(type) : type; + const genericSigs = checker.getSignaturesOfType(genericType, SignatureKind.Call); + const usageSigs = checker.getSignaturesOfType(usageType, SignatureKind.Call); + if (genericSigs.length === 1 && usageSigs.length === 1) { + return inferFromSignatures(genericSigs[0], usageSigs[0], typeParameter); } - return undefined; + return []; } - function getSignatureFromCall(call: CallUsage): Signature { + function inferFromSignatures(genericSig: Signature, usageSig: Signature, typeParameter: Type) { + const types = []; + for (let i = 0; i < genericSig.parameters.length; i++) { + const genericParam = genericSig.parameters[i]; + const usageParam = usageSig.parameters[i]; + const isRest = genericSig.declaration && isRestParameter(genericSig.declaration.parameters[i]); + if (!usageParam) { + break; + } + let genericParamType = checker.getTypeOfSymbolAtLocation(genericParam, genericParam.valueDeclaration); + const elementType = isRest && checker.getElementTypeOfArrayType(genericParamType); + if (elementType) { + genericParamType = elementType; + } + const targetType = (usageParam as SymbolLinks).type || checker.getTypeOfSymbolAtLocation(usageParam, usageParam.valueDeclaration); + types.push(...inferTypeParameters(genericParamType, targetType, typeParameter)); + } + const genericReturn = checker.getReturnTypeOfSignature(genericSig); + const usageReturn = checker.getReturnTypeOfSignature(usageSig); + types.push(...inferTypeParameters(genericReturn, usageReturn, typeParameter)); + return types; + } + + function getFunctionFromCalls(calls: CallUsage[]) { + return checker.createAnonymousType(undefined!, createSymbolTable(), [getSignatureFromCalls(calls)], emptyArray, /*stringIndexInfo*/ undefined, /*numberIndexInfo*/ undefined); + } + + function getSignatureFromCalls(calls: CallUsage[]): Signature { const parameters: Symbol[] = []; - for (let i = 0; i < call.argumentTypes.length; i++) { + const length = Math.max(...calls.map(c => c.argumentTypes.length)); + for (let i = 0; i < length; i++) { const symbol = checker.createSymbol(SymbolFlags.FunctionScopedVariable, escapeLeadingUnderscores(`arg${i}`)); - symbol.type = checker.getWidenedType(checker.getBaseTypeOfLiteralType(call.argumentTypes[i])); + symbol.type = combineTypes(calls.map(call => call.argumentTypes[i] || checker.getUndefinedType())); + if (calls.some(call => call.argumentTypes[i] === undefined)) { + symbol.flags |= SymbolFlags.Optional; + } parameters.push(symbol); } - const returnType = unifyFromUsage(inferFromUsage(call.returnType), checker.getVoidType()); + const returnType = combineFromUsage(combineUsages(calls.map(call => call.return_))); // TODO: GH#18217 - return checker.createSignature(/*declaration*/ undefined!, /*typeParameters*/ undefined, /*thisParameter*/ undefined, parameters, returnType, /*typePredicate*/ undefined, call.argumentTypes.length, /*hasRestParameter*/ false, /*hasLiteralTypes*/ false); + return checker.createSignature(/*declaration*/ undefined!, /*typeParameters*/ undefined, /*thisParameter*/ undefined, parameters, returnType, /*typePredicate*/ undefined, length, /*hasRestParameter*/ false, /*hasLiteralTypes*/ false); } function addCandidateType(usage: Usage, type: Type | undefined) { @@ -933,9 +1067,5 @@ namespace ts.codefix { (usage.candidateThisTypes || (usage.candidateThisTypes = [])).push(type); } } - - function hasCalls(usage: Usage | undefined): boolean { - return !!usage && !!usage.calls; - } } } diff --git a/tests/cases/fourslash/codeFixInferFromCallInAssignment.ts b/tests/cases/fourslash/codeFixInferFromCallInAssignment.ts new file mode 100644 index 0000000000..85e1d3bc50 --- /dev/null +++ b/tests/cases/fourslash/codeFixInferFromCallInAssignment.ts @@ -0,0 +1,9 @@ +/// + +// @noImplicitAny: true +//// function inferAny( [| app |] ) { +//// const result = app.use('hi') +//// return result +//// } + +verify.rangeAfterCodeFix("app: { use: (arg0: string) => any; }"); diff --git a/tests/cases/fourslash/codeFixInferFromExpressionStatement.ts b/tests/cases/fourslash/codeFixInferFromExpressionStatement.ts new file mode 100644 index 0000000000..b5969c7937 --- /dev/null +++ b/tests/cases/fourslash/codeFixInferFromExpressionStatement.ts @@ -0,0 +1,8 @@ +/// + +// @noImplicitAny: true +//// function inferVoid( [| app |] ) { +//// app.use('hi') +//// } + +verify.rangeAfterCodeFix("app: { use: (arg0: string) => void; }"); diff --git a/tests/cases/fourslash/codeFixInferFromFunctionUsage.ts b/tests/cases/fourslash/codeFixInferFromFunctionUsage.ts index 27a039878b..f2dbad67b4 100644 --- a/tests/cases/fourslash/codeFixInferFromFunctionUsage.ts +++ b/tests/cases/fourslash/codeFixInferFromFunctionUsage.ts @@ -2,8 +2,8 @@ // @noImplicitAny: true ////function wrap( [| arr |] ) { -//// arr.sort(function (a: number, b: number) { return a < b ? -1 : 1 }) +//// arr.other(function (a: number, b: number) { return a < b ? -1 : 1 }) //// } // https://github.com/Microsoft/TypeScript/issues/29330 -verify.rangeAfterCodeFix("arr: { sort: (arg0: (a: number, b: number) => 1 | -1) => void; }"); +verify.rangeAfterCodeFix("arr: { other: (arg0: (a: number, b: number) => 1 | -1) => void; }"); diff --git a/tests/cases/fourslash/codeFixInferFromPrimitiveUsage.ts b/tests/cases/fourslash/codeFixInferFromPrimitiveUsage.ts new file mode 100644 index 0000000000..b74e49b4d3 --- /dev/null +++ b/tests/cases/fourslash/codeFixInferFromPrimitiveUsage.ts @@ -0,0 +1,10 @@ +/// + +// @noImplicitAny: true +//// function wrap( [| s |] ) { +//// return s.length + s.indexOf('hi') +//// } + +// https://github.com/Microsoft/TypeScript/issues/29330 +verify.rangeAfterCodeFix("s: string | string[]"); + diff --git a/tests/cases/fourslash/codeFixInferFromUsageAddition.ts b/tests/cases/fourslash/codeFixInferFromUsageAddition.ts new file mode 100644 index 0000000000..4b57d0603f --- /dev/null +++ b/tests/cases/fourslash/codeFixInferFromUsageAddition.ts @@ -0,0 +1,9 @@ +/// + +// @noImplicitAny: true +//// function foo([|a, m |]) { +//// return a + m +//// } + +verify.rangeAfterCodeFix("a: any, m: any", /*includeWhiteSpace*/ undefined, /*errorCode*/ undefined, /*index*/0); + diff --git a/tests/cases/fourslash/codeFixInferFromUsageArray.ts b/tests/cases/fourslash/codeFixInferFromUsageArray.ts new file mode 100644 index 0000000000..75f923d5ae --- /dev/null +++ b/tests/cases/fourslash/codeFixInferFromUsageArray.ts @@ -0,0 +1,14 @@ +/// + +// @noImplicitAny: true +//// function foo([|p, a, b, c, d, e |]) { +//// var x: string = a.pop() +//// b.reverse() +//// var rr: boolean[] = c.reverse() +//// d.some(t => t > 1); // can't infer from callbacks right now +//// var y = e.concat(12); // can't infer from overloaded functions right now +//// return p.push(12) +//// } + +verify.rangeAfterCodeFix("p: number[], a: string[], b: any[], c: boolean[], d: any[], e: any[]", /*includeWhiteSpace*/ undefined, /*errorCode*/ undefined, /*index*/0); + diff --git a/tests/cases/fourslash/codeFixInferFromUsageCallBodyBoth.ts b/tests/cases/fourslash/codeFixInferFromUsageCallBodyBoth.ts index 859d5ea2ce..f59d1bc190 100644 --- a/tests/cases/fourslash/codeFixInferFromUsageCallBodyBoth.ts +++ b/tests/cases/fourslash/codeFixInferFromUsageCallBodyBoth.ts @@ -1,7 +1,7 @@ /// ////class C { -//// +//// p = 2 ////} ////var c = new C() ////function f([|x, y |]) { diff --git a/tests/cases/fourslash/codeFixInferFromUsageCommentAfterParameter.ts b/tests/cases/fourslash/codeFixInferFromUsageCommentAfterParameter.ts index bdefd2b5de..866bfe04c1 100644 --- a/tests/cases/fourslash/codeFixInferFromUsageCommentAfterParameter.ts +++ b/tests/cases/fourslash/codeFixInferFromUsageCommentAfterParameter.ts @@ -16,7 +16,7 @@ verify.codeFix({ index: 0, newFileContent: `/** - * @param {(arg0: any) => void} callback + * @param {(arg0: any) => any} callback */ function coll(callback /*, name1, name2, ... */) { return callback(this); diff --git a/tests/cases/fourslash/codeFixInferFromUsageJSXElement.ts b/tests/cases/fourslash/codeFixInferFromUsageJSXElement.ts index 3200fffafb..abe35fe46c 100644 --- a/tests/cases/fourslash/codeFixInferFromUsageJSXElement.ts +++ b/tests/cases/fourslash/codeFixInferFromUsageJSXElement.ts @@ -30,4 +30,4 @@ //// } -verify.rangeAfterCodeFix("props: { isLoading: any; update: (arg0: any) => void; }",/*includeWhiteSpace*/ undefined, /*errorCode*/ undefined, 0); +verify.rangeAfterCodeFix("props: { isLoading: any; update: (arg0: any) => any; }",/*includeWhiteSpace*/ undefined, /*errorCode*/ undefined, 0); diff --git a/tests/cases/fourslash/codeFixInferFromUsageLiteralTypes.ts b/tests/cases/fourslash/codeFixInferFromUsageLiteralTypes.ts new file mode 100644 index 0000000000..3b8c944115 --- /dev/null +++ b/tests/cases/fourslash/codeFixInferFromUsageLiteralTypes.ts @@ -0,0 +1,10 @@ +/// + +// @noImplicitAny: true +//// function foo([|a, m |]) { +//// a = 'hi' +//// m = 1 +//// } + +verify.rangeAfterCodeFix("a: string, m: number", /*includeWhiteSpace*/ undefined, /*errorCode*/ undefined, /*index*/0); + diff --git a/tests/cases/fourslash/codeFixInferFromUsagePromise.ts b/tests/cases/fourslash/codeFixInferFromUsagePromise.ts new file mode 100644 index 0000000000..d06bb3b6a0 --- /dev/null +++ b/tests/cases/fourslash/codeFixInferFromUsagePromise.ts @@ -0,0 +1,9 @@ +/// + +// @noImplicitAny: true +//// function foo([|p |]) { +//// return p.then((x: string[]) => x[0]) +//// } + +verify.rangeAfterCodeFix("p: Promise", /*includeWhiteSpace*/ undefined, /*errorCode*/ undefined, /*index*/0); + diff --git a/tests/cases/fourslash/codeFixInferFromUsagePropertyAccess.ts b/tests/cases/fourslash/codeFixInferFromUsagePropertyAccess.ts index c8b85ee11e..44d2e2d7b1 100644 --- a/tests/cases/fourslash/codeFixInferFromUsagePropertyAccess.ts +++ b/tests/cases/fourslash/codeFixInferFromUsagePropertyAccess.ts @@ -12,4 +12,4 @@ //// return x.y.z ////} -verify.rangeAfterCodeFix("a: { b: { c: any; }; }, m: { n: () => number; }, x: { y: { z: number[]; }; }", /*includeWhiteSpace*/ undefined, /*errorCode*/ undefined, /*index*/0); \ No newline at end of file +verify.rangeAfterCodeFix("a: { b: { c: void; }; }, m: { n: () => number; }, x: { y: { z: number[]; }; }", /*includeWhiteSpace*/ undefined, /*errorCode*/ undefined, /*index*/0); diff --git a/tests/cases/fourslash/codeFixInferFromUsagePropertyAccessJS.ts b/tests/cases/fourslash/codeFixInferFromUsagePropertyAccessJS.ts index ed9d4b33b3..357b1a991b 100644 --- a/tests/cases/fourslash/codeFixInferFromUsagePropertyAccessJS.ts +++ b/tests/cases/fourslash/codeFixInferFromUsagePropertyAccessJS.ts @@ -21,7 +21,7 @@ verify.codeFix({ index: 0, newFileContent: `/** - * @param {{ b: { c: any; }; }} a + * @param {{ b: { c: void; }; }} a * @param {{ n: () => number; }} m * @param {{ y: { z: number[]; }; }} x */ diff --git a/tests/cases/fourslash/codeFixInferFromUsageString.ts b/tests/cases/fourslash/codeFixInferFromUsageString.ts new file mode 100644 index 0000000000..d58998cf4a --- /dev/null +++ b/tests/cases/fourslash/codeFixInferFromUsageString.ts @@ -0,0 +1,12 @@ +/// + +// @noImplicitAny: true +//// function foo([|p, a, b |]) { +//// var x +//// p.charAt(x) +//// a.charAt(0) +//// b.concat('hi') +//// } + +verify.rangeAfterCodeFix("p: string, a: string, b: string | any[]", /*includeWhiteSpace*/ undefined, /*errorCode*/ undefined, /*index*/0); +