WIP - add extractor, generate snippet_data

This commit is contained in:
Stefan Fejes
2019-08-20 15:52:05 +02:00
parent 88084d3d30
commit cc8f1d8a7a
37396 changed files with 4588842 additions and 133 deletions

View File

@ -0,0 +1,5 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`attempting to create a macros with the configName of options throws an error 1`] = `"You cannot use the configName \\"options\\". It is reserved for babel-plugin-macros."`;
exports[`throws error if it is not transpiled 1`] = `"The macro you imported from \\"untranspiled.macro\\" 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"`;

View File

@ -0,0 +1,381 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`macros Macros are applied in the order respecting plugins order: Macros are applied in the order respecting plugins order 1`] = `
import Wrap from "./fixtures/jsx-id-prefix.macro";
const bar = Wrap(<div id="d1"><p id="p1"></p></div>);
↓ ↓ ↓ ↓ ↓ ↓
const bar = Wrap(<div id="plugin-macro-d1"><p id="plugin-macro-p1"></p></div>);
`;
exports[`macros Supports named imports: Supports named imports 1`] = `
import {css as CSS, styled as STYLED} from './fixtures/emotion.macro'
const red = CSS\`
background-color: red;
\`
const Div = STYLED.div\`
composes: \${red}
color: blue;
\`
↓ ↓ ↓ ↓ ↓ ↓
const red = "background-color: red;";
const Div = STYLED.div\`undefined\`;
`;
exports[`macros Works as a JSXElement: Works as a JSXElement 1`] = `
import MyEval from './fixtures/eval.macro'
const x = <MyEval>34 + 45</MyEval>
↓ ↓ ↓ ↓ ↓ ↓
const x = 79;
`;
exports[`macros appends the npm URL for errors thrown by node modules with a slash: appends the npm URL for errors thrown by node modules with a slash 1`] = `
import errorThrower from 'babel-plugin-macros-test-error-thrower/macro'
errorThrower('hi')
↓ ↓ ↓ ↓ ↓ ↓
Error: not helpful Learn more: https://www.npmjs.com/package/babel-plugin-macros-test-error-thrower
`;
exports[`macros appends the npm URL for errors thrown by node modules: appends the npm URL for errors thrown by node modules 1`] = `
import errorThrower from 'babel-plugin-macros-test-error-thrower.macro'
errorThrower('hi')
↓ ↓ ↓ ↓ ↓ ↓
Error: not helpful Learn more: https://www.npmjs.com/package/babel-plugin-macros-test-error-thrower.macro
`;
exports[`macros does nothing but remove macros if it is unused: does nothing but remove macros if it is unused 1`] = `
import foo from "./fixtures/eval.macro";
const bar = 42;
↓ ↓ ↓ ↓ ↓ ↓
const bar = 42;
`;
exports[`macros forwards MacroErrors thrown by the macro: forwards MacroErrors thrown by the macro 1`] = `
import errorThrower from './fixtures/macro-error-thrower.macro'
errorThrower('hey')
↓ ↓ ↓ ↓ ↓ ↓
MacroError: very helpful
`;
exports[`macros macros can set their configName and get their config: macros can set their configName and get their config 1`] = `
import configured from './configurable.macro'
// eslint-disable-next-line babel/no-unused-expressions
configured\`stuff\`
↓ ↓ ↓ ↓ ↓ ↓
// eslint-disable-next-line babel/no-unused-expressions
configured\`stuff\`;
`;
exports[`macros optionally keep imports (import declaration): optionally keep imports (import declaration) 1`] = `
import macro from './fixtures/keep-imports.macro'
const red = macro('noop');
↓ ↓ ↓ ↓ ↓ ↓
import macro from './fixtures/keep-imports.macro';
const red = macro('noop');
`;
exports[`macros optionally keep imports (variable assignment): optionally keep imports (variable assignment) 1`] = `
const macro = require('./fixtures/keep-imports.macro')
const red = macro('noop');
↓ ↓ ↓ ↓ ↓ ↓
const macro = require('./fixtures/keep-imports.macro');
const red = macro('noop');
`;
exports[`macros optionally keep imports in combination with babel-preset-env (#80): optionally keep imports in combination with babel-preset-env (#80) 1`] = `
import macro from './fixtures/keep-imports.macro'
const red = macro('noop')
↓ ↓ ↓ ↓ ↓ ↓
import macro from './fixtures/keep-imports.macro';
const red = macro('noop');
`;
exports[`macros prepends the relative path for errors thrown by the macro: prepends the relative path for errors thrown by the macro 1`] = `
import errorThrower from './fixtures/error-thrower.macro'
errorThrower('hey')
↓ ↓ ↓ ↓ ↓ ↓
Error: very unhelpful
`;
exports[`macros raises an error if macro does not exist: raises an error if macro does not exist 1`] = `
import foo from './some-macros-that-doesnt-even-need-to-exist.macro'
export default 'something else'
↓ ↓ ↓ ↓ ↓ ↓
Error: Cannot find module './some-macros-that-doesnt-even-need-to-exist.macro' from '<PROJECT_ROOT>/src/__tests__'
`;
exports[`macros supports compiled macros (\`__esModule\` + \`export default\`): supports compiled macros (\`__esModule\` + \`export default\`) 1`] = `
import {css, styled} from './fixtures/emotion-esm.macro'
const red = css\`
background-color: red;
\`
const Div = styled.div\`
composes: \${red}
color: blue;
\`
↓ ↓ ↓ ↓ ↓ ↓
const red = css\`
background-color: red;
\`;
const Div = styled.div\`
composes: \${red}
color: blue;
\`;
`;
exports[`macros supports macros from node_modules: supports macros from node_modules 1`] = `
import fakeMacro from 'babel-plugin-macros-test-fake/macro'
fakeMacro('hi')
↓ ↓ ↓ ↓ ↓ ↓
fakeMacro('hi');
`;
exports[`macros throws an error if the macro is not properly wrapped: throws an error if the macro is not properly wrapped 1`] = `
import unwrapped from './fixtures/non-wrapped.macro'
unwrapped('hey')
↓ ↓ ↓ ↓ ↓ ↓
Error: https://github.com/kentcdodds/babel-plugin-macros/blob/master/other/docs/author.md#writing-a-macro
`;
exports[`macros when a plugin that replaces paths is used, macros still work properly: when a plugin that replaces paths is used, macros still work properly 1`] = `
import myEval from '../eval.macro'
const result = myEval\`+('4' + '2')\`
global.result = result
↓ ↓ ↓ ↓ ↓ ↓
const result = ("foobar", 42);
global.result = result;
`;
exports[`macros when configuration is specified in plugin options: when configuration is specified in plugin options 1`] = `
import configured from './configurable.macro'
// eslint-disable-next-line babel/no-unused-expressions
configured\`stuff\`
↓ ↓ ↓ ↓ ↓ ↓
// eslint-disable-next-line babel/no-unused-expressions
configured\`stuff\`;
`;
exports[`macros when configuration is specified in plugin options: when configuration is specified in plugin options 2`] = `
const configured = require('./configurable.macro')
// eslint-disable-next-line babel/no-unused-expressions
configured\`stuff\`
↓ ↓ ↓ ↓ ↓ ↓
// eslint-disable-next-line babel/no-unused-expressions
configured\`stuff\`;
`;
exports[`macros when configuration is specified incorrectly in plugin options: when configuration is specified incorrectly in plugin options 1`] = `
import configured from './configurable.macro'
// eslint-disable-next-line babel/no-unused-expressions
configured\`stuff\`
↓ ↓ ↓ ↓ ↓ ↓
// eslint-disable-next-line babel/no-unused-expressions
configured\`stuff\`;
`;
exports[`macros when plugin options configuration cannot be merged with file configuration: when plugin options configuration cannot be merged with file configuration 1`] = `
import configured from './configurable.macro'
// eslint-disable-next-line babel/no-unused-expressions
configured\`stuff\`
↓ ↓ ↓ ↓ ↓ ↓
Error: <PROJECT_ROOT>/src/__tests__/fixtures/primitive-config/babel-plugin-macros.config.js specified a configurableMacro config of type object, but the the macros plugin's options.configurableMacro did contain an object. Both configs must contain objects for their options to be mergeable.
`;
exports[`macros when there is an error reading the config, a helpful message is logged 1`] = `
Array [
There was an error trying to load the config "configurableMacro" for the macro imported from "./configurable.macro. Please see the error thrown for more information.,
]
`;
exports[`macros when there is an error reading the config, a helpful message is logged: when there is an error reading the config, a helpful message is logged 1`] = `
import configured from './configurable.macro'
// eslint-disable-next-line babel/no-unused-expressions
configured\`stuff\`
↓ ↓ ↓ ↓ ↓ ↓
Error: this is a cosmiconfig error
`;
exports[`macros when there is no config to load, then no config is passed: when there is no config to load, then no config is passed 1`] = `
import configured from './configurable.macro'
// eslint-disable-next-line babel/no-unused-expressions
configured\`stuff\`
↓ ↓ ↓ ↓ ↓ ↓
// eslint-disable-next-line babel/no-unused-expressions
configured\`stuff\`;
`;
exports[`macros works with function calls: works with function calls 1`] = `
import myEval from './fixtures/eval.macro'
const x = myEval('34 + 45')
↓ ↓ ↓ ↓ ↓ ↓
const x = 79;
`;
exports[`macros works with import: works with import 1`] = `
import myEval from './fixtures/eval.macro'
const x = myEval\`34 + 45\`
↓ ↓ ↓ ↓ ↓ ↓
const x = 79;
`;
exports[`macros works with require destructuring and aliasing: works with require destructuring and aliasing 1`] = `
const {css: CSS, styled: STYLED} = require('./fixtures/emotion.macro')
const red = CSS\`
background-color: red;
\`
const Div = STYLED.div\`
composes: \${red}
color: blue;
\`
↓ ↓ ↓ ↓ ↓ ↓
const red = "background-color: red;";
const Div = STYLED.div\`undefined\`;
`;
exports[`macros works with require destructuring: works with require destructuring 1`] = `
const {css, styled} = require('./fixtures/emotion.macro')
const red = css\`
background-color: red;
\`
const Div = styled.div\`
composes: \${red}
color: blue;
\`
↓ ↓ ↓ ↓ ↓ ↓
const red = "background-color: red;";
const Div = styled.div\`undefined\`;
`;
exports[`macros works with require: works with require 1`] = `
const evaler = require('./fixtures/eval.macro')
const x = evaler\`34 + 45\`
↓ ↓ ↓ ↓ ↓ ↓
const x = 79;
`;

View File

@ -0,0 +1,14 @@
const {createMacro} = require('../')
test('throws error if it is not transpiled', () => {
const untranspiledMacro = createMacro(() => {})
expect(() =>
untranspiledMacro({source: 'untranspiled.macro'}),
).toThrowErrorMatchingSnapshot()
})
test('attempting to create a macros with the configName of options throws an error', () => {
expect(() =>
createMacro(() => {}, {configName: 'options'}),
).toThrowErrorMatchingSnapshot()
})

View File

@ -0,0 +1,6 @@
module.exports = {
configurableMacro: {
fileConfig: true,
someConfig: true,
},
}

View File

@ -0,0 +1,4 @@
const configured = require('./configurable.macro')
// eslint-disable-next-line babel/no-unused-expressions
configured`stuff`

View File

@ -0,0 +1,4 @@
import configured from './configurable.macro'
// eslint-disable-next-line babel/no-unused-expressions
configured`stuff`

View File

@ -0,0 +1,10 @@
const {createMacro} = require('../../../../')
const configName = 'configurableMacro'
const realMacro = jest.fn()
module.exports = createMacro(realMacro, {configName})
// for testing purposes only
Object.assign(module.exports, {
realMacro,
configName,
})

View File

@ -0,0 +1,8 @@
const {createMacro} = require('../../')
export default createMacro(evalMacro)
function evalMacro() {
// we're lazy right now
// we don't want to eval
}

View File

@ -0,0 +1,29 @@
// this is a fake version of emotion
// const printAST = require('ast-pretty-print')
const {createMacro} = require('../../')
module.exports = createMacro(emotionMacro)
function emotionMacro({references, babel}) {
const {types: t} = babel
references.css.forEach(cssRef => {
if (cssRef.parentPath.type === 'TaggedTemplateExpression') {
cssRef.parentPath.replaceWith(
t.stringLiteral(
cssRef.parentPath
.get('quasi')
.evaluate()
.value.trim(),
),
)
}
})
references.styled.forEach(styledRef => {
if (styledRef.parentPath.parentPath.type === 'TaggedTemplateExpression') {
const quasi = styledRef.parentPath.parentPath.get('quasi')
const val = quasi.evaluate().value.trim()
const replacement = t.templateLiteral([t.templateElement(val)], [])
quasi.replaceWith(replacement)
}
})
}

View File

@ -0,0 +1,8 @@
// const printAST = require('ast-pretty-print')
const {createMacro} = require('../../')
module.exports = createMacro(evalMacro)
function evalMacro() {
throw new Error('very unhelpful')
}

View File

@ -0,0 +1,54 @@
const {parse} = require('@babel/parser')
// const printAST = require('ast-pretty-print')
const {createMacro} = require('../../')
module.exports = createMacro(evalMacro)
function evalMacro({references, state}) {
references.default.forEach(referencePath => {
if (referencePath.parentPath.type === 'TaggedTemplateExpression') {
asTag(referencePath.parentPath.get('quasi'), state)
} else if (referencePath.parentPath.type === 'CallExpression') {
asFunction(referencePath.parentPath.get('arguments'), state)
} else if (referencePath.parentPath.type === 'JSXOpeningElement') {
asJSX(
{
attributes: referencePath.parentPath.get('attributes'),
children: referencePath.parentPath.parentPath.get('children'),
},
state,
)
} else {
// TODO: throw a helpful error message
}
})
}
function asTag(quasiPath) {
const value = quasiPath.parentPath.get('quasi').evaluate().value
quasiPath.parentPath.replaceWith(evalToAST(value))
}
function asFunction(argumentsPaths) {
const value = argumentsPaths[0].evaluate().value
argumentsPaths[0].parentPath.replaceWith(evalToAST(value))
}
// eslint-disable-next-line no-unused-vars
function asJSX({attributes, children}) {
// It's a shame you cannot use evaluate() with JSX
const value = children[0].node.value
children[0].parentPath.replaceWith(evalToAST(value))
}
function evalToAST(value) {
let x
// eslint-disable-next-line
eval(`x = ${value}`)
return thingToAST(x)
}
function thingToAST(object) {
const fileNode = parse(`var x = ${JSON.stringify(object)}`)
return fileNode.program.body[0].declarations[0].init
}

View File

@ -0,0 +1,20 @@
// adds "prefix-" to each `id` attribute
const {createMacro} = require('../../')
module.exports = createMacro(wrapWidget)
function wrapWidget({references, babel}) {
const {types: t} = babel
references.default.forEach(wrap => {
wrap.parentPath.traverse({
JSXAttribute(path) {
const name = path.get('name')
if (t.isJSXIdentifier(name) && name.node.name === 'id') {
const value = path.get('value')
if (t.isStringLiteral(value))
value.replaceWith(t.stringLiteral(`macro-${value.node.value}`))
}
},
})
})
}

View File

@ -0,0 +1,23 @@
// babel-plugin adding `plugin-` prefix to each "id" JSX attribute
module.exports = main
function main({types: t}) {
return {
visitor: {
// intentionally traversing from Program,
// if it matches JSXAttribute here the issue won't be reproduced
Program(progPath) {
progPath.traverse({
JSXAttribute(path) {
const name = path.get('name')
if (t.isJSXIdentifier(name) && name.node.name === 'id') {
const value = path.get('value')
if (t.isStringLiteral(value))
value.replaceWith(t.stringLiteral(`plugin-${value.node.value}`))
}
},
})
},
},
}
}

View File

@ -0,0 +1,7 @@
const {createMacro} = require('../../')
module.exports = createMacro(keepImportMacro)
function keepImportMacro() {
return {keepImports: true}
}

View File

@ -0,0 +1,8 @@
// const printAST = require('ast-pretty-print')
const {createMacro, MacroError} = require('../../')
module.exports = createMacro(evalMacro)
function evalMacro() {
throw new MacroError('very helpful')
}

View File

@ -0,0 +1 @@
module.exports = () => {}

View File

@ -0,0 +1,5 @@
import myEval from '../eval.macro'
const result = myEval`+('4' + '2')`
global.result = result

View File

@ -0,0 +1,3 @@
module.exports = {
configurableMacro: 4,
}

View File

@ -0,0 +1,4 @@
import configured from './configurable.macro'
// eslint-disable-next-line babel/no-unused-expressions
configured`stuff`

View File

@ -0,0 +1,10 @@
const {createMacro} = require('../../../../')
const configName = 'configurableMacro'
const realMacro = jest.fn()
module.exports = createMacro(realMacro, {configName})
// for testing purposes only
Object.assign(module.exports, {
realMacro,
configName,
})

View File

@ -0,0 +1,452 @@
/* eslint no-console:0 */
import path from 'path'
import cosmiconfigMock from 'cosmiconfig'
import cpy from 'cpy'
import babel from '@babel/core'
import pluginTester from 'babel-plugin-tester'
import plugin from '../'
const projectRoot = path.join(__dirname, '../../')
jest.mock('cosmiconfig', () => {
const mockSearchSync = jest.fn()
Object.assign(mockSearchSync, {
mockReset() {
return mockSearchSync.mockImplementation(
(filename, configuredCosmiconfig) =>
configuredCosmiconfig.searchSync(filename),
)
},
})
mockSearchSync.mockReset()
const _cosmiconfigMock = (...args) => ({
searchSync(filename) {
return mockSearchSync(
filename,
require.requireActual('cosmiconfig')(...args),
)
},
})
return Object.assign(_cosmiconfigMock, {mockSearchSync})
})
beforeAll(() => {
// copy our mock modules to the node_modules directory
// so we can test how things work when importing a macro
// from the node_modules directory.
return cpy(['**/*.js'], path.join('..', '..', 'node_modules'), {
parents: true,
cwd: path.join(projectRoot, 'other', 'mock-modules'),
})
})
afterEach(() => {
// eslint-disable-next-line
require('babel-plugin-macros-test-fake/macro').innerFn.mockClear()
cosmiconfigMock.mockSearchSync.mockReset()
})
expect.addSnapshotSerializer({
print(val) {
return (
val
.split(projectRoot)
.join('<PROJECT_ROOT>/')
.replace(/\\/g, '/')
// Remove the path of file which thrown an error
.replace(/Error:[^:]*:/, 'Error:')
)
},
test(val) {
return typeof val === 'string'
},
})
pluginTester({
plugin,
snapshot: true,
babelOptions: {
filename: __filename,
parserOpts: {
plugins: ['jsx'],
},
generatorOpts: {quotes: 'double'},
},
tests: [
{
title: 'does nothing to code that does not import macro',
snapshot: false,
code: `
import foo from "./some-file-without-macro";
const bar = require("./some-other-file-without-macro");
`,
},
{
title: 'does nothing but remove macros if it is unused',
snapshot: true,
code: `
import foo from "./fixtures/eval.macro";
const bar = 42;
`,
},
{
title: 'raises an error if macro does not exist',
error: true,
code: `
import foo from './some-macros-that-doesnt-even-need-to-exist.macro'
export default 'something else'
`,
},
{
title: 'works with import',
code: `
import myEval from './fixtures/eval.macro'
const x = myEval\`34 + 45\`
`,
},
{
title: 'works with require',
code: `
const evaler = require('./fixtures/eval.macro')
const x = evaler\`34 + 45\`
`,
},
{
title: 'works with require destructuring',
code: `
const {css, styled} = require('./fixtures/emotion.macro')
const red = css\`
background-color: red;
\`
const Div = styled.div\`
composes: \${red}
color: blue;
\`
`,
},
{
title: 'works with require destructuring and aliasing',
code: `
const {css: CSS, styled: STYLED} = require('./fixtures/emotion.macro')
const red = CSS\`
background-color: red;
\`
const Div = STYLED.div\`
composes: \${red}
color: blue;
\`
`,
},
{
title: 'works with function calls',
code: `
import myEval from './fixtures/eval.macro'
const x = myEval('34 + 45')
`,
},
{
title: 'Works as a JSXElement',
code: `
import MyEval from './fixtures/eval.macro'
const x = <MyEval>34 + 45</MyEval>
`,
},
{
title: 'Supports named imports',
code: `
import {css as CSS, styled as STYLED} from './fixtures/emotion.macro'
const red = CSS\`
background-color: red;
\`
const Div = STYLED.div\`
composes: \${red}
color: blue;
\`
`,
},
{
title: 'supports compiled macros (`__esModule` + `export default`)',
code: `
import {css, styled} from './fixtures/emotion-esm.macro'
const red = css\`
background-color: red;
\`
const Div = styled.div\`
composes: \${red}
color: blue;
\`
`,
},
{
title: 'supports macros from node_modules',
code: `
import fakeMacro from 'babel-plugin-macros-test-fake/macro'
fakeMacro('hi')
`,
teardown() {
try {
// kinda abusing the babel-plugin-tester API here
// to make an extra assertion
// eslint-disable-next-line
const fakeMacro = require('babel-plugin-macros-test-fake/macro')
expect(fakeMacro.innerFn).toHaveBeenCalledTimes(1)
expect(fakeMacro.innerFn).toHaveBeenCalledWith({
references: expect.any(Object),
source: expect.stringContaining(
'babel-plugin-macros-test-fake/macro',
),
state: expect.any(Object),
babel: expect.any(Object),
isBabelMacrosCall: true,
})
expect(fakeMacro.innerFn.mock.calls[0].babel).toBe(babel)
} catch (e) {
console.error(e)
throw e
}
},
},
{
title: 'optionally keep imports (variable assignment)',
code: `
const macro = require('./fixtures/keep-imports.macro')
const red = macro('noop');
`,
},
{
title: 'optionally keep imports (import declaration)',
code: `
import macro from './fixtures/keep-imports.macro'
const red = macro('noop');
`,
},
{
title:
'optionally keep imports in combination with babel-preset-env (#80)',
code: `
import macro from './fixtures/keep-imports.macro'
const red = macro('noop')
`,
babelOptions: {
plugins: [
require.resolve('babel-plugin-transform-es2015-modules-commonjs'),
],
},
},
{
title: 'throws an error if the macro is not properly wrapped',
error: true,
code: `
import unwrapped from './fixtures/non-wrapped.macro'
unwrapped('hey')
`,
},
{
title: 'forwards MacroErrors thrown by the macro',
error: true,
code: `
import errorThrower from './fixtures/macro-error-thrower.macro'
errorThrower('hey')
`,
},
{
title: 'prepends the relative path for errors thrown by the macro',
error: true,
code: `
import errorThrower from './fixtures/error-thrower.macro'
errorThrower('hey')
`,
},
{
title: 'appends the npm URL for errors thrown by node modules',
error: true,
code: `
import errorThrower from 'babel-plugin-macros-test-error-thrower.macro'
errorThrower('hi')
`,
},
{
title:
'appends the npm URL for errors thrown by node modules with a slash',
error: true,
code: `
import errorThrower from 'babel-plugin-macros-test-error-thrower/macro'
errorThrower('hi')
`,
},
{
title: 'macros can set their configName and get their config',
fixture: path.join(__dirname, 'fixtures/config/code.js'),
teardown() {
try {
const babelMacrosConfig = require('./fixtures/config/babel-plugin-macros.config')
const configurableMacro = require('./fixtures/config/configurable.macro')
expect(configurableMacro.realMacro).toHaveBeenCalledTimes(1)
expect(configurableMacro.realMacro.mock.calls[0][0].config).toEqual(
babelMacrosConfig[configurableMacro.configName],
)
configurableMacro.realMacro.mockClear()
} catch (e) {
console.error(e)
throw e
}
},
},
{
title:
'when there is an error reading the config, a helpful message is logged',
error: true,
fixture: path.join(__dirname, 'fixtures/config/code.js'),
setup() {
cosmiconfigMock.mockSearchSync.mockImplementationOnce(() => {
throw new Error('this is a cosmiconfig error')
})
const originalError = console.error
console.error = jest.fn()
return function teardown() {
try {
expect(console.error).toHaveBeenCalledTimes(1)
expect(console.error.mock.calls[0]).toMatchSnapshot()
console.error = originalError
} catch (e) {
console.error(e)
throw e
}
}
},
},
{
title: 'when there is no config to load, then no config is passed',
fixture: path.join(__dirname, 'fixtures/config/code.js'),
setup() {
cosmiconfigMock.mockSearchSync.mockImplementationOnce(() => null)
return function teardown() {
try {
const configurableMacro = require('./fixtures/config/configurable.macro')
expect(configurableMacro.realMacro).toHaveBeenCalledTimes(1)
expect(configurableMacro.realMacro.mock.calls[0][0].config).toEqual(
{},
)
configurableMacro.realMacro.mockClear()
} catch (e) {
console.error(e)
throw e
}
}
},
},
{
title: 'when configuration is specified in plugin options',
pluginOptions: {
configurableMacro: {
someConfig: false,
somePluginConfig: true,
},
},
fixture: path.join(__dirname, 'fixtures/config/code.js'),
teardown() {
try {
const configurableMacro = require('./fixtures/config/configurable.macro')
expect(configurableMacro.realMacro).toHaveBeenCalledTimes(1)
expect(configurableMacro.realMacro.mock.calls[0][0].config).toEqual({
fileConfig: true,
someConfig: true,
somePluginConfig: true,
})
configurableMacro.realMacro.mockClear()
} catch (e) {
console.error(e)
throw e
}
},
},
{
title: 'when configuration is specified in plugin options',
pluginOptions: {
configurableMacro: {
someConfig: false,
somePluginConfig: true,
},
},
fixture: path.join(__dirname, 'fixtures/config/cjs-code.js'),
teardown() {
try {
const configurableMacro = require('./fixtures/config/configurable.macro')
expect(configurableMacro.realMacro).toHaveBeenCalledTimes(1)
expect(configurableMacro.realMacro.mock.calls[0][0].config).toEqual({
fileConfig: true,
someConfig: true,
somePluginConfig: true,
})
configurableMacro.realMacro.mockClear()
} catch (e) {
console.error(e)
throw e
}
},
},
{
title: 'when configuration is specified incorrectly in plugin options',
fixture: path.join(__dirname, 'fixtures/config/code.js'),
pluginOptions: {
configurableMacro: 2,
},
teardown() {
try {
const configurableMacro = require('./fixtures/config/configurable.macro')
expect(configurableMacro.realMacro).toHaveBeenCalledTimes(1)
expect(configurableMacro.realMacro).not.toHaveBeenCalledWith(
expect.objectContaining({
config: expect.any,
}),
)
configurableMacro.realMacro.mockClear()
} catch (e) {
console.error(e)
throw e
}
},
},
{
title:
'when plugin options configuration cannot be merged with file configuration',
error: true,
fixture: path.join(__dirname, 'fixtures/primitive-config/code.js'),
pluginOptions: {
configurableMacro: {},
},
},
{
title:
'when a plugin that replaces paths is used, macros still work properly',
fixture: path.join(
__dirname,
'fixtures/path-replace-issue/variable-assignment.js',
),
babelOptions: {
babelrc: true,
},
},
{
title: 'Macros are applied in the order respecting plugins order',
code: `
import Wrap from "./fixtures/jsx-id-prefix.macro";
const bar = Wrap(<div id="d1"><p id="p1"></p></div>);
`,
babelOptions: {
presets: [{plugins: [require('./fixtures/jsx-id-prefix.plugin')]}],
},
},
],
})

350
node_modules/babel-plugin-macros/dist/index.js generated vendored Normal file
View File

@ -0,0 +1,350 @@
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
var _objectWithoutPropertiesLoose2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutPropertiesLoose"));
const p = require('path');
const resolve = require('resolve'); // const printAST = require('ast-pretty-print')
const macrosRegex = /[./]macro(\.js)?$/; // https://stackoverflow.com/a/32749533/971592
class MacroError extends Error {
constructor(message) {
super(message);
this.name = 'MacroError';
/* istanbul ignore else */
if (typeof Error.captureStackTrace === 'function') {
Error.captureStackTrace(this, this.constructor);
} else if (!this.stack) {
this.stack = new Error(message).stack;
}
}
}
let _configExplorer = null;
function getConfigExporer() {
return _configExplorer = _configExplorer || // Lazy load cosmiconfig since it is a relatively large bundle
require('cosmiconfig')('babel-plugin-macros', {
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'],
packageProp: 'babelMacros',
sync: true
});
}
function createMacro(macro, options = {}) {
if (options.configName === 'options') {
throw new Error(`You cannot use the configName "options". It is reserved for babel-plugin-macros.`);
}
macroWrapper.isBabelMacro = true;
macroWrapper.options = options;
return macroWrapper;
function macroWrapper(args) {
const {
source,
isBabelMacrosCall
} = args;
if (!isBabelMacrosCall) {
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');
}
return macro(args);
}
}
function nodeResolvePath(source, basedir) {
return resolve.sync(source, {
basedir
});
}
function macrosPlugin(babel, _ref = {}) {
let {
require: _require = require,
resolvePath = nodeResolvePath
} = _ref,
options = (0, _objectWithoutPropertiesLoose2.default)(_ref, ["require", "resolvePath"]);
function interopRequire(path) {
// eslint-disable-next-line import/no-dynamic-require
const o = _require(path);
return o && o.__esModule && o.default ? o.default : o;
}
return {
name: 'macros',
visitor: {
Program(progPath, state) {
progPath.traverse({
ImportDeclaration(path) {
const isMacros = looksLike(path, {
node: {
source: {
value: v => macrosRegex.test(v)
}
}
});
if (!isMacros) {
return;
}
const imports = path.node.specifiers.map(s => ({
localName: s.local.name,
importedName: s.type === 'ImportDefaultSpecifier' ? 'default' : s.imported.name
}));
const source = path.node.source.value;
const result = applyMacros({
path,
imports,
source,
state,
babel,
interopRequire,
resolvePath,
options
});
if (!result || !result.keepImports) {
path.remove();
}
},
VariableDeclaration(path) {
const isMacros = child => looksLike(child, {
node: {
init: {
callee: {
type: 'Identifier',
name: 'require'
},
arguments: args => args.length === 1 && macrosRegex.test(args[0].value)
}
}
});
path.get('declarations').filter(isMacros).forEach(child => {
const imports = child.node.id.name ? [{
localName: child.node.id.name,
importedName: 'default'
}] : child.node.id.properties.map(property => ({
localName: property.value.name,
importedName: property.key.name
}));
const call = child.get('init');
const source = call.node.arguments[0].value;
const result = applyMacros({
path: call,
imports,
source,
state,
babel,
interopRequire,
resolvePath,
options
});
if (!result || !result.keepImports) {
child.remove();
}
});
}
});
}
}
};
} // eslint-disable-next-line complexity
function applyMacros({
path,
imports,
source,
state,
babel,
interopRequire,
resolvePath,
options
}) {
/* istanbul ignore next (pretty much only useful for astexplorer I think) */
const {
file: {
opts: {
filename = ''
}
}
} = state;
let hasReferences = false;
const referencePathsByImportName = imports.reduce((byName, {
importedName,
localName
}) => {
const binding = path.scope.getBinding(localName);
byName[importedName] = binding.referencePaths;
hasReferences = hasReferences || Boolean(byName[importedName].length);
return byName;
}, {});
const isRelative = source.indexOf('.') === 0;
const requirePath = resolvePath(source, p.dirname(getFullFilename(filename)));
const macro = interopRequire(requirePath);
if (!macro.isBabelMacro) {
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`);
}
const config = getConfig(macro, filename, source, options);
let result;
try {
/**
* Other plugins that run before babel-plugin-macros might use path.replace, where a path is
* put into its own replacement. Apparently babel does not update the scope after such
* an operation. As a remedy, the whole scope is traversed again with an empty "Identifier"
* visitor - this makes the problem go away.
*
* See: https://github.com/kentcdodds/import-all.macro/issues/7
*/
state.file.scope.path.traverse({
Identifier() {}
});
result = macro({
references: referencePathsByImportName,
source,
state,
babel,
config,
isBabelMacrosCall: true
});
} catch (error) {
if (error.name === 'MacroError') {
throw error;
}
error.message = `${source}: ${error.message}`;
if (!isRelative) {
error.message = `${error.message} Learn more: https://www.npmjs.com/package/${source.replace( // remove everything after package name
// @org/package/macro -> @org/package
// package/macro -> package
/^((?:@[^/]+\/)?[^/]+).*/, '$1')}`;
}
throw error;
}
return result;
}
function getConfigFromFile(configName, filename) {
try {
const loaded = getConfigExporer().searchSync(filename);
if (loaded) {
return {
options: loaded.config[configName],
path: loaded.filepath
};
}
} catch (e) {
return {
error: e
};
}
return {};
}
function getConfigFromOptions(configName, options) {
if (options.hasOwnProperty(configName)) {
if (options[configName] && typeof options[configName] !== 'object') {
// eslint-disable-next-line no-console
console.error(`The macro plugin options' ${configName} property was not an object or null.`);
} else {
return {
options: options[configName]
};
}
}
return {};
}
function getConfig(macro, filename, source, options) {
const {
configName
} = macro.options;
if (configName) {
const fileConfig = getConfigFromFile(configName, filename);
const optionsConfig = getConfigFromOptions(configName, options);
if (optionsConfig.options === undefined && fileConfig.options === undefined) {
// eslint-disable-next-line no-console
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.`);
if (fileConfig.error !== undefined) {
throw fileConfig.error;
}
}
if (fileConfig.options !== undefined && optionsConfig.options !== undefined && typeof fileConfig.options !== 'object') {
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.`);
}
return (0, _extends2.default)({}, optionsConfig.options, fileConfig.options);
}
return undefined;
}
/*
istanbul ignore next
because this is hard to test
and not worth it...
*/
function getFullFilename(filename) {
if (p.isAbsolute(filename)) {
return filename;
}
return p.join(process.cwd(), filename);
}
function looksLike(a, b) {
return a && b && Object.keys(b).every(bKey => {
const bVal = b[bKey];
const aVal = a[bKey];
if (typeof bVal === 'function') {
return bVal(aVal);
}
return isPrimitive(bVal) ? bVal === aVal : looksLike(aVal, bVal);
});
}
function isPrimitive(val) {
// eslint-disable-next-line
return val == null || /^[sbn]/.test(typeof val);
}
module.exports = macrosPlugin;
Object.assign(module.exports, {
createMacro,
MacroError
});