@ethlete/eslint-plugin 1.0.0-next.19 → 1.0.0-next.20
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
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.0.0-next.20
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [`085c95e`](https://github.com/ethlete-io/ethdk/commit/085c95ebe1ecb8489b00b042066997a7a9dc8e5e) Thanks [@TomTomB](https://github.com/TomTomB)! - `require-form-submit` accepts `[etForm]` and `[formRoot]` as a form's submit handling.
|
|
8
|
+
|
|
9
|
+
- [`4350d8e`](https://github.com/ethlete-io/ethdk/commit/4350d8e1eca00447ba130ec5a0ce43a64cfc4496) Thanks [@TomTomB](https://github.com/TomTomB)! - `no-trivial-wrapper-method` no longer flags `focus`, `blur` or `reset` on a component or directive - Angular resolves those by name on the instance.
|
|
10
|
+
|
|
3
11
|
## 1.0.0-next.19
|
|
4
12
|
|
|
5
13
|
### Minor Changes
|
package/package.json
CHANGED
|
@@ -16,14 +16,41 @@
|
|
|
16
16
|
* GOOD:
|
|
17
17
|
* // Call this.value.set(val) directly at the call sites.
|
|
18
18
|
* // If the method wraps something non-trivially (transforms args, adds logic), it is fine.
|
|
19
|
+
* // `focus`/`blur`/`reset` on a @Component/@Directive are exempt - see CONTRACT_METHOD_NAMES.
|
|
19
20
|
*/
|
|
20
21
|
|
|
22
|
+
/**
|
|
23
|
+
* Imperative members Angular and the DOM look up **by name on the instance**. A component that
|
|
24
|
+
* wraps a control in a host directive has to re-expose them itself: signal forms resolves a custom
|
|
25
|
+
* control to the wrapper component, not to its host directives, so a `focus()` that only forwards
|
|
26
|
+
* is the whole contract - there is no call site to inline it into.
|
|
27
|
+
*/
|
|
28
|
+
const CONTRACT_METHOD_NAMES = new Set(['focus', 'blur', 'reset']);
|
|
29
|
+
|
|
21
30
|
/**
|
|
22
31
|
* Returns true when the param is a plain identifier (no default, no destructuring, no rest).
|
|
23
32
|
* @param {import('@typescript-eslint/types').TSESTree.Parameter} param
|
|
24
33
|
*/
|
|
25
34
|
const isSimpleParam = (param) => param.type === 'Identifier';
|
|
26
35
|
|
|
36
|
+
/**
|
|
37
|
+
* Whether the class holding this method is an Angular component or directive.
|
|
38
|
+
* @param {import('eslint').Rule.Node} methodNode MethodDefinition node
|
|
39
|
+
*/
|
|
40
|
+
const isInAngularClass = (methodNode) => {
|
|
41
|
+
const classNode = methodNode.parent?.parent;
|
|
42
|
+
const decorators = classNode && 'decorators' in classNode ? classNode.decorators : null;
|
|
43
|
+
|
|
44
|
+
if (!decorators) return false;
|
|
45
|
+
|
|
46
|
+
return decorators.some((decorator) => {
|
|
47
|
+
const expression = decorator.expression;
|
|
48
|
+
const callee = expression.type === 'CallExpression' ? expression.callee : expression;
|
|
49
|
+
|
|
50
|
+
return callee.type === 'Identifier' && (callee.name === 'Component' || callee.name === 'Directive');
|
|
51
|
+
});
|
|
52
|
+
};
|
|
53
|
+
|
|
27
54
|
/**
|
|
28
55
|
* Returns the single CallExpression in the method body when the method is a
|
|
29
56
|
* trivial all-args-forwarding wrapper, or null otherwise.
|
|
@@ -97,6 +124,8 @@ const noTrivialWrapperMethod = {
|
|
|
97
124
|
const methodName = node.key.type === 'Identifier' ? node.key.name : null;
|
|
98
125
|
if (!methodName) return;
|
|
99
126
|
|
|
127
|
+
if (CONTRACT_METHOD_NAMES.has(methodName) && isInAngularClass(node)) return;
|
|
128
|
+
|
|
100
129
|
const callee = callExpr.callee;
|
|
101
130
|
const targetName =
|
|
102
131
|
callee.type === 'MemberExpression'
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* submit control reaches a form at all.
|
|
7
7
|
*
|
|
8
8
|
* <form (ngSubmit)="save()"> ✔
|
|
9
|
+
* <form [etForm]="form"> ✔ signal forms - the directive submits the field tree
|
|
9
10
|
* <form> ✘ pressing Enter in a field does nothing, or reloads the page
|
|
10
11
|
*
|
|
11
12
|
* <form …><button type="submit"> ✔
|
|
@@ -26,7 +27,9 @@ const hasBinding = (node, name) =>
|
|
|
26
27
|
|
|
27
28
|
/** @param {any} node */
|
|
28
29
|
const handlesSubmit = (node) =>
|
|
29
|
-
node.outputs?.some(/** @param {any} o */ (o) => o.name === 'submit' || o.name === 'ngSubmit')
|
|
30
|
+
node.outputs?.some(/** @param {any} o */ (o) => o.name === 'submit' || o.name === 'ngSubmit') ||
|
|
31
|
+
hasBinding(node, 'etForm') ||
|
|
32
|
+
hasBinding(node, 'formRoot');
|
|
30
33
|
|
|
31
34
|
/** @param {any} node */
|
|
32
35
|
const submitsNatively = (node) =>
|
|
@@ -46,7 +49,7 @@ const requireFormSubmit = {
|
|
|
46
49
|
schema: [],
|
|
47
50
|
messages: {
|
|
48
51
|
missingSubmitHandler:
|
|
49
|
-
'This `<form>` handles no submission - pressing Enter in a field either does nothing or reloads the page. Bind `(ngSubmit)` (reactive forms) or `(submit)`, or use a plain element if this is not a form.',
|
|
52
|
+
'This `<form>` handles no submission - pressing Enter in a field either does nothing or reloads the page. Bind `[etForm]` (signal forms), `(ngSubmit)` (reactive forms) or `(submit)`, or use a plain element if this is not a form.',
|
|
50
53
|
submitOutsideForm:
|
|
51
54
|
'A `type="submit"` control outside a `<form>` submits nothing. Put it inside the form, or associate it with one by id: `form="the-form-id"`.',
|
|
52
55
|
},
|