UNPKG

3.22 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
19function * 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 yield fixer.replaceText(node, replacement);
30}
31
32function * getTemplateLiteralFix(fixer, node, identifierName) {
33 yield fixer.insertTextAfter(node, '`');
34 yield fixer.insertTextBefore(node, '`');
35
36 for (const templateElement of node.quasis) {
37 if (identifierName === 'getElementById') {
38 yield fixer.replaceText(
39 templateElement,
40 getReplacementForId(templateElement.value.cooked)
41 );
42 }
43
44 if (identifierName === 'getElementsByClassName') {
45 yield fixer.replaceText(
46 templateElement,
47 getReplacementForClass(templateElement.value.cooked)
48 );
49 }
50 }
51}
52
53const canBeFixed = node => {
54 if (node.type === 'Literal') {
55 return node.value === null || Boolean(node.value.trim());
56 }
57
58 if (node.type === 'TemplateLiteral') {
59 return (
60 node.expressions.length === 0 &&
61 node.quasis.some(templateElement => templateElement.value.cooked.trim())
62 );
63 }
64
65 return false;
66};
67
68const hasValue = node => {
69 if (node.type === 'Literal') {
70 return node.value;
71 }
72
73 return true;
74};
75
76const fix = (node, identifierName, preferedSelector) => {
77 const nodeToBeFixed = node.arguments[0];
78 if (identifierName === 'getElementsByTagName' || !hasValue(nodeToBeFixed)) {
79 return fixer => fixer.replaceText(node.callee.property, preferedSelector);
80 }
81
82 const getArgumentFix = nodeToBeFixed.type === 'Literal' ? getLiteralFix : getTemplateLiteralFix;
83 return function * (fixer) {
84 yield * getArgumentFix(fixer, nodeToBeFixed, identifierName);
85 yield fixer.replaceText(node.callee.property, preferedSelector);
86 };
87};
88
89const create = context => {
90 return {
91 CallExpression(node) {
92 const {callee: {property, type}} = node;
93 if (!property || type !== 'MemberExpression') {
94 return;
95 }
96
97 const identifierName = property.name;
98 const preferedSelector = forbiddenIdentifierNames.get(identifierName);
99 if (!preferedSelector) {
100 return;
101 }
102
103 const report = {
104 node,
105 message: `Prefer \`.${preferedSelector}()\` over \`.${identifierName}()\`.`
106 };
107
108 if (canBeFixed(node.arguments[0])) {
109 report.fix = fix(node, identifierName, preferedSelector);
110 }
111
112 context.report(report);
113 }
114 };
115};
116
117module.exports = {
118 create,
119 meta: {
120 type: 'suggestion',
121 docs: {
122 url: getDocumentationUrl(__filename)
123 },
124 fixable: 'code'
125 }
126};