From 2314140b3f0e3c746beea57c31b9615c4d917a72 Mon Sep 17 00:00:00 2001 From: Yuya Tanaka Date: Sat, 2 Oct 2021 06:52:02 +0900 Subject: [PATCH 1/5] Report deprecation for object literal assignments --- src/compiler/checker.ts | 44 ++++++++++++-- ...d_suggestion14_objectLiteralAssignments.ts | 57 ++++++++++++++++++ ...ted_suggestion15_objectLiteralArguments.ts | 58 +++++++++++++++++++ ...docDeprecated_suggestion16_jsxArguments.ts | 55 ++++++++++++++++++ ..._suggestion17_jsxArgumentsWithOverloads.ts | 25 ++++++++ ...estion18_functionArgumentsWithOverloads.ts | 23 ++++++++ 6 files changed, 258 insertions(+), 4 deletions(-) create mode 100644 tests/cases/fourslash/jsdocDeprecated_suggestion14_objectLiteralAssignments.ts create mode 100644 tests/cases/fourslash/jsdocDeprecated_suggestion15_objectLiteralArguments.ts create mode 100644 tests/cases/fourslash/jsdocDeprecated_suggestion16_jsxArguments.ts create mode 100644 tests/cases/fourslash/jsdocDeprecated_suggestion17_jsxArgumentsWithOverloads.ts create mode 100644 tests/cases/fourslash/jsdocDeprecated_suggestion18_functionArgumentsWithOverloads.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 92005a36b655e..c49dddb701e33 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -17348,7 +17348,7 @@ namespace ts { containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined, errorOutputContainer: { errors?: Diagnostic[], skipLogging?: boolean } | undefined ): boolean { - if (isTypeRelatedTo(source, target, relation)) return true; + if (isTypeRelatedTo(source, target, relation, /*reportDeprecatedProperties*/ true)) return true; if (!errorNode || !elaborateError(expr, source, target, relation, headMessage, containingMessageChain, errorOutputContainer)) { return checkTypeRelatedTo(source, target, relation, errorNode, headMessage, containingMessageChain, errorOutputContainer); } @@ -18115,7 +18115,7 @@ namespace ts { return false; } - function isTypeRelatedTo(source: Type, target: Type, relation: ESMap) { + function isTypeRelatedTo(source: Type, target: Type, relation: ESMap, reportDeprecatedProperties?: boolean) { if (isFreshLiteralType(source)) { source = (source as FreshableType).regularType; } @@ -18142,7 +18142,9 @@ namespace ts { } } if (source.flags & TypeFlags.StructuredOrInstantiable || target.flags & TypeFlags.StructuredOrInstantiable) { - return checkTypeRelatedTo(source, target, relation, /*errorNode*/ undefined); + return checkTypeRelatedTo(source, target, relation, + /*errorNode*/ undefined, /*headMessage*/ undefined, /*containingMessageChain*/ undefined, /*errorOutputContainer*/ undefined, + reportDeprecatedProperties); } return false; } @@ -18176,6 +18178,7 @@ namespace ts { * @param headMessage If the error chain should be prepended by a head message, then headMessage will be used. * @param containingMessageChain A chain of errors to prepend any new errors found. * @param errorOutputContainer Return the diagnostic. Do not log if 'skipLogging' is truthy. + * @param reportDeprecatedProperties Properties with deprecated annotation will be reported, if result is true. */ function checkTypeRelatedTo( source: Type, @@ -18185,6 +18188,7 @@ namespace ts { headMessage?: DiagnosticMessage, containingMessageChain?: () => DiagnosticMessageChain | undefined, errorOutputContainer?: { errors?: Diagnostic[], skipLogging?: boolean }, + reportDeprecatedProperties?: boolean ): boolean { let errorInfo: DiagnosticMessageChain | undefined; @@ -18201,6 +18205,7 @@ namespace ts { let lastSkippedInfo: [Type, Type] | undefined; let incompatibleStack: [DiagnosticMessage, (string | number)?, (string | number)?, (string | number)?, (string | number)?][] | undefined; let inPropertyCheck = false; + const deprecatedSuggestions: Parameters[] = []; Debug.assert(relation !== identityRelation || !errorNode, "no error reporting in identity checking"); @@ -18252,8 +18257,15 @@ namespace ts { Debug.assert(!!errorOutputContainer.errors, "missed opportunity to interact with error."); } + const isRelated = result !== Ternary.False; - return result !== Ternary.False; + if (isRelated) { + for (const args of deprecatedSuggestions) { + addDeprecatedSuggestion(...args); + } + } + + return isRelated; function resetErrorInfo(saved: ReturnType) { errorInfo = saved.errorInfo; @@ -18587,6 +18599,9 @@ namespace ts { } return Ternary.False; } + if (reportDeprecatedProperties) { + checkDeprecatedProperties(source as FreshObjectLiteralType, target); + } } const isPerformingCommonPropertyChecks = relation !== comparableRelation && !(intersectionState & IntersectionState.Target) && @@ -18822,6 +18837,27 @@ namespace ts { return prop.valueDeclaration && container.valueDeclaration && prop.valueDeclaration.parent === container.valueDeclaration; } + function checkDeprecatedProperties(source: FreshObjectLiteralType, target: Type) { + if (!isExcessPropertyCheckTarget(target)) { + return; + } + + for (const prop of getPropertiesOfType(source)) { + if (!shouldCheckAsExcessProperty(prop, source.symbol)) { + continue; + } + + const symbol = getPropertyOfObjectType(target, prop.escapedName); + if (symbol?.declarations?.some(decl => decl.flags & NodeFlags.Deprecated)) { + const node = prop.valueDeclaration!; + const nameNode = isPropertyAssignment(node) || isShorthandPropertyAssignment(node) || isObjectLiteralMethod(node) || isJsxAttribute(node) + ? node.name + : prop.valueDeclaration!; + deprecatedSuggestions.push([nameNode, symbol.declarations, prop.escapedName as string]); + } + } + } + function unionOrIntersectionRelatedTo(source: Type, target: Type, reportErrors: boolean, intersectionState: IntersectionState): Ternary { // Note that these checks are specifically ordered to produce correct results. In particular, // we need to deconstruct unions before intersections (because unions are always at the top), diff --git a/tests/cases/fourslash/jsdocDeprecated_suggestion14_objectLiteralAssignments.ts b/tests/cases/fourslash/jsdocDeprecated_suggestion14_objectLiteralAssignments.ts new file mode 100644 index 0000000000000..4a396fced98be --- /dev/null +++ b/tests/cases/fourslash/jsdocDeprecated_suggestion14_objectLiteralAssignments.ts @@ -0,0 +1,57 @@ +//// interface Props { +//// /** @deprecated */ +//// foo?: string; +//// bar: string; +//// baz?: { +//// /** @deprecated */ +//// foo?: boolean; +//// bar?: boolean; +//// }; +//// /** @deprecated */ +//// callback?: () => void +//// } +//// const Component = (props: Props) => props &&
; +//// const foo = "foo"; +//// const bar = "bar"; +//// const callback = () => {}; + +//// let props: Props = { [|callback|]: () => {}, [|foo|]: "foo", bar: "bar", baz: { [|foo|]: true } }; +//// props = { [|"foo"|]: "foo", "bar": "bar" }; +//// props = { [|["foo"]|]: "foo", ["bar"]: "bar" }; +//// props = { [|foo|], bar, [|callback|] }; +//// props = { bar, [|callback|]() {} }; + +//// // Skip if there is a type incompatibility error. +//// const props5: Props = { foo: "foo", boo: "boo" }; + +//// // Skip for union types. +//// const props6: { foo: { /** @deprecated */ bar: string } | { bar: string, baz: string } } = { foo: { bar: "bar" } }; + +const ranges = test.ranges(); + +verify.getSuggestionDiagnostics([ + { + message: "'callback' is deprecated.", + code: 6385, + range: ranges[0], + reportsDeprecated: true, + }, + ...ranges.slice(1, ranges.length - 2).map(range => ({ + message: "'foo' is deprecated.", + code: 6385, + range, + reportsDeprecated: true as const, + })), + { + message: "'callback' is deprecated.", + code: 6385, + range: ranges[ranges.length - 2], + reportsDeprecated: true, + }, + { + message: "'callback' is deprecated.", + code: 6385, + range: ranges[ranges.length - 1], + reportsDeprecated: true, + }, +]) diff --git a/tests/cases/fourslash/jsdocDeprecated_suggestion15_objectLiteralArguments.ts b/tests/cases/fourslash/jsdocDeprecated_suggestion15_objectLiteralArguments.ts new file mode 100644 index 0000000000000..ae5925c2bd9aa --- /dev/null +++ b/tests/cases/fourslash/jsdocDeprecated_suggestion15_objectLiteralArguments.ts @@ -0,0 +1,58 @@ +//// interface Props { +//// /** @deprecated */ +//// foo?: string; +//// bar: string; +//// baz?: { +//// /** @deprecated */ +//// foo?: boolean; +//// bar?: boolean; +//// }; +//// /** @deprecated */ +//// callback?: () => void +//// } +//// const func = (_props: Props) => {}; +//// const foo = "foo"; +//// const bar = "bar"; +//// const callback = () => {}; + +//// func({ [|callback|]: () => {}, [|foo|]: "foo", bar: "bar", baz: { [|foo|]: true } }); +//// func({ [|"foo"|]: "foo", "bar": "bar" }); +//// func({ [|["foo"]|]: "foo", ["bar"]: "bar" }); +//// func({ [|foo|], bar, [|callback|] }); +//// func({ bar, [|callback|]() {} }); + +//// // Skip if there is a type incompatibility error. +//// func({ foo: "foo", boo: "boo" }); + +//// // Skip for union types. +//// function test(_args: { foo: { /** @deprecated */ bar: string } | { bar: string, baz: string } }) {} +//// test({ foo: { bar: "bar" } }) + +const ranges = test.ranges(); + +verify.getSuggestionDiagnostics([ + { + message: "'callback' is deprecated.", + code: 6385, + range: ranges[0], + reportsDeprecated: true, + }, + ...ranges.slice(1, ranges.length - 2).map(range => ({ + message: "'foo' is deprecated.", + code: 6385, + range, + reportsDeprecated: true as const, + })), + { + message: "'callback' is deprecated.", + code: 6385, + range: ranges[ranges.length - 2], + reportsDeprecated: true, + }, + { + message: "'callback' is deprecated.", + code: 6385, + range: ranges[ranges.length - 1], + reportsDeprecated: true, + }, +]) diff --git a/tests/cases/fourslash/jsdocDeprecated_suggestion16_jsxArguments.ts b/tests/cases/fourslash/jsdocDeprecated_suggestion16_jsxArguments.ts new file mode 100644 index 0000000000000..096137ef91796 --- /dev/null +++ b/tests/cases/fourslash/jsdocDeprecated_suggestion16_jsxArguments.ts @@ -0,0 +1,55 @@ +// @Filename: a.tsx +//// type Props = { +//// /** @deprecated */ +//// foo?: string; +//// bar: string; +//// baz?: { +//// /** @deprecated */ +//// foo?: boolean; +//// bar?: boolean; +//// }; +//// /** @deprecated */ +//// callback?: () => void +//// /** @deprecated */ +//// bool?: boolean; +//// } +//// const Component = (props: Props) => props &&
; +//// const foo = "foo"; +//// const bar = "bar"; + +//// {}} [|foo|]="foo" bar="bar" baz={{ [|foo|]: true }} />; + +//// // Skip spread in jsx. +//// ; + +//// // Skip if there is a type incompatibility error. +//// ; +//// ; + +//// // Skip for union types. +//// const Component2 = (_props: { foo: { /** @deprecated */ bar: string } | { bar: string, baz: string } }) =>
; +//// ; + +goTo.file('a.tsx') +const ranges = test.ranges(); + +verify.getSuggestionDiagnostics([ + { + message: "'bool' is deprecated.", + code: 6385, + range: ranges[0], + reportsDeprecated: true, + }, + { + message: "'callback' is deprecated.", + code: 6385, + range: ranges[1], + reportsDeprecated: true, + }, + ...ranges.slice(2).map(range => ({ + message: "'foo' is deprecated.", + code: 6385, + range, + reportsDeprecated: true as const, + })) +]) diff --git a/tests/cases/fourslash/jsdocDeprecated_suggestion17_jsxArgumentsWithOverloads.ts b/tests/cases/fourslash/jsdocDeprecated_suggestion17_jsxArgumentsWithOverloads.ts new file mode 100644 index 0000000000000..5ae97f5a2263e --- /dev/null +++ b/tests/cases/fourslash/jsdocDeprecated_suggestion17_jsxArgumentsWithOverloads.ts @@ -0,0 +1,25 @@ +// @Filename: a.tsx +//// function OverridableComponent(props: { +//// /** @deprecated */ +//// a: boolean; +//// c?: boolean; +//// }): JSX.Element +//// function OverridableComponent(props: { a: boolean; b: boolean }): JSX.Element +//// function OverridableComponent(_props: { a: boolean; b?: boolean; c?: boolean }) { +//// return
; +//// } + +//// ; +//// ; + +goTo.file('a.tsx') +const ranges = test.ranges(); + +verify.getSuggestionDiagnostics([ + { + message: "'a' is deprecated.", + code: 6385, + range: ranges[0], + reportsDeprecated: true, + } +]) diff --git a/tests/cases/fourslash/jsdocDeprecated_suggestion18_functionArgumentsWithOverloads.ts b/tests/cases/fourslash/jsdocDeprecated_suggestion18_functionArgumentsWithOverloads.ts new file mode 100644 index 0000000000000..76bc8416eeae4 --- /dev/null +++ b/tests/cases/fourslash/jsdocDeprecated_suggestion18_functionArgumentsWithOverloads.ts @@ -0,0 +1,23 @@ +//// function overloadFunc(props: { +//// /** @deprecated */ +//// a: boolean; +//// c?: boolean; +//// }): JSX.Element +//// function overloadFunc(props: { a: boolean; b: boolean }): JSX.Element +//// function overloadFunc(_props: { a: boolean; b?: boolean; c?: boolean }) { +//// return
; +//// } + +//// overloadFunc({ [|a|]: true }) />; +//// overloadFunc({ a: true, b: true }) />; + +const ranges = test.ranges(); + +verify.getSuggestionDiagnostics([ + { + message: "'a' is deprecated.", + code: 6385, + range: ranges[0], + reportsDeprecated: true, + } +]) From fb52df206bc8278deec1056bbb64d89dd6ab1a99 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Wed, 3 Nov 2021 09:47:04 -0700 Subject: [PATCH 2/5] Move reporting to checkObjectLiteral and checkJsxOpeningLikeElementOrOpeningFragment It is now obvious that this only works when there's a contextual type for the object literal. --- src/compiler/checker.ts | 78 +++++++++---------- ...d_suggestion14_objectLiteralAssignments.ts | 42 ++++++++-- ...ted_suggestion15_objectLiteralArguments.ts | 44 +++++++++-- ...docDeprecated_suggestion16_jsxArguments.ts | 43 +++++++--- ...estion18_functionArgumentsWithOverloads.ts | 13 +--- 5 files changed, 145 insertions(+), 75 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index c49dddb701e33..0668f3c8c1a60 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -17348,7 +17348,7 @@ namespace ts { containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined, errorOutputContainer: { errors?: Diagnostic[], skipLogging?: boolean } | undefined ): boolean { - if (isTypeRelatedTo(source, target, relation, /*reportDeprecatedProperties*/ true)) return true; + if (isTypeRelatedTo(source, target, relation)) return true; if (!errorNode || !elaborateError(expr, source, target, relation, headMessage, containingMessageChain, errorOutputContainer)) { return checkTypeRelatedTo(source, target, relation, errorNode, headMessage, containingMessageChain, errorOutputContainer); } @@ -18115,7 +18115,7 @@ namespace ts { return false; } - function isTypeRelatedTo(source: Type, target: Type, relation: ESMap, reportDeprecatedProperties?: boolean) { + function isTypeRelatedTo(source: Type, target: Type, relation: ESMap) { if (isFreshLiteralType(source)) { source = (source as FreshableType).regularType; } @@ -18143,8 +18143,7 @@ namespace ts { } if (source.flags & TypeFlags.StructuredOrInstantiable || target.flags & TypeFlags.StructuredOrInstantiable) { return checkTypeRelatedTo(source, target, relation, - /*errorNode*/ undefined, /*headMessage*/ undefined, /*containingMessageChain*/ undefined, /*errorOutputContainer*/ undefined, - reportDeprecatedProperties); + /*errorNode*/ undefined, /*headMessage*/ undefined, /*containingMessageChain*/ undefined, /*errorOutputContainer*/ undefined); } return false; } @@ -18178,7 +18177,6 @@ namespace ts { * @param headMessage If the error chain should be prepended by a head message, then headMessage will be used. * @param containingMessageChain A chain of errors to prepend any new errors found. * @param errorOutputContainer Return the diagnostic. Do not log if 'skipLogging' is truthy. - * @param reportDeprecatedProperties Properties with deprecated annotation will be reported, if result is true. */ function checkTypeRelatedTo( source: Type, @@ -18188,7 +18186,6 @@ namespace ts { headMessage?: DiagnosticMessage, containingMessageChain?: () => DiagnosticMessageChain | undefined, errorOutputContainer?: { errors?: Diagnostic[], skipLogging?: boolean }, - reportDeprecatedProperties?: boolean ): boolean { let errorInfo: DiagnosticMessageChain | undefined; @@ -18205,7 +18202,6 @@ namespace ts { let lastSkippedInfo: [Type, Type] | undefined; let incompatibleStack: [DiagnosticMessage, (string | number)?, (string | number)?, (string | number)?, (string | number)?][] | undefined; let inPropertyCheck = false; - const deprecatedSuggestions: Parameters[] = []; Debug.assert(relation !== identityRelation || !errorNode, "no error reporting in identity checking"); @@ -18257,15 +18253,7 @@ namespace ts { Debug.assert(!!errorOutputContainer.errors, "missed opportunity to interact with error."); } - const isRelated = result !== Ternary.False; - - if (isRelated) { - for (const args of deprecatedSuggestions) { - addDeprecatedSuggestion(...args); - } - } - - return isRelated; + return result !== Ternary.False; function resetErrorInfo(saved: ReturnType) { errorInfo = saved.errorInfo; @@ -18599,9 +18587,6 @@ namespace ts { } return Ternary.False; } - if (reportDeprecatedProperties) { - checkDeprecatedProperties(source as FreshObjectLiteralType, target); - } } const isPerformingCommonPropertyChecks = relation !== comparableRelation && !(intersectionState & IntersectionState.Target) && @@ -18837,27 +18822,6 @@ namespace ts { return prop.valueDeclaration && container.valueDeclaration && prop.valueDeclaration.parent === container.valueDeclaration; } - function checkDeprecatedProperties(source: FreshObjectLiteralType, target: Type) { - if (!isExcessPropertyCheckTarget(target)) { - return; - } - - for (const prop of getPropertiesOfType(source)) { - if (!shouldCheckAsExcessProperty(prop, source.symbol)) { - continue; - } - - const symbol = getPropertyOfObjectType(target, prop.escapedName); - if (symbol?.declarations?.some(decl => decl.flags & NodeFlags.Deprecated)) { - const node = prop.valueDeclaration!; - const nameNode = isPropertyAssignment(node) || isShorthandPropertyAssignment(node) || isObjectLiteralMethod(node) || isJsxAttribute(node) - ? node.name - : prop.valueDeclaration!; - deprecatedSuggestions.push([nameNode, symbol.declarations, prop.escapedName as string]); - } - } - } - function unionOrIntersectionRelatedTo(source: Type, target: Type, reportErrors: boolean, intersectionState: IntersectionState): Ternary { // Note that these checks are specifically ordered to produce correct results. In particular, // we need to deconstruct unions before intersections (because unions are always at the top), @@ -27650,6 +27614,17 @@ namespace ts { symbolToString(member), typeToString(contextualType)); } } + if (contextualType) { + const contextualProperty = getPropertyOfType(contextualType, member.escapedName) + const isFromOverload = contextualProperty?.declarations?.some(decl => { + const param = findAncestor(decl, isParameter) + return param && isFunctionDeclaration(param.parent) && countWhere(getSymbolOfNode(param.parent).declarations, isFunctionLike) > 1 + }) + if (!isFromOverload) { + checkDeprecatedProperty(member, contextualProperty) + } + + } prop.declarations = member.declarations; prop.parent = member.parent; @@ -28355,10 +28330,17 @@ namespace ts { } if (isNodeOpeningLikeElement) { - const jsxOpeningLikeNode = node ; - const sig = getResolvedSignature(jsxOpeningLikeNode); + const sig = getResolvedSignature(node); checkDeprecatedSignature(sig, node); - checkJsxReturnAssignableToAppropriateBound(getJsxReferenceKind(jsxOpeningLikeNode), getReturnTypeOfSignature(sig), jsxOpeningLikeNode); + const param = sig.parameters[0] + for (const source of node.attributes.properties) { + const member = source.symbol; + const attributesTarget = getTypeOfSymbol(param) + if (member && attributesTarget) { + checkDeprecatedProperty(member, getPropertyOfType(attributesTarget, member.escapedName)) + } + } + checkJsxReturnAssignableToAppropriateBound(getJsxReferenceKind(node), getReturnTypeOfSignature(sig), node); } } @@ -31587,6 +31569,16 @@ namespace ts { } } + function checkDeprecatedProperty(source: Symbol, target: Symbol | undefined) { + if (target?.declarations?.some(decl => decl.flags & NodeFlags.Deprecated)) { + const node = source.valueDeclaration!; + const nameNode = isPropertyAssignment(node) || isShorthandPropertyAssignment(node) || isObjectLiteralMethod(node) || isJsxAttribute(node) + ? node.name + : source.valueDeclaration!; + addDeprecatedSuggestion(nameNode, target.declarations, source.escapedName as string); + } + } + function getDeprecatedSuggestionNode(node: Node): Node { node = skipParentheses(node); switch (node.kind) { diff --git a/tests/cases/fourslash/jsdocDeprecated_suggestion14_objectLiteralAssignments.ts b/tests/cases/fourslash/jsdocDeprecated_suggestion14_objectLiteralAssignments.ts index 4a396fced98be..54606b7d49a0f 100644 --- a/tests/cases/fourslash/jsdocDeprecated_suggestion14_objectLiteralAssignments.ts +++ b/tests/cases/fourslash/jsdocDeprecated_suggestion14_objectLiteralAssignments.ts @@ -22,7 +22,7 @@ //// props = { bar, [|callback|]() {} }; //// // Skip if there is a type incompatibility error. -//// const props5: Props = { foo: "foo", boo: "boo" }; +//// const props5: Props = { [|foo|]: "foo", boo: "boo" }; //// // Skip for union types. //// const props6: { foo: { /** @deprecated */ bar: string } | { bar: string, baz: string } } = { foo: { bar: "bar" } }; @@ -36,22 +36,52 @@ verify.getSuggestionDiagnostics([ range: ranges[0], reportsDeprecated: true, }, - ...ranges.slice(1, ranges.length - 2).map(range => ({ + { + message: "'foo' is deprecated.", + code: 6385, + range: ranges[1], + reportsDeprecated: true as const, + }, + { + message: "'foo' is deprecated.", + code: 6385, + range: ranges[2], + reportsDeprecated: true as const, + }, + { message: "'foo' is deprecated.", code: 6385, - range, + range: ranges[3], reportsDeprecated: true as const, - })), + }, + { + message: "'foo' is deprecated.", + code: 6385, + range: ranges[4], + reportsDeprecated: true as const, + }, + { + message: "'foo' is deprecated.", + code: 6385, + range: ranges[5], + reportsDeprecated: true as const, + }, { message: "'callback' is deprecated.", code: 6385, - range: ranges[ranges.length - 2], + range: ranges[6], reportsDeprecated: true, }, { message: "'callback' is deprecated.", code: 6385, - range: ranges[ranges.length - 1], + range: ranges[7], reportsDeprecated: true, }, + { + message: "'foo' is deprecated.", + code: 6385, + range: ranges[8], + reportsDeprecated: true as const, + }, ]) diff --git a/tests/cases/fourslash/jsdocDeprecated_suggestion15_objectLiteralArguments.ts b/tests/cases/fourslash/jsdocDeprecated_suggestion15_objectLiteralArguments.ts index ae5925c2bd9aa..05cd88932e69b 100644 --- a/tests/cases/fourslash/jsdocDeprecated_suggestion15_objectLiteralArguments.ts +++ b/tests/cases/fourslash/jsdocDeprecated_suggestion15_objectLiteralArguments.ts @@ -21,8 +21,8 @@ //// func({ [|foo|], bar, [|callback|] }); //// func({ bar, [|callback|]() {} }); -//// // Skip if there is a type incompatibility error. -//// func({ foo: "foo", boo: "boo" }); +//// // Do not skip if there is a type incompatibility error. +//// func({ [|foo|]: "foo", boo: "boo" }); //// // Skip for union types. //// function test(_args: { foo: { /** @deprecated */ bar: string } | { bar: string, baz: string } }) {} @@ -37,22 +37,52 @@ verify.getSuggestionDiagnostics([ range: ranges[0], reportsDeprecated: true, }, - ...ranges.slice(1, ranges.length - 2).map(range => ({ + { + message: "'foo' is deprecated.", + code: 6385, + range: ranges[1], + reportsDeprecated: true as const, + }, + { + message: "'foo' is deprecated.", + code: 6385, + range: ranges[2], + reportsDeprecated: true as const, + }, + { message: "'foo' is deprecated.", code: 6385, - range, + range: ranges[3], reportsDeprecated: true as const, - })), + }, + { + message: "'foo' is deprecated.", + code: 6385, + range: ranges[4], + reportsDeprecated: true as const, + }, + { + message: "'foo' is deprecated.", + code: 6385, + range: ranges[5], + reportsDeprecated: true as const, + }, { message: "'callback' is deprecated.", code: 6385, - range: ranges[ranges.length - 2], + range: ranges[6], reportsDeprecated: true, }, { message: "'callback' is deprecated.", code: 6385, - range: ranges[ranges.length - 1], + range: ranges[7], + reportsDeprecated: true, + }, + { + message: "'foo' is deprecated.", + code: 6385, + range: ranges[8], reportsDeprecated: true, }, ]) diff --git a/tests/cases/fourslash/jsdocDeprecated_suggestion16_jsxArguments.ts b/tests/cases/fourslash/jsdocDeprecated_suggestion16_jsxArguments.ts index 096137ef91796..0801cc0db5243 100644 --- a/tests/cases/fourslash/jsdocDeprecated_suggestion16_jsxArguments.ts +++ b/tests/cases/fourslash/jsdocDeprecated_suggestion16_jsxArguments.ts @@ -1,3 +1,4 @@ +/// // @Filename: a.tsx //// type Props = { //// /** @deprecated */ @@ -19,16 +20,16 @@ //// {}} [|foo|]="foo" bar="bar" baz={{ [|foo|]: true }} />; -//// // Skip spread in jsx. -//// ; +//// // Do not skip spread in jsx +//// ; -//// // Skip if there is a type incompatibility error. -//// ; -//// ; +//// // Do not skip if there is a type incompatibility error. +//// ; +//// ; //// // Skip for union types. //// const Component2 = (_props: { foo: { /** @deprecated */ bar: string } | { bar: string, baz: string } }) =>
; -//// ; +//// ; goTo.file('a.tsx') const ranges = test.ranges(); @@ -46,10 +47,34 @@ verify.getSuggestionDiagnostics([ range: ranges[1], reportsDeprecated: true, }, - ...ranges.slice(2).map(range => ({ + { + message: "'foo' is deprecated.", + code: 6385, + range: ranges[2], + reportsDeprecated: true as const, + }, + { + message: "'foo' is deprecated.", + code: 6385, + range: ranges[3], + reportsDeprecated: true as const, + }, + { message: "'foo' is deprecated.", code: 6385, - range, + range: ranges[4], reportsDeprecated: true as const, - })) + }, + { + message: "'foo' is deprecated.", + code: 6385, + range: ranges[5], + reportsDeprecated: true as const, + }, + { + message: "'foo' is deprecated.", + code: 6385, + range: ranges[6], + reportsDeprecated: true as const, + }, ]) diff --git a/tests/cases/fourslash/jsdocDeprecated_suggestion18_functionArgumentsWithOverloads.ts b/tests/cases/fourslash/jsdocDeprecated_suggestion18_functionArgumentsWithOverloads.ts index 76bc8416eeae4..028ebaf38f1b4 100644 --- a/tests/cases/fourslash/jsdocDeprecated_suggestion18_functionArgumentsWithOverloads.ts +++ b/tests/cases/fourslash/jsdocDeprecated_suggestion18_functionArgumentsWithOverloads.ts @@ -8,16 +8,9 @@ //// return
; //// } -//// overloadFunc({ [|a|]: true }) />; -//// overloadFunc({ a: true, b: true }) />; +//// overloadFunc({ [|a|]: true }); +//// overloadFunc({ a: true, b: true }); const ranges = test.ranges(); -verify.getSuggestionDiagnostics([ - { - message: "'a' is deprecated.", - code: 6385, - range: ranges[0], - reportsDeprecated: true, - } -]) +verify.getSuggestionDiagnostics([]) From 9d3bc7d4655b4158b5be0667757291e9b3de2b19 Mon Sep 17 00:00:00 2001 From: Yuya Tanaka Date: Mon, 16 May 2022 05:02:51 +0900 Subject: [PATCH 3/5] Fix error, fix lint --- src/compiler/checker.ts | 24 ++++++++++--------- ...d_suggestion14_objectLiteralAssignments.ts | 2 +- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 0668f3c8c1a60..d522a16ab50de 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -27615,13 +27615,13 @@ namespace ts { } } if (contextualType) { - const contextualProperty = getPropertyOfType(contextualType, member.escapedName) + const contextualProperty = getPropertyOfType(contextualType, member.escapedName); const isFromOverload = contextualProperty?.declarations?.some(decl => { - const param = findAncestor(decl, isParameter) - return param && isFunctionDeclaration(param.parent) && countWhere(getSymbolOfNode(param.parent).declarations, isFunctionLike) > 1 - }) + const param = findAncestor(decl, isParameter); + return param && isFunctionDeclaration(param.parent) && countWhere(getSymbolOfNode(param.parent).declarations, isFunctionLike) > 1; + }); if (!isFromOverload) { - checkDeprecatedProperty(member, contextualProperty) + checkDeprecatedProperty(member, contextualProperty); } } @@ -28332,12 +28332,14 @@ namespace ts { if (isNodeOpeningLikeElement) { const sig = getResolvedSignature(node); checkDeprecatedSignature(sig, node); - const param = sig.parameters[0] - for (const source of node.attributes.properties) { - const member = source.symbol; - const attributesTarget = getTypeOfSymbol(param) - if (member && attributesTarget) { - checkDeprecatedProperty(member, getPropertyOfType(attributesTarget, member.escapedName)) + const param = sig.parameters[0]; + if (param) { + for (const source of node.attributes.properties) { + const member = source.symbol; + const attributesTarget = getTypeOfSymbol(param); + if (member && attributesTarget) { + checkDeprecatedProperty(member, getPropertyOfType(attributesTarget, member.escapedName)); + } } } checkJsxReturnAssignableToAppropriateBound(getJsxReferenceKind(node), getReturnTypeOfSignature(sig), node); diff --git a/tests/cases/fourslash/jsdocDeprecated_suggestion14_objectLiteralAssignments.ts b/tests/cases/fourslash/jsdocDeprecated_suggestion14_objectLiteralAssignments.ts index 54606b7d49a0f..2d7b616da8278 100644 --- a/tests/cases/fourslash/jsdocDeprecated_suggestion14_objectLiteralAssignments.ts +++ b/tests/cases/fourslash/jsdocDeprecated_suggestion14_objectLiteralAssignments.ts @@ -21,7 +21,7 @@ //// props = { [|foo|], bar, [|callback|] }; //// props = { bar, [|callback|]() {} }; -//// // Skip if there is a type incompatibility error. +//// // Do not skip if there is a type incompatibility error. //// const props5: Props = { [|foo|]: "foo", boo: "boo" }; //// // Skip for union types. From e507dd388c6d9fa4f974745dd350228505bdd5c9 Mon Sep 17 00:00:00 2001 From: Yuya Tanaka Date: Tue, 17 May 2022 03:18:46 +0900 Subject: [PATCH 4/5] Report deprecation also for overloaded calls --- src/compiler/checker.ts | 36 ++++++++++++------- ...estion18_functionArgumentsWithOverloads.ts | 9 ++++- 2 files changed, 32 insertions(+), 13 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index d522a16ab50de..86a5a1febfd26 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -1022,6 +1022,7 @@ namespace ts { const diagnostics = createDiagnosticCollection(); const suggestionDiagnostics = createDiagnosticCollection(); + let deprecationsInChooseOverload: Diagnostic[] | undefined; const typeofTypesByName: ReadonlyESMap = new Map(getEntries({ string: stringType, @@ -1220,7 +1221,12 @@ namespace ts { ); } // We call `addRelatedInfo()` before adding the diagnostic to prevent duplicates. - suggestionDiagnostics.add(diagnostic); + if (deprecationsInChooseOverload) { + deprecationsInChooseOverload.push(diagnostic); + } + else { + suggestionDiagnostics.add(diagnostic); + } return diagnostic; } @@ -27616,14 +27622,7 @@ namespace ts { } if (contextualType) { const contextualProperty = getPropertyOfType(contextualType, member.escapedName); - const isFromOverload = contextualProperty?.declarations?.some(decl => { - const param = findAncestor(decl, isParameter); - return param && isFunctionDeclaration(param.parent) && countWhere(getSymbolOfNode(param.parent).declarations, isFunctionLike) > 1; - }); - if (!isFromOverload) { - checkDeprecatedProperty(member, contextualProperty); - } - + checkDeprecatedProperty(member, contextualProperty); } prop.declarations = member.declarations; @@ -30425,6 +30424,8 @@ namespace ts { const signatureHelpTrailingComma = !!(checkMode & CheckMode.IsForSignatureHelp) && node.kind === SyntaxKind.CallExpression && node.arguments.hasTrailingComma; + const deprecationsOutArray: Diagnostic[] = []; + // Section 4.12.1: // if the candidate list contains one or more signatures for which the type of each argument // expression is a subtype of each corresponding parameter type, the return type of the first @@ -30436,12 +30437,15 @@ namespace ts { // is just important for choosing the best signature. So in the case where there is only one // signature, the subtype pass is useless. So skipping it is an optimization. if (candidates.length > 1) { - result = chooseOverload(candidates, subtypeRelation, isSingleNonGenericCandidate, signatureHelpTrailingComma); + result = chooseOverload(candidates, subtypeRelation, isSingleNonGenericCandidate, signatureHelpTrailingComma, deprecationsOutArray); } if (!result) { - result = chooseOverload(candidates, assignableRelation, isSingleNonGenericCandidate, signatureHelpTrailingComma); + result = chooseOverload(candidates, assignableRelation, isSingleNonGenericCandidate, signatureHelpTrailingComma, deprecationsOutArray); } if (result) { + for (const diagnostic of deprecationsOutArray ?? []) { + suggestionDiagnostics.add(diagnostic); + } return result; } @@ -30558,7 +30562,7 @@ namespace ts { candidateForTypeArgumentError = oldCandidateForTypeArgumentError; } - function chooseOverload(candidates: Signature[], relation: ESMap, isSingleNonGenericCandidate: boolean, signatureHelpTrailingComma = false) { + function chooseOverload(candidates: Signature[], relation: ESMap, isSingleNonGenericCandidate: boolean, signatureHelpTrailingComma = false, deprecationsOutArray?: Diagnostic[]) { candidatesForArgumentError = undefined; candidateForArgumentArityError = undefined; candidateForTypeArgumentError = undefined; @@ -30575,7 +30579,13 @@ namespace ts { return candidate; } + // Suppress suggestions for overload which is not chosen yet. + const oldDeprecationsInChooseOverload = deprecationsInChooseOverload; + deprecationsInChooseOverload = deprecationsOutArray ?? []; + for (let candidateIndex = 0; candidateIndex < candidates.length; candidateIndex++) { + deprecationsInChooseOverload.length = 0; + const candidate = candidates[candidateIndex]; if (!hasCorrectTypeArgumentArity(candidate, typeArguments) || !hasCorrectArity(node, args, candidate, signatureHelpTrailingComma)) { continue; @@ -30636,9 +30646,11 @@ namespace ts { } } candidates[candidateIndex] = checkCandidate; + deprecationsInChooseOverload = oldDeprecationsInChooseOverload; return checkCandidate; } + deprecationsInChooseOverload = oldDeprecationsInChooseOverload; return undefined; } } diff --git a/tests/cases/fourslash/jsdocDeprecated_suggestion18_functionArgumentsWithOverloads.ts b/tests/cases/fourslash/jsdocDeprecated_suggestion18_functionArgumentsWithOverloads.ts index 028ebaf38f1b4..c99ef80bbffa9 100644 --- a/tests/cases/fourslash/jsdocDeprecated_suggestion18_functionArgumentsWithOverloads.ts +++ b/tests/cases/fourslash/jsdocDeprecated_suggestion18_functionArgumentsWithOverloads.ts @@ -13,4 +13,11 @@ const ranges = test.ranges(); -verify.getSuggestionDiagnostics([]) +verify.getSuggestionDiagnostics([ + { + message: "'a' is deprecated.", + code: 6385, + range: ranges[0], + reportsDeprecated: true, + } +]) From 60c62e1a682c580d53d43e9e9fa6bae646968b79 Mon Sep 17 00:00:00 2001 From: Yuya Tanaka Date: Tue, 17 May 2022 03:44:05 +0900 Subject: [PATCH 5/5] Rename test files --- ...s => jsdocDeprecated_suggestion18_objectLiteralAssignments.ts} | 0 ....ts => jsdocDeprecated_suggestion19_objectLiteralArguments.ts} | 0 ...xArguments.ts => jsdocDeprecated_suggestion20_jsxArguments.ts} | 0 ... => jsdocDeprecated_suggestion21_jsxArgumentsWithOverloads.ts} | 0 ...sdocDeprecated_suggestion22_functionArgumentsWithOverloads.ts} | 0 5 files changed, 0 insertions(+), 0 deletions(-) rename tests/cases/fourslash/{jsdocDeprecated_suggestion14_objectLiteralAssignments.ts => jsdocDeprecated_suggestion18_objectLiteralAssignments.ts} (100%) rename tests/cases/fourslash/{jsdocDeprecated_suggestion15_objectLiteralArguments.ts => jsdocDeprecated_suggestion19_objectLiteralArguments.ts} (100%) rename tests/cases/fourslash/{jsdocDeprecated_suggestion16_jsxArguments.ts => jsdocDeprecated_suggestion20_jsxArguments.ts} (100%) rename tests/cases/fourslash/{jsdocDeprecated_suggestion17_jsxArgumentsWithOverloads.ts => jsdocDeprecated_suggestion21_jsxArgumentsWithOverloads.ts} (100%) rename tests/cases/fourslash/{jsdocDeprecated_suggestion18_functionArgumentsWithOverloads.ts => jsdocDeprecated_suggestion22_functionArgumentsWithOverloads.ts} (100%) diff --git a/tests/cases/fourslash/jsdocDeprecated_suggestion14_objectLiteralAssignments.ts b/tests/cases/fourslash/jsdocDeprecated_suggestion18_objectLiteralAssignments.ts similarity index 100% rename from tests/cases/fourslash/jsdocDeprecated_suggestion14_objectLiteralAssignments.ts rename to tests/cases/fourslash/jsdocDeprecated_suggestion18_objectLiteralAssignments.ts diff --git a/tests/cases/fourslash/jsdocDeprecated_suggestion15_objectLiteralArguments.ts b/tests/cases/fourslash/jsdocDeprecated_suggestion19_objectLiteralArguments.ts similarity index 100% rename from tests/cases/fourslash/jsdocDeprecated_suggestion15_objectLiteralArguments.ts rename to tests/cases/fourslash/jsdocDeprecated_suggestion19_objectLiteralArguments.ts diff --git a/tests/cases/fourslash/jsdocDeprecated_suggestion16_jsxArguments.ts b/tests/cases/fourslash/jsdocDeprecated_suggestion20_jsxArguments.ts similarity index 100% rename from tests/cases/fourslash/jsdocDeprecated_suggestion16_jsxArguments.ts rename to tests/cases/fourslash/jsdocDeprecated_suggestion20_jsxArguments.ts diff --git a/tests/cases/fourslash/jsdocDeprecated_suggestion17_jsxArgumentsWithOverloads.ts b/tests/cases/fourslash/jsdocDeprecated_suggestion21_jsxArgumentsWithOverloads.ts similarity index 100% rename from tests/cases/fourslash/jsdocDeprecated_suggestion17_jsxArgumentsWithOverloads.ts rename to tests/cases/fourslash/jsdocDeprecated_suggestion21_jsxArgumentsWithOverloads.ts diff --git a/tests/cases/fourslash/jsdocDeprecated_suggestion18_functionArgumentsWithOverloads.ts b/tests/cases/fourslash/jsdocDeprecated_suggestion22_functionArgumentsWithOverloads.ts similarity index 100% rename from tests/cases/fourslash/jsdocDeprecated_suggestion18_functionArgumentsWithOverloads.ts rename to tests/cases/fourslash/jsdocDeprecated_suggestion22_functionArgumentsWithOverloads.ts