babel-plugin-idx
Advanced tools
Comparing version 1.5.1 to 2.0.0
@@ -38,2 +38,38 @@ /** | ||
function checkIdxBindingNode(file, node) { | ||
if (t.isImportDeclaration(node)) { | ||
// E.g. `import '...'` | ||
if (node.specifiers.length === 0) { | ||
throw file.buildCodeFrameError(node, 'The idx import must have a value.'); | ||
} | ||
// E.g. `import A, {B} from '...'` | ||
// `import A, * as B from '...'` | ||
// `import {A, B} from '...'` | ||
if (node.specifiers.length > 1) { | ||
throw file.buildCodeFrameError(node.specifiers[1], 'The idx import must be a single specifier.'); | ||
} | ||
// `import {default as idx} from '...'` or `import idx from '...'` are ok. | ||
// `import idx, * as idx2 from '...'` is not ok but would've been caught | ||
// above. | ||
if (!t.isSpecifierDefault(node.specifiers[0])) { | ||
throw file.buildCodeFrameError(node.specifiers[0], 'The idx import must be a default import.'); | ||
} | ||
// `importKind` is not a property unless flow syntax is enabled. | ||
// On specifiers, `importKind` is not "value" when it's not a type, it's | ||
// `null`. | ||
// E.g. `import type {...} from '...'` | ||
// `import typeof {...} from '...'` | ||
// `import {type ...} from '...'`. | ||
// `import {typeof ...} from '...'` | ||
if (node.importKind === 'type' || node.importKind === 'typeof' || node.specifiers[0].importKind === 'type' || node.specifiers[0].importKind === 'typeof') { | ||
throw file.buildCodeFrameError(node, 'The idx import must be a value import.'); | ||
} | ||
} else if (t.isVariableDeclarator(node)) { | ||
// E.g. var {idx} or var [idx] | ||
if (!t.isIdentifier(node.id)) { | ||
throw file.buildCodeFrameError(node.specifiers[0], 'The idx declaration must be an identifier.'); | ||
} | ||
} | ||
} | ||
function makeCondition(node, state, inside) { | ||
@@ -48,5 +84,3 @@ if (inside) { | ||
function makeChain(node, state, inside) { | ||
if (t.isCallExpression(node)) { | ||
return makeChain(node.callee, state, makeCondition(t.CallExpression(state.temp, node.arguments), state, inside)); | ||
} else if (t.isMemberExpression(node)) { | ||
if (t.isMemberExpression(node)) { | ||
return makeChain(node.object, state, makeCondition(t.MemberExpression(state.temp, node.property, node.computed), state, inside)); | ||
@@ -59,21 +93,66 @@ } else if (t.isIdentifier(node)) { | ||
} else { | ||
throw state.file.buildCodeFrameError(node, 'The `idx` body can only be composed of properties and methods.'); | ||
throw state.file.buildCodeFrameError(node, 'idx callbacks may only access properties on the callback parameter.'); | ||
} | ||
} | ||
var idxVisitor = { | ||
CallExpression: function CallExpression(path, state) { | ||
var node = path.node; | ||
if (t.isIdentifier(node.callee) && node.callee.name === 'idx') { | ||
checkIdxArguments(state.file, node); | ||
var temp = path.scope.generateUidIdentifier('ref'); | ||
var replacement = makeChain(node.arguments[1].body, { | ||
file: state.file, | ||
input: node.arguments[0], | ||
base: node.arguments[1].params[0], | ||
temp: temp | ||
}); | ||
path.replaceWith(replacement); | ||
path.scope.push({ id: temp }); | ||
function visitIdxCallExpression(path, state) { | ||
var node = path.node; | ||
checkIdxArguments(state.file, node); | ||
var temp = path.scope.generateUidIdentifier('ref'); | ||
var replacement = makeChain(node.arguments[1].body, { | ||
file: state.file, | ||
input: node.arguments[0], | ||
base: node.arguments[1].params[0], | ||
temp: temp | ||
}); | ||
path.replaceWith(replacement); | ||
// Hoist to the top if it's an async method. | ||
if (path.scope.path.isClassMethod({ async: true })) { | ||
path.scope.push({ id: temp, _blockHoist: 3 }); | ||
} else { | ||
path.scope.push({ id: temp }); | ||
} | ||
} | ||
function isIdxImportOrRequire(node, name) { | ||
if (t.isImportDeclaration(node)) { | ||
return t.isStringLiteral(node.source, { value: name }); | ||
} else if (t.isVariableDeclarator(node)) { | ||
return t.isCallExpression(node.init) && t.isIdentifier(node.init.callee, { name: 'require' }) && t.isLiteral(node.init.arguments[0], { value: name }); | ||
} else { | ||
return false; | ||
} | ||
} | ||
var declareVisitor = { | ||
'ImportDeclaration|VariableDeclarator': function ImportDeclarationVariableDeclarator(path, state) { | ||
if (!isIdxImportOrRequire(path.node, state.importName)) { | ||
return; | ||
} | ||
checkIdxBindingNode(state.file, path.node); | ||
var bindingName = t.isImportDeclaration(path.node) ? path.node.specifiers[0].local.name : path.node.id.name; | ||
var idxBinding = path.scope.getOwnBinding(bindingName); | ||
idxBinding.constantViolations.forEach(function (refPath) { | ||
throw state.file.buildCodeFrameError(refPath.node, '`idx` cannot be redefined.'); | ||
}); | ||
var didTransform = false; | ||
var didSkip = false; | ||
idxBinding.referencePaths.forEach(function (refPath) { | ||
if (refPath.node === idxBinding.node) { | ||
// Do nothing... | ||
} else if (refPath.parentPath.isCallExpression()) { | ||
visitIdxCallExpression(refPath.parentPath, state); | ||
didTransform = true; | ||
} else { | ||
// Should this throw? | ||
didSkip = true; | ||
} | ||
}); | ||
if (didTransform && !didSkip) { | ||
path.remove(); | ||
} | ||
} | ||
@@ -85,4 +164,5 @@ }; | ||
Program: function Program(path, state) { | ||
var importName = state.opts.importName || 'idx'; | ||
// If there can't reasonably be an idx call, exit fast. | ||
if (path.scope.getOwnBinding('idx') || idxRe.test(state.file.code)) { | ||
if (importName !== 'idx' || idxRe.test(state.file.code)) { | ||
// We're very strict about the shape of idx. Some transforms, like | ||
@@ -92,3 +172,4 @@ // "babel-plugin-transform-async-to-generator", will convert arrow | ||
// our transformation before any one else interferes. | ||
path.traverse(idxVisitor, state); | ||
var newState = { file: state.file, importName: importName }; | ||
path.traverse(declareVisitor, newState); | ||
} | ||
@@ -95,0 +176,0 @@ } |
{ | ||
"name": "babel-plugin-idx", | ||
"version": "1.5.1", | ||
"version": "2.0.0", | ||
"description": "Babel plugin for transforming the idx utility function.", | ||
@@ -5,0 +5,0 @@ "main": "lib/babel-plugin-idx.js", |
@@ -11,6 +11,6 @@ # idx [![Circle Status](https://circleci.com/gh/facebookincubator/idx/tree/master.svg?style=shield&circle-token=da61f3cf105f22309c8ca0ba4482daa538bf5349)](https://circleci.com/gh/facebookincubator/idx) | ||
Consider the following type: | ||
Consider the following type for `props`: | ||
```javascript | ||
const props: { | ||
type User = { | ||
user: ?{ | ||
@@ -41,2 +41,16 @@ name: string, | ||
## Flow Type | ||
[Flow](https://flow.org/) understands the `idx` idiom: | ||
```javascript | ||
// @flow | ||
import idx from 'idx'; | ||
function getName(props: User): ?string { | ||
return idx(props, _ => _.user.name); | ||
} | ||
``` | ||
## Babel Transform | ||
@@ -46,11 +60,41 @@ | ||
behavior and is not meant to be executed. The `idx` function is used in | ||
conjunction with a Babel plugin that replaces it with better performing code: | ||
conjunction with a Babel plugin that replaces it with better performing code. | ||
This babel plugin searches for requires or imports to the `idx` module and | ||
replaces all its usages, so this code: | ||
```javascript | ||
props.user == null ? props.user : | ||
props.user.friends == null ? props.user.friends : | ||
props.user.friends[0] == null ? props.user.friends[0] : | ||
props.user.friends[0].friends | ||
import idx from 'idx'; | ||
function getFriends() { | ||
return idx(props, _ => _.user.friends[0].friends) | ||
}; | ||
``` | ||
gets transformed to something like: | ||
```javascript | ||
function getFriends() { | ||
props.user == null ? props.user : | ||
props.user.friends == null ? props.user.friends : | ||
props.user.friends[0] == null ? props.user.friends[0] : | ||
return props.user.friends[0].friends | ||
} | ||
``` | ||
(note that the original `import` gets also removed). | ||
It's possible to customize the name of the import/require, so code that is not | ||
directly requiring the `idx` npm package can also get transformed: | ||
```javascript | ||
{ | ||
plugins: [ | ||
["babel-plugin-idx", { | ||
importName: './idx', | ||
}] | ||
] | ||
} | ||
``` | ||
All this machinery exists due to the fact that an existential operator does not | ||
@@ -57,0 +101,0 @@ currently exist in JavaScript. |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
10283
158
104
0