Skip to content

Commit f9b47db

Browse files
committed
Extract rule: template-require-form-method
1 parent 7500a98 commit f9b47db

4 files changed

Lines changed: 391 additions & 0 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,7 @@ rules in templates can be disabled with eslint directives with mustache or html
253253
| [template-no-obsolete-elements](docs/rules/template-no-obsolete-elements.md) | disallow obsolete HTML elements | | | |
254254
| [template-no-outlet-outside-routes](docs/rules/template-no-outlet-outside-routes.md) | disallow {{outlet}} outside of route templates | | | |
255255
| [template-no-page-title-component](docs/rules/template-no-page-title-component.md) | disallow usage of ember-page-title component | | | |
256+
| [template-require-form-method](docs/rules/template-require-form-method.md) | require form method attribute | | | |
256257
| [template-require-has-block-helper](docs/rules/template-require-has-block-helper.md) | require (has-block) helper usage instead of hasBlock property | | 🔧 | |
257258
| [template-require-iframe-src-attribute](docs/rules/template-require-iframe-src-attribute.md) | require iframe elements to have src attribute | | 🔧 | |
258259
| [template-require-splattributes](docs/rules/template-require-splattributes.md) | require splattributes usage in component templates | | | |
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# ember/template-require-form-method
2+
3+
<!-- end auto-generated rule header -->
4+
5+
Require form elements to have a method attribute.
6+
7+
Form elements should explicitly specify the HTTP method they use. This improves code clarity and helps catch potential issues.
8+
9+
## Examples
10+
11+
This rule **forbids** the following:
12+
13+
```gjs
14+
<template>
15+
<form></form>
16+
</template>
17+
```
18+
19+
```gjs
20+
<template>
21+
<form method='DELETE'></form>
22+
</template>
23+
```
24+
25+
This rule **allows** the following:
26+
27+
```gjs
28+
<template>
29+
<form method='POST'></form>
30+
</template>
31+
```
32+
33+
```gjs
34+
<template>
35+
<form method='GET'></form>
36+
</template>
37+
```
38+
39+
```gjs
40+
<template>
41+
<form method='DIALOG'></form>
42+
</template>
43+
```
44+
45+
```gjs
46+
<template>
47+
<form method='{{dynamicMethod}}'></form>
48+
</template>
49+
```
50+
51+
## Configuration
52+
53+
- `allowedMethods` (default: `['POST', 'GET', 'DIALOG']`) - Array of allowed form method values
54+
55+
```js
56+
// .eslintrc.js
57+
module.exports = {
58+
rules: {
59+
'ember/template-require-form-method': [
60+
'error',
61+
{
62+
allowedMethods: ['POST', 'GET'],
63+
},
64+
],
65+
},
66+
};
67+
```
68+
69+
## References
70+
71+
- [HTML Spec - Form Method Attribute](https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#attr-fs-method)
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
// Form `method` attribute keywords:
2+
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#attr-fs-method
3+
const VALID_FORM_METHODS = ['POST', 'GET', 'DIALOG'];
4+
5+
const DEFAULT_CONFIG = {
6+
allowedMethods: VALID_FORM_METHODS,
7+
};
8+
9+
function parseConfig(config) {
10+
if (config === false || config === undefined) {
11+
return false;
12+
}
13+
14+
if (config === true) {
15+
return DEFAULT_CONFIG;
16+
}
17+
18+
if (typeof config === 'object' && Array.isArray(config.allowedMethods)) {
19+
const allowedMethods = config.allowedMethods.map((m) => String(m).toUpperCase());
20+
21+
// Check if all methods are valid
22+
const hasAllValid = allowedMethods.every((m) => VALID_FORM_METHODS.includes(m));
23+
24+
if (hasAllValid) {
25+
return { allowedMethods };
26+
}
27+
}
28+
29+
return false;
30+
}
31+
32+
function makeErrorMessage(methods) {
33+
return `All \`<form>\` elements should have \`method\` attribute with value of \`${methods.join(',')}\``;
34+
}
35+
36+
/** @type {import('eslint').Rule.RuleModule} */
37+
module.exports = {
38+
meta: {
39+
type: 'suggestion',
40+
docs: {
41+
description: 'require form method attribute',
42+
category: 'Best Practices',
43+
recommended: false,
44+
url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-require-form-method.md',
45+
templateMode: 'both',
46+
},
47+
fixable: null,
48+
schema: [
49+
{
50+
oneOf: [
51+
{
52+
type: 'object',
53+
properties: {
54+
allowedMethods: {
55+
type: 'array',
56+
items: {
57+
type: 'string',
58+
},
59+
},
60+
},
61+
additionalProperties: false,
62+
},
63+
],
64+
},
65+
],
66+
messages: {},
67+
originallyFrom: {
68+
name: 'ember-template-lint',
69+
rule: 'lib/rules/require-form-method.js',
70+
docs: 'docs/rule/require-form-method.md',
71+
tests: 'test/unit/rules/require-form-method-test.js',
72+
},
73+
},
74+
75+
create(context) {
76+
// If no options provided, use defaults
77+
let config = context.options[0];
78+
config = config ? parseConfig(config) : DEFAULT_CONFIG;
79+
80+
if (config === false) {
81+
return {};
82+
}
83+
84+
return {
85+
GlimmerElementNode(node) {
86+
if (node.tag !== 'form') {
87+
return;
88+
}
89+
90+
const methodAttribute = node.attributes.find((attr) => attr.name === 'method');
91+
92+
if (!methodAttribute) {
93+
context.report({
94+
node,
95+
message: makeErrorMessage(config.allowedMethods),
96+
});
97+
return;
98+
}
99+
100+
// Check if it's a text value
101+
if (methodAttribute.value && methodAttribute.value.type === 'GlimmerTextNode') {
102+
const methodValue = methodAttribute.value.chars.toUpperCase();
103+
104+
if (!config.allowedMethods.includes(methodValue)) {
105+
context.report({
106+
node,
107+
message: makeErrorMessage(config.allowedMethods),
108+
});
109+
}
110+
}
111+
// If it's a dynamic value (like {{foo}}), don't report
112+
},
113+
};
114+
},
115+
};

0 commit comments

Comments
 (0)