1 | 'use strict';
|
2 | const getDocumentationUrl = require('./utils/get-documentation-url');
|
3 | const isValueNotUsable = require('./utils/is-value-not-usable');
|
4 | const methodSelector = require('./utils/method-selector');
|
5 |
|
6 | const messages = {
|
7 | replaceChildOrInsertBefore:
|
8 | 'Prefer `{{oldChildNode}}.{{preferredMethod}}({{newChildNode}})` over `{{parentNode}}.{{method}}({{newChildNode}}, {{oldChildNode}})`.',
|
9 | insertAdjacentTextOrInsertAdjacentElement:
|
10 | 'Prefer `{{reference}}.{{preferredMethod}}({{content}})` over `{{reference}}.{{method}}({{position}}, {{content}})`.'
|
11 | };
|
12 |
|
13 | const replaceChildOrInsertBeforeSelector = [
|
14 | methodSelector({
|
15 | names: ['replaceChild', 'insertBefore'],
|
16 | length: 2
|
17 | }),
|
18 |
|
19 | '[arguments.0.type="Identifier"]',
|
20 | '[arguments.0.name!="undefined"]',
|
21 | '[arguments.1.type="Identifier"]',
|
22 | '[arguments.1.name!="undefined"]',
|
23 |
|
24 | '[callee.object.type="Identifier"]'
|
25 | ].join('');
|
26 |
|
27 | const forbiddenMethods = new Map([
|
28 | ['replaceChild', 'replaceWith'],
|
29 | ['insertBefore', 'before']
|
30 | ]);
|
31 |
|
32 | const checkForReplaceChildOrInsertBefore = (context, node) => {
|
33 | const method = node.callee.property.name;
|
34 | const parentNode = node.callee.object.name;
|
35 | const [newChildNode, oldChildNode] = node.arguments.map(({name}) => name);
|
36 | const preferredMethod = forbiddenMethods.get(method);
|
37 |
|
38 | const fix = isValueNotUsable(node) ?
|
39 | fixer => fixer.replaceText(
|
40 | node,
|
41 | `${oldChildNode}.${preferredMethod}(${newChildNode})`
|
42 | ) :
|
43 | undefined;
|
44 |
|
45 | return context.report({
|
46 | node,
|
47 | messageId: 'replaceChildOrInsertBefore',
|
48 | data: {
|
49 | parentNode,
|
50 | method,
|
51 | preferredMethod,
|
52 | newChildNode,
|
53 | oldChildNode
|
54 | },
|
55 | fix
|
56 | });
|
57 | };
|
58 |
|
59 | const insertAdjacentTextOrInsertAdjacentElementSelector = [
|
60 | methodSelector({
|
61 | names: ['insertAdjacentText', 'insertAdjacentElement'],
|
62 | length: 2
|
63 | }),
|
64 |
|
65 | '[arguments.0.type="Literal"]',
|
66 |
|
67 | ':matches([arguments.1.type="Literal"], [arguments.1.type="Identifier"])',
|
68 |
|
69 | '[callee.object.type="Identifier"]'
|
70 | ].join('');
|
71 |
|
72 | const positionReplacers = new Map([
|
73 | ['beforebegin', 'before'],
|
74 | ['afterbegin', 'prepend'],
|
75 | ['beforeend', 'append'],
|
76 | ['afterend', 'after']
|
77 | ]);
|
78 |
|
79 | const checkForInsertAdjacentTextOrInsertAdjacentElement = (context, node) => {
|
80 | const method = node.callee.property.name;
|
81 | const [positionNode, contentNode] = node.arguments;
|
82 |
|
83 | const position = positionNode.value;
|
84 |
|
85 | if (!positionReplacers.has(position)) {
|
86 | return;
|
87 | }
|
88 |
|
89 | const preferredMethod = positionReplacers.get(position);
|
90 | const content = context.getSource(contentNode);
|
91 | const reference = context.getSource(node.callee.object);
|
92 |
|
93 | const fix = method === 'insertAdjacentElement' && !isValueNotUsable(node) ?
|
94 | undefined :
|
95 |
|
96 | fixer => fixer.replaceText(
|
97 | node,
|
98 | `${reference}.${preferredMethod}(${content})`
|
99 | );
|
100 |
|
101 | return context.report({
|
102 | node,
|
103 | messageId: 'insertAdjacentTextOrInsertAdjacentElement',
|
104 | data: {
|
105 | reference,
|
106 | method,
|
107 | preferredMethod,
|
108 | position: context.getSource(positionNode),
|
109 | content
|
110 | },
|
111 | fix
|
112 | });
|
113 | };
|
114 |
|
115 | const create = context => {
|
116 | return {
|
117 | [replaceChildOrInsertBeforeSelector](node) {
|
118 | checkForReplaceChildOrInsertBefore(context, node);
|
119 | },
|
120 | [insertAdjacentTextOrInsertAdjacentElementSelector](node) {
|
121 | checkForInsertAdjacentTextOrInsertAdjacentElement(context, node);
|
122 | }
|
123 | };
|
124 | };
|
125 |
|
126 | module.exports = {
|
127 | create,
|
128 | meta: {
|
129 | type: 'suggestion',
|
130 | docs: {
|
131 | url: getDocumentationUrl(__filename)
|
132 | },
|
133 | fixable: 'code',
|
134 | messages
|
135 | }
|
136 | };
|