Skip to content

Commit b63b79d

Browse files
committed
Extract rule: template-no-forbidden-elements
1 parent 589f47e commit b63b79d

4 files changed

Lines changed: 238 additions & 0 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,7 @@ rules in templates can be disabled with eslint directives with mustache or html
215215
| [template-no-debugger](docs/rules/template-no-debugger.md) | disallow {{debugger}} in templates | | | |
216216
| [template-no-dynamic-subexpression-invocations](docs/rules/template-no-dynamic-subexpression-invocations.md) | disallow dynamic subexpression invocations | | | |
217217
| [template-no-element-event-actions](docs/rules/template-no-element-event-actions.md) | disallow element event actions (use {{on}} modifier instead) | | | |
218+
| [template-no-forbidden-elements](docs/rules/template-no-forbidden-elements.md) | disallow specific HTML elements | | | |
218219
| [template-no-html-comments](docs/rules/template-no-html-comments.md) | disallow HTML comments in templates | | 🔧 | |
219220
| [template-no-implicit-this](docs/rules/template-no-implicit-this.md) | require explicit `this` in property access | | | |
220221
| [template-no-index-component-invocation](docs/rules/template-no-index-component-invocation.md) | disallow index component invocations | | | |
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# ember/template-no-forbidden-elements
2+
3+
<!-- end auto-generated rule header -->
4+
5+
This rule disallows the use of forbidden elements in template files.
6+
7+
The rule is configurable so teams can add their own disallowed elements.
8+
The default list of forbidden elements are `meta`, `style`, `html`, and `script`.
9+
10+
## Examples
11+
12+
This rule **forbids** the following:
13+
14+
```gjs
15+
<template><script></script></template>
16+
```
17+
18+
```gjs
19+
<template><style></style></template>
20+
```
21+
22+
```gjs
23+
<template><html></html></template>
24+
```
25+
26+
```gjs
27+
<template><meta charset='utf-8' /></template>
28+
```
29+
30+
This rule **allows** the following:
31+
32+
```gjs
33+
<template><header></header></template>
34+
```
35+
36+
```gjs
37+
<template><div></div></template>
38+
```
39+
40+
```gjs
41+
<template>
42+
<head>
43+
<meta charset='utf-8' />
44+
</head>
45+
</template>
46+
```
47+
48+
Note: `<meta>` inside `<head>` is allowed as an exception.
49+
50+
## Configuration
51+
52+
- `boolean``true` to enable with defaults / `false` to disable
53+
- `string[]` — an array of element names to forbid (default: `['meta', 'style', 'html', 'script']`)
54+
55+
## References
56+
57+
- [Ember guides/template restrictions](https://guides.emberjs.com/release/components/#toc_restrictions)
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
const DEFAULT_FORBIDDEN = ['meta', 'style', 'html', 'script'];
2+
3+
/** @type {import('eslint').Rule.RuleModule} */
4+
module.exports = {
5+
meta: {
6+
type: 'suggestion',
7+
docs: {
8+
description: 'disallow specific HTML elements',
9+
category: 'Best Practices',
10+
recommendedGjs: false,
11+
recommendedGts: false,
12+
url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-forbidden-elements.md',
13+
templateMode: 'both',
14+
},
15+
schema: [
16+
{
17+
oneOf: [{ type: 'array', items: { type: 'string' } }, { type: 'boolean' }],
18+
},
19+
],
20+
messages: { forbidden: 'Use of forbidden element <{{element}}>' },
21+
originallyFrom: {
22+
name: 'ember-template-lint',
23+
rule: 'lib/rules/no-forbidden-elements.js',
24+
docs: 'docs/rule/no-forbidden-elements.md',
25+
tests: 'test/unit/rules/no-forbidden-elements-test.js',
26+
},
27+
},
28+
create(context) {
29+
const rawConfig = context.options[0];
30+
let forbiddenList;
31+
32+
if (rawConfig === true || rawConfig === undefined) {
33+
forbiddenList = DEFAULT_FORBIDDEN;
34+
} else if (Array.isArray(rawConfig)) {
35+
forbiddenList = rawConfig;
36+
} else {
37+
forbiddenList = [];
38+
}
39+
40+
const forbidden = new Set(forbiddenList);
41+
42+
// Track element stack for <meta> in <head> exception
43+
const elementStack = [];
44+
45+
return {
46+
GlimmerElementNode(node) {
47+
elementStack.push(node.tag);
48+
49+
if (!forbidden.has(node.tag)) {
50+
return;
51+
}
52+
53+
// Exception: <meta> inside <head> is allowed
54+
if (node.tag === 'meta' && elementStack.includes('head')) {
55+
return;
56+
}
57+
58+
context.report({ node, messageId: 'forbidden', data: { element: node.tag } });
59+
},
60+
'GlimmerElementNode:exit'() {
61+
elementStack.pop();
62+
},
63+
};
64+
},
65+
};
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
const rule = require('../../../lib/rules/template-no-forbidden-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-forbidden-elements', rule, {
9+
valid: [
10+
{ code: '<template><div></div></template>', options: [['script']] },
11+
'<template><header></header></template>',
12+
'<template><footer></footer></template>',
13+
'<template><p></p></template>',
14+
'<template><head><meta charset="utf-8"></head></template>',
15+
],
16+
invalid: [
17+
{
18+
code: '<template><script></script></template>',
19+
output: null,
20+
options: [['script']],
21+
errors: [{ messageId: 'forbidden' }],
22+
},
23+
24+
{
25+
code: '<template><html></html></template>',
26+
output: null,
27+
errors: [{ messageId: 'forbidden' }],
28+
},
29+
{
30+
code: '<template><style></style></template>',
31+
output: null,
32+
errors: [{ messageId: 'forbidden' }],
33+
},
34+
{
35+
code: '<template><meta charset="utf-8"></template>',
36+
output: null,
37+
errors: [{ messageId: 'forbidden' }],
38+
},
39+
{
40+
code: '<template><head><html></html></head></template>',
41+
output: null,
42+
errors: [{ messageId: 'forbidden' }],
43+
},
44+
{
45+
code: '<template><Foo /></template>',
46+
output: null,
47+
options: [['Foo']],
48+
errors: [{ messageId: 'forbidden' }],
49+
},
50+
],
51+
});
52+
53+
const hbsRuleTester = new RuleTester({
54+
parser: require.resolve('ember-eslint-parser/hbs'),
55+
parserOptions: {
56+
ecmaVersion: 2022,
57+
sourceType: 'module',
58+
},
59+
});
60+
61+
hbsRuleTester.run('template-no-forbidden-elements', rule, {
62+
valid: [
63+
'<header></header>',
64+
'<div></div>',
65+
'<footer></footer>',
66+
'<p></p>',
67+
'<head><meta charset="utf-8"></head>',
68+
// Custom forbidden list (script not included).
69+
{
70+
code: '<script></script>',
71+
options: [['html', 'meta', 'style']],
72+
},
73+
],
74+
invalid: [
75+
// Default config.
76+
{
77+
code: '<script></script>',
78+
output: null,
79+
errors: [{ message: 'Use of forbidden element <script>' }],
80+
},
81+
{
82+
code: '<html></html>',
83+
output: null,
84+
errors: [{ message: 'Use of forbidden element <html>' }],
85+
},
86+
{
87+
code: '<style></style>',
88+
output: null,
89+
errors: [{ message: 'Use of forbidden element <style>' }],
90+
},
91+
{
92+
code: '<meta charset="utf-8">',
93+
output: null,
94+
errors: [{ message: 'Use of forbidden element <meta>' }],
95+
},
96+
{
97+
code: '<head><html></html></head>',
98+
output: null,
99+
errors: [{ message: 'Use of forbidden element <html>' }],
100+
},
101+
// Custom forbidden list.
102+
{
103+
code: '<div></div>',
104+
output: null,
105+
options: [['div']],
106+
errors: [{ message: 'Use of forbidden element <div>' }],
107+
},
108+
{
109+
code: '<Foo />',
110+
output: null,
111+
options: [['Foo']],
112+
errors: [{ message: 'Use of forbidden element <Foo>' }],
113+
},
114+
],
115+
});

0 commit comments

Comments
 (0)