Skip to content

Commit 25a995d

Browse files
Merge pull request #2488 from NullVoxPopuli/nvp/template-lint-extract-rule-template-no-obsolete-elements
Extract rule: template-no-obsolete-elements
2 parents c3b6d72 + d8771b4 commit 25a995d

4 files changed

Lines changed: 320 additions & 0 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,7 @@ rules in templates can be disabled with eslint directives with mustache or html
208208
| [template-no-input-placeholder](docs/rules/template-no-input-placeholder.md) | disallow placeholder attribute on input elements | | | |
209209
| [template-no-input-tagname](docs/rules/template-no-input-tagname.md) | disallow tagName attribute on {{input}} helper | | | |
210210
| [template-no-log](docs/rules/template-no-log.md) | disallow {{log}} in templates | | | |
211+
| [template-no-obsolete-elements](docs/rules/template-no-obsolete-elements.md) | disallow obsolete HTML elements | | | |
211212
| [template-no-outlet-outside-routes](docs/rules/template-no-outlet-outside-routes.md) | disallow {{outlet}} outside of route templates | | | |
212213
| [template-no-page-title-component](docs/rules/template-no-page-title-component.md) | disallow usage of ember-page-title component | | | |
213214

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# ember/template-no-obsolete-elements
2+
3+
<!-- end auto-generated rule header -->
4+
5+
Some elements are entirely obsolete and must not be used by authors.
6+
7+
This rule forbids the use of obsolete elements.
8+
9+
## Examples
10+
11+
This rule **forbids** the following:
12+
13+
```gjs
14+
<template>
15+
<acronym></acronym>
16+
<applet></applet>
17+
<basefont></basefont>
18+
<bgsound></bgsound>
19+
<big></big>
20+
<blink></blink>
21+
<center></center>
22+
<dir></dir>
23+
<font></font>
24+
<frame></frame>
25+
<frameset></frameset>
26+
<isindex></isindex>
27+
<keygen />
28+
<listing></listing>
29+
<marquee></marquee>
30+
<menuitem></menuitem>
31+
<multicol></multicol>
32+
<nextid></nextid>
33+
<nobr></nobr>
34+
<noembed></noembed>
35+
<noframes></noframes>
36+
<param>
37+
<plaintext></plaintext>
38+
<rb></rb>
39+
<rtc></rtc>
40+
<spacer></spacer>
41+
<strike></strike>
42+
<tt></tt>
43+
<xmp></xmp>
44+
</template>
45+
```
46+
47+
This rule **allows** anything that is not an obsolete element.
48+
49+
## Migration
50+
51+
- replace any use of these elements with the appropriate updated element or a `div` element.
52+
53+
## References
54+
55+
- [HTML non-conforming features](https://html.spec.whatwg.org/multipage/obsolete.html#non-conforming-features)
56+
- [HTML5 obsolete features](https://dev.w3.org/html5/spec-LC/obsolete.html)
57+
- [Failure of Success Criterion 2.2.2 due to using the blink element](https://www.w3.org/TR/WCAG20-TECHS/failures.html#F47)
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
const OBSOLETE = [
2+
'acronym',
3+
'applet',
4+
'basefont',
5+
'bgsound',
6+
'big',
7+
'blink',
8+
'center',
9+
'dir',
10+
'font',
11+
'frame',
12+
'frameset',
13+
'isindex',
14+
'keygen',
15+
'listing',
16+
'marquee',
17+
'menuitem',
18+
'multicol',
19+
'nextid',
20+
'nobr',
21+
'noembed',
22+
'noframes',
23+
'param',
24+
'plaintext',
25+
'rb',
26+
'rtc',
27+
'spacer',
28+
'strike',
29+
'tt',
30+
'xmp',
31+
];
32+
/** @type {import('eslint').Rule.RuleModule} */
33+
module.exports = {
34+
meta: {
35+
type: 'suggestion',
36+
docs: {
37+
description: 'disallow obsolete HTML elements',
38+
category: 'Best Practices',
39+
url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-obsolete-elements.md',
40+
templateMode: 'both',
41+
},
42+
schema: [],
43+
messages: { obsolete: '<{{element}}> is obsolete, use modern alternatives' },
44+
originallyFrom: {
45+
name: 'ember-template-lint',
46+
rule: 'lib/rules/no-obsolete-elements.js',
47+
docs: 'docs/rule/no-obsolete-elements.md',
48+
tests: 'test/unit/rules/no-obsolete-elements-test.js',
49+
},
50+
},
51+
create(context) {
52+
const obsolete = new Set(OBSOLETE);
53+
const blockParamsInScope = [];
54+
55+
return {
56+
GlimmerBlockStatement(node) {
57+
const params = node.program?.blockParams || [];
58+
blockParamsInScope.push(...params);
59+
},
60+
'GlimmerBlockStatement:exit'(node) {
61+
const params = node.program?.blockParams || [];
62+
for (let i = 0; i < params.length; i++) {
63+
blockParamsInScope.pop();
64+
}
65+
},
66+
GlimmerElementNode(node) {
67+
if (blockParamsInScope.includes(node.tag)) {
68+
return;
69+
}
70+
if (obsolete.has(node.tag)) {
71+
context.report({ node, messageId: 'obsolete', data: { element: node.tag } });
72+
}
73+
},
74+
};
75+
},
76+
};
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
const rule = require('../../../lib/rules/template-no-obsolete-elements');
2+
const RuleTester = require('eslint').RuleTester;
3+
4+
const ruleTester = new RuleTester({
5+
parser: require.resolve('ember-eslint-parser'),
6+
parserOptions: { ecmaVersion: 2022, sourceType: 'module' },
7+
});
8+
ruleTester.run('template-no-obsolete-elements', rule, {
9+
valid: [
10+
'<template><div></div></template>',
11+
`<template>{{#let (component 'whatever-here') as |plaintext|}}
12+
<plaintext />
13+
{{/let}}</template>`,
14+
],
15+
invalid: [
16+
{
17+
code: '<template><marquee></marquee></template>',
18+
output: null,
19+
errors: [{ messageId: 'obsolete' }],
20+
},
21+
],
22+
});
23+
24+
const hbsRuleTester = new RuleTester({
25+
parser: require.resolve('ember-eslint-parser/hbs'),
26+
parserOptions: {
27+
ecmaVersion: 2022,
28+
sourceType: 'module',
29+
},
30+
});
31+
32+
hbsRuleTester.run('template-no-obsolete-elements', rule, {
33+
valid: [
34+
'<div></div>',
35+
`{{#let (component 'whatever-here') as |plaintext|}}
36+
<plaintext />
37+
{{/let}}`,
38+
],
39+
invalid: [
40+
{
41+
code: '<acronym></acronym>',
42+
output: null,
43+
errors: [{ messageId: 'obsolete', data: { element: 'acronym' } }],
44+
},
45+
{
46+
code: '<applet></applet>',
47+
output: null,
48+
errors: [{ messageId: 'obsolete', data: { element: 'applet' } }],
49+
},
50+
{
51+
code: '<basefont></basefont>',
52+
output: null,
53+
errors: [{ messageId: 'obsolete', data: { element: 'basefont' } }],
54+
},
55+
{
56+
code: '<bgsound></bgsound>',
57+
output: null,
58+
errors: [{ messageId: 'obsolete', data: { element: 'bgsound' } }],
59+
},
60+
{
61+
code: '<big></big>',
62+
output: null,
63+
errors: [{ messageId: 'obsolete', data: { element: 'big' } }],
64+
},
65+
{
66+
code: '<blink></blink>',
67+
output: null,
68+
errors: [{ messageId: 'obsolete', data: { element: 'blink' } }],
69+
},
70+
{
71+
code: '<center></center>',
72+
output: null,
73+
errors: [{ messageId: 'obsolete', data: { element: 'center' } }],
74+
},
75+
{
76+
code: '<dir></dir>',
77+
output: null,
78+
errors: [{ messageId: 'obsolete', data: { element: 'dir' } }],
79+
},
80+
{
81+
code: '<font></font>',
82+
output: null,
83+
errors: [{ messageId: 'obsolete', data: { element: 'font' } }],
84+
},
85+
{
86+
code: '<frame></frame>',
87+
output: null,
88+
errors: [{ messageId: 'obsolete', data: { element: 'frame' } }],
89+
},
90+
{
91+
code: '<frameset></frameset>',
92+
output: null,
93+
errors: [{ messageId: 'obsolete', data: { element: 'frameset' } }],
94+
},
95+
{
96+
code: '<isindex></isindex>',
97+
output: null,
98+
errors: [{ messageId: 'obsolete', data: { element: 'isindex' } }],
99+
},
100+
{
101+
code: '<keygen>',
102+
output: null,
103+
errors: [{ messageId: 'obsolete', data: { element: 'keygen' } }],
104+
},
105+
{
106+
code: '<listing></listing>',
107+
output: null,
108+
errors: [{ messageId: 'obsolete', data: { element: 'listing' } }],
109+
},
110+
{
111+
code: '<marquee></marquee>',
112+
output: null,
113+
errors: [{ messageId: 'obsolete', data: { element: 'marquee' } }],
114+
},
115+
{
116+
code: '<menuitem></menuitem>',
117+
output: null,
118+
errors: [{ messageId: 'obsolete', data: { element: 'menuitem' } }],
119+
},
120+
{
121+
code: '<multicol></multicol>',
122+
output: null,
123+
errors: [{ messageId: 'obsolete', data: { element: 'multicol' } }],
124+
},
125+
{
126+
code: '<nextid></nextid>',
127+
output: null,
128+
errors: [{ messageId: 'obsolete', data: { element: 'nextid' } }],
129+
},
130+
{
131+
code: '<nobr></nobr>',
132+
output: null,
133+
errors: [{ messageId: 'obsolete', data: { element: 'nobr' } }],
134+
},
135+
{
136+
code: '<noembed></noembed>',
137+
output: null,
138+
errors: [{ messageId: 'obsolete', data: { element: 'noembed' } }],
139+
},
140+
{
141+
code: '<noframes></noframes>',
142+
output: null,
143+
errors: [{ messageId: 'obsolete', data: { element: 'noframes' } }],
144+
},
145+
{
146+
code: '<param>',
147+
output: null,
148+
errors: [{ messageId: 'obsolete', data: { element: 'param' } }],
149+
},
150+
{
151+
code: '<plaintext></plaintext>',
152+
output: null,
153+
errors: [{ messageId: 'obsolete', data: { element: 'plaintext' } }],
154+
},
155+
{
156+
code: '<rb></rb>',
157+
output: null,
158+
errors: [{ messageId: 'obsolete', data: { element: 'rb' } }],
159+
},
160+
{
161+
code: '<rtc></rtc>',
162+
output: null,
163+
errors: [{ messageId: 'obsolete', data: { element: 'rtc' } }],
164+
},
165+
{
166+
code: '<spacer></spacer>',
167+
output: null,
168+
errors: [{ messageId: 'obsolete', data: { element: 'spacer' } }],
169+
},
170+
{
171+
code: '<strike></strike>',
172+
output: null,
173+
errors: [{ messageId: 'obsolete', data: { element: 'strike' } }],
174+
},
175+
{
176+
code: '<tt></tt>',
177+
output: null,
178+
errors: [{ messageId: 'obsolete', data: { element: 'tt' } }],
179+
},
180+
{
181+
code: '<xmp></xmp>',
182+
output: null,
183+
errors: [{ messageId: 'obsolete', data: { element: 'xmp' } }],
184+
},
185+
],
186+
});

0 commit comments

Comments
 (0)