Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

feat(eslint-plugin): [no-misused-object-like] add rule#9369

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

Closed
Show file tree
Hide file tree
Changes fromall commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletionspackages/eslint-plugin/src/rules/index.ts
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -64,6 +64,7 @@ import noLossOfPrecision from './no-loss-of-precision';
import noMagicNumbers from './no-magic-numbers';
import noMeaninglessVoidOperator from './no-meaningless-void-operator';
import noMisusedNew from './no-misused-new';
import noMisusedObjectLikes from './no-misused-object-likes';
import noMisusedPromises from './no-misused-promises';
import noMixedEnums from './no-mixed-enums';
import noNamespace from './no-namespace';
Expand DownExpand Up@@ -210,6 +211,7 @@ export default {
'no-magic-numbers': noMagicNumbers,
'no-meaningless-void-operator': noMeaninglessVoidOperator,
'no-misused-new': noMisusedNew,
'no-misused-object-likes': noMisusedObjectLikes,
'no-misused-promises': noMisusedPromises,
'no-mixed-enums': noMixedEnums,
'no-namespace': noNamespace,
Expand Down
149 changes: 149 additions & 0 deletionspackages/eslint-plugin/src/rules/no-misused-object-likes.ts
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
import type { Identifier, MemberExpression } from '@typescript-eslint/ast-spec';
import type { TSESTree } from '@typescript-eslint/utils';

import { createRule, getParserServices } from '../util';

export type Options = [
{
checkObjectKeysForMap?: boolean;
checkObjectValuesForMap?: boolean;
checkObjectEntriesForMap?: boolean;
checkObjectKeysForSet?: boolean;
checkObjectValuesForSet?: boolean;
checkObjectEntriesForSet?: boolean;
},
];

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

[Question] Why allow such a granular set of options? Is there a known use case where someone would want to skip, say, onlyObject.values?

export type MessageIds =
| 'objectKeysForMap'
| 'objectValuesForMap'
| 'objectEntriesForMap'
| 'objectKeysForSet'
| 'objectValuesForSet'
| 'objectEntriesForSet';

export default createRule<Options, MessageIds>({
name: 'no-misused-object-likes',
defaultOptions: [
{
checkObjectKeysForMap: true,
checkObjectValuesForMap: true,
checkObjectEntriesForMap: true,
checkObjectKeysForSet: true,
checkObjectValuesForSet: true,
checkObjectEntriesForSet: true,
},
],

meta: {
type: 'problem',
docs: {
description:
'Enforce check `Object.values(...)`, `Object.keys(...)`, `Object.entries(...)` usage with Map/Set objects',
requiresTypeChecking: false,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Pretty sure this is required?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Yup, theservices.getTypeAtLocation below necessitates it.

},
messages: {
objectKeysForMap: "Don't use `Object.keys()` for Map objects",

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I think this may be overly specific, granularity forSetLike andMapLike is likely sufficient since it would be weird to allow callingObject.values( on a Map but banningObject.keys

objectValuesForMap: "Don't use `Object.values()` for Map objects",
objectEntriesForMap: "Don't use `Object.entries()` for Map objects",
objectKeysForSet: "Don't use `Object.keys()` for Set",
objectValuesForSet: "Don't use `Object.values()` for Set",
objectEntriesForSet: "Don't use `Object.entries()` for Set",
},
schema: [
{
type: 'object',
additionalProperties: false,
properties: {
checkObjectKeysForMap: {
description: 'Check usage Object.keys for Map object',
type: 'boolean',
},
checkObjectValuesForMap: {
description: 'Check usage Object.values for Map object',
type: 'boolean',
},
checkObjectEntriesForMap: {
description: 'Check usage Object.entries for Map object',
type: 'boolean',
},
checkObjectKeysForSet: {
description: 'Check usage Object.keys for Set object',
type: 'boolean',
},
checkObjectValuesForSet: {
description: 'Check usage Object.values for Set object',
type: 'boolean',
},
checkObjectEntriesForSet: {
description: 'Check usage Object.entries for Set object',
type: 'boolean',
},
},
},
],
},

create(context, [options]) {
const services = getParserServices(context);

function checkObjectMethodCall(
callExpression: TSESTree.CallExpression,
): void {
const argument = callExpression.arguments[0];
const type = services.getTypeAtLocation(argument);
const argumentTypeName = type.getSymbol()?.name;
const callee = callExpression.callee as MemberExpression;
const objectMethod = (callee.property as Identifier).name;
Comment on lines +95 to +96

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

[Bug] Using anas assertion on AST nodes is generally a sign of either:

  • 🍎 The node is known to be a more specific type than what you've told the type system
  • 🍌 The code isn't properly handling edge cases

TheCallExpression[callee.object.name=Object][callee.property.name=keys][arguments.length=1] below makes me think it's 🍎. Which means you'd want to use a type for the node.

typeCallExpressionWithStuff=TSESTree.CallExpression&{callee:{object:{name:'Object';// ...


if (argumentTypeName === 'Map') {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

[Bug] If a user defines their ownMap class, this will falsely report:

classMap{value=1;}constmap=newMap();Object.values(map);

if (objectMethod === 'keys' && options.checkObjectKeysForMap) {
context.report({
node: callExpression,
messageId: 'objectKeysForMap',
});
}
if (objectMethod === 'values' && options.checkObjectValuesForMap) {
context.report({
node: callExpression,
messageId: 'objectValuesForMap',
});
}
if (objectMethod === 'entries' && options.checkObjectEntriesForMap) {
context.report({
node: callExpression,
messageId: 'objectEntriesForMap',
});
}
}
if (argumentTypeName === 'Set') {
if (objectMethod === 'keys' && options.checkObjectKeysForSet) {
context.report({
node: callExpression,
messageId: 'objectKeysForSet',
});
}
if (objectMethod === 'values' && options.checkObjectValuesForSet) {
context.report({
node: callExpression,
messageId: 'objectValuesForSet',
});
}
if (objectMethod === 'entries' && options.checkObjectEntriesForSet) {
context.report({
node: callExpression,
messageId: 'objectEntriesForSet',
});
}
}
}

return {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

[Bug] Looking at built-in global objects inhttps://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects#keyed_collections, I think at leastWeakMap andWeakSet should also be handled by this rule.

'CallExpression[callee.object.name=Object][callee.property.name=keys][arguments.length=1]':

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

This is missing various other ones such asObject.assign/groupBy/hasOwn?, it might be better to move this check into thecheckObjectMethodCall function and have a list of banned methods there.

JoshuaKGoldberg reacted with thumbs up emoji

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

object.name=Object

[Bug] False report case:

constObject={keys:(value)=>[value]};exportconstkeys=Object.keys(newMap());

checkObjectMethodCall,
'CallExpression[callee.object.name=Object][callee.property.name=values][arguments.length=1]':
checkObjectMethodCall,
'CallExpression[callee.object.name=Object][callee.property.name=entries][arguments.length=1]':
checkObjectMethodCall,
};
},
});
174 changes: 174 additions & 0 deletionspackages/eslint-plugin/tests/rules/no-misused-object-likes.test.ts
View file
Open in desktop

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

[Testing] Some missing test cases:

  • Intersections and unions: withMap,Set, and/or other types
  • Readonlies:ReadonlyMap,ReadonlySet

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
import { RuleTester } from '@typescript-eslint/rule-tester';

import rule from '../../src/rules/no-misused-object-likes';
import { getFixturesRootDir } from '../RuleTester';

const rootPath = getFixturesRootDir();

const ruleTester = new RuleTester({
parser: '@typescript-eslint/parser',
parserOptions: {
tsconfigRootDir: rootPath,
project: './tsconfig.json',
},
});

ruleTester.run('no-misused-object-likes', rule, {
valid: [
{
code: `
class ExMap extends Map {}
const map = new ExMap();
Object.keys(map);
`,
},
{
code: `
class ExMap extends Map {}
const map = new ExMap();
Object.values(map);
`,
},
{
code: `
class ExMap extends Map {}
const map = new ExMap();
Object.entries(map);
`,
},
{
code: `
const test = {};
Object.entries(test);
`,
},
{
code: `
const test = {};
Object.keys(test);
`,
},
{
code: `
const test = {};
Object.values(test);
`,
},
{
code: `
const test = [];
Object.keys(test);
`,
},
{
code: `
const test = [];
Object.values(test);
`,
},
{
code: `
const test = [];
Object.entries(test);
`,
},
{
options: [{ checkObjectKeysForMap: false }],
code: `
const map = new Map();
const result = Object.keys(map);
`,
},
{
options: [{ checkObjectEntriesForMap: false }],
code: `
const map = new Map();
const result = Object.entries(map);
`,
},
{
options: [{ checkObjectValuesForMap: false }],
code: `
const map = new Map();
const result = Object.values(map);
`,
},
{
options: [{ checkObjectKeysForSet: false }],
code: `
const set = new Set();
const result = Object.keys(set);
`,
},
{
options: [{ checkObjectEntriesForSet: false }],
code: `
const set = new Set();
const result = Object.entries(set);
`,
},
{
options: [{ checkObjectValuesForSet: false }],
code: `
const set = new Set();
const result = Object.values(set);
`,
},
{
code: `
const test = 123;
Object.keys(test);
`,
},
{
code: `
const test = new WeakMap();
Object.keys(test);
`,
},
],
invalid: [
{
code: `
const map = new Map();
const result = Object.keys(map);
`,
errors: [{ messageId: 'objectKeysForMap' }],
},
{
code: `
const map = new Map();
const result = Object.entries(map);
`,
errors: [{ messageId: 'objectEntriesForMap' }],
},
{
code: `
const map = new Map();
const result = Object.values(map);
`,
errors: [{ messageId: 'objectValuesForMap' }],
},
{
code: `
const set = new Set();
const result = Object.keys(set);
`,
errors: [{ messageId: 'objectKeysForSet' }],
},
{
code: `
const set = new Set();
const result = Object.entries(set);
`,
errors: [{ messageId: 'objectEntriesForSet' }],
},
{
code: `
const set = new Set();
const result = Object.values(set);
`,
errors: [{ messageId: 'objectValuesForSet' }],
},
],
});

[8]ページ先頭

©2009-2025 Movatter.jp