Skip to content

Commit 292c12d

Browse files
Merge pull request #2435 from NullVoxPopuli/nvp/template-lint-extract-rule-template-no-forbidden-elements
Extract rule: template-no-forbidden-elements
2 parents 589f47e + 8d9f8d3 commit 292c12d

4 files changed

Lines changed: 264 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: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
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: [
18+
{ type: 'array', items: { type: 'string' } },
19+
{ type: 'boolean' },
20+
{
21+
type: 'object',
22+
properties: {
23+
forbidden: { type: 'array', items: { type: 'string' } },
24+
},
25+
additionalProperties: false,
26+
},
27+
],
28+
},
29+
],
30+
messages: { forbidden: 'Use of forbidden element <{{element}}>' },
31+
originallyFrom: {
32+
name: 'ember-template-lint',
33+
rule: 'lib/rules/no-forbidden-elements.js',
34+
docs: 'docs/rule/no-forbidden-elements.md',
35+
tests: 'test/unit/rules/no-forbidden-elements-test.js',
36+
},
37+
},
38+
create(context) {
39+
const rawConfig = context.options[0];
40+
let forbiddenList;
41+
42+
if (rawConfig === true || rawConfig === undefined) {
43+
forbiddenList = DEFAULT_FORBIDDEN;
44+
} else if (Array.isArray(rawConfig)) {
45+
forbiddenList = rawConfig;
46+
} else if (rawConfig && typeof rawConfig === 'object') {
47+
forbiddenList = rawConfig.forbidden ?? DEFAULT_FORBIDDEN;
48+
} else {
49+
forbiddenList = [];
50+
}
51+
52+
const forbidden = new Set(forbiddenList);
53+
54+
// Track element stack for <meta> in <head> exception
55+
const elementStack = [];
56+
57+
return {
58+
GlimmerElementNode(node) {
59+
elementStack.push(node.tag);
60+
61+
if (!forbidden.has(node.tag)) {
62+
return;
63+
}
64+
65+
// Exception: <meta> inside <head> is allowed
66+
if (node.tag === 'meta' && elementStack.includes('head')) {
67+
return;
68+
}
69+
70+
context.report({ node, messageId: 'forbidden', data: { element: node.tag } });
71+
},
72+
'GlimmerElementNode:exit'() {
73+
elementStack.pop();
74+
},
75+
};
76+
},
77+
};
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
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+
// Object config form
12+
{ code: '<template><div></div></template>', options: [{ forbidden: ['script'] }] },
13+
{ code: '<template><script></script></template>', options: [{ forbidden: ['html'] }] },
14+
'<template><header></header></template>',
15+
'<template><footer></footer></template>',
16+
'<template><p></p></template>',
17+
'<template><head><meta charset="utf-8"></head></template>',
18+
],
19+
invalid: [
20+
{
21+
code: '<template><script></script></template>',
22+
output: null,
23+
options: [{ forbidden: ['script'] }],
24+
errors: [{ messageId: 'forbidden' }],
25+
},
26+
{
27+
code: '<template><script></script></template>',
28+
output: null,
29+
options: [['script']],
30+
errors: [{ messageId: 'forbidden' }],
31+
},
32+
33+
{
34+
code: '<template><html></html></template>',
35+
output: null,
36+
errors: [{ messageId: 'forbidden' }],
37+
},
38+
{
39+
code: '<template><style></style></template>',
40+
output: null,
41+
errors: [{ messageId: 'forbidden' }],
42+
},
43+
{
44+
code: '<template><meta charset="utf-8"></template>',
45+
output: null,
46+
errors: [{ messageId: 'forbidden' }],
47+
},
48+
{
49+
code: '<template><head><html></html></head></template>',
50+
output: null,
51+
errors: [{ messageId: 'forbidden' }],
52+
},
53+
{
54+
code: '<template><Foo /></template>',
55+
output: null,
56+
options: [['Foo']],
57+
errors: [{ messageId: 'forbidden' }],
58+
},
59+
],
60+
});
61+
62+
const hbsRuleTester = new RuleTester({
63+
parser: require.resolve('ember-eslint-parser/hbs'),
64+
parserOptions: {
65+
ecmaVersion: 2022,
66+
sourceType: 'module',
67+
},
68+
});
69+
70+
hbsRuleTester.run('template-no-forbidden-elements', rule, {
71+
valid: [
72+
'<header></header>',
73+
'<div></div>',
74+
'<footer></footer>',
75+
'<p></p>',
76+
'<head><meta charset="utf-8"></head>',
77+
// Custom forbidden list (script not included).
78+
{
79+
code: '<script></script>',
80+
options: [['html', 'meta', 'style']],
81+
},
82+
// Object config form.
83+
{
84+
code: '<script></script>',
85+
options: [{ forbidden: ['html', 'meta', 'style'] }],
86+
},
87+
],
88+
invalid: [
89+
// Default config.
90+
{
91+
code: '<script></script>',
92+
output: null,
93+
errors: [{ message: 'Use of forbidden element <script>' }],
94+
},
95+
{
96+
code: '<html></html>',
97+
output: null,
98+
errors: [{ message: 'Use of forbidden element <html>' }],
99+
},
100+
{
101+
code: '<style></style>',
102+
output: null,
103+
errors: [{ message: 'Use of forbidden element <style>' }],
104+
},
105+
{
106+
code: '<meta charset="utf-8">',
107+
output: null,
108+
errors: [{ message: 'Use of forbidden element <meta>' }],
109+
},
110+
{
111+
code: '<head><html></html></head>',
112+
output: null,
113+
errors: [{ message: 'Use of forbidden element <html>' }],
114+
},
115+
// Custom forbidden list.
116+
{
117+
code: '<div></div>',
118+
output: null,
119+
options: [['div']],
120+
errors: [{ message: 'Use of forbidden element <div>' }],
121+
},
122+
{
123+
code: '<Foo />',
124+
output: null,
125+
options: [['Foo']],
126+
errors: [{ message: 'Use of forbidden element <Foo>' }],
127+
},
128+
],
129+
});

0 commit comments

Comments
 (0)