@dartess/eslint-plugin 0.8.1 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,17 @@
2
2
 
3
3
  [//]: # (https://keepachangelog.com/en/1.1.0/)
4
4
 
5
+ ## [0.10.0] - 2026-02-23
6
+
7
+ - add rule `@dartess/mobx-no-action-bound`
8
+ - add `@dartess/mobx-no-action-bound` to `mobx` config
9
+ - change `no-sequences` option `allowInParentheses` from `true` to `false`
10
+ - allow `mobx-require-observer` without settings because `mobx` can be used without hooks
11
+
12
+ ## [0.9.0] - 2026-02-03
13
+
14
+ - add `eslint-plugin-complete` to `recommended` config
15
+
5
16
  ## [0.8.1] - 2026-01-24
6
17
 
7
18
  - fix `mobx-require-observer`: now it doesn't lose generics
package/README.md CHANGED
@@ -8,6 +8,7 @@ Also extends
8
8
  * `eslint-plugin-import-x` — `recommended` & `typescript`
9
9
  * `@eslint-community/eslint-plugin-eslint-comments` — `recommended`
10
10
  * `eslint-plugin-de-morgan` — `recommended`
11
+ * `eslint-plugin-complete` — `recommended`
11
12
 
12
13
  Also can extends (if it is applicable)
13
14
  * `@eslint-react/eslint-plugin` — `strict-type-checked`
@@ -38,6 +39,7 @@ npm i -D eslint \
38
39
  eslint-plugin-import-x \
39
40
  eslint-import-resolver-typescript \
40
41
  eslint-plugin-unicorn \
42
+ eslint-plugin-complete \
41
43
  eslint-plugin-decorator-position \
42
44
  eslint-plugin-de-morgan \
43
45
  typescript-eslint \
@@ -68,6 +70,7 @@ npm i -D eslint-plugin-storybook
68
70
  Edit or create `eslint.config.ts` (or `eslint.config.mts`). You probably have to install `jiti` for it.
69
71
 
70
72
  ```ts
73
+ import type { TSESLint } from '@typescript-eslint/utils';
71
74
  import dartessEslintPluginRecommended from '@dartess/eslint-plugin/recommended';
72
75
  import dartessEslintPluginRecommendedPostFormat from '@dartess/eslint-plugin/recommended-post-format';
73
76
 
@@ -108,7 +111,7 @@ export default [
108
111
  // @see `Fine Tuning` -> `Formatters` section below
109
112
 
110
113
  ...dartessEslintPluginRecommendedPostFormat,
111
- ]
114
+ ] satisfies TSESLint.FlatConfig.ConfigArray;
112
115
 
113
116
  ```
114
117
 
@@ -152,6 +155,8 @@ This plugin requires manual setup for you build tools.
152
155
 
153
156
  If you're using Mobx with legacy decorators, you have to enable rule `mobx/missing-make-observable` manually.
154
157
 
158
+ if you're using Mobx stores with your custom hooks, set `settings.mobx.storeHooks` [by docs](docs/rules/mobx-require-observer.md).
159
+
155
160
  ## Supported Rules
156
161
 
157
162
  Each rule has emojis denoting:
@@ -177,6 +182,7 @@ Each rule has emojis denoting:
177
182
  | [mobx-require-observer](docs/rules/mobx-require-observer.md) | Components using the stores must be wrapped in an `observer` | ✅ | 🔧 | |
178
183
  | [mobx-sync-autorun](docs/rules/mobx-sync-autorun.md) | Enforce synchronous autorun callback | ✅ | | |
179
184
  | [mobx-sync-action](docs/rules/mobx-sync-action.md) | Enforce synchronous actions | ✅ | | |
185
+ | [mobx-no-action-bound](docs/rules/mobx-no-action-bound.md) | Enforce using arrow functions for binging `this` to actions | ✅ | | |
180
186
 
181
187
  ## Code Reuse Policy
182
188
 
@@ -9,6 +9,7 @@ const config = [
9
9
  'mobx/missing-make-observable': 'off', // useless with modern decorators syntax. TODO check original plugin?
10
10
  '@dartess/mobx-strict-observable-components-declaration': 'error',
11
11
  '@dartess/mobx-require-observer': 'error',
12
+ '@dartess/mobx-no-action-bound': 'error',
12
13
  '@dartess/mobx-sync-autorun': 'error', // TODO implement it by types?
13
14
  '@dartess/mobx-sync-action': 'error', // TODO implement it by types?
14
15
  },
@@ -10,6 +10,7 @@ import eslintCommentsPlugin from '@eslint-community/eslint-plugin-eslint-comment
10
10
  // @ts-ignore: https://github.com/NullVoxPopuli/eslint-plugin-decorator-position/issues/778
11
11
  import eslintPluginDecoratorPosition from 'eslint-plugin-decorator-position';
12
12
  import eslintPluginDeMorgan from 'eslint-plugin-de-morgan';
13
+ import esLintPluginComplete from 'eslint-plugin-complete';
13
14
  import dartessPlugin from "../index.js";
14
15
  import vendorRulesBestPractices from "./vendor-rules/best-practices.js";
15
16
  import vendorRulesErrors from "./vendor-rules/errors.js";
@@ -47,6 +48,7 @@ const config = [
47
48
  convertWarnsToErrorsIfNeeded(eslintPluginImportX.flatConfigs.typescript),
48
49
  convertWarnsToErrorsIfNeeded(eslintCommentsPlugin.recommended),
49
50
  convertWarnsToErrorsIfNeeded(eslintPluginDeMorgan.configs.recommended),
51
+ ...convertWarnsToErrorsIfNeeded(esLintPluginComplete.configs.recommended),
50
52
  {
51
53
  name: '@dartess/recommended',
52
54
  plugins: {
@@ -108,6 +110,8 @@ const config = [
108
110
  methods: 'above',
109
111
  },
110
112
  ],
113
+ // totally disable comma operator
114
+ 'no-sequences': ['error', { allowInParentheses: false }],
111
115
  },
112
116
  },
113
117
  {
@@ -224,6 +228,12 @@ const config = [
224
228
  ],
225
229
  // require names for tuple elements
226
230
  '@dartess/ts-named-tuple-elements': 'error',
231
+ // disable some recommended rules
232
+ 'complete/prefer-readonly-parameter-types': 'off', // can be flase-positive for Built-in methods
233
+ 'complete/no-mutable-return': 'off', // can be hamful
234
+ 'complete/strict-undefined-functions': 'off', // prefer unicorn/no-useless-undefined
235
+ 'complete/require-break': 'off', // can be false-positive with TS7027
236
+ 'complete/no-void-return-type': 'off', // conflict with @typescript-eslint/explicit-module-boundary-types
227
237
  },
228
238
  },
229
239
  {
@@ -8,9 +8,6 @@ declare const rules: {
8
8
  }];
9
9
  'default-case-last': "error";
10
10
  'default-param-last': "error";
11
- eqeqeq: ["error", string, {
12
- null: string;
13
- }];
14
11
  'grouped-accessor-pairs': "error";
15
12
  'guard-for-in': "error";
16
13
  'max-classes-per-file': ["error", number];
@@ -86,9 +83,7 @@ declare const rules: {
86
83
  'no-return-assign': ["error", string];
87
84
  'no-script-url': "error";
88
85
  'no-self-compare': "error";
89
- 'no-sequences': "error";
90
86
  'no-useless-concat': "error";
91
- 'no-useless-return': "error";
92
87
  'no-void': "error";
93
88
  'prefer-promise-reject-errors': ["error", {
94
89
  allowEmptyReject: boolean;
@@ -17,9 +17,6 @@ const rules = {
17
17
  'default-case-last': 'error',
18
18
  // https://eslint.org/docs/rules/default-param-last
19
19
  'default-param-last': 'error',
20
- // require the use of === and !==
21
- // https://eslint.org/docs/rules/eqeqeq
22
- eqeqeq: ['error', 'always', { null: 'ignore' }],
23
20
  // Require grouped accessor pairs in object literals and classes
24
21
  // https://eslint.org/docs/rules/grouped-accessor-pairs
25
22
  'grouped-accessor-pairs': 'error',
@@ -168,15 +165,9 @@ const rules = {
168
165
  // disallow comparisons where both sides are exactly the same
169
166
  // https://eslint.org/docs/rules/no-self-compare
170
167
  'no-self-compare': 'error',
171
- // disallow use of comma operator
172
- // https://eslint.org/docs/rules/no-sequences
173
- 'no-sequences': 'error',
174
168
  // disallow useless string concatenation
175
169
  // https://eslint.org/docs/rules/no-useless-concat
176
170
  'no-useless-concat': 'error',
177
- // disallow redundant return; keywords
178
- // https://eslint.org/docs/rules/no-useless-return
179
- 'no-useless-return': 'error',
180
171
  // disallow use of void operator
181
172
  // https://eslint.org/docs/rules/no-void
182
173
  'no-void': 'error',
@@ -3,7 +3,6 @@ declare const rules: {
3
3
  'no-cond-assign': ["error", string];
4
4
  'no-inner-declarations': "error";
5
5
  'no-promise-executor-return': "error";
6
- 'no-template-curly-in-string': "error";
7
6
  'no-unreachable-loop': ["error", {
8
7
  ignore: never[];
9
8
  }];
@@ -13,9 +13,6 @@ const rules = {
13
13
  // Disallow returning values from Promise executor functions
14
14
  // https://eslint.org/docs/rules/no-promise-executor-return
15
15
  'no-promise-executor-return': 'error',
16
- // Disallow template literal placeholder syntax in regular strings
17
- // https://eslint.org/docs/rules/no-template-curly-in-string
18
- 'no-template-curly-in-string': 'error',
19
16
  // Disallow loops with a body that allows only one iteration
20
17
  // https://eslint.org/docs/rules/no-unreachable-loop
21
18
  'no-unreachable-loop': [
@@ -12,10 +12,6 @@ declare const rules: {
12
12
  ignoreConstructors: boolean;
13
13
  avoidQuotes: boolean;
14
14
  }];
15
- 'prefer-const': ["error", {
16
- destructuring: string;
17
- ignoreReadBeforeAssign: boolean;
18
- }];
19
15
  'prefer-destructuring': ["error", {
20
16
  VariableDeclarator: {
21
17
  array: boolean;
@@ -37,14 +37,6 @@ const rules = {
37
37
  avoidQuotes: true,
38
38
  },
39
39
  ],
40
- // suggest using of const declaration for variables that are never modified after declared
41
- 'prefer-const': [
42
- 'error',
43
- {
44
- destructuring: 'any',
45
- ignoreReadBeforeAssign: true,
46
- },
47
- ],
48
40
  // Prefer destructuring from arrays and objects
49
41
  // https://eslint.org/docs/rules/prefer-destructuring
50
42
  'prefer-destructuring': [
@@ -14,7 +14,6 @@ declare const rules: {
14
14
  'no-lonely-if': "error";
15
15
  'no-multi-assign': ["error"];
16
16
  'no-nested-ternary': "error";
17
- 'no-plusplus': "error";
18
17
  'no-underscore-dangle': ["error", {
19
18
  allow: never[];
20
19
  allowAfterThis: boolean;
@@ -39,9 +39,6 @@ const rules = {
39
39
  'no-multi-assign': ['error'],
40
40
  // disallow nested ternary expressions
41
41
  'no-nested-ternary': 'error',
42
- // disallow use of unary operators, ++ and --
43
- // https://eslint.org/docs/rules/no-plusplus
44
- 'no-plusplus': 'error',
45
42
  // disallow dangling underscores in identifiers
46
43
  // https://eslint.org/docs/rules/no-underscore-dangle
47
44
  'no-underscore-dangle': [
package/dist/index.js CHANGED
@@ -5,6 +5,7 @@ import ruleStoriesExportMeta from "./rules/stories-export-meta.js";
5
5
  import ruleStoriesExportTyped from "./rules/stories-export-typed.js";
6
6
  import ruleMobxStrictObservableComponentsDeclaration from "./rules/mobx-strict-observable-components-declaration.js";
7
7
  import ruleMobxRequireObserver from "./rules/mobx-require-observer.js";
8
+ import ruleMobxNoActionBound from "./rules/mobx-no-action-bound.js";
8
9
  import ruleImportsMaxParentDepth from "./rules/imports-max-parent-depth.js";
9
10
  import ruleTsNamedTupleElements from "./rules/ts-named-tuple-elements.js";
10
11
  import ruleMobxSyncAutorun from "./rules/mobx-sync-autorun.js";
@@ -23,6 +24,7 @@ const plugin = {
23
24
  'mobx-sync-action': ruleMobxSyncAction,
24
25
  'mobx-sync-autorun': ruleMobxSyncAutorun,
25
26
  'mobx-require-observer': ruleMobxRequireObserver,
27
+ 'mobx-no-action-bound': ruleMobxNoActionBound,
26
28
  'stories-export-typed': ruleStoriesExportTyped,
27
29
  'stories-export-meta': ruleStoriesExportMeta,
28
30
  'ts-named-tuple-elements': ruleTsNamedTupleElements,
@@ -0,0 +1,3 @@
1
+ import { ESLintUtils } from '@typescript-eslint/utils';
2
+ declare const _default: ESLintUtils.RuleModule<"noActionBound", [], unknown, ESLintUtils.RuleListener>;
3
+ export default _default;
@@ -0,0 +1,25 @@
1
+ import { ESLintUtils } from '@typescript-eslint/utils';
2
+ export default ESLintUtils.RuleCreator(() => '')({
3
+ name: 'mobx-no-action-bound',
4
+ defaultOptions: [],
5
+ meta: {
6
+ type: 'problem',
7
+ docs: {
8
+ description: 'Enforce using arrow functions for binging `this` to actions',
9
+ },
10
+ messages: {
11
+ noActionBound: 'rewrite action with arrow function for binding `this`',
12
+ },
13
+ schema: [],
14
+ },
15
+ create(context) {
16
+ return {
17
+ "Decorator[expression.object.name='action'][expression.property.name='bound']": function (node) {
18
+ context.report({
19
+ node,
20
+ messageId: 'noActionBound',
21
+ });
22
+ },
23
+ };
24
+ },
25
+ });
@@ -54,7 +54,7 @@ export default ESLintUtils.RuleCreator(() => '')({
54
54
  const mobxSettings = context.settings.mobx;
55
55
  const hooks = Array.isArray(mobxSettings?.storeHooks) ? mobxSettings.storeHooks : [];
56
56
  if (hooks.length === 0) {
57
- throw new Error('Please fill settings.mobx.storeHooks');
57
+ return {};
58
58
  }
59
59
  const { sourceCode } = context;
60
60
  const program = sourceCode.ast;
@@ -138,7 +138,7 @@ export default ESLintUtils.RuleCreator(() => '')({
138
138
  const replacement = `const ${name} = observer(${original});`;
139
139
  fixes.push(fixer.replaceText(fn, replacement));
140
140
  }
141
- else if (varDecl && varDecl.type === AST_NODE_TYPES.VariableDeclaration) {
141
+ else if (varDecl?.type === AST_NODE_TYPES.VariableDeclaration) {
142
142
  // const Name = () => {} or function expression
143
143
  const id = fn.parent.id;
144
144
  const { name } = id;
@@ -0,0 +1,40 @@
1
+ # Enforce using arrow functions for binging `this` to actions (mobx-no-action-bound)
2
+
3
+ Decorator `@action.bound` automatically bind methods to class instances to avoid context loss (`this`).
4
+ However, this can also be achieved by declaring methods as an properies with arrow functions.
5
+
6
+ This is the first problem with bound methods—their functionality is duplicated by native abilities.
7
+
8
+ In general, for optimization purposes, it's not recommended to bind _everything_; by default,
9
+ class methods should be on the prototype.
10
+
11
+ To resolve the dilemma of "how to avoid binding unnecessary things and avoid losing context"
12
+ the [`@typescript-eslint/unbound-method`](https://typescript-eslint.io/rules/unbound-method/) rule exists.
13
+
14
+ However, it can't detect whether a method is bound via an `@action.bound`. Therefore, it's best to avoid
15
+ using `@action.bound` at all and leave only one binding method—arrow functions with `@typescript-eslint/unbound-method`.
16
+ In this case you don't need think about it.
17
+
18
+ ## Rule Details
19
+
20
+ Examples of **incorrect** code for this rule:
21
+
22
+ ```ts
23
+ class Store {
24
+ @action.bound
25
+ reset() {
26
+ this.value = null;
27
+ };
28
+ }
29
+ ```
30
+
31
+ Examples of **correct** code for this rule:
32
+
33
+ ```ts
34
+ class Store {
35
+ @action
36
+ reset = () => {
37
+ this.value = null;
38
+ };
39
+ }
40
+ ```
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@dartess/eslint-plugin",
3
3
  "type": "module",
4
- "version": "0.8.1",
4
+ "version": "0.10.0",
5
5
  "description": "A set of rules and configs for various TypeScript projects",
6
6
  "keywords": [
7
7
  "eslint",
@@ -53,6 +53,7 @@
53
53
  "@stylistic/eslint-plugin": "^5.7.0",
54
54
  "eslint": "^9.0.0",
55
55
  "eslint-import-resolver-typescript": "^4.0.0",
56
+ "eslint-plugin-complete": "^1.2.0",
56
57
  "eslint-plugin-de-morgan": "^2.0.0",
57
58
  "eslint-plugin-decorator-position": "^6.0.0",
58
59
  "eslint-plugin-import-x": "^4.1.0",