Skip to content

Commit 6e89179

Browse files
committed
Extract rule: template-no-scope-outside-table-headings
1 parent eaf6f9f commit 6e89179

4 files changed

Lines changed: 235 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: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# ember/template-no-scope-outside-table-headings
2+
3+
<!-- end auto-generated rule header -->
4+
5+
Disallow the `scope` attribute on elements other than `<th>`.
6+
7+
## Rule Details
8+
9+
The `scope` attribute is only valid on `<th>` elements within tables. Using it on other elements (including `<td>`) is invalid HTML and should be avoided.
10+
11+
## Examples
12+
13+
Examples of **incorrect** code for this rule:
14+
15+
```gjs
16+
<template>
17+
<div scope="col">Not a table cell</div>
18+
</template>
19+
```
20+
21+
```gjs
22+
<template>
23+
<span scope="row">Wrong element</span>
24+
</template>
25+
```
26+
27+
```gjs
28+
<template>
29+
<p scope="col">Paragraph</p>
30+
</template>
31+
```
32+
33+
Examples of **correct** code for this rule:
34+
35+
```gjs
36+
<template>
37+
<th scope="col">Header</th>
38+
</template>
39+
```
40+
41+
```gjs
42+
<template>
43+
<th scope="row">Row header</th>
44+
</template>
45+
```
46+
47+
```gjs
48+
<template>
49+
<div>Content without scope</div>
50+
</template>
51+
```
52+
53+
## References
54+
55+
- [eslint-plugin-ember template-no-scope-outside-table-headings](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/template-no-scope-outside-table-headings.md)
56+
- [MDN: scope attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/th#attr-scope)
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/** @type {import('eslint').Rule.RuleModule} */
2+
module.exports = {
3+
meta: {
4+
type: 'problem',
5+
docs: {
6+
description: 'disallow scope attribute outside th elements',
7+
category: 'Possible Errors',
8+
url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-scope-outside-table-headings.md',
9+
templateMode: 'both',
10+
},
11+
schema: [],
12+
messages: {
13+
noScopeOutsideTableHeadings: 'Unexpected scope attribute on <{{tagName}}>. Use only on <th>.',
14+
},
15+
originallyFrom: {
16+
name: 'ember-template-lint',
17+
rule: 'lib/rules/no-scope-outside-table-headings.js',
18+
docs: 'docs/rule/no-scope-outside-table-headings.md',
19+
tests: 'test/unit/rules/no-scope-outside-table-headings-test.js',
20+
},
21+
},
22+
23+
create(context) {
24+
return {
25+
GlimmerElementNode(node) {
26+
const tagName = node.tag;
27+
28+
// Skip custom components (non-HTML elements)
29+
if (!tagName || /^[A-Z]/.test(tagName) || tagName.includes('.')) {
30+
return;
31+
}
32+
33+
const hasScopeAttr = node.attributes.some(
34+
(attr) => attr.type === 'GlimmerAttrNode' && attr.name === 'scope'
35+
);
36+
37+
if (hasScopeAttr && tagName !== 'th') {
38+
context.report({
39+
node,
40+
messageId: 'noScopeOutsideTableHeadings',
41+
data: { tagName },
42+
});
43+
}
44+
},
45+
};
46+
},
47+
};
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
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="col">Header</th></template>',
16+
'<template><th scope="row">Header</th></template>',
17+
'<template><div>Content</div></template>',
18+
19+
'<template><th scope="row">Some table heading></th></template>',
20+
`<template>
21+
<table>
22+
<th scope="col">Table header</th>
23+
<td>Some data</td>
24+
</table>
25+
</template>`,
26+
'<template><CustomComponent scope /></template>',
27+
'<template><CustomComponent scope="row" /></template>',
28+
'<template><CustomComponent scope={{foo}} /></template>',
29+
'<template>{{foo-component scope="row"}}</template>',
30+
],
31+
invalid: [
32+
{
33+
code: '<template><div scope="col">Not a table cell</div></template>',
34+
output: null,
35+
errors: [{ messageId: 'noScopeOutsideTableHeadings' }],
36+
},
37+
{
38+
code: '<template><span scope="row">Wrong element</span></template>',
39+
output: null,
40+
errors: [{ messageId: 'noScopeOutsideTableHeadings' }],
41+
},
42+
{
43+
code: '<template><p scope="col">Paragraph</p></template>',
44+
output: null,
45+
errors: [{ messageId: 'noScopeOutsideTableHeadings' }],
46+
},
47+
48+
{
49+
code: '<template><td scope="row"></td></template>',
50+
output: null,
51+
errors: [{ messageId: 'noScopeOutsideTableHeadings' }],
52+
},
53+
{
54+
code: '<template><td scope></td></template>',
55+
output: null,
56+
errors: [{ messageId: 'noScopeOutsideTableHeadings' }],
57+
},
58+
{
59+
code: '<template><a scope="row" /></template>',
60+
output: null,
61+
errors: [{ messageId: 'noScopeOutsideTableHeadings' }],
62+
},
63+
{
64+
code: `<template>
65+
<table>
66+
<th>Some header</th>
67+
<td scope="col">Some data</td>
68+
</table>
69+
</template>`,
70+
output: null,
71+
errors: [{ messageId: 'noScopeOutsideTableHeadings' }],
72+
},
73+
],
74+
});
75+
76+
const hbsRuleTester = new RuleTester({
77+
parser: require.resolve('ember-eslint-parser/hbs'),
78+
parserOptions: {
79+
ecmaVersion: 2022,
80+
sourceType: 'module',
81+
},
82+
});
83+
84+
hbsRuleTester.run('template-no-scope-outside-table-headings', rule, {
85+
valid: [
86+
'<th scope="row">Some table heading></th>',
87+
`
88+
<table>
89+
<th scope="col">Table header</th>
90+
<td>Some data</td>
91+
</table>
92+
`,
93+
'<CustomComponent scope />',
94+
'<CustomComponent scope="row" />',
95+
'<CustomComponent scope={{foo}} />',
96+
'{{foo-component scope="row"}}',
97+
],
98+
invalid: [
99+
{
100+
code: '<td scope="row"></td>',
101+
output: null,
102+
errors: [{ message: 'Unexpected scope attribute on <td>. Use only on <th>.' }],
103+
},
104+
{
105+
code: '<td scope></td>',
106+
output: null,
107+
errors: [{ message: 'Unexpected scope attribute on <td>. Use only on <th>.' }],
108+
},
109+
{
110+
code: '<a scope="row" />',
111+
output: null,
112+
errors: [{ message: 'Unexpected scope attribute on <a>. Use only on <th>.' }],
113+
},
114+
{
115+
code: `
116+
<table>
117+
<th>Some header</th>
118+
<td scope="col">Some data</td>
119+
</table>
120+
`,
121+
output: null,
122+
errors: [{ message: 'Unexpected scope attribute on <td>. Use only on <th>.' }],
123+
},
124+
],
125+
});

0 commit comments

Comments
 (0)