UNPKG

3.41 kBJavaScriptView Raw
1'use strict';
2const getDocumentationUrl = require('./utils/get-documentation-url');
3const quoteString = require('./utils/quote-string');
4const replaceTemplateElement = require('./utils/replace-template-element');
5const escapeTemplateElementRaw = require('./utils/escape-template-element-raw');
6
7const ignoredIdentifier = new Set([
8 'gql',
9 'html',
10 'svg'
11]);
12
13const ignoredMemberExpressionObject = new Set([
14 'styled'
15]);
16
17const isIgnoredTag = node => {
18 if (!node.parent || !node.parent.parent || !node.parent.parent.tag) {
19 return false;
20 }
21
22 const {tag} = node.parent.parent;
23
24 if (tag.type === 'Identifier' && ignoredIdentifier.has(tag.name)) {
25 return true;
26 }
27
28 if (tag.type === 'MemberExpression') {
29 const {object} = tag;
30 if (
31 object.type === 'Identifier' &&
32 ignoredMemberExpressionObject.has(object.name)
33 ) {
34 return true;
35 }
36 }
37
38 return false;
39};
40
41const defaultMessage = 'Prefer `{{suggest}}` over `{{match}}`.';
42const SUGGESTION_MESSAGE_ID = 'replace';
43
44function getReplacements(patterns) {
45 return Object.entries(patterns)
46 .map(([match, options]) => {
47 if (typeof options === 'string') {
48 options = {
49 suggest: options
50 };
51 }
52
53 return {
54 match,
55 regex: new RegExp(match, 'gu'),
56 fix: true,
57 ...options
58 };
59 });
60}
61
62const create = context => {
63 const {patterns} = {
64 patterns: {},
65 ...context.options[0]
66 };
67 const replacements = getReplacements(patterns);
68
69 if (replacements.length === 0) {
70 return {};
71 }
72
73 return {
74 'Literal, TemplateElement': node => {
75 const {type} = node;
76
77 let string;
78 if (type === 'Literal') {
79 string = node.value;
80 } else if (!isIgnoredTag(node)) {
81 string = node.value.raw;
82 }
83
84 if (!string || typeof string !== 'string') {
85 return;
86 }
87
88 const replacement = replacements.find(({regex}) => regex.test(string));
89
90 if (!replacement) {
91 return;
92 }
93
94 const {fix: autoFix, message = defaultMessage, match, suggest} = replacement;
95 const messageData = {
96 match,
97 suggest
98 };
99 const problem = {
100 node,
101 message,
102 data: messageData
103 };
104
105 const fixed = string.replace(replacement.regex, suggest);
106 const fix = type === 'Literal' ?
107 fixer => fixer.replaceText(
108 node,
109 quoteString(fixed, node.raw[0])
110 ) :
111 fixer => replaceTemplateElement(
112 fixer,
113 node,
114 escapeTemplateElementRaw(fixed)
115 );
116
117 if (autoFix) {
118 problem.fix = fix;
119 } else {
120 problem.suggest = [
121 {
122 messageId: SUGGESTION_MESSAGE_ID,
123 data: messageData,
124 fix
125 }
126 ];
127 }
128
129 context.report(problem);
130 }
131 };
132};
133
134const schema = [
135 {
136 type: 'object',
137 properties: {
138 patterns: {
139 type: 'object',
140 additionalProperties: {
141 anyOf: [
142 {
143 type: 'string'
144 },
145 {
146 type: 'object',
147 required: [
148 'suggest'
149 ],
150 properties: {
151 suggest: {
152 type: 'string'
153 },
154 fix: {
155 type: 'boolean'
156 // Default: true
157 },
158 message: {
159 type: 'string'
160 // Default: ''
161 }
162 },
163 additionalProperties: false
164 }
165 ]
166 }}
167 },
168 additionalProperties: false
169 }
170];
171
172module.exports = {
173 create,
174 meta: {
175 type: 'suggestion',
176 docs: {
177 url: getDocumentationUrl(__filename)
178 },
179 fixable: 'code',
180 schema,
181 messages: {
182 [SUGGESTION_MESSAGE_ID]: 'Replace `{{match}}` with `{{suggest}}`.'
183 }
184 }
185};