diff --git a/packages/eslint-plugin/src/rules/no-deprecated.ts b/packages/eslint-plugin/src/rules/no-deprecated.ts index bf0d80889ceb..6c733c8826c0 100644 --- a/packages/eslint-plugin/src/rules/no-deprecated.ts +++ b/packages/eslint-plugin/src/rules/no-deprecated.ts @@ -274,11 +274,35 @@ export default createRule({ ); } + function getJSXAttributeDeprecation( + openingElement: TSESTree.JSXOpeningElement, + propertyName: string, + ): string | undefined { + const tsNode = services.esTreeNodeToTSNodeMap.get(openingElement.name); + + const contextualType = nullThrows( + checker.getContextualType(tsNode as ts.Expression), + 'Expected JSX opening element name to have contextualType', + ); + + const symbol = contextualType.getProperty(propertyName); + + return getJsDocDeprecation(symbol); + } + function getDeprecationReason(node: IdentifierLike): string | undefined { const callLikeNode = getCallLikeNode(node); if (callLikeNode) { return getCallLikeDeprecation(callLikeNode); } + + if ( + node.parent.type === AST_NODE_TYPES.JSXAttribute && + node.type !== AST_NODE_TYPES.Super + ) { + return getJSXAttributeDeprecation(node.parent.parent, node.name); + } + if ( node.parent.type === AST_NODE_TYPES.Property && node.type !== AST_NODE_TYPES.Super diff --git a/packages/eslint-plugin/tests/rules/no-deprecated.test.ts b/packages/eslint-plugin/tests/rules/no-deprecated.test.ts index 8c99d8e161a2..8bbbe83a0837 100644 --- a/packages/eslint-plugin/tests/rules/no-deprecated.test.ts +++ b/packages/eslint-plugin/tests/rules/no-deprecated.test.ts @@ -205,21 +205,6 @@ ruleTester.run('no-deprecated', rule, { default as ts, } from 'typescript'; `, - - // TODO: Can anybody figure out how to get this to report on `b`? - // getContextualType retrieves the union type, but it has no symbol... - ` - interface AProps { - /** @deprecated */ - b: number | string; - } - - function A(props: AProps) { - return
; - } - - const a = ; - `, ` namespace A { /** @deprecated */ @@ -284,8 +269,75 @@ ruleTester.run('no-deprecated', rule, { } } `, + ` + declare namespace JSX {} + + ; + `, + ` + declare namespace JSX { + interface IntrinsicElements { + foo: any; + } + } + + ; + `, + ` + declare namespace JSX { + interface IntrinsicElements { + foo: unknown; + } + } + + ; + `, + ` + declare namespace JSX { + interface IntrinsicElements { + foo: { + bar: any; + }; + } + } + ; + `, + ` + declare namespace JSX { + interface IntrinsicElements { + foo: { + bar: unknown; + }; + } + } + ; + `, ], invalid: [ + { + code: ` + interface AProps { + /** @deprecated */ + b: number | string; + } + + function A(props: AProps) { + return
; + } + + const a = ; + `, + errors: [ + { + column: 22, + data: { name: 'b' }, + endColumn: 23, + endLine: 11, + line: 11, + messageId: 'deprecated', + }, + ], + }, { code: ` /** @deprecated */ var a = undefined; @@ -2550,5 +2602,77 @@ class B extends A { }, ], }, + { + code: 'const a =
;', + errors: [ + { + column: 16, + data: { name: 'aria-grabbed', reason: 'in ARIA 1.1' }, + endColumn: 28, + endLine: 1, + line: 1, + messageId: 'deprecatedWithReason', + }, + ], + }, + { + code: ` + declare namespace JSX { + interface IntrinsicElements { + 'foo-bar:baz-bam': { + name: string; + /** + * @deprecated + */ + deprecatedProp: string; + }; + } + } + + const componentDashed = ; + `, + errors: [ + { + column: 59, + data: { name: 'deprecatedProp' }, + endColumn: 73, + endLine: 14, + line: 14, + messageId: 'deprecated', + }, + ], + }, + { + code: ` + import * as React from 'react'; + + interface Props { + /** + * @deprecated + */ + deprecatedProp: string; + } + + interface Tab { + List: React.FC; + } + + const Tab: Tab = { + List: () =>
Hi
, + }; + + const anotherExample = ; + `, + errors: [ + { + column: 42, + data: { name: 'deprecatedProp' }, + endColumn: 56, + endLine: 19, + line: 19, + messageId: 'deprecated', + }, + ], + }, ], });