source: frontend/node_modules/babel-plugin-macros/dist/index.js

Last change on this file was 9af201e, checked in by MBK <marija.karapandzova@…>, 13 days ago

Fix frontend appearance

  • Property mode set to 100644
File size: 9.9 KB
Line 
1"use strict";
2
3const p = require('path');
4
5const resolve = require('resolve'); // const printAST = require('ast-pretty-print')
6
7
8const macrosRegex = /[./]macro(\.c?js)?$/;
9
10const testMacrosRegex = v => macrosRegex.test(v); // https://stackoverflow.com/a/32749533/971592
11
12
13class MacroError extends Error {
14 constructor(message) {
15 super(message);
16 this.name = 'MacroError';
17 /* istanbul ignore else */
18
19 if (typeof Error.captureStackTrace === 'function') {
20 Error.captureStackTrace(this, this.constructor);
21 } else if (!this.stack) {
22 this.stack = new Error(message).stack;
23 }
24 }
25
26}
27
28let _configExplorer = null;
29
30function getConfigExplorer() {
31 return _configExplorer = _configExplorer || // Lazy load cosmiconfig since it is a relatively large bundle
32 require('cosmiconfig').cosmiconfigSync('babel-plugin-macros', {
33 searchPlaces: ['package.json', '.babel-plugin-macrosrc', '.babel-plugin-macrosrc.json', '.babel-plugin-macrosrc.yaml', '.babel-plugin-macrosrc.yml', '.babel-plugin-macrosrc.js', 'babel-plugin-macros.config.js'],
34 packageProp: 'babelMacros'
35 });
36}
37
38function createMacro(macro, options = {}) {
39 if (options.configName === 'options') {
40 throw new Error(`You cannot use the configName "options". It is reserved for babel-plugin-macros.`);
41 }
42
43 macroWrapper.isBabelMacro = true;
44 macroWrapper.options = options;
45 return macroWrapper;
46
47 function macroWrapper(args) {
48 const {
49 source,
50 isBabelMacrosCall
51 } = args;
52
53 if (!isBabelMacrosCall) {
54 throw new MacroError(`The macro you imported from "${source}" is being executed outside the context of compilation with babel-plugin-macros. ` + `This indicates that you don't have the babel plugin "babel-plugin-macros" configured correctly. ` + `Please see the documentation for how to configure babel-plugin-macros properly: ` + 'https://github.com/kentcdodds/babel-plugin-macros/blob/master/other/docs/user.md');
55 }
56
57 return macro(args);
58 }
59}
60
61function nodeResolvePath(source, basedir) {
62 return resolve.sync(source, {
63 basedir,
64 extensions: ['.js', '.ts', '.tsx', '.mjs', '.cjs', '.jsx'],
65 // This is here to support the package being globally installed
66 // read more: https://github.com/kentcdodds/babel-plugin-macros/pull/138
67 paths: [p.resolve(__dirname, '../../')]
68 });
69}
70
71function macrosPlugin(babel, // istanbul doesn't like the default of an object for the plugin options
72// but I think older versions of babel didn't always pass options
73// istanbul ignore next
74{
75 require: _require = require,
76 resolvePath = nodeResolvePath,
77 isMacrosName = testMacrosRegex,
78 ...options
79} = {}) {
80 function interopRequire(path) {
81 // eslint-disable-next-line import/no-dynamic-require
82 const o = _require(path);
83
84 return o && o.__esModule && o.default ? o.default : o;
85 }
86
87 return {
88 name: 'macros',
89 visitor: {
90 Program(progPath, state) {
91 progPath.traverse({
92 ImportDeclaration(path) {
93 const isMacros = looksLike(path, {
94 node: {
95 source: {
96 value: v => isMacrosName(v)
97 }
98 }
99 });
100
101 if (!isMacros) {
102 return;
103 }
104
105 const imports = path.node.specifiers.map(s => ({
106 localName: s.local.name,
107 importedName: s.type === 'ImportDefaultSpecifier' ? 'default' : s.imported.name
108 }));
109 const source = path.node.source.value;
110 const result = applyMacros({
111 path,
112 imports,
113 source,
114 state,
115 babel,
116 interopRequire,
117 resolvePath,
118 options
119 });
120
121 if (!result || !result.keepImports) {
122 path.remove();
123 }
124 },
125
126 VariableDeclaration(path) {
127 const isMacros = child => looksLike(child, {
128 node: {
129 init: {
130 callee: {
131 type: 'Identifier',
132 name: 'require'
133 },
134 arguments: args => args.length === 1 && isMacrosName(args[0].value)
135 }
136 }
137 });
138
139 path.get('declarations').filter(isMacros).forEach(child => {
140 const imports = child.node.id.name ? [{
141 localName: child.node.id.name,
142 importedName: 'default'
143 }] : child.node.id.properties.map(property => ({
144 localName: property.value.name,
145 importedName: property.key.name
146 }));
147 const call = child.get('init');
148 const source = call.node.arguments[0].value;
149 const result = applyMacros({
150 path: call,
151 imports,
152 source,
153 state,
154 babel,
155 interopRequire,
156 resolvePath,
157 options
158 });
159
160 if (!result || !result.keepImports) {
161 child.remove();
162 }
163 });
164 }
165
166 });
167 }
168
169 }
170 };
171} // eslint-disable-next-line complexity
172
173
174function applyMacros({
175 path,
176 imports,
177 source,
178 state,
179 babel,
180 interopRequire,
181 resolvePath,
182 options
183}) {
184 /* istanbul ignore next (pretty much only useful for astexplorer I think) */
185 const {
186 file: {
187 opts: {
188 filename = ''
189 }
190 }
191 } = state;
192 let hasReferences = false;
193 const referencePathsByImportName = imports.reduce((byName, {
194 importedName,
195 localName
196 }) => {
197 const binding = path.scope.getBinding(localName);
198 byName[importedName] = binding.referencePaths;
199 hasReferences = hasReferences || Boolean(byName[importedName].length);
200 return byName;
201 }, {});
202 const isRelative = source.indexOf('.') === 0;
203 const requirePath = resolvePath(source, p.dirname(getFullFilename(filename)));
204 const macro = interopRequire(requirePath);
205
206 if (!macro.isBabelMacro) {
207 throw new Error(`The macro imported from "${source}" must be wrapped in "createMacro" ` + `which you can get from "babel-plugin-macros". ` + `Please refer to the documentation to see how to do this properly: https://github.com/kentcdodds/babel-plugin-macros/blob/master/other/docs/author.md#writing-a-macro`);
208 }
209
210 const config = getConfig(macro, filename, source, options);
211 let result;
212
213 try {
214 /**
215 * Other plugins that run before babel-plugin-macros might use path.replace, where a path is
216 * put into its own replacement. Apparently babel does not update the scope after such
217 * an operation. As a remedy, the whole scope is traversed again with an empty "Identifier"
218 * visitor - this makes the problem go away.
219 *
220 * See: https://github.com/kentcdodds/import-all.macro/issues/7
221 */
222 state.file.scope.path.traverse({
223 Identifier() {}
224
225 });
226 result = macro({
227 references: referencePathsByImportName,
228 source,
229 state,
230 babel,
231 config,
232 isBabelMacrosCall: true
233 });
234 } catch (error) {
235 if (error.name === 'MacroError') {
236 throw error;
237 }
238
239 error.message = `${source}: ${error.message}`;
240
241 if (!isRelative) {
242 error.message = `${error.message} Learn more: https://www.npmjs.com/package/${source.replace( // remove everything after package name
243 // @org/package/macro -> @org/package
244 // package/macro -> package
245 /^((?:@[^/]+\/)?[^/]+).*/, '$1')}`;
246 }
247
248 throw error;
249 }
250
251 return result;
252}
253
254function getConfigFromFile(configName, filename) {
255 try {
256 const loaded = getConfigExplorer().search(filename);
257
258 if (loaded) {
259 return {
260 options: loaded.config[configName],
261 path: loaded.filepath
262 };
263 }
264 } catch (e) {
265 return {
266 error: e
267 };
268 }
269
270 return {};
271}
272
273function getConfigFromOptions(configName, options) {
274 if (options.hasOwnProperty(configName)) {
275 if (options[configName] && typeof options[configName] !== 'object') {
276 // eslint-disable-next-line no-console
277 console.error(`The macro plugin options' ${configName} property was not an object or null.`);
278 } else {
279 return {
280 options: options[configName]
281 };
282 }
283 }
284
285 return {};
286}
287
288function getConfig(macro, filename, source, options) {
289 const {
290 configName
291 } = macro.options;
292
293 if (configName) {
294 const fileConfig = getConfigFromFile(configName, filename);
295 const optionsConfig = getConfigFromOptions(configName, options);
296
297 if (optionsConfig.options === undefined && fileConfig.options === undefined && fileConfig.error !== undefined) {
298 // eslint-disable-next-line no-console
299 console.error(`There was an error trying to load the config "${configName}" ` + `for the macro imported from "${source}. ` + `Please see the error thrown for more information.`);
300 throw fileConfig.error;
301 }
302
303 if (fileConfig.options !== undefined && optionsConfig.options !== undefined && typeof fileConfig.options !== 'object') {
304 throw new Error(`${fileConfig.path} specified a ${configName} config of type ` + `${typeof optionsConfig.options}, but the the macros plugin's ` + `options.${configName} did contain an object. Both configs must ` + `contain objects for their options to be mergeable.`);
305 }
306
307 return { ...optionsConfig.options,
308 ...fileConfig.options
309 };
310 }
311
312 return undefined;
313}
314/*
315 istanbul ignore next
316 because this is hard to test
317 and not worth it...
318 */
319
320
321function getFullFilename(filename) {
322 if (p.isAbsolute(filename)) {
323 return filename;
324 }
325
326 return p.join(process.cwd(), filename);
327}
328
329function looksLike(a, b) {
330 return a && b && Object.keys(b).every(bKey => {
331 const bVal = b[bKey];
332 const aVal = a[bKey];
333
334 if (typeof bVal === 'function') {
335 return bVal(aVal);
336 }
337
338 return isPrimitive(bVal) ? bVal === aVal : looksLike(aVal, bVal);
339 });
340}
341
342function isPrimitive(val) {
343 // eslint-disable-next-line
344 return val == null || /^[sbn]/.test(typeof val);
345}
346
347module.exports = macrosPlugin;
348Object.assign(module.exports, {
349 createMacro,
350 MacroError
351});
Note: See TracBrowser for help on using the repository browser.