-
Notifications
You must be signed in to change notification settings - Fork 648
Expand file tree
/
Copy pathno-document-body-mutation.ts
More file actions
102 lines (96 loc) · 3.27 KB
/
no-document-body-mutation.ts
File metadata and controls
102 lines (96 loc) · 3.27 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import type {TSESTree} from '@typescript-eslint/utils';
import {createRule} from './utils/ruleCreator.ts';
function isWidgetShowCallWithDocumentBody(node: TSESTree.Node): node is TSESTree.CallExpression&{
callee: TSESTree.MemberExpression&{
object: TSESTree.Identifier,
property: TSESTree.Identifier & {
name: 'show',
},
},
arguments: [TSESTree.MemberExpression&{
object: TSESTree.Identifier & {name: 'document'},
property: TSESTree.Identifier & {
name: 'body',
},
}],
}
{
return node.type === 'CallExpression' && node.callee.type === 'MemberExpression' &&
node.callee.object.type === 'Identifier' && node.callee.property.type === 'Identifier' &&
node.callee.property.name === 'show' && node.arguments.length === 1 &&
node.arguments[0].type === 'MemberExpression' && node.arguments[0].object.type === 'Identifier' &&
node.arguments[0].object.name === 'document' && node.arguments[0].property.type === 'Identifier' &&
node.arguments[0].property.name === 'body';
}
const forbiddenDocumentBodyMethods = new Set(['append', 'appendChild']);
/**
* Matches code that looks like:
* document.body.appendChild()
*
* (a method call on document.body)
*/
function isDocumentBodyMethodCallToAddDOM(node: TSESTree.Node): node is TSESTree.MemberExpression&{
object: TSESTree.MemberExpression&{
object: TSESTree.Identifier & {
name: 'document',
},
property: TSESTree.Identifier & {
name: 'body',
},
},
property: TSESTree.Identifier&{
name: 'append' | 'appendChild',
},
}
{
return node.type === 'MemberExpression' && node.object.type === 'MemberExpression' &&
node.object.object.type === 'Identifier' && node.object.object.name === 'document' &&
node.object.property.type === 'Identifier' && node.object.property.name === 'body' &&
node.property.type === 'Identifier' && forbiddenDocumentBodyMethods.has(node.property.name);
}
export default createRule({
name: 'no-document-body-mutation',
meta: {
type: 'problem',
docs: {
description: 'Disallow usage of widget.show(document.body) in unit tests.',
category: 'Possible Errors',
},
messages: {
invalidShowUsage: 'Avoid mounting widgets into document.body in tests. Prefer the `renderElementIntoDOM` helper.',
doNotRenderIntoBody:
'Avoid using methods that render DOM directly into the body. Prefer the `renderElementIntoDOM` helper.',
},
fixable: 'code',
schema: [], // no options
},
defaultOptions: [],
create(context) {
return {
MemberExpression(node) {
if (!isDocumentBodyMethodCallToAddDOM(node)) {
return;
}
context.report({
node,
messageId: 'doNotRenderIntoBody',
});
},
CallExpression(node) {
if (!isWidgetShowCallWithDocumentBody(node)) {
return;
}
context.report({
node,
messageId: 'invalidShowUsage',
fix(fixer) {
return fixer.replaceText(node, `renderElementIntoDOM(${node.callee.object.name})`);
}
});
},
};
},
});