Skip to content

Commit b3afb2e

Browse files
committed
Extract rule: template-no-action-on-submit-button
1 parent a15aa2a commit b3afb2e

4 files changed

Lines changed: 484 additions & 0 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,7 @@ rules in templates can be disabled with eslint directives with mustache or html
196196
| :----------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------- | :- | :- | :- |
197197
| [template-builtin-component-arguments](docs/rules/template-builtin-component-arguments.md) | disallow setting certain attributes on builtin components | | | |
198198
| [template-no-action-modifiers](docs/rules/template-no-action-modifiers.md) | disallow usage of {{action}} modifiers | | | |
199+
| [template-no-action-on-submit-button](docs/rules/template-no-action-on-submit-button.md) | disallow action attribute on submit buttons | | | |
199200
| [template-no-arguments-for-html-elements](docs/rules/template-no-arguments-for-html-elements.md) | disallow @arguments on HTML elements | | | |
200201
| [template-no-array-prototype-extensions](docs/rules/template-no-array-prototype-extensions.md) | disallow usage of Ember Array prototype extensions | | | |
201202
| [template-no-block-params-for-html-elements](docs/rules/template-no-block-params-for-html-elements.md) | disallow block params on HTML elements | | | |
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# ember/template-no-action-on-submit-button
2+
3+
<!-- end auto-generated rule header -->
4+
5+
Disallow `action` attribute on submit buttons.
6+
7+
Using the `action` attribute on submit buttons is a common mistake. Instead, you should use the `{{on}}` modifier to handle click events, or handle form submission at the form level.
8+
9+
## Rule Details
10+
11+
This rule disallows using the `action` attribute on `<button>` elements (which default to type="submit") and `<input type="submit">` elements.
12+
13+
## Examples
14+
15+
### Incorrect ❌
16+
17+
```gjs
18+
<template>
19+
<button action="save">Save</button>
20+
</template>
21+
```
22+
23+
```gjs
24+
<template>
25+
<button type="submit" action="submit">Submit</button>
26+
</template>
27+
```
28+
29+
```gjs
30+
<template>
31+
<input type="submit" action="go" />
32+
</template>
33+
```
34+
35+
### Correct ✅
36+
37+
```gjs
38+
<template>
39+
<button {{on "click" this.handleClick}}>Save</button>
40+
</template>
41+
```
42+
43+
```gjs
44+
<template>
45+
<button type="button" action="doSomething">Click</button>
46+
</template>
47+
```
48+
49+
```gjs
50+
<template>
51+
<form {{on "submit" this.handleSubmit}}>
52+
<button type="submit">Submit</button>
53+
</form>
54+
</template>
55+
```
56+
57+
## Related Rules
58+
59+
- [template-no-action-modifiers](./template-no-action-modifiers.md)
60+
61+
## References
62+
63+
- [eslint-plugin-ember template-no-invalid-interactive](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/template-no-invalid-interactive.md)
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
function isInsideForm(node) {
2+
let current = node.parent;
3+
while (current) {
4+
if (current.type === 'GlimmerElementNode' && current.tag === 'form') {
5+
return true;
6+
}
7+
current = current.parent;
8+
}
9+
return false;
10+
}
11+
12+
function isSubmitButton(node) {
13+
for (const attr of node.attributes || []) {
14+
if (
15+
attr.name === 'type' &&
16+
attr.value?.type === 'GlimmerTextNode' &&
17+
attr.value.chars !== 'submit'
18+
) {
19+
return false;
20+
}
21+
}
22+
return true;
23+
}
24+
25+
function hasClickHandlingModifier(node) {
26+
for (const mod of node.modifiers || []) {
27+
if (mod.path?.original === 'action') {
28+
// {{action ...}} defaults to click event
29+
const onPair = mod.hash?.pairs?.find((p) => p.key === 'on');
30+
if (!onPair) {
31+
return true;
32+
}
33+
const eventValue = onPair.value?.value ?? onPair.value?.chars;
34+
if (eventValue === 'click') {
35+
return true;
36+
}
37+
}
38+
if (mod.path?.original === 'on') {
39+
// {{on "event" handler}}
40+
if (mod.params?.length > 0 && mod.params[0].value === 'click') {
41+
return true;
42+
}
43+
}
44+
}
45+
return false;
46+
}
47+
48+
/** @type {import('eslint').Rule.RuleModule} */
49+
module.exports = {
50+
meta: {
51+
type: 'problem',
52+
docs: {
53+
description: 'disallow action attribute on submit buttons',
54+
category: 'Best Practices',
55+
url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-action-on-submit-button.md',
56+
},
57+
fixable: null,
58+
schema: [],
59+
messages: {
60+
noActionOnSubmitButton:
61+
'Do not use action attribute on submit buttons. Use on modifier instead or handle form submission.',
62+
},
63+
originallyFrom: {
64+
name: 'ember-template-lint',
65+
rule: 'lib/rules/no-action-on-submit-button.js',
66+
docs: 'docs/rule/no-action-on-submit-button.md',
67+
tests: 'test/unit/rules/no-action-on-submit-button-test.js',
68+
},
69+
},
70+
71+
create(context) {
72+
return {
73+
// eslint-disable-next-line complexity
74+
GlimmerElementNode(node) {
75+
if (node.tag !== 'button' && node.tag !== 'input') {
76+
return;
77+
}
78+
79+
let hasActionAttribute = false;
80+
let isSubmitButtonExplicit = false;
81+
let hasNonSubmitType = false;
82+
83+
for (const attr of node.attributes || []) {
84+
if (attr.type === 'GlimmerAttrNode') {
85+
if (attr.name === 'action') {
86+
hasActionAttribute = true;
87+
}
88+
if (attr.name === 'type') {
89+
const value = attr.value;
90+
if (value.type === 'GlimmerTextNode' && value.chars === 'submit') {
91+
isSubmitButtonExplicit = true;
92+
} else if (value.type === 'GlimmerTextNode' && value.chars !== 'submit') {
93+
hasNonSubmitType = true;
94+
}
95+
}
96+
}
97+
}
98+
99+
const isDefaultSubmitButton =
100+
node.tag === 'button' && !hasNonSubmitType && !isSubmitButtonExplicit;
101+
102+
// Existing: check for action HTML attribute on submit buttons
103+
if (hasActionAttribute && (isSubmitButtonExplicit || isDefaultSubmitButton)) {
104+
context.report({
105+
node,
106+
messageId: 'noActionOnSubmitButton',
107+
});
108+
return;
109+
}
110+
111+
// New: check for action/on click modifiers on submit buttons inside forms
112+
if (
113+
node.tag === 'button' &&
114+
isSubmitButton(node) &&
115+
isInsideForm(node) &&
116+
hasClickHandlingModifier(node)
117+
) {
118+
context.report({
119+
node,
120+
messageId: 'noActionOnSubmitButton',
121+
});
122+
}
123+
},
124+
};
125+
},
126+
};

0 commit comments

Comments
 (0)