Skip to content

Commit

Permalink
feat: add es-roikoren/no-object-hasown rule
Browse files Browse the repository at this point in the history
  • Loading branch information
roikoren755 committed Nov 11, 2021
1 parent f425126 commit 9b40bb3
Show file tree
Hide file tree
Showing 9 changed files with 84 additions and 7 deletions.
5 changes: 5 additions & 0 deletions .changeset/thin-cows-notice.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'eslint-plugin-es-roikoren': patch
---

feat: add `es-roikoren/no-object-hasown` rule
1 change: 1 addition & 0 deletions docs/rules/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ There is a config that enables the rules in this category: `plugin:es-roikoren/n
| Rule ID | Description | |
|:--------|:------------|:--:|
| [es-roikoren/no-class-fields](./no-class-fields.md) | disallow class fields. | |
| [es-roikoren/no-object-hasown](./no-object-hasown.md) | disallow the `Object.hasOwn` method. | |
| [es-roikoren/no-regexp-d-flag](./no-regexp-d-flag.md) | disallow RegExp `d` flag. | |

## ES2021
Expand Down
20 changes: 20 additions & 0 deletions docs/rules/no-object-hasown.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# es-roikoren/no-object-hasown
> disallow the `Object.hasOwn` method.
- ✅ The following configurations enable this rule: `plugin:es-roikoren/no-new-in-esnext`

This rule reports ES2022 `Object.hasOwn` method as errors.

## Examples

⛔ Examples of **incorrect** code for this rule:

```js
/*eslint es-roikoren/no-object-hasown: error */
const hasFoo = Object.hasOwn(obj, 'foo');
```

## 📚 References

- [Rule source](https://github.com/roikoren755/eslint-plugin-es/blob/v0.0.3/src/rules/no-object-hasown.ts)
- [Test source](https://github.com/roikoren755/eslint-plugin-es/blob/v0.0.3/tests/src/rules/no-object-hasown.ts)
15 changes: 10 additions & 5 deletions scripts/new-rule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,22 @@ import { writeFileSync } from 'fs';
import path from 'path';
import { TSESLint } from '@typescript-eslint/experimental-utils';

const run = async (ruleId: string): Promise<void> => {
if (!ruleId) {
const run = async (appliedRuleId: string): Promise<void> => {
if (!appliedRuleId) {
console.error('Usage: npm run new <RuleID>');
process.exitCode = 1;

return;
}

if (!/^[a-z0-9-]+$/u.test(ruleId)) {
console.error("Invalid RuleID '%s'.", ruleId);
if (!/^[a-z0-9-]+$/u.test(appliedRuleId)) {
console.error("Invalid RuleID '%s'.", appliedRuleId);
process.exitCode = 1;

return;
}

const ruleId = `no-${appliedRuleId}`;
const ruleFile = path.resolve(__dirname, `../src/rules/${ruleId}.ts`);
const testFile = path.resolve(__dirname, `../tests/src/rules/${ruleId}.ts`);
const docFile = path.resolve(__dirname, `../docs/rules/${ruleId}.md`);
Expand All @@ -43,9 +44,13 @@ export default createRule<[], 'forbidden'>({
);
writeFileSync(
testFile,
`import { RuleTester } from '../../tester';
`import { AST_NODE_TYPES } from '@typescript-eslint/types';
import { RuleTester } from '../../tester';
import rule from '../../../src/rules/${ruleId}';
const error = { messageId: 'forbidden' as const, line: 1, column: 1, type: AST_NODE_TYPES.MemberExpression, data: {} };
if (!RuleTester.isSupported(2022)) {
console.log('Skip the tests of ${ruleId}.');
} else {
Expand Down
6 changes: 5 additions & 1 deletion src/configs/no-new-in-esnext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,9 @@
*/
export default {
plugins: ['es-roikoren'],
rules: { 'es-roikoren/no-class-fields': 'error', 'es-roikoren/no-regexp-d-flag': 'error' },
rules: {
'es-roikoren/no-class-fields': 'error',
'es-roikoren/no-object-hasown': 'error',
'es-roikoren/no-regexp-d-flag': 'error',
},
};
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ import noObjectGetownpropertydescriptors from './rules/no-object-getownpropertyd
import noObjectGetownpropertynames from './rules/no-object-getownpropertynames';
import noObjectGetownpropertysymbols from './rules/no-object-getownpropertysymbols';
import noObjectGetprototypeof from './rules/no-object-getprototypeof';
import noObjectHasown from './rules/no-object-hasown';
import noObjectIs from './rules/no-object-is';
import noObjectIsextensible from './rules/no-object-isextensible';
import noObjectIsfrozen from './rules/no-object-isfrozen';
Expand Down Expand Up @@ -283,6 +284,7 @@ export default {
'no-object-getownpropertynames': noObjectGetownpropertynames,
'no-object-getownpropertysymbols': noObjectGetownpropertysymbols,
'no-object-getprototypeof': noObjectGetprototypeof,
'no-object-hasown': noObjectHasown,
'no-object-is': noObjectIs,
'no-object-isextensible': noObjectIsextensible,
'no-object-isfrozen': noObjectIsfrozen,
Expand Down
19 changes: 19 additions & 0 deletions src/rules/no-object-hasown.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { ASTUtils } from '@typescript-eslint/experimental-utils';

import { createRule } from '../util/create-rule';
import { referenceTracker } from '../util/reference-tracker';

export const category = 'ES2022';
export default createRule<[], 'forbidden'>({
name: 'no-object-hasown',
meta: {
type: 'problem',
docs: { description: 'disallow the `Object.hasOwn` method.', recommended: false },
schema: [],
messages: { forbidden: "ES2022 '{{name}}' method is forbidden." },
},
defaultOptions: [],
create(context) {
return referenceTracker(context, { Object: { hasOwn: { [ASTUtils.ReferenceTracker.READ]: true } } });
},
});
2 changes: 1 addition & 1 deletion src/util/reference-tracker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { TSESLint } from '@typescript-eslint/experimental-utils';

export const referenceTracker = (
context: TSESLint.RuleContext<'forbidden', readonly []>,
traceMap: ASTUtils.ReferenceTracker.TraceMap<boolean>,
traceMap: ASTUtils.ReferenceTracker.TraceMap<true>,
): TSESLint.RuleListener => ({
'Program:exit'() {
const tracker = new ASTUtils.ReferenceTracker(context.getScope());
Expand Down
21 changes: 21 additions & 0 deletions tests/src/rules/no-object-hasown.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { AST_NODE_TYPES } from '@typescript-eslint/types';

import { RuleTester } from '../../tester';
import rule from '../../../src/rules/no-object-hasown';

const error = {
messageId: 'forbidden' as const,
line: 1,
column: 1,
type: AST_NODE_TYPES.MemberExpression,
data: { name: 'Object.hasOwn' },
};

if (!RuleTester.isSupported(2022)) {
console.log('Skip the tests of no-object-hasown.');
} else {
new RuleTester().run('no-object-hasown', rule, {
valid: ['Object', 'Object.assign', 'let Object = 0; Object.is'],
invalid: [{ code: 'Object.hasOwn', errors: [error] }],
});
}

0 comments on commit 9b40bb3

Please sign in to comment.