UNPKG

3.26 kBJavaScriptView Raw
1'use strict';
2const getDocumentationUrl = require('./utils/get-documentation-url');
3
4const forbiddenIdentifierNames = new Map([
5 ['getElementById', 'querySelector'],
6 ['getElementsByClassName', 'querySelectorAll'],
7 ['getElementsByTagName', 'querySelectorAll']
8]);
9
10const getReplacementForId = value => `#${value}`;
11const getReplacementForClass = value => value.match(/\S+/g).map(className => `.${className}`).join('');
12
13const getQuotedReplacement = (node, value) => {
14 const leftQuote = node.raw.charAt(0);
15 const rightQuote = node.raw.charAt(node.raw.length - 1);
16 return `${leftQuote}${value}${rightQuote}`;
17};
18
19const getLiteralFix = (fixer, node, identifierName) => {
20 let replacement = node.raw;
21 if (identifierName === 'getElementById') {
22 replacement = getQuotedReplacement(node, getReplacementForId(node.value));
23 }
24
25 if (identifierName === 'getElementsByClassName') {
26 replacement = getQuotedReplacement(node, getReplacementForClass(node.value));
27 }
28
29 return [fixer.replaceText(node, replacement)];
30};
31
32const getTemplateLiteralFix = (fixer, node, identifierName) => {
33 const fix = [
34 fixer.insertTextAfter(node, '`'),
35 fixer.insertTextBefore(node, '`')
36 ];
37
38 node.quasis.forEach(templateElement => {
39 if (identifierName === 'getElementById') {
40 fix.push(
41 fixer.replaceText(
42 templateElement,
43 getReplacementForId(templateElement.value.cooked)
44 )
45 );
46 }
47
48 if (identifierName === 'getElementsByClassName') {
49 fix.push(
50 fixer.replaceText(
51 templateElement,
52 getReplacementForClass(templateElement.value.cooked)
53 )
54 );
55 }
56 });
57
58 return fix;
59};
60
61const canBeFixed = node => {
62 if (node.type === 'Literal') {
63 return node.value === null || Boolean(node.value.trim());
64 }
65
66 if (node.type === 'TemplateLiteral') {
67 return (
68 node.expressions.length === 0 &&
69 node.quasis.some(templateElement => templateElement.value.cooked.trim())
70 );
71 }
72
73 return false;
74};
75
76const hasValue = node => {
77 if (node.type === 'Literal') {
78 return node.value;
79 }
80
81 return true;
82};
83
84const fix = (node, identifierName, preferedSelector) => {
85 const nodeToBeFixed = node.arguments[0];
86 if (identifierName === 'getElementsByTagName' || !hasValue(nodeToBeFixed)) {
87 return fixer => fixer.replaceText(node.callee.property, preferedSelector);
88 }
89
90 const getArgumentFix = nodeToBeFixed.type === 'Literal' ? getLiteralFix : getTemplateLiteralFix;
91 return fixer => [
92 ...getArgumentFix(fixer, nodeToBeFixed, identifierName),
93 fixer.replaceText(node.callee.property, preferedSelector)
94 ];
95};
96
97const create = context => {
98 return {
99 CallExpression(node) {
100 const {callee: {property, type}} = node;
101 if (!property || type !== 'MemberExpression') {
102 return;
103 }
104
105 const identifierName = property.name;
106 const preferedSelector = forbiddenIdentifierNames.get(identifierName);
107 if (!preferedSelector) {
108 return;
109 }
110
111 const report = {
112 node,
113 message: `Prefer \`.${preferedSelector}()\` over \`.${identifierName}()\`.`
114 };
115
116 if (canBeFixed(node.arguments[0])) {
117 report.fix = fix(node, identifierName, preferedSelector);
118 }
119
120 context.report(report);
121 }
122 };
123};
124
125module.exports = {
126 create,
127 meta: {
128 type: 'suggestion',
129 docs: {
130 url: getDocumentationUrl(__filename)
131 },
132 fixable: 'code'
133 }
134};