Skip to content

Commit

Permalink
Merge pull request #18 from jeremejevs/mind-other-plugins
Browse files Browse the repository at this point in the history
Improve interoperability with other plugins
  • Loading branch information
timneutkens authored Sep 28, 2018
2 parents 90f9994 + e914542 commit 64fde2e
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 17 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
/node_modules/
/html-report/
/lib-cov/
/package-lock.json
45 changes: 32 additions & 13 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,26 +1,45 @@
export default function ({ types: t }) {
return {
visitor: {
JSXOpeningElement(path, { file }) {
file.set('hasJSX', true);
},

Program: {
enter(path, { file }) {
file.set('hasJSX', false);
const ourNode = t.importDeclaration([
t.importDefaultSpecifier(t.identifier('React')),
], t.stringLiteral('react'));

// Add an import early, so that other plugins get to see it
file.set('ourPath', path.unshiftContainer('body', ourNode)[0]);
},

exit({ node, scope }, { file }) {
if (!(file.get('hasJSX') && !scope.hasBinding('React'))) {
return;
exit(_, { file }) {
// If our import is still intact and we haven't encountered any JSX in
// the program, then we just remove it. There's an edge case, where
// some other plugin could add JSX in its `Program.exit`, so our
// `JSXOpeningElement` will trigger only after this method, but it's
// likely that said plugin will also add a React import too.
const ourPath = file.get('ourPath');
if (ourPath && !file.get('hasJSX')) {
if (!ourPath.removed) ourPath.remove();
file.set('ourPath', undefined);
}
},
},

const reactImportDeclaration = t.importDeclaration([
t.importDefaultSpecifier(t.identifier('React')),
], t.stringLiteral('react'));
ImportDeclaration(path, { file }) {
// Return early if this has nothing to do with React
if (path.node.specifiers.every(x => x.local.name !== 'React')) return;

node.body.unshift(reactImportDeclaration);
},
// If our import is still intact and we encounter some other import
// which also imports `React`, then we remove ours.
const ourPath = file.get('ourPath');
if (ourPath && path !== ourPath) {
if (!ourPath.removed) ourPath.remove();
file.set('ourPath', undefined);
}
},

JSXOpeningElement(_, { file }) {
file.set('hasJSX', true);
},
},
};
Expand Down
52 changes: 48 additions & 4 deletions test/indexTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,50 @@ try {
reactPlugin = require('../src/index').default;
}

const somePluginEnter = ({ types: t }) => ({
visitor: {
Program(path) {
path.unshiftContainer('body', t.importDeclaration([
t.importDefaultSpecifier(t.identifier('React')),
], t.stringLiteral('react')));
},
},
});

const somePluginExit = ({ types: t }) => ({
visitor: {
Program: {
exit(path) {
path.unshiftContainer('body', t.importDeclaration([
t.importDefaultSpecifier(t.identifier('React')),
], t.stringLiteral('react')));
},
},
},
});

const somePluginCrazy = () => ({
visitor: {
Program(_, { file }) {
file.get('ourPath').remove();
},
},
});

const genericInput = 'export default class Component {render() {return <div />}}';
const genericOutput = 'import React from "react";\nexport default class Component {\n render() {\n return <div />;\n }\n}';

describe('babel-plugin-react', () => {
beforeEach(() => {
transform = code => babel.transform(code, {
plugins: ['syntax-jsx', reactPlugin],
transform = (code, pluginsBefore = [], pluginsAfter = []) => babel.transform(code, {
plugins: ['syntax-jsx', ...pluginsBefore, reactPlugin, ...pluginsAfter],
}).code;
});

it('should return transpiled code with required React', () => {
const transformed = transform('export default class Component {render() {return <div />}}');
const transformed = transform(genericInput);

assert.equal(transformed, 'import React from "react";\nexport default class Component {\n render() {\n return <div />;\n }\n}');
assert.equal(transformed, genericOutput);
});

it('should return not transpiled code', () => {
Expand All @@ -41,4 +74,15 @@ describe('babel-plugin-react', () => {

assert.equal(transformed, 'import React from "react/addons";\nclass Component {\n render() {\n return <div />;\n }\n}');
});

it('should get along with other plugins which add React import', () => {
assert.equal(transform(genericInput, [somePluginEnter]), genericOutput);
assert.equal(transform(genericInput, [somePluginExit]), genericOutput);
assert.equal(transform(genericInput, [], [somePluginEnter]), genericOutput);
assert.equal(transform(genericInput, [], [somePluginExit]), genericOutput);
});

it('should not blow up if another plugin removes our import', () => {
assert.equal(transform(genericInput, [], [somePluginCrazy]), 'export default class Component {\n render() {\n return <div />;\n }\n}');
});
});

0 comments on commit 64fde2e

Please sign in to comment.