@dartess/eslint-plugin 0.10.0 → 0.11.2

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.11.0] - 2026-03-27
6
+
7
+ - remove throwing errors from rules
8
+ - add `unicorn/numeric-separators-style` to `recommended` config
9
+ - replace `import-x/order` with `perfectionist/sort-imports`: now sort side-effects as first group and styles as last group; please, install `eslint-plugin-perfectionist`
10
+ - add rule `@dartess/no-props-with-children-type`: Disallow the use of PropsWithChildren utility type. Prefer explicit children prop typing instead.
11
+ - add `@dartess/no-props-with-children-type` to `react` config
12
+ - update deps
13
+
5
14
  ## [0.10.0] - 2026-02-23
6
15
 
7
16
  - add rule `@dartess/mobx-no-action-bound`
package/README.md CHANGED
@@ -42,6 +42,7 @@ npm i -D eslint \
42
42
  eslint-plugin-complete \
43
43
  eslint-plugin-decorator-position \
44
44
  eslint-plugin-de-morgan \
45
+ eslint-plugin-perfectionist \
45
46
  typescript-eslint \
46
47
  @eslint-community/eslint-plugin-eslint-comments \
47
48
  @dartess/eslint-plugin
@@ -174,6 +175,7 @@ Each rule has emojis denoting:
174
175
  | **React** | _config: react_ | | | |
175
176
  | [jsx-no-text-as-child](docs/rules/jsx-no-text-as-child.md) | JSX elements should not have text without translation | | | |
176
177
  | [jsx-no-cross-context-classes](docs/rules/jsx-no-cross-context-classes.md) | Prevent mixing of outer and inner classes to avoid dependency on style order. | | | |
178
+ | [no-props-with-children-type](docs/rules/no-props-with-children-type.md) | Disallow the use of `PropsWithChildren` utility type in favor explicit types. | ✅ | | |
177
179
  | **Storybook** | _config: storybook_ | | | |
178
180
  | [stories-export-meta](docs/rules/stories-export-meta.md) | Storybook's Meta should be typed | ✅ | | |
179
181
  | [stories-export-typed](docs/rules/stories-export-typed.md) | Storybook's Stories should be typed | ✅ | | |
@@ -58,6 +58,7 @@ const config = [
58
58
  '@eslint-react/naming-convention/component-name': 'error',
59
59
  '@stylistic/jsx-curly-brace-presence': ['error', { props: 'never', children: 'never' }],
60
60
  '@stylistic/jsx-self-closing-comp': 'error',
61
+ '@dartess/no-props-with-children-type': 'error',
61
62
  },
62
63
  },
63
64
  ];
@@ -11,6 +11,7 @@ import eslintCommentsPlugin from '@eslint-community/eslint-plugin-eslint-comment
11
11
  import eslintPluginDecoratorPosition from 'eslint-plugin-decorator-position';
12
12
  import eslintPluginDeMorgan from 'eslint-plugin-de-morgan';
13
13
  import esLintPluginComplete from 'eslint-plugin-complete';
14
+ import esLintPluginPerfectionist from 'eslint-plugin-perfectionist';
14
15
  import dartessPlugin from "../index.js";
15
16
  import vendorRulesBestPractices from "./vendor-rules/best-practices.js";
16
17
  import vendorRulesErrors from "./vendor-rules/errors.js";
@@ -54,7 +55,9 @@ const config = [
54
55
  plugins: {
55
56
  unicorn: eslintPluginUnicorn,
56
57
  '@dartess': dartessPlugin,
58
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- https://github.com/NullVoxPopuli/eslint-plugin-decorator-position/issues/778
57
59
  'decorator-position': eslintPluginDecoratorPosition,
60
+ perfectionist: esLintPluginPerfectionist,
58
61
  },
59
62
  languageOptions: {
60
63
  parser: tsEslint.parser,
@@ -87,12 +90,20 @@ const config = [
87
90
  ],
88
91
  'unicorn/no-useless-undefined': 'error',
89
92
  'unicorn/prefer-node-protocol': 'error',
93
+ 'unicorn/numeric-separators-style': 'error',
90
94
  '@dartess/imports-max-parent-depth': 'error',
91
- 'import-x/order': [
95
+ 'perfectionist/sort-imports': [
92
96
  'error',
93
97
  {
94
- groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index'],
95
- 'newlines-between': 'always',
98
+ type: 'unsorted',
99
+ groups: [
100
+ 'side-effect',
101
+ ...['builtin', 'external', 'internal', 'parent', 'sibling', 'index'].map(group => [
102
+ `type-${group}`,
103
+ `value-${group}`,
104
+ ]),
105
+ 'style',
106
+ ],
96
107
  },
97
108
  ],
98
109
  'prefer-arrow-callback': [
@@ -234,6 +245,7 @@ const config = [
234
245
  'complete/strict-undefined-functions': 'off', // prefer unicorn/no-useless-undefined
235
246
  'complete/require-break': 'off', // can be false-positive with TS7027
236
247
  'complete/no-void-return-type': 'off', // conflict with @typescript-eslint/explicit-module-boundary-types
248
+ 'complete/require-ascii': 'off', // absoule useless
237
249
  },
238
250
  },
239
251
  {
package/dist/index.js CHANGED
@@ -10,6 +10,7 @@ import ruleImportsMaxParentDepth from "./rules/imports-max-parent-depth.js";
10
10
  import ruleTsNamedTupleElements from "./rules/ts-named-tuple-elements.js";
11
11
  import ruleMobxSyncAutorun from "./rules/mobx-sync-autorun.js";
12
12
  import ruleMobxSyncAction from "./rules/mobx-sync-action.js";
13
+ import ruleNoPropsWithChildrenType from "./rules/no-props-with-children-type.js";
13
14
  const plugin = {
14
15
  meta: {
15
16
  name: packageJson.name,
@@ -28,6 +29,7 @@ const plugin = {
28
29
  'stories-export-typed': ruleStoriesExportTyped,
29
30
  'stories-export-meta': ruleStoriesExportMeta,
30
31
  'ts-named-tuple-elements': ruleTsNamedTupleElements,
32
+ 'no-props-with-children-type': ruleNoPropsWithChildrenType,
31
33
  },
32
34
  };
33
35
  export default plugin;
@@ -1,3 +1,5 @@
1
1
  import { ESLintUtils } from '@typescript-eslint/utils';
2
- declare const _default: ESLintUtils.RuleModule<"tooDeep", [], unknown, ESLintUtils.RuleListener>;
2
+ declare const _default: ESLintUtils.RuleModule<"tooDeep", [], unknown, ESLintUtils.RuleListener> & {
3
+ name: string;
4
+ };
3
5
  export default _default;
@@ -5,9 +5,11 @@ import { ESLintUtils } from '@typescript-eslint/utils';
5
5
  */
6
6
  type Options = [
7
7
  mainOptions: {
8
- libName?: string;
8
+ libName: string;
9
9
  }
10
10
  ];
11
11
  type MessageIds = 'avoidMix' | 'avoidRenaming';
12
- declare const _default: ESLintUtils.RuleModule<MessageIds, Options, unknown, ESLintUtils.RuleListener>;
12
+ declare const _default: ESLintUtils.RuleModule<MessageIds, Options, unknown, ESLintUtils.RuleListener> & {
13
+ name: string;
14
+ };
13
15
  export default _default;
@@ -1,7 +1,7 @@
1
1
  import { ESLintUtils, AST_NODE_TYPES } from '@typescript-eslint/utils';
2
2
  export default ESLintUtils.RuleCreator(() => '')({
3
3
  name: 'jsx-no-cross-context-classes',
4
- defaultOptions: [{}],
4
+ defaultOptions: [{ libName: 'clsx' }],
5
5
  meta: {
6
6
  type: 'suggestion',
7
7
  docs: {
@@ -26,10 +26,7 @@ export default ESLintUtils.RuleCreator(() => '')({
26
26
  },
27
27
  create(context) {
28
28
  const [options] = context.options;
29
- const libName = options?.libName;
30
- if (!libName || typeof libName !== 'string') {
31
- throw new Error('libName option is required and must be a string');
32
- }
29
+ const { libName } = options;
33
30
  const isClassLike = (string) => /class/i.exec(string);
34
31
  return {
35
32
  CallExpression(node) {
@@ -1,3 +1,7 @@
1
+ /**
2
+ * @fileoverview Disallows text as child in JSX elements, except specified characters like emojis, digits, special symbols and extra strings
3
+ * @author Sergey Kozlov
4
+ */
1
5
  import { ESLintUtils } from '@typescript-eslint/utils';
2
6
  type Options = [
3
7
  mainOptions: {
@@ -9,5 +13,7 @@ type Options = [
9
13
  }
10
14
  ];
11
15
  type MessageIds = 'textAsChild' | 'disallowedSymbols';
12
- declare const _default: ESLintUtils.RuleModule<MessageIds, Options, unknown, ESLintUtils.RuleListener>;
16
+ declare const _default: ESLintUtils.RuleModule<MessageIds, Options, unknown, ESLintUtils.RuleListener> & {
17
+ name: string;
18
+ };
13
19
  export default _default;
@@ -1,8 +1,8 @@
1
- import { ESLintUtils, AST_NODE_TYPES } from '@typescript-eslint/utils';
2
1
  /**
3
2
  * @fileoverview Disallows text as child in JSX elements, except specified characters like emojis, digits, special symbols and extra strings
4
3
  * @author Sergey Kozlov
5
4
  */
5
+ import { ESLintUtils, AST_NODE_TYPES } from '@typescript-eslint/utils';
6
6
  import emojiRegex from 'emoji-regex';
7
7
  export default ESLintUtils.RuleCreator(() => '')({
8
8
  name: 'jsx-no-text-as-child',
@@ -1,3 +1,5 @@
1
1
  import { ESLintUtils } from '@typescript-eslint/utils';
2
- declare const _default: ESLintUtils.RuleModule<"noActionBound", [], unknown, ESLintUtils.RuleListener>;
2
+ declare const _default: ESLintUtils.RuleModule<"noActionBound", [], unknown, ESLintUtils.RuleListener> & {
3
+ name: string;
4
+ };
3
5
  export default _default;
@@ -1,3 +1,5 @@
1
1
  import { ESLintUtils } from '@typescript-eslint/utils';
2
- declare const _default: ESLintUtils.RuleModule<"requireObserver", [], unknown, ESLintUtils.RuleListener>;
2
+ declare const _default: ESLintUtils.RuleModule<"requireObserver", [], unknown, ESLintUtils.RuleListener> & {
3
+ name: string;
4
+ };
3
5
  export default _default;
@@ -10,5 +10,7 @@ type Options = [
10
10
  }
11
11
  ];
12
12
  type MessageIds = 'observedIsInlined' | 'observedIsFE' | 'extraCallsAsParam' | 'observedIsNamed' | 'componentIsNamed' | 'componentIsNamedAsObserved';
13
- declare const _default: ESLintUtils.RuleModule<MessageIds, Options, unknown, ESLintUtils.RuleListener>;
13
+ declare const _default: ESLintUtils.RuleModule<MessageIds, Options, unknown, ESLintUtils.RuleListener> & {
14
+ name: string;
15
+ };
14
16
  export default _default;
@@ -1,3 +1,5 @@
1
1
  import { ESLintUtils } from '@typescript-eslint/utils';
2
- declare const _default: ESLintUtils.RuleModule<"requireSyncAction", [], unknown, ESLintUtils.RuleListener>;
2
+ declare const _default: ESLintUtils.RuleModule<"requireSyncAction", [], unknown, ESLintUtils.RuleListener> & {
3
+ name: string;
4
+ };
3
5
  export default _default;
@@ -1,3 +1,5 @@
1
1
  import { ESLintUtils } from '@typescript-eslint/utils';
2
- declare const _default: ESLintUtils.RuleModule<"requireSyncAutorun", [], unknown, ESLintUtils.RuleListener>;
2
+ declare const _default: ESLintUtils.RuleModule<"requireSyncAutorun", [], unknown, ESLintUtils.RuleListener> & {
3
+ name: string;
4
+ };
3
5
  export default _default;
@@ -0,0 +1,9 @@
1
+ /**
2
+ * @fileoverview Disallow the use of `PropsWithChildren` utility type in favor explicit types
3
+ * @author Sergey Kozlov
4
+ */
5
+ import { ESLintUtils } from '@typescript-eslint/utils';
6
+ declare const _default: ESLintUtils.RuleModule<"unexpectedPropsWithChildren", [], unknown, ESLintUtils.RuleListener> & {
7
+ name: string;
8
+ };
9
+ export default _default;
@@ -0,0 +1,45 @@
1
+ /**
2
+ * @fileoverview Disallow the use of `PropsWithChildren` utility type in favor explicit types
3
+ * @author Sergey Kozlov
4
+ */
5
+ import { ESLintUtils, AST_NODE_TYPES } from '@typescript-eslint/utils';
6
+ import { exhaustiveCheck } from "./utils/exhaustiveCheck.js";
7
+ function getTypeName(tsEntityName) {
8
+ switch (tsEntityName.type) {
9
+ case AST_NODE_TYPES.Identifier:
10
+ return tsEntityName.name;
11
+ case AST_NODE_TYPES.TSQualifiedName:
12
+ return tsEntityName.right.name;
13
+ case AST_NODE_TYPES.ThisExpression:
14
+ return null;
15
+ default:
16
+ exhaustiveCheck(tsEntityName);
17
+ }
18
+ }
19
+ export default ESLintUtils.RuleCreator(() => '')({
20
+ name: 'no-props-with-children-type',
21
+ defaultOptions: [],
22
+ meta: {
23
+ type: 'problem',
24
+ docs: {
25
+ description: 'Disallow the use of PropsWithChildren utility type. Prefer explicit children prop typing instead.',
26
+ },
27
+ messages: {
28
+ unexpectedPropsWithChildren: 'Do not use PropsWithChildren. Type the children prop explicitly instead.',
29
+ },
30
+ schema: [],
31
+ },
32
+ create(context) {
33
+ return {
34
+ TSTypeReference(node) {
35
+ const name = getTypeName(node.typeName);
36
+ if (name === 'PropsWithChildren') {
37
+ context.report({
38
+ node,
39
+ messageId: 'unexpectedPropsWithChildren',
40
+ });
41
+ }
42
+ },
43
+ };
44
+ },
45
+ });
@@ -1,3 +1,5 @@
1
1
  import { ESLintUtils } from '@typescript-eslint/utils';
2
- declare const _default: ESLintUtils.RuleModule<"exportDefaultTypeCheck", [], unknown, ESLintUtils.RuleListener>;
2
+ declare const _default: ESLintUtils.RuleModule<"exportDefaultTypeCheck", [], unknown, ESLintUtils.RuleListener> & {
3
+ name: string;
4
+ };
3
5
  export default _default;
@@ -1,3 +1,5 @@
1
1
  import { ESLintUtils } from '@typescript-eslint/utils';
2
- declare const _default: ESLintUtils.RuleModule<"exportStoryType", [], unknown, ESLintUtils.RuleListener>;
2
+ declare const _default: ESLintUtils.RuleModule<"exportStoryType", [], unknown, ESLintUtils.RuleListener> & {
3
+ name: string;
4
+ };
3
5
  export default _default;
@@ -9,5 +9,7 @@ type Options = [
9
9
  }
10
10
  ];
11
11
  type MessageIds = 'requireNames' | 'forbidNames';
12
- declare const _default: ESLintUtils.RuleModule<MessageIds, Options, unknown, ESLintUtils.RuleListener>;
12
+ declare const _default: ESLintUtils.RuleModule<MessageIds, Options, unknown, ESLintUtils.RuleListener> & {
13
+ name: string;
14
+ };
13
15
  export default _default;
@@ -1,4 +1,5 @@
1
1
  import { ESLintUtils } from '@typescript-eslint/utils';
2
+ import { exhaustiveCheck } from "./utils/exhaustiveCheck.js";
2
3
  export default ESLintUtils.RuleCreator(() => '')({
3
4
  name: 'ts-named-tuple-elements',
4
5
  defaultOptions: [{ mode: 'always' }],
@@ -41,7 +42,7 @@ export default ESLintUtils.RuleCreator(() => '')({
41
42
  },
42
43
  };
43
44
  default:
44
- throw new Error(`Invalig mode value: ${String(mode)}`);
45
+ exhaustiveCheck(mode);
45
46
  }
46
47
  },
47
48
  });
@@ -0,0 +1 @@
1
+ export declare function exhaustiveCheck(param: never): never;
@@ -0,0 +1,11 @@
1
+ export function exhaustiveCheck(param) {
2
+ const paramAsString = (() => {
3
+ try {
4
+ return JSON.stringify(param);
5
+ }
6
+ catch {
7
+ return String(param);
8
+ }
9
+ })();
10
+ throw new Error(`unknown param: ${paramAsString}`);
11
+ }
@@ -20,7 +20,7 @@ import a from '../../two-up';
20
20
 
21
21
  ## Options
22
22
 
23
- This rule does not accept inline options, but requires a global ESLint setting:
23
+ This rule does not accept inline options, but allows a global ESLint setting:
24
24
 
25
25
  ```json
26
26
  {
@@ -0,0 +1,29 @@
1
+ # Disallow `PropsWithChildren` utility type (no-props-with-children-type)
2
+
3
+ Disallow the use of `PropsWithChildren` utility type from `@types/react`.
4
+ Prefer explicit `children` prop typing instead, as `PropsWithChildren` makes `children` optional by default,
5
+ which may hide bugs. Additionally, `children` is often narrower than `ReactNode` and should be typed explicitly.
6
+
7
+ ## Rule Details
8
+
9
+ Examples of **incorrect** code for this rule:
10
+
11
+ ```ts
12
+ type ModalProps = PropsWithChildren<{ title: string }>;
13
+ ```
14
+
15
+ Examples of **correct** code for this rule:
16
+
17
+ ```ts
18
+ type ModalProps = {
19
+ title: string;
20
+ children: React.ReactNode;
21
+ };
22
+ ```
23
+
24
+ ```ts
25
+ type ModalProps = {
26
+ title: string;
27
+ children: string | number;
28
+ };
29
+ ```
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@dartess/eslint-plugin",
3
3
  "type": "module",
4
- "version": "0.10.0",
4
+ "version": "0.11.2",
5
5
  "description": "A set of rules and configs for various TypeScript projects",
6
6
  "keywords": [
7
7
  "eslint",
@@ -59,6 +59,7 @@
59
59
  "eslint-plugin-import-x": "^4.1.0",
60
60
  "eslint-plugin-jsx-a11y": "^6.10.0",
61
61
  "eslint-plugin-mobx": "~0.0.13",
62
+ "eslint-plugin-perfectionist": "^5.6.0",
62
63
  "eslint-plugin-react-hooks": "^7.0.0",
63
64
  "eslint-plugin-storybook": "^10.0.0",
64
65
  "eslint-plugin-unicorn": ">=58.0.0",
@@ -88,32 +89,40 @@
88
89
  }
89
90
  },
90
91
  "devDependencies": {
91
- "@eslint-community/eslint-plugin-eslint-comments": "^4.6.0",
92
+ "@eslint-community/eslint-plugin-eslint-comments": "^4.7.1",
93
+ "@eslint-react/eslint-plugin": "^2.13.0",
92
94
  "@eslint/js": "^9.39.2",
95
+ "@next/eslint-plugin-next": "^16.1.6",
96
+ "@stylistic/eslint-plugin": "^5.10.0",
93
97
  "@types/confusing-browser-globals": "^1.0.3",
94
98
  "@types/eslint-plugin-jsx-a11y": "^6.10.1",
95
99
  "@types/eslint-plugin-mobx": "~0.0.0",
96
100
  "@types/node": "^25.0.2",
97
- "@typescript-eslint/rule-tester": "^8.50.0",
98
- "eslint": "^9.39.2",
101
+ "@typescript-eslint/rule-tester": "^8.56.1",
102
+ "eslint": "^9.39.4",
99
103
  "eslint-config-prettier": "^10.1.8",
100
104
  "eslint-import-resolver-typescript": "^4.4.4",
101
- "eslint-plugin-eslint-plugin": "^7.2.0",
105
+ "eslint-plugin-complete": "^1.3.0",
106
+ "eslint-plugin-de-morgan": "^2.1.1",
107
+ "eslint-plugin-decorator-position": "^6.0.0",
108
+ "eslint-plugin-eslint-plugin": "^7.3.2",
102
109
  "eslint-plugin-import-x": "^4.16.1",
103
- "eslint-plugin-prettier": "^5.5.4",
110
+ "eslint-plugin-perfectionist": "^5.6.0",
111
+ "eslint-plugin-prettier": "^5.5.5",
104
112
  "eslint-plugin-react-hooks": "^7.0.1",
105
- "eslint-plugin-unicorn": "^62.0.0",
113
+ "eslint-plugin-storybook": "^10.2.16",
114
+ "eslint-plugin-unicorn": "^63.0.0",
106
115
  "husky": "^9.1.7",
107
116
  "jiti": "^2.6.1",
108
- "rimraf": "^6.1.2",
117
+ "rimraf": "^6.1.3",
109
118
  "typescript": "~5.9.3",
110
- "typescript-eslint": "^8.50.0"
119
+ "typescript-eslint": "^8.56.1"
111
120
  },
112
121
  "dependencies": {
113
- "@eslint/compat": "^2.0.0",
114
- "@typescript-eslint/utils": "^8.50.0",
122
+ "@eslint/compat": "^2.0.3",
123
+ "@typescript-eslint/utils": "^8.56.1",
115
124
  "confusing-browser-globals": "^1.0.11",
116
125
  "emoji-regex": "^10.6.0",
117
- "globals": "^16.5.0"
126
+ "globals": "^17.4.0"
118
127
  }
119
128
  }