Skip to content

Commit 4d35aff

Browse files
authored
Merge branch 'master' into fix/nested-interactive-message-context
2 parents 28bfda5 + d2d8c7f commit 4d35aff

17 files changed

Lines changed: 558 additions & 61 deletions

.release-plan.json

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,22 @@
11
{
22
"solution": {
33
"eslint-plugin-ember": {
4-
"impact": "patch",
5-
"oldVersion": "13.1.2",
6-
"newVersion": "13.1.3",
4+
"impact": "minor",
5+
"oldVersion": "13.1.4",
6+
"newVersion": "13.2.0",
77
"tagName": "latest",
88
"constraints": [
99
{
10-
"impact": "patch",
11-
"reason": "Appears in changelog section :bug: Bug Fix"
10+
"impact": "minor",
11+
"reason": "Appears in changelog section :rocket: Enhancement"
1212
},
1313
{
1414
"impact": "patch",
15-
"reason": "Appears in changelog section :house: Internal"
15+
"reason": "Appears in changelog section :bug: Bug Fix"
1616
}
1717
],
1818
"pkgJSONPath": "./package.json"
1919
}
2020
},
21-
"description": "## Release (2026-04-25)\n\n* eslint-plugin-ember 13.1.3 (patch)\n\n#### :bug: Bug Fix\n* `eslint-plugin-ember`\n * [#2730](https://github.com/ember-cli/eslint-plugin-ember/pull/2730) BUGFIX: template-require-valid-alt-text — reject empty-string aria-label/labelledby/alt on <input type=image>, <object>, <area> ([@johanrd](https://github.com/johanrd))\n * [#2729](https://github.com/ember-cli/eslint-plugin-ember/pull/2729) BUGFIX: template-no-invalid-role — support DPUB/Graphics-ARIA and role-fallback lists ([@johanrd](https://github.com/johanrd))\n * [#2726](https://github.com/ember-cli/eslint-plugin-ember/pull/2726) BUGFIX: template-no-unsupported-role-attributes — honor aria-query attribute constraints ([@johanrd](https://github.com/johanrd))\n * [#2727](https://github.com/ember-cli/eslint-plugin-ember/pull/2727) BUGFIX: template-no-redundant-role — case-insensitive match + <select>→combobox ([@johanrd](https://github.com/johanrd))\n\n#### :house: Internal\n* `eslint-plugin-ember`\n * [#2746](https://github.com/ember-cli/eslint-plugin-ember/pull/2746) refactor: extract isNativeElement util (fix component-vs-HTML-tag misclassification) ([@johanrd](https://github.com/johanrd))\n\n#### Committers: 1\n- [@johanrd](https://github.com/johanrd)\n"
21+
"description": "## Release (2026-04-28)\n\n* eslint-plugin-ember 13.2.0 (minor)\n\n#### :rocket: Enhancement\n* `eslint-plugin-ember`\n * [#2763](https://github.com/ember-cli/eslint-plugin-ember/pull/2763) feat: add template-require-input-type ([@johanrd](https://github.com/johanrd))\n\n#### :bug: Bug Fix\n* `eslint-plugin-ember`\n * [#2766](https://github.com/ember-cli/eslint-plugin-ember/pull/2766) Update parser ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n\n#### Committers: 2\n- [@NullVoxPopuli](https://github.com/NullVoxPopuli)\n- [@johanrd](https://github.com/johanrd)\n"
2222
}

CHANGELOG.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,38 @@
11
# Changelog
22

3+
## Release (2026-04-28)
4+
5+
* eslint-plugin-ember 13.2.0 (minor)
6+
7+
#### :rocket: Enhancement
8+
* `eslint-plugin-ember`
9+
* [#2763](https://github.com/ember-cli/eslint-plugin-ember/pull/2763) feat: add template-require-input-type ([@johanrd](https://github.com/johanrd))
10+
11+
#### :bug: Bug Fix
12+
* `eslint-plugin-ember`
13+
* [#2766](https://github.com/ember-cli/eslint-plugin-ember/pull/2766) Update parser ([@NullVoxPopuli](https://github.com/NullVoxPopuli))
14+
15+
#### Committers: 2
16+
- [@NullVoxPopuli](https://github.com/NullVoxPopuli)
17+
- [@johanrd](https://github.com/johanrd)
18+
19+
## Release (2026-04-27)
20+
21+
* eslint-plugin-ember 13.1.4 (patch)
22+
23+
#### :bug: Bug Fix
24+
* `eslint-plugin-ember`
25+
* [#2752](https://github.com/ember-cli/eslint-plugin-ember/pull/2752) Update ember-eslint-parser to 0.11.2 ([@NullVoxPopuli](https://github.com/NullVoxPopuli))
26+
* [#2728](https://github.com/ember-cli/eslint-plugin-ember/pull/2728) BUGFIX: template-require-mandatory-role-attributes — lowercase role + split whitespace role lists ([@johanrd](https://github.com/johanrd))
27+
28+
#### :house: Internal
29+
* `eslint-plugin-ember`
30+
* [#2748](https://github.com/ember-cli/eslint-plugin-ember/pull/2748) refactor: extract `html-interactive-content` util (HTML §3.2.5.2.7 authority) ([@johanrd](https://github.com/johanrd))
31+
32+
#### Committers: 2
33+
- [@NullVoxPopuli](https://github.com/NullVoxPopuli)
34+
- [@johanrd](https://github.com/johanrd)
35+
336
## Release (2026-04-25)
437

538
* eslint-plugin-ember 13.1.3 (patch)

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,7 @@ To disable a rule for an entire `.gjs`/`.gts` file, use a regular ESLint file-le
358358
| [template-require-form-method](docs/rules/template-require-form-method.md) | require form method attribute | | 🔧 | |
359359
| [template-require-has-block-helper](docs/rules/template-require-has-block-helper.md) | require (has-block) helper usage instead of hasBlock property | 📋 | 🔧 | |
360360
| [template-require-iframe-src-attribute](docs/rules/template-require-iframe-src-attribute.md) | require iframe elements to have src attribute | | 🔧 | |
361+
| [template-require-input-type](docs/rules/template-require-input-type.md) | require input elements to have a valid type attribute | | 🔧 | |
361362
| [template-require-splattributes](docs/rules/template-require-splattributes.md) | require splattributes usage in component templates | | | |
362363
| [template-require-strict-mode](docs/rules/template-require-strict-mode.md) | require templates to be in strict mode | | | |
363364
| [template-require-valid-named-block-naming-format](docs/rules/template-require-valid-named-block-naming-format.md) | require valid named block naming format | 📋 | 🔧 | |
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# ember/template-require-input-type
2+
3+
🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).
4+
5+
<!-- end auto-generated rule header -->
6+
7+
This rule rejects `<input type="...">` values that are not one of the input
8+
types defined by the HTML spec, and (optionally) requires every `<input>` to
9+
declare a `type` attribute.
10+
11+
An invalid value like `<input type="foo">` silently falls back to the Text
12+
state — the browser reports no error, but the author's intent (validation,
13+
inputmode hint, platform keyboard) is lost. That's a genuine silent-failure
14+
class, which this rule always flags and auto-fixes to `type="text"`.
15+
16+
A missing `type` attribute (`<input />`) is _spec-compliant_ — the
17+
missing-value default is the Text state — so flagging it is a style /
18+
consistency choice, not a correctness one. Opt in with `requireExplicit: true`
19+
if your team wants parity with `template-require-button-type`.
20+
21+
## Examples
22+
23+
This rule **forbids** the following (always):
24+
25+
```hbs
26+
<input type='' />
27+
<input type='foo' />
28+
<input type='TEXTY' />
29+
```
30+
31+
With `requireExplicit: true` the rule **also forbids**:
32+
33+
```hbs
34+
<input />
35+
<input name='email' />
36+
```
37+
38+
This rule **allows** the following:
39+
40+
```hbs
41+
<input type='text' />
42+
<input type='email' />
43+
<input type='checkbox' />
44+
<input type={{this.inputType}} />
45+
```
46+
47+
Dynamic values such as `type={{this.inputType}}` are not flagged at lint time.
48+
49+
## Configuration
50+
51+
- `requireExplicit` (`boolean`, default `false`): when true, also flag
52+
`<input>` elements that have no `type` attribute. Auto-fix inserts
53+
`type="text"`.
54+
55+
```js
56+
module.exports = {
57+
rules: {
58+
'ember/template-require-input-type': ['error', { requireExplicit: true }],
59+
},
60+
};
61+
```
62+
63+
## References
64+
65+
- [HTML spec — the input element](https://html.spec.whatwg.org/multipage/input.html#the-input-element)
66+
- Adapted from [`html-validate`'s `no-implicit-input-type`](https://html-validate.org/rules/no-implicit-input-type.html) (MIT).

lib/rules/template-no-duplicate-landmark-elements.js

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
// This rule inspects aria-label / aria-labelledby before classifying a node
2+
// as a landmark (see getLabel + the dynamic-label skip in GlimmerElementNode),
3+
// so it can safely include `region` — it won't flag an unnamed <section> as
4+
// a landmark duplicate. Use the full spec-listed 8-role set.
5+
const { ALL_LANDMARK_ROLES: LANDMARK_ROLES } = require('../utils/landmark-roles');
6+
17
/** @type {import('eslint').Rule.RuleModule} */
28
module.exports = {
39
meta: {
@@ -33,18 +39,6 @@ module.exports = {
3339
form: 'form',
3440
};
3541

36-
// Landmark ARIA roles
37-
const LANDMARK_ROLES = new Set([
38-
'banner',
39-
'complementary',
40-
'contentinfo',
41-
'form',
42-
'main',
43-
'navigation',
44-
'region',
45-
'search',
46-
]);
47-
4842
// Sectioning elements that strip banner/contentinfo roles from header/footer
4943
const SECTIONING_ELEMENTS = new Set(['article', 'aside', 'main', 'nav', 'section']);
5044
const elementStack = [];

lib/rules/template-no-nested-landmark.js

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,4 @@
1-
const LANDMARK_ROLES = new Set([
2-
'banner',
3-
'complementary',
4-
'contentinfo',
5-
'form',
6-
'main',
7-
'navigation',
8-
'search',
9-
]);
1+
const { LANDMARK_ROLES } = require('../utils/landmark-roles');
102

113
const LANDMARK_ELEMENTS = new Set(['header', 'aside', 'footer', 'form', 'main', 'nav']);
124

lib/rules/template-no-redundant-role.js

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
const { roles } = require('aria-query');
2+
const { LANDMARK_ROLES } = require('../utils/landmark-roles');
23

34
const DEFAULT_CONFIG = {
45
checkAllHTMLElements: true,
@@ -19,17 +20,6 @@ function createErrorMessageAnyElement(element, role) {
1920
return `Use of redundant or invalid role: ${role} on <${element}> detected.`;
2021
}
2122

22-
// https://www.w3.org/TR/html-aria/#docconformance
23-
const LANDMARK_ROLES = new Set([
24-
'banner',
25-
'main',
26-
'complementary',
27-
'search',
28-
'form',
29-
'navigation',
30-
'contentinfo',
31-
]);
32-
3323
const ALLOWED_ELEMENT_ROLES = [
3424
{ name: 'nav', role: 'navigation' },
3525
{ name: 'form', role: 'search' },
@@ -114,7 +104,12 @@ const ROLE_TO_ELEMENTS = {
114104
navigation: ['nav'],
115105
option: ['option'],
116106
radio: ['input'],
117-
region: ['section'],
107+
// `region` is intentionally NOT mapped to `<section>` here. `<section>`
108+
// only gets the `region` landmark role when it has an accessible name
109+
// (aria-label / aria-labelledby / title); without one it has role
110+
// `generic`. A static linter cannot verify accessible-name presence.
111+
// Spec: https://www.w3.org/WAI/ARIA/apg/patterns/landmarks/examples/HTML5.html
112+
// See #2694 where the same reasoning was applied to template-no-nested-landmark.
118113
row: ['tr'],
119114
rowgroup: ['tbody', 'tfoot', 'thead'],
120115
rowheader: ['th'],
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
'use strict';
2+
3+
// See html-validate (https://html-validate.org) for the peer rule concept.
4+
5+
const { isNativeElement } = require('../utils/is-native-element');
6+
7+
const VALID_TYPES = new Set([
8+
'button',
9+
'checkbox',
10+
'color',
11+
'date',
12+
'datetime-local',
13+
'email',
14+
'file',
15+
'hidden',
16+
'image',
17+
'month',
18+
'number',
19+
'password',
20+
'radio',
21+
'range',
22+
'reset',
23+
'search',
24+
'submit',
25+
'tel',
26+
'text',
27+
'time',
28+
'url',
29+
'week',
30+
]);
31+
32+
/** @type {import('eslint').Rule.RuleModule} */
33+
module.exports = {
34+
meta: {
35+
type: 'problem',
36+
docs: {
37+
description: 'require input elements to have a valid type attribute',
38+
category: 'Best Practices',
39+
url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-require-input-type.md',
40+
templateMode: 'both',
41+
},
42+
fixable: 'code',
43+
schema: [
44+
{
45+
type: 'object',
46+
properties: {
47+
requireExplicit: {
48+
type: 'boolean',
49+
},
50+
},
51+
additionalProperties: false,
52+
},
53+
],
54+
messages: {
55+
missing: 'All `<input>` elements should have a `type` attribute',
56+
invalid: '`<input type="{{value}}">` is not a valid input type',
57+
},
58+
},
59+
60+
create(context) {
61+
// Flagging a missing `type` is a style/consistency check, not a correctness
62+
// one: `<input>` without `type` is spec-compliant (defaults to the Text
63+
// state). Opt-in so teams that want parity with template-require-button-
64+
// type can enable it without imposing it on others.
65+
const requireExplicit = Boolean(context.options[0]?.requireExplicit);
66+
const sourceCode = context.sourceCode || context.getSourceCode();
67+
68+
return {
69+
GlimmerElementNode(node) {
70+
if (node.tag !== 'input') {
71+
return;
72+
}
73+
// In strict GJS, a lowercase local binding can shadow the native
74+
// `<input>` element. `isNativeElement` consults html/svg/mathml tag
75+
// lists and checks bindings in the scope chain to filter out
76+
// scope-shadowed cases.
77+
if (!isNativeElement(node, sourceCode)) {
78+
return;
79+
}
80+
81+
const typeAttr = node.attributes?.find((attr) => attr.name === 'type');
82+
83+
if (!typeAttr) {
84+
if (!requireExplicit) {
85+
return;
86+
}
87+
context.report({
88+
node,
89+
messageId: 'missing',
90+
fix(fixer) {
91+
// Insert right after `<input` so the new attribute is the first
92+
// one — avoids the fragile "find end of open tag" regex that can
93+
// mis-place the attribute past the `/` in self-closing syntax.
94+
const insertPos = node.range[0] + '<input'.length;
95+
return fixer.insertTextBeforeRange([insertPos, insertPos], ' type="text"');
96+
},
97+
});
98+
return;
99+
}
100+
101+
const value = typeAttr.value;
102+
103+
// Valueless attribute form (`<input type />`) — per HTML spec, a
104+
// present-but-empty type attribute resolves to the missing-value
105+
// default ("Text state"). That's the same runtime result as
106+
// `type=""`, which we already flag. Treat them consistently:
107+
// flag as invalid('') and autofix to `type="text"`.
108+
if (!value) {
109+
context.report({
110+
node: typeAttr,
111+
messageId: 'invalid',
112+
data: { value: '' },
113+
fix(fixer) {
114+
return fixer.replaceText(typeAttr, 'type="text"');
115+
},
116+
});
117+
return;
118+
}
119+
120+
if (value.type === 'GlimmerTextNode') {
121+
const typeValue = value.chars.toLowerCase();
122+
if (typeValue === '') {
123+
context.report({
124+
node: typeAttr,
125+
messageId: 'invalid',
126+
data: { value: '' },
127+
fix(fixer) {
128+
return fixer.replaceText(typeAttr, 'type="text"');
129+
},
130+
});
131+
} else if (!VALID_TYPES.has(typeValue)) {
132+
context.report({
133+
node: typeAttr,
134+
messageId: 'invalid',
135+
data: { value: value.chars },
136+
fix(fixer) {
137+
return fixer.replaceText(typeAttr, 'type="text"');
138+
},
139+
});
140+
}
141+
}
142+
},
143+
};
144+
},
145+
};

lib/utils/is-native-element.js

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,19 +21,17 @@ const ELEMENT_TAGS = new Set([...htmlTags, ...svgTags, ...mathmlTagNames]);
2121
* MathML spec registries, reached via the `html-tags` / `svg-tags` /
2222
* `mathml-tag-names` packages). It is NOT the same as:
2323
*
24-
* - "native accessibility" / "widget-ness" — see `interactive-roles.js`
25-
* (aria-query widget taxonomy; an ARIA-tree-semantics question)
26-
* - "native interactive content" / "focus behavior" — see
27-
* `html-interactive-content.js` (HTML §3.2.5.2.7; an HTML-content-model
28-
* question about which tags can be nested inside what)
24+
* - "native accessibility" / "widget-ness" — an ARIA-tree-semantics
25+
* question (for example, whether something maps to a widget role)
26+
* - "native interactive content" / "focus behavior" — an HTML content-model
27+
* question about which elements are considered interactive in the spec
2928
* - "natively focusable" / sequential-focus — see HTML §6.6.3
3029
*
3130
* This util answers only: "is this tag a first-class built-in element of one
3231
* of the three markup-language standards, rather than a component invocation
33-
* or a shadowed local binding?" Callers compose it with the other utils
34-
* above when they need a more specific question (see e.g. `template-no-
35-
* noninteractive-tabindex`, which consults both this and
36-
* `html-interactive-content`).
32+
* or a shadowed local binding?" Callers should combine it with whatever
33+
* accessibility, interactivity, or focusability checks they need for more
34+
* specific questions.
3735
*
3836
* Returns false for:
3937
* - components (PascalCase, dotted, @-prefixed, this.-prefixed, ::-namespaced —

0 commit comments

Comments
 (0)