From ca600d7851449a2351c1826f96578f7219e987b1 Mon Sep 17 00:00:00 2001 From: "Azat S." Date: Sat, 22 Feb 2025 14:44:03 +0300 Subject: [PATCH 1/3] fix(eslint-plugin): [no-deprecated] support computed member access --- .../eslint-plugin/src/rules/no-deprecated.ts | 51 ++++++++++++++++ .../tests/rules/no-deprecated.test.ts | 60 +++++++++++++++++++ 2 files changed, 111 insertions(+) diff --git a/packages/eslint-plugin/src/rules/no-deprecated.ts b/packages/eslint-plugin/src/rules/no-deprecated.ts index fb426747edd2..ebb9b4fc2f7a 100644 --- a/packages/eslint-plugin/src/rules/no-deprecated.ts +++ b/packages/eslint-plugin/src/rules/no-deprecated.ts @@ -395,6 +395,56 @@ export default createRule({ }); } + function checkMemberExpression(node: TSESTree.MemberExpression): void { + if (!node.computed) { + return; + } + + const propertyType = services.getTypeAtLocation(node.property); + + if (propertyType.isStringLiteral() || propertyType.isLiteral()) { + const objectType = services.getTypeAtLocation(node.object); + + let propertyName: string | undefined; + + if (propertyType.isStringLiteral()) { + propertyName = propertyType.value; + } else if (typeof propertyType.value === 'string') { + propertyName = propertyType.value; + } else if (typeof propertyType.value === 'number') { + propertyName = String(propertyType.value); + } + + if (!propertyName) { + return; + } + + 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 +452,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..b342bdb6bcd1 100644 --- a/packages/eslint-plugin/tests/rules/no-deprecated.test.ts +++ b/packages/eslint-plugin/tests/rules/no-deprecated.test.ts @@ -2912,5 +2912,65 @@ 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', + }, + ], + }, ], }); From a19754f6703dcc41444ddd29da732b87d7fb6c15 Mon Sep 17 00:00:00 2001 From: "Azat S." Date: Sun, 2 Mar 2025 14:59:26 +0300 Subject: [PATCH 2/3] test(eslint-plugin): [no-deprecated] increase test coverage --- .../eslint-plugin/src/rules/no-deprecated.ts | 12 +- .../tests/rules/no-deprecated.test.ts | 209 +++++++++++++++++- 2 files changed, 208 insertions(+), 13 deletions(-) diff --git a/packages/eslint-plugin/src/rules/no-deprecated.ts b/packages/eslint-plugin/src/rules/no-deprecated.ts index ebb9b4fc2f7a..5bdcbd85a97f 100644 --- a/packages/eslint-plugin/src/rules/no-deprecated.ts +++ b/packages/eslint-plugin/src/rules/no-deprecated.ts @@ -405,18 +405,12 @@ export default createRule({ if (propertyType.isStringLiteral() || propertyType.isLiteral()) { const objectType = services.getTypeAtLocation(node.object); - let propertyName: string | undefined; + let propertyName: string; if (propertyType.isStringLiteral()) { propertyName = propertyType.value; - } else if (typeof propertyType.value === 'string') { - propertyName = propertyType.value; - } else if (typeof propertyType.value === 'number') { - propertyName = String(propertyType.value); - } - - if (!propertyName) { - return; + } else { + propertyName = String(propertyType.value as number); } const property = objectType.getProperty(propertyName); diff --git a/packages/eslint-plugin/tests/rules/no-deprecated.test.ts b/packages/eslint-plugin/tests/rules/no-deprecated.test.ts index b342bdb6bcd1..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', + }; + + const complex = Symbol() as any; + const c = a[complex]; + `, + ` + const a = { + b: 'string', + }; - c() { - this.#b(); + 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: [ @@ -2972,5 +3085,93 @@ class B extends A { }, ], }, + { + 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', + }, + ], + }, ], }); From 8bff4a61aff9f78ae54b4f570e38063790918c60 Mon Sep 17 00:00:00 2001 From: "Azat S." Date: Mon, 5 May 2025 19:20:28 +0300 Subject: [PATCH 3/3] fix(eslint-plugin): [no-deprecated] add fix by review --- packages/eslint-plugin/src/rules/no-deprecated.ts | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/packages/eslint-plugin/src/rules/no-deprecated.ts b/packages/eslint-plugin/src/rules/no-deprecated.ts index 5bdcbd85a97f..725ddaf3fce9 100644 --- a/packages/eslint-plugin/src/rules/no-deprecated.ts +++ b/packages/eslint-plugin/src/rules/no-deprecated.ts @@ -402,16 +402,12 @@ export default createRule({ const propertyType = services.getTypeAtLocation(node.property); - if (propertyType.isStringLiteral() || propertyType.isLiteral()) { + if (propertyType.isLiteral()) { const objectType = services.getTypeAtLocation(node.object); - let propertyName: string; - - if (propertyType.isStringLiteral()) { - propertyName = propertyType.value; - } else { - propertyName = String(propertyType.value as number); - } + const propertyName = propertyType.isStringLiteral() + ? propertyType.value + : String(propertyType.value as number); const property = objectType.getProperty(propertyName);