eslint-plugin-nextfriday 1.1.1 → 1.2.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 +6 -0
- package/README.md +4 -0
- package/docs/rules/ENFORCE_READONLY_COMPONENT_PROPS.md +104 -0
- package/lib/index.cjs +165 -70
- package/lib/index.cjs.map +1 -1
- package/lib/index.d.cts +14 -0
- package/lib/index.d.ts +14 -0
- package/lib/index.js +165 -70
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# eslint-plugin-nextfriday
|
|
2
2
|
|
|
3
|
+
## 1.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#16](https://github.com/next-friday/eslint-plugin-nextfriday/pull/16) [`6059183`](https://github.com/next-friday/eslint-plugin-nextfriday/commit/60591838ee3bbe9c5f1a497cb571028f973311b6) Thanks [@nextfridaydeveloper](https://github.com/nextfridaydeveloper)! - A new ESLint rule `enforce-readonly-component-props` that enforces the use of `Readonly<>` wrapper for React component props when using named types or interfaces. This rule helps prevent accidental mutations of props and makes the immutable nature of React props explicit in the type system.
|
|
8
|
+
|
|
3
9
|
## 1.1.1
|
|
4
10
|
|
|
5
11
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -69,6 +69,7 @@ export default [
|
|
|
69
69
|
"nextfriday/jsx-pascal-case": "error",
|
|
70
70
|
"nextfriday/prefer-interface-over-inline-types": "error",
|
|
71
71
|
"nextfriday/react-props-destructure": "error",
|
|
72
|
+
"nextfriday/enforce-readonly-component-props": "error",
|
|
72
73
|
},
|
|
73
74
|
},
|
|
74
75
|
];
|
|
@@ -117,6 +118,7 @@ module.exports = {
|
|
|
117
118
|
| [prefer-interface-over-inline-types](docs/rules/PREFER_INTERFACE_OVER_INLINE_TYPES.md) | Enforce interface declarations over inline types for React props | ❌ |
|
|
118
119
|
| [prefer-react-import-types](docs/rules/PREFER_REACT_IMPORT_TYPES.md) | Enforce direct imports from 'react' instead of React.X | ✅ |
|
|
119
120
|
| [react-props-destructure](docs/rules/REACT_PROPS_DESTRUCTURE.md) | Enforce destructuring props inside React component body | ❌ |
|
|
121
|
+
| [enforce-readonly-component-props](docs/rules/ENFORCE_READONLY_COMPONENT_PROPS.md) | Enforce Readonly wrapper for React component props | ✅ |
|
|
120
122
|
|
|
121
123
|
## Configurations
|
|
122
124
|
|
|
@@ -148,6 +150,7 @@ Includes all base rules plus React-specific rules:
|
|
|
148
150
|
- `nextfriday/jsx-pascal-case`: `"error"`
|
|
149
151
|
- `nextfriday/prefer-interface-over-inline-types`: `"error"`
|
|
150
152
|
- `nextfriday/react-props-destructure`: `"error"`
|
|
153
|
+
- `nextfriday/enforce-readonly-component-props`: `"error"`
|
|
151
154
|
|
|
152
155
|
#### `react/recommended`
|
|
153
156
|
|
|
@@ -163,6 +166,7 @@ Includes all rules suitable for Next.js projects:
|
|
|
163
166
|
- `nextfriday/jsx-pascal-case`: `"error"`
|
|
164
167
|
- `nextfriday/prefer-interface-over-inline-types`: `"error"`
|
|
165
168
|
- `nextfriday/react-props-destructure`: `"error"`
|
|
169
|
+
- `nextfriday/enforce-readonly-component-props`: `"error"`
|
|
166
170
|
|
|
167
171
|
#### `nextjs/recommended`
|
|
168
172
|
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# enforce-readonly-component-props
|
|
2
|
+
|
|
3
|
+
Enforce `Readonly<>` wrapper for React component props when using named types or interfaces.
|
|
4
|
+
|
|
5
|
+
## Rule Details
|
|
6
|
+
|
|
7
|
+
This rule enforces that React component props using named types (interfaces or type aliases) must be wrapped with `Readonly<>` for immutability. This helps prevent accidental mutations of props and makes the immutable nature of React props explicit in the type system.
|
|
8
|
+
|
|
9
|
+
## Examples
|
|
10
|
+
|
|
11
|
+
### ❌ Incorrect
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
interface Props {
|
|
15
|
+
children: ReactNode;
|
|
16
|
+
}
|
|
17
|
+
const Component = (props: Props) => <div>{props.children}</div>;
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
```tsx
|
|
21
|
+
type ComponentProps = {
|
|
22
|
+
title: string;
|
|
23
|
+
onClick: () => void;
|
|
24
|
+
};
|
|
25
|
+
const Component = (props: ComponentProps) => <div>{props.title}</div>;
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
```tsx
|
|
29
|
+
interface LayoutProps {
|
|
30
|
+
children: ReactNode;
|
|
31
|
+
title?: string;
|
|
32
|
+
}
|
|
33
|
+
function Layout(props: LayoutProps) {
|
|
34
|
+
return <div>{props.children}</div>;
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### ✅ Correct
|
|
39
|
+
|
|
40
|
+
```tsx
|
|
41
|
+
interface Props {
|
|
42
|
+
children: ReactNode;
|
|
43
|
+
}
|
|
44
|
+
const Component = (props: Readonly<Props>) => <div>{props.children}</div>;
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
```tsx
|
|
48
|
+
type ComponentProps = {
|
|
49
|
+
title: string;
|
|
50
|
+
onClick: () => void;
|
|
51
|
+
};
|
|
52
|
+
const Component = (props: Readonly<ComponentProps>) => <div>{props.title}</div>;
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
```tsx
|
|
56
|
+
interface LayoutProps {
|
|
57
|
+
children: ReactNode;
|
|
58
|
+
title?: string;
|
|
59
|
+
}
|
|
60
|
+
function Layout(props: Readonly<LayoutProps>) {
|
|
61
|
+
return <div>{props.children}</div>;
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
```tsx
|
|
66
|
+
// Inline types are handled by prefer-interface-over-inline-types rule
|
|
67
|
+
const Component = (props: { children: ReactNode }) => <div>{props.children}</div>;
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
```tsx
|
|
71
|
+
// Non-React functions are ignored
|
|
72
|
+
interface HelperProps {
|
|
73
|
+
value: number;
|
|
74
|
+
}
|
|
75
|
+
const helper = (props: HelperProps) => {
|
|
76
|
+
return props.value * 2;
|
|
77
|
+
};
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## When Not To Use
|
|
81
|
+
|
|
82
|
+
This rule should not be used if:
|
|
83
|
+
|
|
84
|
+
- You prefer inline types for component props (use `prefer-interface-over-inline-types` instead)
|
|
85
|
+
- You don't want to enforce immutability patterns in your React components
|
|
86
|
+
- You're working with a codebase that doesn't use TypeScript strict mode
|
|
87
|
+
|
|
88
|
+
## Fixable
|
|
89
|
+
|
|
90
|
+
This rule is fixable using ESLint's `--fix` option. The fixer will automatically wrap named types with `Readonly<>`.
|
|
91
|
+
|
|
92
|
+
## Related Rules
|
|
93
|
+
|
|
94
|
+
- [`prefer-interface-over-inline-types`](./PREFER_INTERFACE_OVER_INLINE_TYPES.md) - Enforces interface declarations over inline types
|
|
95
|
+
- [`react-props-destructure`](./REACT_PROPS_DESTRUCTURE.md) - Enforces destructuring props inside component body
|
|
96
|
+
|
|
97
|
+
## Configuration
|
|
98
|
+
|
|
99
|
+
This rule is included in the following configurations:
|
|
100
|
+
|
|
101
|
+
- `react` (warn)
|
|
102
|
+
- `react/recommended` (error)
|
|
103
|
+
- `nextjs` (warn)
|
|
104
|
+
- `nextjs/recommended` (error)
|
package/lib/index.cjs
CHANGED
|
@@ -40,7 +40,7 @@ module.exports = __toCommonJS(index_exports);
|
|
|
40
40
|
// package.json
|
|
41
41
|
var package_default = {
|
|
42
42
|
name: "eslint-plugin-nextfriday",
|
|
43
|
-
version: "1.
|
|
43
|
+
version: "1.2.0",
|
|
44
44
|
description: "A comprehensive ESLint plugin providing custom rules and configurations for Next Friday development workflows.",
|
|
45
45
|
keywords: [
|
|
46
46
|
"eslint",
|
|
@@ -155,19 +155,111 @@ var package_default = {
|
|
|
155
155
|
}
|
|
156
156
|
};
|
|
157
157
|
|
|
158
|
-
// src/rules/
|
|
159
|
-
var import_path = __toESM(require("path"), 1);
|
|
158
|
+
// src/rules/enforce-readonly-component-props.ts
|
|
160
159
|
var import_utils = require("@typescript-eslint/utils");
|
|
161
160
|
var createRule = import_utils.ESLintUtils.RuleCreator(
|
|
162
161
|
(name) => `https://github.com/next-friday/eslint-plugin-nextfriday/blob/main/docs/rules/${name}.md`
|
|
163
162
|
);
|
|
163
|
+
var enforceReadonlyComponentProps = createRule({
|
|
164
|
+
name: "enforce-readonly-component-props",
|
|
165
|
+
meta: {
|
|
166
|
+
type: "suggestion",
|
|
167
|
+
docs: {
|
|
168
|
+
description: "Enforce Readonly wrapper for React component props when using named types or interfaces"
|
|
169
|
+
},
|
|
170
|
+
fixable: "code",
|
|
171
|
+
schema: [],
|
|
172
|
+
messages: {
|
|
173
|
+
useReadonly: "Component props should be wrapped with Readonly<> for immutability"
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
defaultOptions: [],
|
|
177
|
+
create(context) {
|
|
178
|
+
function hasJSXInConditional(node) {
|
|
179
|
+
return node.consequent.type === import_utils.AST_NODE_TYPES.JSXElement || node.consequent.type === import_utils.AST_NODE_TYPES.JSXFragment || node.alternate.type === import_utils.AST_NODE_TYPES.JSXElement || node.alternate.type === import_utils.AST_NODE_TYPES.JSXFragment;
|
|
180
|
+
}
|
|
181
|
+
function hasJSXInLogical(node) {
|
|
182
|
+
return node.right.type === import_utils.AST_NODE_TYPES.JSXElement || node.right.type === import_utils.AST_NODE_TYPES.JSXFragment;
|
|
183
|
+
}
|
|
184
|
+
function hasJSXReturn(block) {
|
|
185
|
+
return block.body.some((stmt) => {
|
|
186
|
+
if (stmt.type === import_utils.AST_NODE_TYPES.ReturnStatement && stmt.argument) {
|
|
187
|
+
return stmt.argument.type === import_utils.AST_NODE_TYPES.JSXElement || stmt.argument.type === import_utils.AST_NODE_TYPES.JSXFragment || stmt.argument.type === import_utils.AST_NODE_TYPES.ConditionalExpression && hasJSXInConditional(stmt.argument) || stmt.argument.type === import_utils.AST_NODE_TYPES.LogicalExpression && hasJSXInLogical(stmt.argument);
|
|
188
|
+
}
|
|
189
|
+
return false;
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
function isReactComponent(node) {
|
|
193
|
+
if (node.type === import_utils.AST_NODE_TYPES.ArrowFunctionExpression) {
|
|
194
|
+
if (node.body.type === import_utils.AST_NODE_TYPES.JSXElement || node.body.type === import_utils.AST_NODE_TYPES.JSXFragment) {
|
|
195
|
+
return true;
|
|
196
|
+
}
|
|
197
|
+
if (node.body.type === import_utils.AST_NODE_TYPES.BlockStatement) {
|
|
198
|
+
return hasJSXReturn(node.body);
|
|
199
|
+
}
|
|
200
|
+
} else if (node.type === import_utils.AST_NODE_TYPES.FunctionExpression || node.type === import_utils.AST_NODE_TYPES.FunctionDeclaration) {
|
|
201
|
+
if (node.body && node.body.type === import_utils.AST_NODE_TYPES.BlockStatement) {
|
|
202
|
+
return hasJSXReturn(node.body);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return false;
|
|
206
|
+
}
|
|
207
|
+
function isNamedType(node) {
|
|
208
|
+
return node.type === import_utils.AST_NODE_TYPES.TSTypeReference;
|
|
209
|
+
}
|
|
210
|
+
function isAlreadyReadonly(node) {
|
|
211
|
+
if (node.type === import_utils.AST_NODE_TYPES.TSTypeReference && node.typeName) {
|
|
212
|
+
if (node.typeName.type === import_utils.AST_NODE_TYPES.Identifier && node.typeName.name === "Readonly") {
|
|
213
|
+
return true;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return false;
|
|
217
|
+
}
|
|
218
|
+
function checkFunction(node) {
|
|
219
|
+
if (!isReactComponent(node)) {
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
if (node.params.length !== 1) {
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
const param = node.params[0];
|
|
226
|
+
if (param.type === import_utils.AST_NODE_TYPES.Identifier && param.typeAnnotation) {
|
|
227
|
+
const { typeAnnotation } = param.typeAnnotation;
|
|
228
|
+
if (isNamedType(typeAnnotation) && !isAlreadyReadonly(typeAnnotation)) {
|
|
229
|
+
const { sourceCode } = context;
|
|
230
|
+
const typeText = sourceCode.getText(typeAnnotation);
|
|
231
|
+
context.report({
|
|
232
|
+
node: param.typeAnnotation,
|
|
233
|
+
messageId: "useReadonly",
|
|
234
|
+
fix(fixer) {
|
|
235
|
+
return fixer.replaceText(typeAnnotation, `Readonly<${typeText}>`);
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
return {
|
|
242
|
+
ArrowFunctionExpression: checkFunction,
|
|
243
|
+
FunctionExpression: checkFunction,
|
|
244
|
+
FunctionDeclaration: checkFunction
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
var enforce_readonly_component_props_default = enforceReadonlyComponentProps;
|
|
249
|
+
|
|
250
|
+
// src/rules/file-kebab-case.ts
|
|
251
|
+
var import_path = __toESM(require("path"), 1);
|
|
252
|
+
var import_utils2 = require("@typescript-eslint/utils");
|
|
253
|
+
var createRule2 = import_utils2.ESLintUtils.RuleCreator(
|
|
254
|
+
(name) => `https://github.com/next-friday/eslint-plugin-nextfriday/blob/main/docs/rules/${name}.md`
|
|
255
|
+
);
|
|
164
256
|
var isKebabCase = (str) => {
|
|
165
257
|
if (/\.(config|rc|setup|spec|test)$/.test(str)) {
|
|
166
258
|
return /^[a-z0-9]+(?:-[a-z0-9]+)*(?:\.[a-z0-9]+)*$/.test(str);
|
|
167
259
|
}
|
|
168
260
|
return /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(str);
|
|
169
261
|
};
|
|
170
|
-
var fileKebabCase =
|
|
262
|
+
var fileKebabCase = createRule2({
|
|
171
263
|
name: "file-kebab-case",
|
|
172
264
|
meta: {
|
|
173
265
|
type: "problem",
|
|
@@ -203,12 +295,12 @@ var file_kebab_case_default = fileKebabCase;
|
|
|
203
295
|
|
|
204
296
|
// src/rules/jsx-pascal-case.ts
|
|
205
297
|
var import_path2 = __toESM(require("path"), 1);
|
|
206
|
-
var
|
|
207
|
-
var
|
|
298
|
+
var import_utils3 = require("@typescript-eslint/utils");
|
|
299
|
+
var createRule3 = import_utils3.ESLintUtils.RuleCreator(
|
|
208
300
|
(name) => `https://github.com/next-friday/eslint-plugin-nextfriday/blob/main/docs/rules/${name}.md`
|
|
209
301
|
);
|
|
210
302
|
var isPascalCase = (str) => /^[A-Z][a-zA-Z0-9]*$/.test(str) && !/^[A-Z]+$/.test(str);
|
|
211
|
-
var jsxPascalCase =
|
|
303
|
+
var jsxPascalCase = createRule3({
|
|
212
304
|
name: "jsx-pascal-case",
|
|
213
305
|
meta: {
|
|
214
306
|
type: "problem",
|
|
@@ -244,11 +336,11 @@ var jsx_pascal_case_default = jsxPascalCase;
|
|
|
244
336
|
|
|
245
337
|
// src/rules/md-filename-case-restriction.ts
|
|
246
338
|
var import_path3 = __toESM(require("path"), 1);
|
|
247
|
-
var
|
|
248
|
-
var
|
|
339
|
+
var import_utils4 = require("@typescript-eslint/utils");
|
|
340
|
+
var createRule4 = import_utils4.ESLintUtils.RuleCreator(
|
|
249
341
|
(name) => `https://github.com/next-friday/eslint-plugin-nextfriday/blob/main/docs/rules/${name}.md`
|
|
250
342
|
);
|
|
251
|
-
var mdFilenameCaseRestriction =
|
|
343
|
+
var mdFilenameCaseRestriction = createRule4({
|
|
252
344
|
name: "md-filename-case-restriction",
|
|
253
345
|
meta: {
|
|
254
346
|
type: "problem",
|
|
@@ -290,11 +382,11 @@ var md_filename_case_restriction_default = mdFilenameCaseRestriction;
|
|
|
290
382
|
|
|
291
383
|
// src/rules/no-emoji.ts
|
|
292
384
|
var import_emoji_regex = __toESM(require("emoji-regex"), 1);
|
|
293
|
-
var
|
|
294
|
-
var
|
|
385
|
+
var import_utils5 = require("@typescript-eslint/utils");
|
|
386
|
+
var createRule5 = import_utils5.ESLintUtils.RuleCreator(
|
|
295
387
|
(name) => `https://github.com/next-friday/eslint-plugin-nextfriday/blob/main/docs/rules/${name}.md`
|
|
296
388
|
);
|
|
297
|
-
var noEmoji =
|
|
389
|
+
var noEmoji = createRule5({
|
|
298
390
|
name: "no-emoji",
|
|
299
391
|
meta: {
|
|
300
392
|
type: "problem",
|
|
@@ -328,11 +420,11 @@ var noEmoji = createRule4({
|
|
|
328
420
|
var no_emoji_default = noEmoji;
|
|
329
421
|
|
|
330
422
|
// src/rules/no-explicit-return-type.ts
|
|
331
|
-
var
|
|
332
|
-
var
|
|
423
|
+
var import_utils6 = require("@typescript-eslint/utils");
|
|
424
|
+
var createRule6 = import_utils6.ESLintUtils.RuleCreator(
|
|
333
425
|
(name) => `https://github.com/next-friday/eslint-plugin-nextfriday/blob/main/docs/rules/${name}.md`
|
|
334
426
|
);
|
|
335
|
-
var noExplicitReturnType =
|
|
427
|
+
var noExplicitReturnType = createRule6({
|
|
336
428
|
name: "no-explicit-return-type",
|
|
337
429
|
meta: {
|
|
338
430
|
type: "suggestion",
|
|
@@ -372,11 +464,11 @@ var noExplicitReturnType = createRule5({
|
|
|
372
464
|
var no_explicit_return_type_default = noExplicitReturnType;
|
|
373
465
|
|
|
374
466
|
// src/rules/prefer-destructuring-params.ts
|
|
375
|
-
var
|
|
376
|
-
var
|
|
467
|
+
var import_utils7 = require("@typescript-eslint/utils");
|
|
468
|
+
var createRule7 = import_utils7.ESLintUtils.RuleCreator(
|
|
377
469
|
(name) => `https://github.com/next-friday/eslint-plugin-nextfriday/blob/main/docs/rules/${name}.md`
|
|
378
470
|
);
|
|
379
|
-
var preferDestructuringParams =
|
|
471
|
+
var preferDestructuringParams = createRule7({
|
|
380
472
|
name: "prefer-destructuring-params",
|
|
381
473
|
meta: {
|
|
382
474
|
type: "suggestion",
|
|
@@ -395,7 +487,7 @@ var preferDestructuringParams = createRule6({
|
|
|
395
487
|
return;
|
|
396
488
|
}
|
|
397
489
|
const hasNonDestructuredParams = node.params.some(
|
|
398
|
-
(param) => param.type !==
|
|
490
|
+
(param) => param.type !== import_utils7.AST_NODE_TYPES.ObjectPattern && param.type !== import_utils7.AST_NODE_TYPES.RestElement
|
|
399
491
|
);
|
|
400
492
|
if (hasNonDestructuredParams) {
|
|
401
493
|
context.report({
|
|
@@ -414,11 +506,11 @@ var preferDestructuringParams = createRule6({
|
|
|
414
506
|
var prefer_destructuring_params_default = preferDestructuringParams;
|
|
415
507
|
|
|
416
508
|
// src/rules/prefer-import-type.ts
|
|
417
|
-
var
|
|
418
|
-
var
|
|
509
|
+
var import_utils8 = require("@typescript-eslint/utils");
|
|
510
|
+
var createRule8 = import_utils8.ESLintUtils.RuleCreator(
|
|
419
511
|
(name) => `https://github.com/next-friday/eslint-plugin-nextfriday/blob/main/docs/rules/${name}.md`
|
|
420
512
|
);
|
|
421
|
-
var preferImportType =
|
|
513
|
+
var preferImportType = createRule8({
|
|
422
514
|
name: "prefer-import-type",
|
|
423
515
|
meta: {
|
|
424
516
|
type: "suggestion",
|
|
@@ -449,14 +541,14 @@ var preferImportType = createRule7({
|
|
|
449
541
|
return;
|
|
450
542
|
}
|
|
451
543
|
const isTypeOnlyImport = node.specifiers.every((specifier) => {
|
|
452
|
-
if (specifier.type ===
|
|
544
|
+
if (specifier.type === import_utils8.AST_NODE_TYPES.ImportDefaultSpecifier) {
|
|
453
545
|
return false;
|
|
454
546
|
}
|
|
455
|
-
if (specifier.type ===
|
|
547
|
+
if (specifier.type === import_utils8.AST_NODE_TYPES.ImportNamespaceSpecifier) {
|
|
456
548
|
return false;
|
|
457
549
|
}
|
|
458
|
-
if (specifier.type ===
|
|
459
|
-
const importedName = specifier.imported.type ===
|
|
550
|
+
if (specifier.type === import_utils8.AST_NODE_TYPES.ImportSpecifier) {
|
|
551
|
+
const importedName = specifier.imported.type === import_utils8.AST_NODE_TYPES.Identifier ? specifier.imported.name : specifier.imported.value;
|
|
460
552
|
const isKnownTypeOnly = node.source.value === "@typescript-eslint/utils" && ["TSESTree", "RuleContext"].includes(importedName) || node.source.value === "react" && ["Component", "ComponentProps", "ReactNode", "FC", "JSX", "ReactElement", "PropsWithChildren"].includes(
|
|
461
553
|
importedName
|
|
462
554
|
) || importedName.endsWith("Type") || importedName.endsWith("Interface") || importedName.endsWith("Props");
|
|
@@ -484,11 +576,11 @@ var preferImportType = createRule7({
|
|
|
484
576
|
var prefer_import_type_default = preferImportType;
|
|
485
577
|
|
|
486
578
|
// src/rules/prefer-interface-over-inline-types.ts
|
|
487
|
-
var
|
|
488
|
-
var
|
|
579
|
+
var import_utils9 = require("@typescript-eslint/utils");
|
|
580
|
+
var createRule9 = import_utils9.ESLintUtils.RuleCreator(
|
|
489
581
|
(name) => `https://github.com/next-friday/eslint-plugin-nextfriday/blob/main/docs/rules/${name}.md`
|
|
490
582
|
);
|
|
491
|
-
var preferInterfaceOverInlineTypes =
|
|
583
|
+
var preferInterfaceOverInlineTypes = createRule9({
|
|
492
584
|
name: "prefer-interface-over-inline-types",
|
|
493
585
|
meta: {
|
|
494
586
|
type: "suggestion",
|
|
@@ -504,54 +596,54 @@ var preferInterfaceOverInlineTypes = createRule8({
|
|
|
504
596
|
defaultOptions: [],
|
|
505
597
|
create(context) {
|
|
506
598
|
function hasJSXInConditional(node) {
|
|
507
|
-
return node.consequent.type ===
|
|
599
|
+
return node.consequent.type === import_utils9.AST_NODE_TYPES.JSXElement || node.consequent.type === import_utils9.AST_NODE_TYPES.JSXFragment || node.alternate.type === import_utils9.AST_NODE_TYPES.JSXElement || node.alternate.type === import_utils9.AST_NODE_TYPES.JSXFragment;
|
|
508
600
|
}
|
|
509
601
|
function hasJSXInLogical(node) {
|
|
510
|
-
return node.right.type ===
|
|
602
|
+
return node.right.type === import_utils9.AST_NODE_TYPES.JSXElement || node.right.type === import_utils9.AST_NODE_TYPES.JSXFragment;
|
|
511
603
|
}
|
|
512
604
|
function hasJSXReturn(block) {
|
|
513
605
|
return block.body.some((stmt) => {
|
|
514
|
-
if (stmt.type ===
|
|
515
|
-
return stmt.argument.type ===
|
|
606
|
+
if (stmt.type === import_utils9.AST_NODE_TYPES.ReturnStatement && stmt.argument) {
|
|
607
|
+
return stmt.argument.type === import_utils9.AST_NODE_TYPES.JSXElement || stmt.argument.type === import_utils9.AST_NODE_TYPES.JSXFragment || stmt.argument.type === import_utils9.AST_NODE_TYPES.ConditionalExpression && hasJSXInConditional(stmt.argument) || stmt.argument.type === import_utils9.AST_NODE_TYPES.LogicalExpression && hasJSXInLogical(stmt.argument);
|
|
516
608
|
}
|
|
517
609
|
return false;
|
|
518
610
|
});
|
|
519
611
|
}
|
|
520
612
|
function isReactComponent(node) {
|
|
521
|
-
if (node.type ===
|
|
522
|
-
if (node.body.type ===
|
|
613
|
+
if (node.type === import_utils9.AST_NODE_TYPES.ArrowFunctionExpression) {
|
|
614
|
+
if (node.body.type === import_utils9.AST_NODE_TYPES.JSXElement || node.body.type === import_utils9.AST_NODE_TYPES.JSXFragment) {
|
|
523
615
|
return true;
|
|
524
616
|
}
|
|
525
|
-
if (node.body.type ===
|
|
617
|
+
if (node.body.type === import_utils9.AST_NODE_TYPES.BlockStatement) {
|
|
526
618
|
return hasJSXReturn(node.body);
|
|
527
619
|
}
|
|
528
|
-
} else if (node.type ===
|
|
529
|
-
if (node.body && node.body.type ===
|
|
620
|
+
} else if (node.type === import_utils9.AST_NODE_TYPES.FunctionExpression || node.type === import_utils9.AST_NODE_TYPES.FunctionDeclaration) {
|
|
621
|
+
if (node.body && node.body.type === import_utils9.AST_NODE_TYPES.BlockStatement) {
|
|
530
622
|
return hasJSXReturn(node.body);
|
|
531
623
|
}
|
|
532
624
|
}
|
|
533
625
|
return false;
|
|
534
626
|
}
|
|
535
627
|
function isInlineTypeAnnotation(node) {
|
|
536
|
-
if (node.type ===
|
|
628
|
+
if (node.type === import_utils9.AST_NODE_TYPES.TSTypeLiteral) {
|
|
537
629
|
return true;
|
|
538
630
|
}
|
|
539
|
-
if (node.type ===
|
|
540
|
-
return node.typeArguments.params.some((param) => param.type ===
|
|
631
|
+
if (node.type === import_utils9.AST_NODE_TYPES.TSTypeReference && node.typeArguments) {
|
|
632
|
+
return node.typeArguments.params.some((param) => param.type === import_utils9.AST_NODE_TYPES.TSTypeLiteral);
|
|
541
633
|
}
|
|
542
|
-
if (node.type ===
|
|
634
|
+
if (node.type === import_utils9.AST_NODE_TYPES.TSUnionType) {
|
|
543
635
|
return node.types.some((type) => isInlineTypeAnnotation(type));
|
|
544
636
|
}
|
|
545
637
|
return false;
|
|
546
638
|
}
|
|
547
639
|
function hasInlineObjectType(node) {
|
|
548
|
-
if (node.type ===
|
|
640
|
+
if (node.type === import_utils9.AST_NODE_TYPES.TSTypeLiteral) {
|
|
549
641
|
return true;
|
|
550
642
|
}
|
|
551
|
-
if (node.type ===
|
|
552
|
-
return node.typeArguments.params.some((param) => param.type ===
|
|
643
|
+
if (node.type === import_utils9.AST_NODE_TYPES.TSTypeReference && node.typeArguments) {
|
|
644
|
+
return node.typeArguments.params.some((param) => param.type === import_utils9.AST_NODE_TYPES.TSTypeLiteral);
|
|
553
645
|
}
|
|
554
|
-
if (node.type ===
|
|
646
|
+
if (node.type === import_utils9.AST_NODE_TYPES.TSUnionType) {
|
|
555
647
|
return node.types.some((type) => hasInlineObjectType(type));
|
|
556
648
|
}
|
|
557
649
|
return false;
|
|
@@ -564,7 +656,7 @@ var preferInterfaceOverInlineTypes = createRule8({
|
|
|
564
656
|
return;
|
|
565
657
|
}
|
|
566
658
|
const param = node.params[0];
|
|
567
|
-
if (param.type ===
|
|
659
|
+
if (param.type === import_utils9.AST_NODE_TYPES.Identifier && param.typeAnnotation) {
|
|
568
660
|
const { typeAnnotation } = param.typeAnnotation;
|
|
569
661
|
if (isInlineTypeAnnotation(typeAnnotation) && hasInlineObjectType(typeAnnotation)) {
|
|
570
662
|
context.report({
|
|
@@ -584,11 +676,11 @@ var preferInterfaceOverInlineTypes = createRule8({
|
|
|
584
676
|
var prefer_interface_over_inline_types_default = preferInterfaceOverInlineTypes;
|
|
585
677
|
|
|
586
678
|
// src/rules/prefer-react-import-types.ts
|
|
587
|
-
var
|
|
588
|
-
var
|
|
679
|
+
var import_utils10 = require("@typescript-eslint/utils");
|
|
680
|
+
var createRule10 = import_utils10.ESLintUtils.RuleCreator(
|
|
589
681
|
(name) => `https://github.com/next-friday/eslint-plugin-nextfriday/blob/main/docs/rules/${name}.md`
|
|
590
682
|
);
|
|
591
|
-
var preferReactImportTypes =
|
|
683
|
+
var preferReactImportTypes = createRule10({
|
|
592
684
|
name: "prefer-react-import-types",
|
|
593
685
|
meta: {
|
|
594
686
|
type: "suggestion",
|
|
@@ -664,7 +756,7 @@ var preferReactImportTypes = createRule9({
|
|
|
664
756
|
]);
|
|
665
757
|
const allReactExports = /* @__PURE__ */ new Set([...reactTypes, ...reactRuntimeExports]);
|
|
666
758
|
function checkMemberExpression(node) {
|
|
667
|
-
if (node.object.type ===
|
|
759
|
+
if (node.object.type === import_utils10.AST_NODE_TYPES.Identifier && node.object.name === "React" && node.property.type === import_utils10.AST_NODE_TYPES.Identifier && allReactExports.has(node.property.name)) {
|
|
668
760
|
const typeName = node.property.name;
|
|
669
761
|
const isType = reactTypes.has(typeName);
|
|
670
762
|
const importStatement = isType ? `import type { ${typeName} } from "react"` : `import { ${typeName} } from "react"`;
|
|
@@ -681,7 +773,7 @@ var preferReactImportTypes = createRule9({
|
|
|
681
773
|
return {
|
|
682
774
|
MemberExpression: checkMemberExpression,
|
|
683
775
|
"TSTypeReference > TSQualifiedName": (node) => {
|
|
684
|
-
if (node.left.type ===
|
|
776
|
+
if (node.left.type === import_utils10.AST_NODE_TYPES.Identifier && node.left.name === "React" && node.right.type === import_utils10.AST_NODE_TYPES.Identifier && allReactExports.has(node.right.name)) {
|
|
685
777
|
const typeName = node.right.name;
|
|
686
778
|
const isType = reactTypes.has(typeName);
|
|
687
779
|
const importStatement = isType ? `import type { ${typeName} } from "react"` : `import { ${typeName} } from "react"`;
|
|
@@ -701,11 +793,11 @@ var preferReactImportTypes = createRule9({
|
|
|
701
793
|
var prefer_react_import_types_default = preferReactImportTypes;
|
|
702
794
|
|
|
703
795
|
// src/rules/react-props-destructure.ts
|
|
704
|
-
var
|
|
705
|
-
var
|
|
796
|
+
var import_utils11 = require("@typescript-eslint/utils");
|
|
797
|
+
var createRule11 = import_utils11.ESLintUtils.RuleCreator(
|
|
706
798
|
(name) => `https://github.com/next-friday/eslint-plugin-nextfriday/blob/main/docs/rules/${name}.md`
|
|
707
799
|
);
|
|
708
|
-
var reactPropsDestructure =
|
|
800
|
+
var reactPropsDestructure = createRule11({
|
|
709
801
|
name: "react-props-destructure",
|
|
710
802
|
meta: {
|
|
711
803
|
type: "suggestion",
|
|
@@ -721,29 +813,29 @@ var reactPropsDestructure = createRule10({
|
|
|
721
813
|
defaultOptions: [],
|
|
722
814
|
create(context) {
|
|
723
815
|
function hasJSXInConditional(node) {
|
|
724
|
-
return node.consequent.type ===
|
|
816
|
+
return node.consequent.type === import_utils11.AST_NODE_TYPES.JSXElement || node.consequent.type === import_utils11.AST_NODE_TYPES.JSXFragment || node.alternate.type === import_utils11.AST_NODE_TYPES.JSXElement || node.alternate.type === import_utils11.AST_NODE_TYPES.JSXFragment;
|
|
725
817
|
}
|
|
726
818
|
function hasJSXInLogical(node) {
|
|
727
|
-
return node.right.type ===
|
|
819
|
+
return node.right.type === import_utils11.AST_NODE_TYPES.JSXElement || node.right.type === import_utils11.AST_NODE_TYPES.JSXFragment;
|
|
728
820
|
}
|
|
729
821
|
function hasJSXReturn(block) {
|
|
730
822
|
return block.body.some((stmt) => {
|
|
731
|
-
if (stmt.type ===
|
|
732
|
-
return stmt.argument.type ===
|
|
823
|
+
if (stmt.type === import_utils11.AST_NODE_TYPES.ReturnStatement && stmt.argument) {
|
|
824
|
+
return stmt.argument.type === import_utils11.AST_NODE_TYPES.JSXElement || stmt.argument.type === import_utils11.AST_NODE_TYPES.JSXFragment || stmt.argument.type === import_utils11.AST_NODE_TYPES.ConditionalExpression && hasJSXInConditional(stmt.argument) || stmt.argument.type === import_utils11.AST_NODE_TYPES.LogicalExpression && hasJSXInLogical(stmt.argument);
|
|
733
825
|
}
|
|
734
826
|
return false;
|
|
735
827
|
});
|
|
736
828
|
}
|
|
737
829
|
function isReactComponent(node) {
|
|
738
|
-
if (node.type ===
|
|
739
|
-
if (node.body.type ===
|
|
830
|
+
if (node.type === import_utils11.AST_NODE_TYPES.ArrowFunctionExpression) {
|
|
831
|
+
if (node.body.type === import_utils11.AST_NODE_TYPES.JSXElement || node.body.type === import_utils11.AST_NODE_TYPES.JSXFragment) {
|
|
740
832
|
return true;
|
|
741
833
|
}
|
|
742
|
-
if (node.body.type ===
|
|
834
|
+
if (node.body.type === import_utils11.AST_NODE_TYPES.BlockStatement) {
|
|
743
835
|
return hasJSXReturn(node.body);
|
|
744
836
|
}
|
|
745
|
-
} else if (node.type ===
|
|
746
|
-
if (node.body && node.body.type ===
|
|
837
|
+
} else if (node.type === import_utils11.AST_NODE_TYPES.FunctionExpression || node.type === import_utils11.AST_NODE_TYPES.FunctionDeclaration) {
|
|
838
|
+
if (node.body && node.body.type === import_utils11.AST_NODE_TYPES.BlockStatement) {
|
|
747
839
|
return hasJSXReturn(node.body);
|
|
748
840
|
}
|
|
749
841
|
}
|
|
@@ -757,9 +849,9 @@ var reactPropsDestructure = createRule10({
|
|
|
757
849
|
return;
|
|
758
850
|
}
|
|
759
851
|
const param = node.params[0];
|
|
760
|
-
if (param.type ===
|
|
761
|
-
const properties = param.properties.filter((prop) => prop.type ===
|
|
762
|
-
if (prop.key.type ===
|
|
852
|
+
if (param.type === import_utils11.AST_NODE_TYPES.ObjectPattern) {
|
|
853
|
+
const properties = param.properties.filter((prop) => prop.type === import_utils11.AST_NODE_TYPES.Property).map((prop) => {
|
|
854
|
+
if (prop.key.type === import_utils11.AST_NODE_TYPES.Identifier) {
|
|
763
855
|
return prop.key.name;
|
|
764
856
|
}
|
|
765
857
|
return null;
|
|
@@ -791,6 +883,7 @@ var meta = {
|
|
|
791
883
|
version: package_default.version
|
|
792
884
|
};
|
|
793
885
|
var rules = {
|
|
886
|
+
"enforce-readonly-component-props": enforce_readonly_component_props_default,
|
|
794
887
|
"file-kebab-case": file_kebab_case_default,
|
|
795
888
|
"jsx-pascal-case": jsx_pascal_case_default,
|
|
796
889
|
"md-filename-case-restriction": md_filename_case_restriction_default,
|
|
@@ -827,12 +920,14 @@ var baseRecommendedRules = {
|
|
|
827
920
|
var jsxRules = {
|
|
828
921
|
"nextfriday/jsx-pascal-case": "warn",
|
|
829
922
|
"nextfriday/prefer-interface-over-inline-types": "warn",
|
|
830
|
-
"nextfriday/react-props-destructure": "warn"
|
|
923
|
+
"nextfriday/react-props-destructure": "warn",
|
|
924
|
+
"nextfriday/enforce-readonly-component-props": "warn"
|
|
831
925
|
};
|
|
832
926
|
var jsxRecommendedRules = {
|
|
833
927
|
"nextfriday/jsx-pascal-case": "error",
|
|
834
928
|
"nextfriday/prefer-interface-over-inline-types": "error",
|
|
835
|
-
"nextfriday/react-props-destructure": "error"
|
|
929
|
+
"nextfriday/react-props-destructure": "error",
|
|
930
|
+
"nextfriday/enforce-readonly-component-props": "error"
|
|
836
931
|
};
|
|
837
932
|
var createConfig = (configRules) => ({
|
|
838
933
|
plugins: {
|