diff --git a/packages/eslint-plugin/src/rules/no-deprecated.ts b/packages/eslint-plugin/src/rules/no-deprecated.ts index fb426747edd2..725ddaf3fce9 100644 --- a/packages/eslint-plugin/src/rules/no-deprecated.ts +++ b/packages/eslint-plugin/src/rules/no-deprecated.ts @@ -395,6 +395,46 @@ export default createRule({ }); } + function checkMemberExpression(node: TSESTree.MemberExpression): void { + if (!node.computed) { + return; + } + + const propertyType = services.getTypeAtLocation(node.property); + + if (propertyType.isLiteral()) { + const objectType = services.getTypeAtLocation(node.object); + + const propertyName = propertyType.isStringLiteral() + ? propertyType.value + : String(propertyType.value as number); + + const property = objectType.getProperty(propertyName); + + const reason = getJsDocDeprecation(property); + if (reason == null) { + return; + } + + if (typeMatchesSomeSpecifier(objectType, allow, services.program)) { + return; + } + + context.report({ + ...(reason + ? { + messageId: 'deprecatedWithReason', + data: { name: propertyName, reason }, + } + : { + messageId: 'deprecated', + data: { name: propertyName }, + }), + node: node.property, + }); + } + } + return { Identifier: checkIdentifier, JSXIdentifier(node): void { @@ -402,6 +442,7 @@ export default createRule({ checkIdentifier(node); } }, + MemberExpression: checkMemberExpression, PrivateIdentifier: checkIdentifier, Super: checkIdentifier, }; diff --git a/packages/eslint-plugin/tests/rules/no-deprecated.test.ts b/packages/eslint-plugin/tests/rules/no-deprecated.test.ts index 43ea34aa5471..1c8f354886df 100644 --- a/packages/eslint-plugin/tests/rules/no-deprecated.test.ts +++ b/packages/eslint-plugin/tests/rules/no-deprecated.test.ts @@ -364,13 +364,126 @@ exists('/foo'); const bar = { test }; `, ` - class A { - #b = () => {}; + const a = { + /** @deprecated */ + b: 'string', + }; - c() { - this.#b(); + const complex = Symbol() as any; + const c = a[complex]; + `, + ` + const a = { + b: 'string', + }; + + const c = a['b']; + `, + { + code: ` + interface AllowedType { + /** @deprecated */ + prop: string; } + + const obj: AllowedType = { + prop: 'test', + }; + + const value = obj['prop']; + `, + options: [ + { + allow: [ + { + from: 'file', + name: 'AllowedType', + }, + ], + }, + ], + }, + ` + const a = { + /** @deprecated */ + b: 'string', + }; + + const key = {}; + const c = a[key as any]; + `, + ` + const a = { + /** @deprecated */ + b: 'string', + }; + + const key = Symbol(); + const c = a[key as any]; + `, + ` + const a = { + /** @deprecated */ + b: 'string', + }; + + const key = undefined; + const c = a[key as any]; + `, + ` + const a = { + /** @deprecated */ + b: 'string', + }; + + const c = a['nonExistentProperty']; + `, + ` + const a = { + /** @deprecated */ + b: 'string', + }; + + function getKey() { + return 'c'; } + + const c = a[getKey()]; + `, + ` + const a = { + /** @deprecated */ + b: 'string', + }; + + const key = {}; + const c = a[key]; + `, + ` + const stringObj = new String('b'); + const a = { + /** @deprecated */ + b: 'string', + }; + const c = a[stringObj]; + `, + ` + const a = { + /** @deprecated */ + b: 'string', + }; + + const key = Symbol('key'); + const c = a[key]; + `, + ` + const a = { + /** @deprecated */ + b: 'string', + }; + + const key = null; + const c = a[key as any]; `, ], invalid: [ @@ -2912,5 +3025,153 @@ class B extends A { }, ], }, + { + code: ` + const a = { + /** @deprecated */ + b: 'string', + }; + + const c = a['b']; + `, + errors: [ + { + column: 21, + data: { name: 'b' }, + endColumn: 24, + endLine: 7, + line: 7, + messageId: 'deprecated', + }, + ], + }, + { + code: ` + const a = { + /** @deprecated */ + b: 'string', + }; + const x = 'b'; + const c = a[x]; + `, + errors: [ + { + column: 21, + data: { name: 'b' }, + endColumn: 22, + endLine: 7, + line: 7, + messageId: 'deprecated', + }, + ], + }, + { + code: ` + const a = { + /** @deprecated */ + [2]: 'string', + }; + const x = 'b'; + const c = a[2]; + `, + errors: [ + { + column: 21, + data: { name: '2' }, + endColumn: 22, + endLine: 7, + line: 7, + messageId: 'deprecated', + }, + ], + }, + { + code: ` + const a = { + /** @deprecated reason for deprecation */ + b: 'string', + }; + + const key = 'b'; + const stringKey = key as const; + const c = a[stringKey]; + `, + errors: [ + { + column: 21, + data: { name: 'b', reason: 'reason for deprecation' }, + endColumn: 30, + endLine: 9, + line: 9, + messageId: 'deprecatedWithReason', + }, + ], + }, + { + code: ` + enum Keys { + B = 'b', + } + + const a = { + /** @deprecated reason for deprecation */ + b: 'string', + }; + + const key = Keys.B; + const c = a[key]; + `, + errors: [ + { + column: 21, + data: { name: 'b', reason: 'reason for deprecation' }, + endColumn: 24, + endLine: 12, + line: 12, + messageId: 'deprecatedWithReason', + }, + ], + }, + { + code: ` + const a = { + /** @deprecated */ + b: 'string', + }; + + const key = \`b\`; + const c = a[key]; + `, + errors: [ + { + column: 21, + data: { name: 'b' }, + endColumn: 24, + endLine: 8, + line: 8, + messageId: 'deprecated', + }, + ], + }, + { + code: ` + const stringObj = 'b'; + const a = { + /** @deprecated */ + b: 'string', + }; + const c = a[stringObj]; + `, + errors: [ + { + column: 21, + data: { name: 'b' }, + endColumn: 30, + endLine: 7, + line: 7, + messageId: 'deprecated', + }, + ], + }, ], });