Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork2.8k
feat(eslint-plugin): [no-misused-objects-like] add new rule#10573
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
Uh oh!
There was an error while loading.Please reload this page.
Changes fromall commits
1f024a20e39ae1b08e8a554cd241813dd7dad28a4ac0ba704139d0e70af7510c1ebe07605c8e20ccee09a5658fd2d21b9a8dec282b1394e219fea55b335a78File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading.Please reload this page.
Jump to
Uh oh!
There was an error while loading.Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| --- | ||
| description: 'Disallow using `Object` methods on `Map` or `Set` when it might cause unexpected behavior.' | ||
| --- | ||
| import Tabs from '@theme/Tabs'; | ||
| import TabItem from '@theme/TabItem'; | ||
| > 🛑 This file is source code, not the primary documentation location! 🛑 | ||
| > | ||
| > See **https://typescript-eslint.io/rules/no-misused-object-likes** for documentation. | ||
| Object methods like `Object.keys` and `Object.values` are intended for plain objects. Using them on `Map` or `Set` is a common mistake that may lead to unexpected behavior. | ||
| This rule disallows such usage, encouraging correct alternatives. | ||
| <Tabs> | ||
| <TabItem value="❌ Incorrect"> | ||
| ```ts | ||
| const mySet = new Set(['foo', 'bar']); | ||
| Object.values(mySet); | ||
| const myMap = new Map([['foo', 'bar'], ['hello', 'world']]); | ||
| Object.entries(myMap); | ||
| ``` | ||
| </TabItem> | ||
| <TabItem value="✅ Correct"> | ||
| ```ts | ||
| const mySet = new Set(['foo', 'bar']); | ||
| mySet.keys(); | ||
| const myMap = new Map([['foo', 'bar'], ['hello', 'world']]); | ||
| myMap.entries(); | ||
| ``` | ||
| </TabItem> | ||
| </Tabs> | ||
| ## When Not To Use It | ||
| If your application extends the `Map` or `Set` types in unusual ways or manipulating their class prototype chains, you might not want this rule. | ||
| You might consider using [ESLint disable comments](https://eslint.org/docs/latest/use/configure/rules#using-configuration-comments-1) for those specific situations instead of completely disabling this rule. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,173 @@ | ||
| import type { | ||
| ParserServicesWithTypeInformation, | ||
| TSESTree, | ||
| } from '@typescript-eslint/utils'; | ||
| import type { RuleContext } from '@typescript-eslint/utils/ts-eslint'; | ||
| import type * as ts from 'typescript'; | ||
| import { | ||
| getConstrainedTypeAtLocation, | ||
| isBuiltinSymbolLike, | ||
| } from '@typescript-eslint/type-utils'; | ||
| import { AST_NODE_TYPES } from '@typescript-eslint/utils'; | ||
| import { | ||
| createRule, | ||
| getParserServices, | ||
| getStaticMemberAccessValue, | ||
| } from '../util'; | ||
| type Options = []; | ||
| type MessageIds = | ||
| | 'noMapOrSetInObjectAssign' | ||
| | 'noMapOrSetInObjectEntries' | ||
| | 'noMapOrSetInObjectHasOwn' | ||
| | 'noMapOrSetInObjectHasOwnProperty' | ||
| | 'noMapOrSetInObjectKeys' | ||
| | 'noMapOrSetInObjectValues'; | ||
| const objectConstructorMethodsMap = new Map<string, MessageIds>([ | ||
| ['assign', 'noMapOrSetInObjectAssign'], | ||
| ['entries', 'noMapOrSetInObjectEntries'], | ||
| ['hasOwn', 'noMapOrSetInObjectHasOwn'], | ||
| ['hasOwnProperty', 'noMapOrSetInObjectHasOwnProperty'], | ||
| ['keys', 'noMapOrSetInObjectKeys'], | ||
| ['values', 'noMapOrSetInObjectValues'], | ||
| ]); | ||
| export default createRule<Options, MessageIds>({ | ||
| name: 'no-misused-object-likes', | ||
| meta: { | ||
| type: 'problem', | ||
| docs: { | ||
| description: | ||
| 'Disallow using `Object` methods on `Map` or `Set` when it might cause unexpected behavior', | ||
| requiresTypeChecking: true, | ||
| }, | ||
| messages: { | ||
| noMapOrSetInObjectAssign: | ||
| "Using 'Object.assign()' with a '{{type}}' may lead to unexpected results. Consider alternative approaches instead.", | ||
| noMapOrSetInObjectEntries: | ||
| "Using 'Object.entries()' on a '{{type}}' will return an empty array. Consider using the 'entries()' method instead.", | ||
| noMapOrSetInObjectHasOwn: | ||
| "Using 'Object.hasOwn()' on a '{{type}}' may lead to unexpected results. Consider using the 'has(key)' method instead.", | ||
| noMapOrSetInObjectHasOwnProperty: | ||
| "Using 'Object.hasOwnProperty()' on a '{{type}}' may lead to unexpected results. Consider using the 'has(key)' method instead.", | ||
| noMapOrSetInObjectKeys: | ||
| "Using 'Object.keys()' on a '{{type}}' will return an empty array. Consider using the 'keys()' method instead.", | ||
| noMapOrSetInObjectValues: | ||
| "Using 'Object.values()' on a '{{type}}' will return an empty array. Consider using the 'values()' method instead.", | ||
| }, | ||
| schema: [], | ||
| }, | ||
| defaultOptions: [], | ||
| create(context) { | ||
| const services = getParserServices(context); | ||
| return { | ||
| CallExpression(node): void { | ||
| const objectMethodName = getPotentiallyMisusedObjectConstructorMethod( | ||
| context, | ||
| services, | ||
| node, | ||
| ); | ||
| if (objectMethodName == null) { | ||
| return; | ||
| } | ||
| const objectArgument = node.arguments.at(0); | ||
| if (!objectArgument) { | ||
| return; | ||
| } | ||
| const typeName = getViolatingTypeName( | ||
| services, | ||
| getConstrainedTypeAtLocation(services, objectArgument), | ||
| ); | ||
| if (typeName) { | ||
| const messageId = objectConstructorMethodsMap.get(objectMethodName); | ||
| if (messageId) { | ||
| context.report({ | ||
| node, | ||
| messageId, | ||
| data: { | ||
| type: typeName, | ||
| }, | ||
| }); | ||
| } | ||
| } | ||
| }, | ||
| }; | ||
| }, | ||
| }); | ||
| function getViolatingTypeName( | ||
| services: ParserServicesWithTypeInformation, | ||
| type: ts.Type, | ||
| ): string | null { | ||
| if (isSet(services.program, type)) { | ||
| return 'Set'; | ||
| } | ||
| if (isMap(services.program, type)) { | ||
| return 'Map'; | ||
| } | ||
| return null; | ||
| } | ||
| function isMap(program: ts.Program, type: ts.Type): boolean { | ||
| return isTypeRecurser(type, t => | ||
| isBuiltinSymbolLike(program, t, ['Map', 'ReadonlyMap', 'WeakMap']), | ||
| ); | ||
| } | ||
| function isSet(program: ts.Program, type: ts.Type): boolean { | ||
| return isTypeRecurser(type, t => | ||
| isBuiltinSymbolLike(program, t, ['Set', 'ReadonlySet', 'WeakSet']), | ||
| ); | ||
| } | ||
| function isTypeRecurser( | ||
| type: ts.Type, | ||
| predicate: (t: ts.Type) => boolean, | ||
| ): boolean { | ||
| if (type.isUnionOrIntersection()) { | ||
| return type.types.some(t => isTypeRecurser(t, predicate)); | ||
| } | ||
| return predicate(type); | ||
| } | ||
| function getPotentiallyMisusedObjectConstructorMethod( | ||
| context: RuleContext<string, unknown[]>, | ||
| services: ParserServicesWithTypeInformation, | ||
| node: TSESTree.CallExpression, | ||
| ): string | null { | ||
| if (node.callee.type !== AST_NODE_TYPES.MemberExpression) { | ||
| return null; | ||
| } | ||
| const objectType = getConstrainedTypeAtLocation(services, node.callee.object); | ||
| if (!isBuiltinSymbolLike(services.program, objectType, 'ObjectConstructor')) { | ||
| return null; | ||
| } | ||
| const staticAccessValue = getStaticMemberAccessValue(node.callee, context); | ||
| if (typeof staticAccessValue !== 'string') { | ||
| return null; | ||
| } | ||
| if (!objectConstructorMethodsMap.has(staticAccessValue)) { | ||
| return null; | ||
| } | ||
| return staticAccessValue; | ||
| } |
Some generated files are not rendered by default. Learn more abouthow customized files appear on GitHub.
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.