From f9b47dbaf2263279207aea440deb14196d278fab Mon Sep 17 00:00:00 2001
From: NullVoxPopuli <199018+NullVoxPopuli@users.noreply.github.com>
Date: Wed, 18 Mar 2026 18:12:14 -0400
Subject: [PATCH 1/2] Extract rule: template-require-form-method
---
README.md | 1 +
docs/rules/template-require-form-method.md | 71 ++++++
lib/rules/template-require-form-method.js | 115 ++++++++++
.../lib/rules/template-require-form-method.js | 204 ++++++++++++++++++
4 files changed, 391 insertions(+)
create mode 100644 docs/rules/template-require-form-method.md
create mode 100644 lib/rules/template-require-form-method.js
create mode 100644 tests/lib/rules/template-require-form-method.js
diff --git a/README.md b/README.md
index a59e35f549..78c9033a53 100644
--- a/README.md
+++ b/README.md
@@ -253,6 +253,7 @@ rules in templates can be disabled with eslint directives with mustache or html
| [template-no-obsolete-elements](docs/rules/template-no-obsolete-elements.md) | disallow obsolete HTML elements | | | |
| [template-no-outlet-outside-routes](docs/rules/template-no-outlet-outside-routes.md) | disallow {{outlet}} outside of route templates | | | |
| [template-no-page-title-component](docs/rules/template-no-page-title-component.md) | disallow usage of ember-page-title component | | | |
+| [template-require-form-method](docs/rules/template-require-form-method.md) | require form method attribute | | | |
| [template-require-has-block-helper](docs/rules/template-require-has-block-helper.md) | require (has-block) helper usage instead of hasBlock property | | 🔧 | |
| [template-require-iframe-src-attribute](docs/rules/template-require-iframe-src-attribute.md) | require iframe elements to have src attribute | | 🔧 | |
| [template-require-splattributes](docs/rules/template-require-splattributes.md) | require splattributes usage in component templates | | | |
diff --git a/docs/rules/template-require-form-method.md b/docs/rules/template-require-form-method.md
new file mode 100644
index 0000000000..a200040974
--- /dev/null
+++ b/docs/rules/template-require-form-method.md
@@ -0,0 +1,71 @@
+# ember/template-require-form-method
+
+
+
+Require form elements to have a method attribute.
+
+Form elements should explicitly specify the HTTP method they use. This improves code clarity and helps catch potential issues.
+
+## Examples
+
+This rule **forbids** the following:
+
+```gjs
+
+
+
+```
+
+```gjs
+
+
+
+```
+
+This rule **allows** the following:
+
+```gjs
+
+
+
+```
+
+```gjs
+
+
+
+```
+
+```gjs
+
+
+
+```
+
+```gjs
+
+
+
+```
+
+## Configuration
+
+- `allowedMethods` (default: `['POST', 'GET', 'DIALOG']`) - Array of allowed form method values
+
+```js
+// .eslintrc.js
+module.exports = {
+ rules: {
+ 'ember/template-require-form-method': [
+ 'error',
+ {
+ allowedMethods: ['POST', 'GET'],
+ },
+ ],
+ },
+};
+```
+
+## References
+
+- [HTML Spec - Form Method Attribute](https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#attr-fs-method)
diff --git a/lib/rules/template-require-form-method.js b/lib/rules/template-require-form-method.js
new file mode 100644
index 0000000000..d2879f3dd9
--- /dev/null
+++ b/lib/rules/template-require-form-method.js
@@ -0,0 +1,115 @@
+// Form `method` attribute keywords:
+// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#attr-fs-method
+const VALID_FORM_METHODS = ['POST', 'GET', 'DIALOG'];
+
+const DEFAULT_CONFIG = {
+ allowedMethods: VALID_FORM_METHODS,
+};
+
+function parseConfig(config) {
+ if (config === false || config === undefined) {
+ return false;
+ }
+
+ if (config === true) {
+ return DEFAULT_CONFIG;
+ }
+
+ if (typeof config === 'object' && Array.isArray(config.allowedMethods)) {
+ const allowedMethods = config.allowedMethods.map((m) => String(m).toUpperCase());
+
+ // Check if all methods are valid
+ const hasAllValid = allowedMethods.every((m) => VALID_FORM_METHODS.includes(m));
+
+ if (hasAllValid) {
+ return { allowedMethods };
+ }
+ }
+
+ return false;
+}
+
+function makeErrorMessage(methods) {
+ return `All \`
` elements should have `method` attribute with value of `GET`',
+ },
+ ],
+ },
+ {
+ code: '',
+ output: null,
+ options: [{ allowedMethods: ['POST'] }],
+ errors: [
+ {
+ message: 'All `` elements should have `method` attribute with value of `POST`',
+ },
+ ],
+ },
+
+ {
+ code: '',
+ output: null,
+ errors: [
+ {
+ message:
+ 'All `` elements should have `method` attribute with value of `POST,GET,DIALOG`',
+ },
+ ],
+ },
+ {
+ code: '',
+ output: null,
+ errors: [
+ {
+ message:
+ 'All `` elements should have `method` attribute with value of `POST,GET,DIALOG`',
+ },
+ ],
+ },
+ {
+ code: '',
+ output: null,
+ errors: [
+ {
+ message:
+ 'All `` elements should have `method` attribute with value of `POST,GET,DIALOG`',
+ },
+ ],
+ },
+ {
+ code: '',
+ output: null,
+ errors: [
+ {
+ message:
+ 'All `` elements should have `method` attribute with value of `POST,GET,DIALOG`',
+ },
+ ],
+ },
+ ],
+});
+
+const hbsRuleTester = new RuleTester({
+ parser: require.resolve('ember-eslint-parser/hbs'),
+ parserOptions: {
+ ecmaVersion: 2022,
+ sourceType: 'module',
+ },
+});
+
+hbsRuleTester.run('template-require-form-method', rule, {
+ valid: [
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ // Config: allowedMethods
+ {
+ code: '',
+ options: [{ allowedMethods: ['get'] }],
+ },
+ ],
+ invalid: [
+ {
+ code: '',
+ output: null,
+ errors: [
+ {
+ message:
+ 'All `` elements should have `method` attribute with value of `POST,GET,DIALOG`',
+ },
+ ],
+ },
+ {
+ code: '',
+ output: null,
+ errors: [
+ {
+ message:
+ 'All `` elements should have `method` attribute with value of `POST,GET,DIALOG`',
+ },
+ ],
+ },
+ {
+ code: '',
+ output: null,
+ errors: [
+ {
+ message:
+ 'All `` elements should have `method` attribute with value of `POST,GET,DIALOG`',
+ },
+ ],
+ },
+ {
+ code: '',
+ output: null,
+ errors: [
+ {
+ message:
+ 'All `` elements should have `method` attribute with value of `POST,GET,DIALOG`',
+ },
+ ],
+ },
+ {
+ code: '',
+ output: null,
+ errors: [
+ {
+ message:
+ 'All `` elements should have `method` attribute with value of `POST,GET,DIALOG`',
+ },
+ ],
+ },
+ // Config: allowedMethods
+ {
+ code: '',
+ output: null,
+ options: [{ allowedMethods: ['get'] }],
+ errors: [
+ {
+ message: 'All `` elements should have `method` attribute with value of `GET`',
+ },
+ ],
+ },
+ {
+ code: '',
+ output: null,
+ options: [{ allowedMethods: ['POST'] }],
+ errors: [
+ {
+ message: 'All `` elements should have `method` attribute with value of `POST`',
+ },
+ ],
+ },
+ ],
+});
From 1ebfdf1f69e8f9475e108a8de2b7f229535e2084 Mon Sep 17 00:00:00 2001
From: NullVoxPopuli <199018+NullVoxPopuli@users.noreply.github.com>
Date: Fri, 20 Mar 2026 14:24:51 -0400
Subject: [PATCH 2/2] Sync with template-lint
---
README.md | 2 +-
docs/rules/template-require-form-method.md | 70 ++---
lib/rules/template-require-form-method.js | 33 ++-
.../lib/rules/template-require-form-method.js | 268 ++++++------------
4 files changed, 139 insertions(+), 234 deletions(-)
diff --git a/README.md b/README.md
index 78c9033a53..b4c08414ab 100644
--- a/README.md
+++ b/README.md
@@ -253,7 +253,7 @@ rules in templates can be disabled with eslint directives with mustache or html
| [template-no-obsolete-elements](docs/rules/template-no-obsolete-elements.md) | disallow obsolete HTML elements | | | |
| [template-no-outlet-outside-routes](docs/rules/template-no-outlet-outside-routes.md) | disallow {{outlet}} outside of route templates | | | |
| [template-no-page-title-component](docs/rules/template-no-page-title-component.md) | disallow usage of ember-page-title component | | | |
-| [template-require-form-method](docs/rules/template-require-form-method.md) | require form method attribute | | | |
+| [template-require-form-method](docs/rules/template-require-form-method.md) | require form method attribute | | 🔧 | |
| [template-require-has-block-helper](docs/rules/template-require-has-block-helper.md) | require (has-block) helper usage instead of hasBlock property | | 🔧 | |
| [template-require-iframe-src-attribute](docs/rules/template-require-iframe-src-attribute.md) | require iframe elements to have src attribute | | 🔧 | |
| [template-require-splattributes](docs/rules/template-require-splattributes.md) | require splattributes usage in component templates | | | |
diff --git a/docs/rules/template-require-form-method.md b/docs/rules/template-require-form-method.md
index a200040974..7eb3d987aa 100644
--- a/docs/rules/template-require-form-method.md
+++ b/docs/rules/template-require-form-method.md
@@ -1,71 +1,55 @@
# ember/template-require-form-method
+🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).
+
-Require form elements to have a method attribute.
+This rule requires all `` elements to have `method` attribute with `POST`, `GET` or `DIALOG` value.
-Form elements should explicitly specify the HTTP method they use. This improves code clarity and helps catch potential issues.
+By default `form` elements without `method` attribute are submitted as `GET` requests.
+In usual applications `submit` event listeners are attached to `form` elements and `event.preventDefault()` is called to avoid form submission.
-## Examples
+However in case of failure to prevent default action, form submission as `GET` request can leak sensitive end-user information.
-This rule **forbids** the following:
+Example uses of `GET` requests:
-```gjs
-
-
-
-```
+- non-secure data
+- bookmarking the submission result
+- data search query strings
-```gjs
-
-
-
-```
+**Caution** - this rules does not check for `formmethod` attribute on `form` elements themselves.
-This rule **allows** the following:
+## Examples
-```gjs
-
-
-
-```
+This rule **forbids** the following:
```gjs
-
+ Hello world!
+
+ Hello world!
```
-```gjs
-
-
-
-```
+This rule **allows** the following:
```gjs
-
+ Hello world!
+ Hello world!
+ Hello world!
```
## Configuration
-- `allowedMethods` (default: `['POST', 'GET', 'DIALOG']`) - Array of allowed form method values
-
-```js
-// .eslintrc.js
-module.exports = {
- rules: {
- 'ember/template-require-form-method': [
- 'error',
- {
- allowedMethods: ['POST', 'GET'],
- },
- ],
- },
-};
-```
+The following values are valid configuration:
+
+- boolean - `true` to enable / `false` to disable
+- object -- An object with the following keys:
+ - `allowedMethods` -- An array of allowed form `method` attribute values, default: `['POST', 'GET', 'DIALOG']`
## References
-- [HTML Spec - Form Method Attribute](https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#attr-fs-method)
+- [MDN - form method attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form#attr-method)
+- [HTML spec - form method attribute](https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#attr-fs-method)
diff --git a/lib/rules/template-require-form-method.js b/lib/rules/template-require-form-method.js
index d2879f3dd9..66be830ca5 100644
--- a/lib/rules/template-require-form-method.js
+++ b/lib/rules/template-require-form-method.js
@@ -33,6 +33,10 @@ function makeErrorMessage(methods) {
return `All \`\` elements should have \`method\` attribute with value of \`${methods.join(',')}\``;
}
+function getFixedMethod(config) {
+ return config.allowedMethods[0];
+}
+
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
meta: {
@@ -40,11 +44,10 @@ module.exports = {
docs: {
description: 'require form method attribute',
category: 'Best Practices',
- recommended: false,
url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-require-form-method.md',
templateMode: 'both',
},
- fixable: null,
+ fixable: 'code',
schema: [
{
oneOf: [
@@ -63,7 +66,9 @@ module.exports = {
],
},
],
- messages: {},
+ messages: {
+ invalidMethod: '{{message}}',
+ },
originallyFrom: {
name: 'ember-template-lint',
rule: 'lib/rules/require-form-method.js',
@@ -92,7 +97,16 @@ module.exports = {
if (!methodAttribute) {
context.report({
node,
- message: makeErrorMessage(config.allowedMethods),
+ messageId: 'invalidMethod',
+ data: {
+ message: makeErrorMessage(config.allowedMethods),
+ },
+ fix(fixer) {
+ return fixer.insertTextAfterRange(
+ [node.parts.at(-1).range[1], node.parts.at(-1).range[1]],
+ ` method="${getFixedMethod(config)}"`
+ );
+ },
});
return;
}
@@ -104,7 +118,16 @@ module.exports = {
if (!config.allowedMethods.includes(methodValue)) {
context.report({
node,
- message: makeErrorMessage(config.allowedMethods),
+ messageId: 'invalidMethod',
+ data: {
+ message: makeErrorMessage(config.allowedMethods),
+ },
+ fix(fixer) {
+ return fixer.replaceTextRange(
+ methodAttribute.value.range,
+ `"${getFixedMethod(config)}"`
+ );
+ },
});
}
}
diff --git a/tests/lib/rules/template-require-form-method.js b/tests/lib/rules/template-require-form-method.js
index 789800b801..5e3573b565 100644
--- a/tests/lib/rules/template-require-form-method.js
+++ b/tests/lib/rules/template-require-form-method.js
@@ -1,104 +1,91 @@
const rule = require('../../../lib/rules/template-require-form-method');
const RuleTester = require('eslint').RuleTester;
-const ruleTester = new RuleTester({
+const DEFAULT_ERROR =
+ 'All `` elements should have `method` attribute with value of `POST,GET,DIALOG`';
+
+const validHbs = [
+ {
+ options: [{ allowedMethods: ['get'] }],
+ code: '',
+ },
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+];
+
+const invalidHbs = [
+ {
+ options: [{ allowedMethods: ['get'] }],
+ code: '',
+ output: '',
+ errors: [
+ { message: 'All `` elements should have `method` attribute with value of `GET`' },
+ ],
+ },
+ {
+ options: [{ allowedMethods: ['POST'] }],
+ code: '',
+ output: '',
+ errors: [
+ { message: 'All `` elements should have `method` attribute with value of `POST`' },
+ ],
+ },
+ {
+ code: '',
+ output: '',
+ errors: [{ message: DEFAULT_ERROR }],
+ },
+ {
+ code: '',
+ output: '',
+ errors: [{ message: DEFAULT_ERROR }],
+ },
+ {
+ code: '',
+ output: '',
+ errors: [{ message: DEFAULT_ERROR }],
+ },
+ {
+ code: '',
+ output: '',
+ errors: [{ message: DEFAULT_ERROR }],
+ },
+ {
+ code: '',
+ output: '',
+ errors: [{ message: DEFAULT_ERROR }],
+ },
+];
+
+function wrapTemplate(entry) {
+ if (typeof entry === 'string') {
+ return `${entry}`;
+ }
+
+ return {
+ ...entry,
+ code: `${entry.code}`,
+ output: entry.output ? `${entry.output}` : entry.output,
+ };
+}
+
+const gjsRuleTester = new RuleTester({
parser: require.resolve('ember-eslint-parser'),
parserOptions: { ecmaVersion: 2022, sourceType: 'module' },
});
-ruleTester.run('template-require-form-method', rule, {
- valid: [
- '',
- '',
- '',
- '',
- '',
- '',
- '',
- '',
- '',
- {
- code: '',
- output: null,
- options: [{ allowedMethods: ['get'] }],
- },
-
- '',
- '',
- ],
- invalid: [
- {
- code: '',
- output: null,
- errors: [
- {
- message:
- 'All `` elements should have `method` attribute with value of `POST,GET,DIALOG`',
- },
- ],
- },
- {
- code: '',
- output: null,
- options: [{ allowedMethods: ['GET'] }],
- errors: [
- {
- message: 'All `` elements should have `method` attribute with value of `GET`',
- },
- ],
- },
- {
- code: '',
- output: null,
- options: [{ allowedMethods: ['POST'] }],
- errors: [
- {
- message: 'All `` elements should have `method` attribute with value of `POST`',
- },
- ],
- },
-
- {
- code: '',
- output: null,
- errors: [
- {
- message:
- 'All `` elements should have `method` attribute with value of `POST,GET,DIALOG`',
- },
- ],
- },
- {
- code: '',
- output: null,
- errors: [
- {
- message:
- 'All `` elements should have `method` attribute with value of `POST,GET,DIALOG`',
- },
- ],
- },
- {
- code: '',
- output: null,
- errors: [
- {
- message:
- 'All `` elements should have `method` attribute with value of `POST,GET,DIALOG`',
- },
- ],
- },
- {
- code: '',
- output: null,
- errors: [
- {
- message:
- 'All `` elements should have `method` attribute with value of `POST,GET,DIALOG`',
- },
- ],
- },
- ],
+gjsRuleTester.run('template-require-form-method', rule, {
+ valid: validHbs.map(wrapTemplate),
+ invalid: invalidHbs.map(wrapTemplate),
});
const hbsRuleTester = new RuleTester({
@@ -110,95 +97,6 @@ const hbsRuleTester = new RuleTester({
});
hbsRuleTester.run('template-require-form-method', rule, {
- valid: [
- '',
- '',
- '',
- '',
- '',
- '',
- '',
- '',
- '',
- '',
- '',
- // Config: allowedMethods
- {
- code: '',
- options: [{ allowedMethods: ['get'] }],
- },
- ],
- invalid: [
- {
- code: '',
- output: null,
- errors: [
- {
- message:
- 'All `` elements should have `method` attribute with value of `POST,GET,DIALOG`',
- },
- ],
- },
- {
- code: '',
- output: null,
- errors: [
- {
- message:
- 'All `` elements should have `method` attribute with value of `POST,GET,DIALOG`',
- },
- ],
- },
- {
- code: '',
- output: null,
- errors: [
- {
- message:
- 'All `` elements should have `method` attribute with value of `POST,GET,DIALOG`',
- },
- ],
- },
- {
- code: '',
- output: null,
- errors: [
- {
- message:
- 'All `` elements should have `method` attribute with value of `POST,GET,DIALOG`',
- },
- ],
- },
- {
- code: '',
- output: null,
- errors: [
- {
- message:
- 'All `` elements should have `method` attribute with value of `POST,GET,DIALOG`',
- },
- ],
- },
- // Config: allowedMethods
- {
- code: '',
- output: null,
- options: [{ allowedMethods: ['get'] }],
- errors: [
- {
- message: 'All `` elements should have `method` attribute with value of `GET`',
- },
- ],
- },
- {
- code: '',
- output: null,
- options: [{ allowedMethods: ['POST'] }],
- errors: [
- {
- message: 'All `` elements should have `method` attribute with value of `POST`',
- },
- ],
- },
- ],
+ valid: validHbs,
+ invalid: invalidHbs,
});