@dartess/eslint-plugin 0.7.1 → 0.8.1

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,15 @@
2
2
 
3
3
  [//]: # (https://keepachangelog.com/en/1.1.0/)
4
4
 
5
+ ## [0.8.1] - 2026-01-24
6
+
7
+ - fix `mobx-require-observer`: now it doesn't lose generics
8
+ - fix `mobx-require-observer`: now it respects directives like `'use client'`
9
+
10
+ ## [0.8.0] - 2026-01-22
11
+
12
+ - All recommended warnings are converted to errors because warnings are useless.
13
+
5
14
  ## [0.7.1] - 2026-01-18
6
15
  - rename some rules for more clear naming and consistency:
7
16
 
package/README.md CHANGED
@@ -17,11 +17,13 @@ Also can extends (if it is applicable)
17
17
  * `eslint-plugin-mobx` — `recommended`
18
18
  * `eslint-plugin-storybook` — `recommended` & `csf-strict`
19
19
 
20
+ _Note: all recommended warnings are converted to errors because warnings are useless._
21
+
20
22
  All of it pinched with extra configs, setups and extra rules. Just take it and use it!
21
23
 
22
24
  ### Notes
23
25
 
24
- 1. The package is intended for use with TypeScript (it'll be useful for plain JS, but it hasn't been weel-tested).
26
+ 1. The package is intended for use with TypeScript (it'll be useful for plain JS, but it hasn't been well-tested).
25
27
 
26
28
  2. The package is intended for use only with the `flat` eslint config.
27
29
 
@@ -1,3 +1,3 @@
1
- import type { Linter } from 'eslint';
2
- declare const config: Array<Linter.Config>;
1
+ import type { TSESLint } from '@typescript-eslint/utils';
2
+ declare const config: TSESLint.FlatConfig.ConfigArray;
3
3
  export default config;
@@ -1,6 +1,7 @@
1
1
  import pluginMobx from 'eslint-plugin-mobx';
2
+ import { convertWarnsToErrorsIfNeeded } from "./utils/convertWarnsToErrorsIfNeeded.js";
2
3
  const config = [
3
- pluginMobx.flatConfigs.recommended,
4
+ convertWarnsToErrorsIfNeeded(pluginMobx.flatConfigs.recommended),
4
5
  {
5
6
  name: '@dartess/mobx',
6
7
  rules: {
@@ -1,3 +1,3 @@
1
- import type { Linter } from 'eslint';
2
- declare const config: Array<Linter.Config>;
1
+ import type { TSESLint } from '@typescript-eslint/utils';
2
+ declare const config: TSESLint.FlatConfig.ConfigArray;
3
3
  export default config;
@@ -1,3 +1,3 @@
1
1
  import type { TSESLint } from '@typescript-eslint/utils';
2
- declare const config: Array<TSESLint.FlatConfig.Config>;
2
+ declare const config: TSESLint.FlatConfig.ConfigArray;
3
3
  export default config;
@@ -2,6 +2,7 @@ import eslintReact from '@eslint-react/eslint-plugin';
2
2
  import stylisticPlugin from '@stylistic/eslint-plugin';
3
3
  import reactHooksPlugin from 'eslint-plugin-react-hooks';
4
4
  import jsxA11yPlugin from 'eslint-plugin-jsx-a11y';
5
+ import { convertWarnsToErrorsIfNeeded } from "./utils/convertWarnsToErrorsIfNeeded.js";
5
6
  const config = [
6
7
  {
7
8
  name: '@dartess/react-setup',
@@ -16,8 +17,8 @@ const config = [
16
17
  },
17
18
  },
18
19
  },
19
- eslintReact.configs['strict-type-checked'],
20
- reactHooksPlugin.configs.flat.recommended,
20
+ convertWarnsToErrorsIfNeeded(eslintReact.configs['strict-type-checked']),
21
+ convertWarnsToErrorsIfNeeded(reactHooksPlugin.configs.flat.recommended),
21
22
  {
22
23
  name: '@dartess/react',
23
24
  files: ['**/*.{jsx,tsx}'],
@@ -51,20 +52,6 @@ const config = [
51
52
  '@eslint-react/naming-convention/filename': 'error', // enforce corrent filename
52
53
  // disable some recommended rules
53
54
  '@eslint-react/prefer-destructuring-assignment': 'off', // can break discriminated union types
54
- // mark some recommended warns as errors
55
- '@eslint-react/dom/no-missing-button-type': 'error',
56
- '@eslint-react/naming-convention/use-state': 'error',
57
- '@eslint-react/naming-convention/ref-name': 'error',
58
- '@eslint-react/naming-convention/context-name': 'error',
59
- '@eslint-react/dom/no-missing-iframe-sandbox': 'error',
60
- '@eslint-react/dom/no-unsafe-iframe-sandbox': 'error',
61
- '@eslint-react/jsx-no-comment-textnodes': 'error',
62
- '@eslint-react/no-unstable-context-value': 'error',
63
- '@eslint-react/dom/no-script-url': 'error',
64
- '@eslint-react/dom/no-unsafe-target-blank': 'error',
65
- '@eslint-react/no-useless-fragment': 'error',
66
- '@eslint-react/dom/no-dangerously-set-innerhtml': 'error',
67
- '@eslint-react/no-forward-ref': 'error',
68
55
  // enable airbnb-style rules
69
56
  '@eslint-react/jsx-shorthand-boolean': 'error',
70
57
  '@eslint-react/jsx-shorthand-fragment': 'error',
@@ -20,6 +20,7 @@ import vendorRulesImports from "./vendor-rules/imports.js";
20
20
  import vendorRulesStrict from "./vendor-rules/strict.js";
21
21
  import vendorRulesTypescriptDisablings from "./vendor-rules/typescript-disablings.js";
22
22
  import vendorRulesTypescript from "./vendor-rules/typescript.js";
23
+ import { convertWarnsToErrorsIfNeeded } from "./utils/convertWarnsToErrorsIfNeeded.js";
23
24
  const NO_MIDDLE_ABBRS = '(?!.*[A-Z]{3})';
24
25
  const NO_END_ABBRS = '(?!.*[A-Z]{2}$)';
25
26
  const NO_MIDDLE_UNDERSCORE = '(?!.*_{2})';
@@ -38,14 +39,14 @@ const config = [
38
39
  },
39
40
  {
40
41
  name: '@eslint/js/recommended',
41
- ...pluginJs.configs.recommended,
42
+ ...convertWarnsToErrorsIfNeeded(pluginJs.configs.recommended),
42
43
  },
43
- ...tsEslint.configs.strictTypeChecked,
44
- ...tsEslint.configs.stylisticTypeChecked,
45
- eslintPluginImportX.flatConfigs.recommended,
46
- eslintPluginImportX.flatConfigs.typescript,
47
- eslintCommentsPlugin.recommended,
48
- eslintPluginDeMorgan.configs.recommended,
44
+ ...convertWarnsToErrorsIfNeeded(tsEslint.configs.strictTypeChecked),
45
+ ...convertWarnsToErrorsIfNeeded(tsEslint.configs.stylisticTypeChecked),
46
+ convertWarnsToErrorsIfNeeded(eslintPluginImportX.flatConfigs.recommended),
47
+ convertWarnsToErrorsIfNeeded(eslintPluginImportX.flatConfigs.typescript),
48
+ convertWarnsToErrorsIfNeeded(eslintCommentsPlugin.recommended),
49
+ convertWarnsToErrorsIfNeeded(eslintPluginDeMorgan.configs.recommended),
49
50
  {
50
51
  name: '@dartess/recommended',
51
52
  plugins: {
@@ -227,7 +228,7 @@ const config = [
227
228
  },
228
229
  {
229
230
  files: ['**/*.js', '**/*.mjs', '**/*.cjs', '**/*.jsx'],
230
- ...tsEslint.configs.disableTypeChecked,
231
+ ...convertWarnsToErrorsIfNeeded(tsEslint.configs.disableTypeChecked),
231
232
  },
232
233
  {
233
234
  name: '@dartess/recommended-js',
@@ -1,3 +1,3 @@
1
1
  import type { TSESLint } from '@typescript-eslint/utils';
2
- declare const config: Array<TSESLint.FlatConfig.Config>;
2
+ declare const config: TSESLint.FlatConfig.ConfigArray;
3
3
  export default config;
@@ -1,10 +1,11 @@
1
1
  import pluginStorybook from 'eslint-plugin-storybook';
2
+ import { convertWarnsToErrorsIfNeeded } from "./utils/convertWarnsToErrorsIfNeeded.js";
2
3
  const config = [
3
4
  {
4
5
  ignores: ['!.storybook'],
5
6
  },
6
- ...pluginStorybook.configs['flat/recommended'],
7
- ...pluginStorybook.configs['flat/csf-strict'],
7
+ ...convertWarnsToErrorsIfNeeded(pluginStorybook.configs['flat/recommended']),
8
+ ...convertWarnsToErrorsIfNeeded(pluginStorybook.configs['flat/csf-strict']),
8
9
  {
9
10
  name: '@dartess/storybook',
10
11
  files: ['**/*.stories.tsx'],
@@ -0,0 +1,4 @@
1
+ import type { TSESLint } from '@typescript-eslint/utils';
2
+ declare function convertWarnsToErrorsIfNeeded(configs: TSESLint.FlatConfig.Config): TSESLint.FlatConfig.Config;
3
+ declare function convertWarnsToErrorsIfNeeded(configs: TSESLint.FlatConfig.ConfigArray): TSESLint.FlatConfig.ConfigArray;
4
+ export { convertWarnsToErrorsIfNeeded };
@@ -0,0 +1,36 @@
1
+ const FORCE_ERRORS_INSTEAD_OF_WARNS = true;
2
+ function updateRuleLevel(ruleLevel) {
3
+ switch (ruleLevel) {
4
+ case 1:
5
+ return 2;
6
+ case 'warn':
7
+ return 'error';
8
+ default:
9
+ return ruleLevel;
10
+ }
11
+ }
12
+ function implementation(config) {
13
+ if (!config.rules) {
14
+ return config;
15
+ }
16
+ const rules = Object.fromEntries(Object.entries(config.rules).map(([ruleName, ruleEntry]) => {
17
+ if (ruleEntry === undefined) {
18
+ return [ruleName, ruleEntry];
19
+ }
20
+ if (Array.isArray(ruleEntry)) {
21
+ const [ruleLevel, ...options] = ruleEntry;
22
+ return [ruleName, [updateRuleLevel(ruleLevel), ...options]];
23
+ }
24
+ return [ruleName, updateRuleLevel(ruleEntry)];
25
+ }));
26
+ return { ...config, rules };
27
+ }
28
+ function convertWarnsToErrorsIfNeeded(configs) {
29
+ if (!FORCE_ERRORS_INSTEAD_OF_WARNS) {
30
+ return configs;
31
+ }
32
+ return Array.isArray(configs)
33
+ ? configs.map(config => implementation(config))
34
+ : implementation(configs);
35
+ }
36
+ export { convertWarnsToErrorsIfNeeded };
@@ -5,7 +5,6 @@ declare const rules: {
5
5
  }];
6
6
  'import-x/default': "off";
7
7
  'import-x/namespace': "off";
8
- 'import-x/no-named-as-default': "error";
9
8
  'import-x/no-extraneous-dependencies': ["error", {
10
9
  devDependencies: string[];
11
10
  optionalDependencies: boolean;
@@ -14,7 +13,6 @@ declare const rules: {
14
13
  'import-x/no-amd': "error";
15
14
  'import-x/no-nodejs-modules': "error";
16
15
  'import-x/first': "error";
17
- 'import-x/no-duplicates': "error";
18
16
  'import-x/extensions': ["error", string, {
19
17
  js: string;
20
18
  mjs: string;
@@ -13,9 +13,6 @@ const rules = {
13
13
  // https://github.com/import-js/eslint-plugin-import/blob/master/docs/rules/namespace.md
14
14
  'import-x/namespace': 'off',
15
15
  // Helpful warnings:
16
- // do not allow a default import name to match a named export
17
- // https://github.com/import-js/eslint-plugin-import/blob/master/docs/rules/no-named-as-default.md
18
- 'import-x/no-named-as-default': 'error',
19
16
  // Forbid the use of extraneous packages
20
17
  // https://github.com/import-js/eslint-plugin-import/blob/master/docs/rules/no-extraneous-dependencies.md
21
18
  // paths are treated both as absolute paths, and relative to process.cwd()
@@ -58,9 +55,6 @@ const rules = {
58
55
  // disallow non-import statements appearing before import statements
59
56
  // https://github.com/import-js/eslint-plugin-import/blob/master/docs/rules/first.md
60
57
  'import-x/first': 'error',
61
- // disallow duplicate imports
62
- // https://github.com/import-js/eslint-plugin-import/blob/master/docs/rules/no-duplicates.md
63
- 'import-x/no-duplicates': 'error',
64
58
  // Ensure consistent use of file extension within the import path
65
59
  // https://github.com/import-js/eslint-plugin-import/blob/master/docs/rules/extensions.md
66
60
  'import-x/extensions': [
package/dist/index.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- import type { ESLint } from 'eslint';
2
- declare const plugin: ESLint.Plugin;
1
+ import type { Linter } from '@typescript-eslint/utils/ts-eslint';
2
+ declare const plugin: Linter.Plugin;
3
3
  export default plugin;
@@ -1,4 +1,4 @@
1
- import { ESLintUtils, AST_NODE_TYPES } from '@typescript-eslint/utils';
1
+ import { ESLintUtils, AST_NODE_TYPES, } from '@typescript-eslint/utils';
2
2
  function isCustomHook(fn) {
3
3
  if (fn.type === AST_NODE_TYPES.FunctionDeclaration && fn.id?.name.startsWith('use')) {
4
4
  return true;
@@ -13,6 +13,29 @@ function isCustomHook(fn) {
13
13
  }
14
14
  return false;
15
15
  }
16
+ function getTypeParamsText(fn, sourceCode) {
17
+ return fn.typeParameters ? sourceCode.getText(fn.typeParameters) : '';
18
+ }
19
+ function getObserverImportAnchor(program) {
20
+ const firstImport = program.body.find(n => n.type === AST_NODE_TYPES.ImportDeclaration);
21
+ if (firstImport) {
22
+ return { anchor: firstImport, method: 'insertTextBefore' };
23
+ }
24
+ let lastPrologue = null;
25
+ for (const node of program.body) {
26
+ if (node.type === AST_NODE_TYPES.ExpressionStatement &&
27
+ node.expression.type === AST_NODE_TYPES.Literal &&
28
+ typeof node.expression.value === 'string') {
29
+ lastPrologue = node;
30
+ continue;
31
+ }
32
+ break;
33
+ }
34
+ if (lastPrologue) {
35
+ return { anchor: lastPrologue, method: 'insertTextAfter' };
36
+ }
37
+ return { anchor: program.body[0], method: 'insertTextBefore' };
38
+ }
16
39
  export default ESLintUtils.RuleCreator(() => '')({
17
40
  name: 'mobx-require-observer',
18
41
  meta: {
@@ -97,7 +120,10 @@ export default ESLintUtils.RuleCreator(() => '')({
97
120
  fix(fixer) {
98
121
  const fixes = [];
99
122
  if (!hasObserverImport) {
100
- fixes.push(fixer.insertTextBefore(program.body[0], "import { observer } from 'mobx-react-lite';\n"));
123
+ const { anchor, method } = getObserverImportAnchor(program);
124
+ const nBefore = method === 'insertTextAfter' ? '\n' : '';
125
+ const nAfter = method === 'insertTextBefore' ? '\n' : '';
126
+ fixes.push(fixer[method](anchor, `${nBefore}import { observer } from 'mobx-react-lite';${nAfter}`));
101
127
  }
102
128
  const paramsText = fn.params.map(p => sourceCode.getText(p)).join(', ');
103
129
  const bodyText = sourceCode.getText(fn.body);
@@ -117,7 +143,8 @@ export default ESLintUtils.RuleCreator(() => '')({
117
143
  const id = fn.parent.id;
118
144
  const { name } = id;
119
145
  const isExport = exportDecl?.type === AST_NODE_TYPES.ExportNamedDeclaration;
120
- const funcExpression = `function ${name}(${paramsText}) ${bodyText}`;
146
+ const typeParamsText = getTypeParamsText(fn, sourceCode);
147
+ const funcExpression = `function ${name}${typeParamsText}(${paramsText}) ${bodyText}`;
121
148
  const replacementNodeText = `${isExport ? 'export ' : ''}const ${name} = observer(${funcExpression});`;
122
149
  fixes.push(fixer.replaceText(exportDecl || varDecl, replacementNodeText));
123
150
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@dartess/eslint-plugin",
3
3
  "type": "module",
4
- "version": "0.7.1",
4
+ "version": "0.8.1",
5
5
  "description": "A set of rules and configs for various TypeScript projects",
6
6
  "keywords": [
7
7
  "eslint",