UNPKG

3.84 kBJavaScriptView Raw
1'use strict';
2const getDocumentationUrl = require('./utils/get-documentation-url');
3const isValueNotUsable = require('./utils/is-value-not-usable');
4const methodSelector = require('./utils/method-selector');
5
6const 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
13const replaceChildOrInsertBeforeSelector = [
14 methodSelector({
15 names: ['replaceChild', 'insertBefore'],
16 length: 2
17 }),
18 // We only allow Identifier for now
19 '[arguments.0.type="Identifier"]',
20 '[arguments.0.name!="undefined"]',
21 '[arguments.1.type="Identifier"]',
22 '[arguments.1.name!="undefined"]',
23 // This check makes sure that only the first method of chained methods with same identifier name e.g: parentNode.insertBefore(alfa, beta).insertBefore(charlie, delta); gets reported
24 '[callee.object.type="Identifier"]'
25].join('');
26
27const forbiddenMethods = new Map([
28 ['replaceChild', 'replaceWith'],
29 ['insertBefore', 'before']
30]);
31
32const 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
59const insertAdjacentTextOrInsertAdjacentElementSelector = [
60 methodSelector({
61 names: ['insertAdjacentText', 'insertAdjacentElement'],
62 length: 2
63 }),
64 // Position argument should be `string`
65 '[arguments.0.type="Literal"]',
66 // TODO: remove this limits on second argument
67 ':matches([arguments.1.type="Literal"], [arguments.1.type="Identifier"])',
68 // TODO: remove this limits on callee
69 '[callee.object.type="Identifier"]'
70].join('');
71
72const positionReplacers = new Map([
73 ['beforebegin', 'before'],
74 ['afterbegin', 'prepend'],
75 ['beforeend', 'append'],
76 ['afterend', 'after']
77]);
78
79const checkForInsertAdjacentTextOrInsertAdjacentElement = (context, node) => {
80 const method = node.callee.property.name;
81 const [positionNode, contentNode] = node.arguments;
82
83 const position = positionNode.value;
84 // Return early when specified position value of first argument is not a recognized value.
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 // TODO: make a better fix, don't touch reference
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
115const create = context => {
116 return {
117 [replaceChildOrInsertBeforeSelector](node) {
118 checkForReplaceChildOrInsertBefore(context, node);
119 },
120 [insertAdjacentTextOrInsertAdjacentElementSelector](node) {
121 checkForInsertAdjacentTextOrInsertAdjacentElement(context, node);
122 }
123 };
124};
125
126module.exports = {
127 create,
128 meta: {
129 type: 'suggestion',
130 docs: {
131 url: getDocumentationUrl(__filename)
132 },
133 fixable: 'code',
134 messages
135 }
136};