eslint-plugin-nextfriday 1.1.1 → 1.2.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 +12 -0
- package/README.md +4 -0
- package/docs/rules/ENFORCE_READONLY_COMPONENT_PROPS.md +104 -0
- package/lib/index.cjs +175 -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 +175 -70
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# eslint-plugin-nextfriday
|
|
2
2
|
|
|
3
|
+
## 1.2.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#18](https://github.com/next-friday/eslint-plugin-nextfriday/pull/18) [`ddfad3d`](https://github.com/next-friday/eslint-plugin-nextfriday/commit/ddfad3d5a112c429ee084c134517dad8cac01c7c) Thanks [@nextfridaydeveloper](https://github.com/nextfridaydeveloper)! - Fixed prefer-destructuring-params rule to only target user-defined functions and skip built-in/library/third-party functions.
|
|
8
|
+
|
|
9
|
+
## 1.2.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- [#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.
|
|
14
|
+
|
|
3
15
|
## 1.1.1
|
|
4
16
|
|
|
5
17
|
### 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.1",
|
|
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",
|
|
@@ -391,11 +483,21 @@ var preferDestructuringParams = createRule6({
|
|
|
391
483
|
defaultOptions: [],
|
|
392
484
|
create(context) {
|
|
393
485
|
const checkFunction = (node) => {
|
|
486
|
+
const { filename } = context;
|
|
487
|
+
if (filename.includes("node_modules") || filename.includes(".d.ts")) {
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
if (node.type === import_utils7.AST_NODE_TYPES.FunctionDeclaration && node.id) {
|
|
491
|
+
const functionName = node.id.name;
|
|
492
|
+
if (functionName.startsWith("_") || functionName.includes("$") || /^[A-Z][a-zA-Z]*$/.test(functionName)) {
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
}
|
|
394
496
|
if (node.params.length <= 1) {
|
|
395
497
|
return;
|
|
396
498
|
}
|
|
397
499
|
const hasNonDestructuredParams = node.params.some(
|
|
398
|
-
(param) => param.type !==
|
|
500
|
+
(param) => param.type !== import_utils7.AST_NODE_TYPES.ObjectPattern && param.type !== import_utils7.AST_NODE_TYPES.RestElement
|
|
399
501
|
);
|
|
400
502
|
if (hasNonDestructuredParams) {
|
|
401
503
|
context.report({
|
|
@@ -414,11 +516,11 @@ var preferDestructuringParams = createRule6({
|
|
|
414
516
|
var prefer_destructuring_params_default = preferDestructuringParams;
|
|
415
517
|
|
|
416
518
|
// src/rules/prefer-import-type.ts
|
|
417
|
-
var
|
|
418
|
-
var
|
|
519
|
+
var import_utils8 = require("@typescript-eslint/utils");
|
|
520
|
+
var createRule8 = import_utils8.ESLintUtils.RuleCreator(
|
|
419
521
|
(name) => `https://github.com/next-friday/eslint-plugin-nextfriday/blob/main/docs/rules/${name}.md`
|
|
420
522
|
);
|
|
421
|
-
var preferImportType =
|
|
523
|
+
var preferImportType = createRule8({
|
|
422
524
|
name: "prefer-import-type",
|
|
423
525
|
meta: {
|
|
424
526
|
type: "suggestion",
|
|
@@ -449,14 +551,14 @@ var preferImportType = createRule7({
|
|
|
449
551
|
return;
|
|
450
552
|
}
|
|
451
553
|
const isTypeOnlyImport = node.specifiers.every((specifier) => {
|
|
452
|
-
if (specifier.type ===
|
|
554
|
+
if (specifier.type === import_utils8.AST_NODE_TYPES.ImportDefaultSpecifier) {
|
|
453
555
|
return false;
|
|
454
556
|
}
|
|
455
|
-
if (specifier.type ===
|
|
557
|
+
if (specifier.type === import_utils8.AST_NODE_TYPES.ImportNamespaceSpecifier) {
|
|
456
558
|
return false;
|
|
457
559
|
}
|
|
458
|
-
if (specifier.type ===
|
|
459
|
-
const importedName = specifier.imported.type ===
|
|
560
|
+
if (specifier.type === import_utils8.AST_NODE_TYPES.ImportSpecifier) {
|
|
561
|
+
const importedName = specifier.imported.type === import_utils8.AST_NODE_TYPES.Identifier ? specifier.imported.name : specifier.imported.value;
|
|
460
562
|
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
563
|
importedName
|
|
462
564
|
) || importedName.endsWith("Type") || importedName.endsWith("Interface") || importedName.endsWith("Props");
|
|
@@ -484,11 +586,11 @@ var preferImportType = createRule7({
|
|
|
484
586
|
var prefer_import_type_default = preferImportType;
|
|
485
587
|
|
|
486
588
|
// src/rules/prefer-interface-over-inline-types.ts
|
|
487
|
-
var
|
|
488
|
-
var
|
|
589
|
+
var import_utils9 = require("@typescript-eslint/utils");
|
|
590
|
+
var createRule9 = import_utils9.ESLintUtils.RuleCreator(
|
|
489
591
|
(name) => `https://github.com/next-friday/eslint-plugin-nextfriday/blob/main/docs/rules/${name}.md`
|
|
490
592
|
);
|
|
491
|
-
var preferInterfaceOverInlineTypes =
|
|
593
|
+
var preferInterfaceOverInlineTypes = createRule9({
|
|
492
594
|
name: "prefer-interface-over-inline-types",
|
|
493
595
|
meta: {
|
|
494
596
|
type: "suggestion",
|
|
@@ -504,54 +606,54 @@ var preferInterfaceOverInlineTypes = createRule8({
|
|
|
504
606
|
defaultOptions: [],
|
|
505
607
|
create(context) {
|
|
506
608
|
function hasJSXInConditional(node) {
|
|
507
|
-
return node.consequent.type ===
|
|
609
|
+
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
610
|
}
|
|
509
611
|
function hasJSXInLogical(node) {
|
|
510
|
-
return node.right.type ===
|
|
612
|
+
return node.right.type === import_utils9.AST_NODE_TYPES.JSXElement || node.right.type === import_utils9.AST_NODE_TYPES.JSXFragment;
|
|
511
613
|
}
|
|
512
614
|
function hasJSXReturn(block) {
|
|
513
615
|
return block.body.some((stmt) => {
|
|
514
|
-
if (stmt.type ===
|
|
515
|
-
return stmt.argument.type ===
|
|
616
|
+
if (stmt.type === import_utils9.AST_NODE_TYPES.ReturnStatement && stmt.argument) {
|
|
617
|
+
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
618
|
}
|
|
517
619
|
return false;
|
|
518
620
|
});
|
|
519
621
|
}
|
|
520
622
|
function isReactComponent(node) {
|
|
521
|
-
if (node.type ===
|
|
522
|
-
if (node.body.type ===
|
|
623
|
+
if (node.type === import_utils9.AST_NODE_TYPES.ArrowFunctionExpression) {
|
|
624
|
+
if (node.body.type === import_utils9.AST_NODE_TYPES.JSXElement || node.body.type === import_utils9.AST_NODE_TYPES.JSXFragment) {
|
|
523
625
|
return true;
|
|
524
626
|
}
|
|
525
|
-
if (node.body.type ===
|
|
627
|
+
if (node.body.type === import_utils9.AST_NODE_TYPES.BlockStatement) {
|
|
526
628
|
return hasJSXReturn(node.body);
|
|
527
629
|
}
|
|
528
|
-
} else if (node.type ===
|
|
529
|
-
if (node.body && node.body.type ===
|
|
630
|
+
} else if (node.type === import_utils9.AST_NODE_TYPES.FunctionExpression || node.type === import_utils9.AST_NODE_TYPES.FunctionDeclaration) {
|
|
631
|
+
if (node.body && node.body.type === import_utils9.AST_NODE_TYPES.BlockStatement) {
|
|
530
632
|
return hasJSXReturn(node.body);
|
|
531
633
|
}
|
|
532
634
|
}
|
|
533
635
|
return false;
|
|
534
636
|
}
|
|
535
637
|
function isInlineTypeAnnotation(node) {
|
|
536
|
-
if (node.type ===
|
|
638
|
+
if (node.type === import_utils9.AST_NODE_TYPES.TSTypeLiteral) {
|
|
537
639
|
return true;
|
|
538
640
|
}
|
|
539
|
-
if (node.type ===
|
|
540
|
-
return node.typeArguments.params.some((param) => param.type ===
|
|
641
|
+
if (node.type === import_utils9.AST_NODE_TYPES.TSTypeReference && node.typeArguments) {
|
|
642
|
+
return node.typeArguments.params.some((param) => param.type === import_utils9.AST_NODE_TYPES.TSTypeLiteral);
|
|
541
643
|
}
|
|
542
|
-
if (node.type ===
|
|
644
|
+
if (node.type === import_utils9.AST_NODE_TYPES.TSUnionType) {
|
|
543
645
|
return node.types.some((type) => isInlineTypeAnnotation(type));
|
|
544
646
|
}
|
|
545
647
|
return false;
|
|
546
648
|
}
|
|
547
649
|
function hasInlineObjectType(node) {
|
|
548
|
-
if (node.type ===
|
|
650
|
+
if (node.type === import_utils9.AST_NODE_TYPES.TSTypeLiteral) {
|
|
549
651
|
return true;
|
|
550
652
|
}
|
|
551
|
-
if (node.type ===
|
|
552
|
-
return node.typeArguments.params.some((param) => param.type ===
|
|
653
|
+
if (node.type === import_utils9.AST_NODE_TYPES.TSTypeReference && node.typeArguments) {
|
|
654
|
+
return node.typeArguments.params.some((param) => param.type === import_utils9.AST_NODE_TYPES.TSTypeLiteral);
|
|
553
655
|
}
|
|
554
|
-
if (node.type ===
|
|
656
|
+
if (node.type === import_utils9.AST_NODE_TYPES.TSUnionType) {
|
|
555
657
|
return node.types.some((type) => hasInlineObjectType(type));
|
|
556
658
|
}
|
|
557
659
|
return false;
|
|
@@ -564,7 +666,7 @@ var preferInterfaceOverInlineTypes = createRule8({
|
|
|
564
666
|
return;
|
|
565
667
|
}
|
|
566
668
|
const param = node.params[0];
|
|
567
|
-
if (param.type ===
|
|
669
|
+
if (param.type === import_utils9.AST_NODE_TYPES.Identifier && param.typeAnnotation) {
|
|
568
670
|
const { typeAnnotation } = param.typeAnnotation;
|
|
569
671
|
if (isInlineTypeAnnotation(typeAnnotation) && hasInlineObjectType(typeAnnotation)) {
|
|
570
672
|
context.report({
|
|
@@ -584,11 +686,11 @@ var preferInterfaceOverInlineTypes = createRule8({
|
|
|
584
686
|
var prefer_interface_over_inline_types_default = preferInterfaceOverInlineTypes;
|
|
585
687
|
|
|
586
688
|
// src/rules/prefer-react-import-types.ts
|
|
587
|
-
var
|
|
588
|
-
var
|
|
689
|
+
var import_utils10 = require("@typescript-eslint/utils");
|
|
690
|
+
var createRule10 = import_utils10.ESLintUtils.RuleCreator(
|
|
589
691
|
(name) => `https://github.com/next-friday/eslint-plugin-nextfriday/blob/main/docs/rules/${name}.md`
|
|
590
692
|
);
|
|
591
|
-
var preferReactImportTypes =
|
|
693
|
+
var preferReactImportTypes = createRule10({
|
|
592
694
|
name: "prefer-react-import-types",
|
|
593
695
|
meta: {
|
|
594
696
|
type: "suggestion",
|
|
@@ -664,7 +766,7 @@ var preferReactImportTypes = createRule9({
|
|
|
664
766
|
]);
|
|
665
767
|
const allReactExports = /* @__PURE__ */ new Set([...reactTypes, ...reactRuntimeExports]);
|
|
666
768
|
function checkMemberExpression(node) {
|
|
667
|
-
if (node.object.type ===
|
|
769
|
+
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
770
|
const typeName = node.property.name;
|
|
669
771
|
const isType = reactTypes.has(typeName);
|
|
670
772
|
const importStatement = isType ? `import type { ${typeName} } from "react"` : `import { ${typeName} } from "react"`;
|
|
@@ -681,7 +783,7 @@ var preferReactImportTypes = createRule9({
|
|
|
681
783
|
return {
|
|
682
784
|
MemberExpression: checkMemberExpression,
|
|
683
785
|
"TSTypeReference > TSQualifiedName": (node) => {
|
|
684
|
-
if (node.left.type ===
|
|
786
|
+
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
787
|
const typeName = node.right.name;
|
|
686
788
|
const isType = reactTypes.has(typeName);
|
|
687
789
|
const importStatement = isType ? `import type { ${typeName} } from "react"` : `import { ${typeName} } from "react"`;
|
|
@@ -701,11 +803,11 @@ var preferReactImportTypes = createRule9({
|
|
|
701
803
|
var prefer_react_import_types_default = preferReactImportTypes;
|
|
702
804
|
|
|
703
805
|
// src/rules/react-props-destructure.ts
|
|
704
|
-
var
|
|
705
|
-
var
|
|
806
|
+
var import_utils11 = require("@typescript-eslint/utils");
|
|
807
|
+
var createRule11 = import_utils11.ESLintUtils.RuleCreator(
|
|
706
808
|
(name) => `https://github.com/next-friday/eslint-plugin-nextfriday/blob/main/docs/rules/${name}.md`
|
|
707
809
|
);
|
|
708
|
-
var reactPropsDestructure =
|
|
810
|
+
var reactPropsDestructure = createRule11({
|
|
709
811
|
name: "react-props-destructure",
|
|
710
812
|
meta: {
|
|
711
813
|
type: "suggestion",
|
|
@@ -721,29 +823,29 @@ var reactPropsDestructure = createRule10({
|
|
|
721
823
|
defaultOptions: [],
|
|
722
824
|
create(context) {
|
|
723
825
|
function hasJSXInConditional(node) {
|
|
724
|
-
return node.consequent.type ===
|
|
826
|
+
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
827
|
}
|
|
726
828
|
function hasJSXInLogical(node) {
|
|
727
|
-
return node.right.type ===
|
|
829
|
+
return node.right.type === import_utils11.AST_NODE_TYPES.JSXElement || node.right.type === import_utils11.AST_NODE_TYPES.JSXFragment;
|
|
728
830
|
}
|
|
729
831
|
function hasJSXReturn(block) {
|
|
730
832
|
return block.body.some((stmt) => {
|
|
731
|
-
if (stmt.type ===
|
|
732
|
-
return stmt.argument.type ===
|
|
833
|
+
if (stmt.type === import_utils11.AST_NODE_TYPES.ReturnStatement && stmt.argument) {
|
|
834
|
+
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
835
|
}
|
|
734
836
|
return false;
|
|
735
837
|
});
|
|
736
838
|
}
|
|
737
839
|
function isReactComponent(node) {
|
|
738
|
-
if (node.type ===
|
|
739
|
-
if (node.body.type ===
|
|
840
|
+
if (node.type === import_utils11.AST_NODE_TYPES.ArrowFunctionExpression) {
|
|
841
|
+
if (node.body.type === import_utils11.AST_NODE_TYPES.JSXElement || node.body.type === import_utils11.AST_NODE_TYPES.JSXFragment) {
|
|
740
842
|
return true;
|
|
741
843
|
}
|
|
742
|
-
if (node.body.type ===
|
|
844
|
+
if (node.body.type === import_utils11.AST_NODE_TYPES.BlockStatement) {
|
|
743
845
|
return hasJSXReturn(node.body);
|
|
744
846
|
}
|
|
745
|
-
} else if (node.type ===
|
|
746
|
-
if (node.body && node.body.type ===
|
|
847
|
+
} else if (node.type === import_utils11.AST_NODE_TYPES.FunctionExpression || node.type === import_utils11.AST_NODE_TYPES.FunctionDeclaration) {
|
|
848
|
+
if (node.body && node.body.type === import_utils11.AST_NODE_TYPES.BlockStatement) {
|
|
747
849
|
return hasJSXReturn(node.body);
|
|
748
850
|
}
|
|
749
851
|
}
|
|
@@ -757,9 +859,9 @@ var reactPropsDestructure = createRule10({
|
|
|
757
859
|
return;
|
|
758
860
|
}
|
|
759
861
|
const param = node.params[0];
|
|
760
|
-
if (param.type ===
|
|
761
|
-
const properties = param.properties.filter((prop) => prop.type ===
|
|
762
|
-
if (prop.key.type ===
|
|
862
|
+
if (param.type === import_utils11.AST_NODE_TYPES.ObjectPattern) {
|
|
863
|
+
const properties = param.properties.filter((prop) => prop.type === import_utils11.AST_NODE_TYPES.Property).map((prop) => {
|
|
864
|
+
if (prop.key.type === import_utils11.AST_NODE_TYPES.Identifier) {
|
|
763
865
|
return prop.key.name;
|
|
764
866
|
}
|
|
765
867
|
return null;
|
|
@@ -791,6 +893,7 @@ var meta = {
|
|
|
791
893
|
version: package_default.version
|
|
792
894
|
};
|
|
793
895
|
var rules = {
|
|
896
|
+
"enforce-readonly-component-props": enforce_readonly_component_props_default,
|
|
794
897
|
"file-kebab-case": file_kebab_case_default,
|
|
795
898
|
"jsx-pascal-case": jsx_pascal_case_default,
|
|
796
899
|
"md-filename-case-restriction": md_filename_case_restriction_default,
|
|
@@ -827,12 +930,14 @@ var baseRecommendedRules = {
|
|
|
827
930
|
var jsxRules = {
|
|
828
931
|
"nextfriday/jsx-pascal-case": "warn",
|
|
829
932
|
"nextfriday/prefer-interface-over-inline-types": "warn",
|
|
830
|
-
"nextfriday/react-props-destructure": "warn"
|
|
933
|
+
"nextfriday/react-props-destructure": "warn",
|
|
934
|
+
"nextfriday/enforce-readonly-component-props": "warn"
|
|
831
935
|
};
|
|
832
936
|
var jsxRecommendedRules = {
|
|
833
937
|
"nextfriday/jsx-pascal-case": "error",
|
|
834
938
|
"nextfriday/prefer-interface-over-inline-types": "error",
|
|
835
|
-
"nextfriday/react-props-destructure": "error"
|
|
939
|
+
"nextfriday/react-props-destructure": "error",
|
|
940
|
+
"nextfriday/enforce-readonly-component-props": "error"
|
|
836
941
|
};
|
|
837
942
|
var createConfig = (configRules) => ({
|
|
838
943
|
plugins: {
|