Skip to content

Commit 33af5b4

Browse files
Merge pull request #2572 from NullVoxPopuli/nvp/template-lint-extract-rule-template-no-scope-outside-table-headings
Extract rule: template-no-scope-outside-table-headings
2 parents eaf6f9f + 8ae8f92 commit 33af5b4

4 files changed

Lines changed: 190 additions & 6 deletions

File tree

README.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -405,12 +405,13 @@ rules in templates can be disabled with eslint directives with mustache or html
405405

406406
### Possible Errors
407407

408-
| Name                                  | Description | 💼 | 🔧 | 💡 |
409-
| :------------------------------------------------------------------------------------------- | :---------------------------------------------------------------- | :- | :- | :- |
410-
| [template-no-extra-mut-helper-argument](docs/rules/template-no-extra-mut-helper-argument.md) | disallow passing more than one argument to the mut helper | | | |
411-
| [template-no-jsx-attributes](docs/rules/template-no-jsx-attributes.md) | disallow JSX-style camelCase attributes | | 🔧 | |
412-
| [template-no-shadowed-elements](docs/rules/template-no-shadowed-elements.md) | disallow ambiguity with block param names shadowing HTML elements | | | |
413-
| [template-no-unbalanced-curlies](docs/rules/template-no-unbalanced-curlies.md) | disallow unbalanced mustache curlies | | | |
408+
| Name                                     | Description | 💼 | 🔧 | 💡 |
409+
| :------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------- | :- | :- | :- |
410+
| [template-no-extra-mut-helper-argument](docs/rules/template-no-extra-mut-helper-argument.md) | disallow passing more than one argument to the mut helper | | | |
411+
| [template-no-jsx-attributes](docs/rules/template-no-jsx-attributes.md) | disallow JSX-style camelCase attributes | | 🔧 | |
412+
| [template-no-scope-outside-table-headings](docs/rules/template-no-scope-outside-table-headings.md) | disallow scope attribute outside th elements | | | |
413+
| [template-no-shadowed-elements](docs/rules/template-no-shadowed-elements.md) | disallow ambiguity with block param names shadowing HTML elements | | | |
414+
| [template-no-unbalanced-curlies](docs/rules/template-no-unbalanced-curlies.md) | disallow unbalanced mustache curlies | | | |
414415

415416
### Routes
416417

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# ember/template-no-scope-outside-table-headings
2+
3+
<!-- end auto-generated rule header -->
4+
5+
The scope attribute is used on `<th>` elements to clarify the relationship between a table's header cells and data cells for screen readers. Scope is set to "row" or "col" for header cells that refer to a given row or column. For header cells that reference multiple rows or columns, set the scope attribute to "rowgroup" and "colgroup" and define the range for the rows or columns.
6+
7+
This rule disallows the use of the scope attribute on HTML elements other than the `<th>` element.
8+
9+
## Examples
10+
11+
This rule **forbids** the following:
12+
13+
```hbs
14+
<a scope='col'></a>
15+
<table scope='rowgroup'></table>
16+
```
17+
18+
This rule **allows** the following:
19+
20+
```hbs
21+
<th scope='row'>A header cell</th>
22+
<CustomHeader scope={{foo}} />
23+
```
24+
25+
## References
26+
27+
- [HTML \<th\> scope Attribute](https://www.w3schools.com/tags/att_th_scope.asp)
28+
- [scope - eslint-plugin-jsx-a11y](https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/scope.md)
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
const htmlTags = require('html-tags');
2+
3+
const HTML_ELEMENTS = new Set(htmlTags);
4+
5+
/** @type {import('eslint').Rule.RuleModule} */
6+
module.exports = {
7+
meta: {
8+
type: 'problem',
9+
docs: {
10+
description: 'disallow scope attribute outside th elements',
11+
category: 'Possible Errors',
12+
url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-scope-outside-table-headings.md',
13+
templateMode: 'both',
14+
},
15+
schema: [],
16+
messages: {
17+
noScopeOutsideTableHeadings: 'Unexpected scope attribute on <{{tagName}}>. Use only on <th>.',
18+
},
19+
originallyFrom: {
20+
name: 'ember-template-lint',
21+
rule: 'lib/rules/no-scope-outside-table-headings.js',
22+
docs: 'docs/rule/no-scope-outside-table-headings.md',
23+
tests: 'test/unit/rules/no-scope-outside-table-headings-test.js',
24+
},
25+
},
26+
27+
create(context) {
28+
return {
29+
GlimmerElementNode(node) {
30+
const tagName = node.tag;
31+
32+
if (!tagName || !HTML_ELEMENTS.has(tagName)) {
33+
return;
34+
}
35+
36+
const hasScopeAttr = node.attributes.some(
37+
(attr) => attr.type === 'GlimmerAttrNode' && attr.name === 'scope'
38+
);
39+
40+
if (hasScopeAttr && tagName !== 'th') {
41+
context.report({
42+
node,
43+
messageId: 'noScopeOutsideTableHeadings',
44+
data: { tagName },
45+
});
46+
}
47+
},
48+
};
49+
},
50+
};
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
//------------------------------------------------------------------------------
2+
// Requirements
3+
//------------------------------------------------------------------------------
4+
5+
const rule = require('../../../lib/rules/template-no-scope-outside-table-headings');
6+
const RuleTester = require('eslint').RuleTester;
7+
8+
const ruleTester = new RuleTester({
9+
parser: require.resolve('ember-eslint-parser'),
10+
parserOptions: { ecmaVersion: 2022, sourceType: 'module' },
11+
});
12+
13+
ruleTester.run('template-no-scope-outside-table-headings', rule, {
14+
valid: [
15+
'<template><th scope="row">Some table heading></th></template>',
16+
`<template>
17+
<table>
18+
<th scope="col">Table header</th>
19+
<td>Some data</td>
20+
</table>
21+
</template>`,
22+
'<template><CustomComponent scope /></template>',
23+
'<template><CustomComponent scope="row" /></template>',
24+
'<template><CustomComponent scope={{foo}} /></template>',
25+
'<template>{{foo-component scope="row"}}</template>',
26+
],
27+
invalid: [
28+
{
29+
code: '<template><td scope="row"></td></template>',
30+
output: null,
31+
errors: [{ messageId: 'noScopeOutsideTableHeadings' }],
32+
},
33+
{
34+
code: '<template><td scope></td></template>',
35+
output: null,
36+
errors: [{ messageId: 'noScopeOutsideTableHeadings' }],
37+
},
38+
{
39+
code: '<template><a scope="row" /></template>',
40+
output: null,
41+
errors: [{ messageId: 'noScopeOutsideTableHeadings' }],
42+
},
43+
{
44+
code: `<template>
45+
<table>
46+
<th>Some header</th>
47+
<td scope="col">Some data</td>
48+
</table>
49+
</template>`,
50+
output: null,
51+
errors: [{ messageId: 'noScopeOutsideTableHeadings' }],
52+
},
53+
],
54+
});
55+
56+
const hbsRuleTester = new RuleTester({
57+
parser: require.resolve('ember-eslint-parser/hbs'),
58+
parserOptions: {
59+
ecmaVersion: 2022,
60+
sourceType: 'module',
61+
},
62+
});
63+
64+
hbsRuleTester.run('template-no-scope-outside-table-headings', rule, {
65+
valid: [
66+
'<th scope="row">Some table heading></th>',
67+
`
68+
<table>
69+
<th scope="col">Table header</th>
70+
<td>Some data</td>
71+
</table>
72+
`,
73+
'<CustomComponent scope />',
74+
'<CustomComponent scope="row" />',
75+
'<CustomComponent scope={{foo}} />',
76+
'{{foo-component scope="row"}}',
77+
],
78+
invalid: [
79+
{
80+
code: '<td scope="row"></td>',
81+
output: null,
82+
errors: [{ message: 'Unexpected scope attribute on <td>. Use only on <th>.' }],
83+
},
84+
{
85+
code: '<td scope></td>',
86+
output: null,
87+
errors: [{ message: 'Unexpected scope attribute on <td>. Use only on <th>.' }],
88+
},
89+
{
90+
code: '<a scope="row" />',
91+
output: null,
92+
errors: [{ message: 'Unexpected scope attribute on <a>. Use only on <th>.' }],
93+
},
94+
{
95+
code: `
96+
<table>
97+
<th>Some header</th>
98+
<td scope="col">Some data</td>
99+
</table>
100+
`,
101+
output: null,
102+
errors: [{ message: 'Unexpected scope attribute on <td>. Use only on <th>.' }],
103+
},
104+
],
105+
});

0 commit comments

Comments
 (0)