babel-plugin-which-builtins
Advanced tools
Comparing version 0.0.1 to 0.0.2
@@ -18,2 +18,6 @@ const babel = require('babel-core'); | ||
expect(getBuiltinsForCode('Array.of([1,2,3]);')).toEqual(['core-js/modules/es6.array.iterator','core-js/modules/es6.array.of']); | ||
// negative cases, calling a static method on the wrong object. | ||
expect(getBuiltinsForCode('Reflect.from([1,2,3]);')).toEqual(['core-js/modules/es6.array.iterator']); | ||
expect(getBuiltinsForCode('Reflect.of([1,2,3]);')).toEqual(['core-js/modules/es6.array.iterator']); | ||
}); | ||
@@ -24,4 +28,23 @@ | ||
expect(getBuiltinsForCode('Array["of"]([1,2,3]);')).toEqual(['core-js/modules/es6.array.iterator','core-js/modules/es6.array.of']); | ||
// negative cases, calling a static method on the wrong object. | ||
expect(getBuiltinsForCode('Reflect["from"]([1,2,3]);')).toEqual(['core-js/modules/es6.array.iterator']); | ||
expect(getBuiltinsForCode('Reflect["of"]([1,2,3]);')).toEqual(['core-js/modules/es6.array.iterator']); | ||
}); | ||
test('test destructure static method', () => { | ||
expect(getBuiltinsForCode('var { from } = Array;')).toEqual(['core-js/modules/es6.array.from','core-js/modules/es6.array.iterator']); | ||
expect(getBuiltinsForCode('var { foo, from } = Array;')).toEqual(['core-js/modules/es6.array.from','core-js/modules/es6.array.iterator']); | ||
expect(getBuiltinsForCode('var { from: foo } = Array;')).toEqual(['core-js/modules/es6.array.from','core-js/modules/es6.array.iterator']); | ||
expect(getBuiltinsForCode('var { foo, from: bar } = Array;')).toEqual(['core-js/modules/es6.array.from','core-js/modules/es6.array.iterator']); | ||
expect(getBuiltinsForCode('const { of } = Array;')).toEqual(['core-js/modules/es6.array.iterator','core-js/modules/es6.array.of']); | ||
// negative cases, calling a static method on the wrong object. | ||
expect(getBuiltinsForCode('var { from } = Reflect;')).toEqual(['core-js/modules/es6.array.iterator']); | ||
expect(getBuiltinsForCode('var { foo, from } = Reflect;')).toEqual(['core-js/modules/es6.array.iterator']); | ||
expect(getBuiltinsForCode('var { from: foo } = Reflect;')).toEqual(['core-js/modules/es6.array.iterator']); | ||
expect(getBuiltinsForCode('var { foo, from: bar } = Reflect;')).toEqual(['core-js/modules/es6.array.iterator']); | ||
expect(getBuiltinsForCode('const { of } = Reflect;')).toEqual(['core-js/modules/es6.array.iterator']); | ||
}); | ||
test('test ES5 static method', () => { | ||
@@ -122,1 +145,9 @@ expect(getBuiltinsForCode('Array.isArray([1,2,3]);')).toEqual(['core-js/modules/es6.array.iterator']); | ||
}) | ||
test('destructured instance method', () => { | ||
expect(getBuiltinsForCode('var { codePointAt } = foo;')).toEqual(['core-js/modules/es6.array.iterator','core-js/modules/es6.string.code-point-at']); | ||
}) | ||
test('destructured instance method 2', () => { | ||
expect(getBuiltinsForCode('const { includes } = foo;')).toEqual(['core-js/modules/es6.array.iterator','core-js/modules/es6.string.includes','core-js/modules/es7.array.includes']); | ||
}) |
65
index.js
@@ -127,2 +127,30 @@ const alwaysInclude = [ | ||
function handleStaticPropertyAccess(objectName, propertyName, builtins) { | ||
const localBuiltins = builtins; | ||
if (staticMethods[objectName] | ||
&& staticMethods[objectName][propertyName]) { | ||
// this is an ES2015/2016/2017 static method that is being used in the code. | ||
localBuiltins[staticMethods[objectName][propertyName]] = true; | ||
} | ||
} | ||
function handleInstancePropertyAccess(propertyName, builtins) { | ||
const localBuiltins = builtins; | ||
if (instanceMethods[propertyName]) { | ||
// this is **potentially** a use of an ES2015/2016/2017/2017 instance method. | ||
// include that instance method's polyfill in case. | ||
instanceMethods[propertyName].forEach((module) => { | ||
localBuiltins[module] = true; | ||
}); | ||
} | ||
} | ||
function handlePropertyAccess(objectName, propertyName, scope, builtins) { | ||
if (objectName && (!scope.hasBinding(objectName) || isFromInherentScope(objectName, scope))) { | ||
// this is an ES2015/2016/2017 static method that is being used in the code. | ||
handleStaticPropertyAccess(objectName, propertyName, builtins); | ||
} | ||
handleInstancePropertyAccess(propertyName, builtins); | ||
} | ||
function whichBuiltinsPlugin({ types: t }) { | ||
@@ -154,2 +182,19 @@ let builtins = {}; | ||
VariableDeclarator(path) { | ||
// we should ignore this variable declaration if it's not a destructuring | ||
// declaration. | ||
if (!t.isObjectPattern(path.node.id) || !t.isIdentifier(path.node.init)) { | ||
return; | ||
} | ||
const objectName = path.node.init.name; | ||
path.node.id.properties.forEach((property) => { | ||
if (!t.isProperty(property) || !t.isIdentifier(property.key)) { | ||
return; | ||
} | ||
const propertyName = property.key.name; | ||
handlePropertyAccess(objectName, propertyName, path.scope, builtins); | ||
}); | ||
}, | ||
MemberExpression(path) { | ||
@@ -171,20 +216,8 @@ const object = path.node.object; | ||
if (t.isIdentifier(object) | ||
&& staticMethods[object.name] | ||
&& staticMethods[object.name][propertyName] | ||
&& ( | ||
!path.scope.hasBinding(object.name) || isFromInherentScope(object.name, path.scope) | ||
)) { | ||
// this is an ES2015/2016/2017 static method that is being used in the code. | ||
builtins[staticMethods[object.name][propertyName]] = true; | ||
return; | ||
let objectName = null; | ||
if (t.isIdentifier(object)) { | ||
objectName = object.name; | ||
} | ||
if (instanceMethods[propertyName]) { | ||
// this is **potentially** a use of an ES2015/2016/2017/2017 instance method. | ||
// include that instance method's polyfill in case. | ||
instanceMethods[propertyName].forEach((module) => { | ||
builtins[module] = true; | ||
}); | ||
} | ||
handlePropertyAccess(objectName, propertyName, path.scope, builtins); | ||
}, | ||
@@ -191,0 +224,0 @@ }, |
{ | ||
"name": "babel-plugin-which-builtins", | ||
"version": "0.0.1", | ||
"version": "0.0.2", | ||
"description": "", | ||
"main": "index.js", | ||
"scripts": { | ||
"test": "eslint --ignore-path ../../.eslintignore --quiet *.js && jest" | ||
"test": "eslint --quiet *.js && jest" | ||
}, | ||
@@ -14,4 +14,8 @@ "author": "Sasha Aickin", | ||
"eslint": "^3.12.0", | ||
"eslint-config-airbnb": "^12.0.0", | ||
"eslint-plugin-import": "^1.16.0", | ||
"eslint-plugin-jsx-a11y": "^2.2.3", | ||
"eslint-plugin-react": "^6.4.1", | ||
"jest": "^17.0.3" | ||
} | ||
} |
164
README.md
#babel-plugin-which-builtins | ||
============================ | ||
@@ -10,3 +9,2 @@ This is a Babel plugin which attempts to determine which ECMAScript 2015/2016/2017 | ||
##Instructions | ||
============ | ||
@@ -29,3 +27,2 @@ First install the plugin to your project, along with `core-js` and `regenerator-runtime`: | ||
##What does it do? | ||
================== | ||
@@ -57,3 +54,2 @@ When this plugin encounters code that seems to use new built-in JavaScript global | ||
##When does the plugin fail? | ||
============================ | ||
@@ -72,2 +68,3 @@ Unfortunately, in a completely dynamic language like JavaScript, static analysis | ||
var a = Math.cos(90); | ||
var { cos } = Math; | ||
@@ -78,2 +75,3 @@ // this case will not work because Math doesn't have a direct | ||
var b = getMath().cos(90); | ||
var { cos } = getMath(); | ||
``` | ||
@@ -88,17 +86,22 @@ | ||
var b = "foo"["startsWith"]("f"); | ||
var { startsWith } = "foo"; | ||
var c = Math.cos(90); | ||
var d = Math["cos"](90); | ||
var { cos } = Math; | ||
// these cases will not work. | ||
var e = "foo"["starts" + "With"]("f"); | ||
var f = "startsWith".forEach(method => "foo"[method]("f"))[0]; | ||
var g = Math["c" + "os"](90); | ||
var { ["starts" + "With"]: f } = "foo"; | ||
var g = "startsWith".forEach(method => "foo"[method]("f"))[0]; | ||
var h = Math["c" + "os"](90); | ||
var { ["c" + "os"]: i } = Math; | ||
function getCos() { | ||
return "cos"; | ||
} | ||
var h = Math[getCos()](90); | ||
var j = Math[getCos()](90); | ||
var { [getCos()]: k } = Math; | ||
``` | ||
##Instance methods may produce false positives | ||
============================================== | ||
Some of the new built-ins in ES2015/2016/2017 are instance methods, like | ||
@@ -114,2 +117,3 @@ `Array.prototype.find` or `String.prototype.startsWith`. It's very difficult | ||
var a = "foo".startsWith("f"); | ||
var { startsWith } = "foo"; | ||
@@ -119,2 +123,3 @@ // this also triggers an import of String.prototype.startsWith (false positive). | ||
var c = b.startsWith("f"); | ||
var { startsWith } = b; | ||
@@ -129,77 +134,76 @@ // this also triggers an import of String.prototype.startsWith (false positive). | ||
#What features are polyfilled? | ||
============================== | ||
* Generators | ||
* `regeneratorRuntime` | ||
* `regeneratorRuntime` | ||
* Global objects | ||
* `DataView` | ||
* `Int8Array` | ||
* `Uint8Array` | ||
* `Uint8ClampedArray` | ||
* `Int16Array` | ||
* `Uint16Array` | ||
* `Int32Array` | ||
* `Uint32Array` | ||
* `Float32Array` | ||
* `Float64Array` | ||
* `Map` | ||
* `Set` | ||
* `WeakMap` | ||
* `WeakSet` | ||
* `Promise` | ||
* `Symbol` | ||
* `Reflect` | ||
* `DataView` | ||
* `Int8Array` | ||
* `Uint8Array` | ||
* `Uint8ClampedArray` | ||
* `Int16Array` | ||
* `Uint16Array` | ||
* `Int32Array` | ||
* `Uint32Array` | ||
* `Float32Array` | ||
* `Float64Array` | ||
* `Map` | ||
* `Set` | ||
* `WeakMap` | ||
* `WeakSet` | ||
* `Promise` | ||
* `Symbol` | ||
* `Reflect` | ||
* Static methods | ||
* `Array.from` | ||
* `Array.of` | ||
* `Math.acosh` | ||
* `Math.asinh` | ||
* `Math.atanh` | ||
* `Math.cbrt` | ||
* `Math.clz32` | ||
* `Math.cosh` | ||
* `Math.expm1` | ||
* `Math.fround` | ||
* `Math.hypot` | ||
* `Math.imul` | ||
* `Math.log1p` | ||
* `Math.log10` | ||
* `Math.log2` | ||
* `Math.sign` | ||
* `Math.sinh` | ||
* `Math.tanh` | ||
* `Math.trunc` | ||
* `Number.isFinite` | ||
* `Number.isInteger` | ||
* `Number.isSafeInteger` | ||
* `Number.isNaN` | ||
* `Number.EPSILON` | ||
* `Number.MIN_SAFE_INTEGER` | ||
* `Number.MAX_SAFE_INTEGER` | ||
* `Object.assign` | ||
* `Object.is` | ||
* `Object.getOwnPropertySymbols` | ||
* `Object.setPrototypeOf` | ||
* `Object.values` | ||
* `Object.entries` | ||
* `Object.getOwnPropertyDescriptors` | ||
* `String.raw` | ||
* `Array.from` | ||
* `Array.of` | ||
* `Math.acosh` | ||
* `Math.asinh` | ||
* `Math.atanh` | ||
* `Math.cbrt` | ||
* `Math.clz32` | ||
* `Math.cosh` | ||
* `Math.expm1` | ||
* `Math.fround` | ||
* `Math.hypot` | ||
* `Math.imul` | ||
* `Math.log1p` | ||
* `Math.log10` | ||
* `Math.log2` | ||
* `Math.sign` | ||
* `Math.sinh` | ||
* `Math.tanh` | ||
* `Math.trunc` | ||
* `Number.isFinite` | ||
* `Number.isInteger` | ||
* `Number.isSafeInteger` | ||
* `Number.isNaN` | ||
* `Number.EPSILON` | ||
* `Number.MIN_SAFE_INTEGER` | ||
* `Number.MAX_SAFE_INTEGER` | ||
* `Object.assign` | ||
* `Object.is` | ||
* `Object.getOwnPropertySymbols` | ||
* `Object.setPrototypeOf` | ||
* `Object.values` | ||
* `Object.entries` | ||
* `Object.getOwnPropertyDescriptors` | ||
* `String.raw` | ||
* Instance methods | ||
* `Array.prototype.copyWithin` | ||
* `Array.prototype.find` | ||
* `Array.prototype.findIndex` | ||
* `Array.prototype.fill` | ||
* `Array.prototype.includes` | ||
* `Function.prototype.name` | ||
* `RegExp.prototype.flags` | ||
* `RegExp.prototype.match` | ||
* `RegExp.prototype.replace` | ||
* `RegExp.prototype.split` | ||
* `RegExp.prototype.search` | ||
* `String.prototype.codePointAt` | ||
* `String.prototype.fromCodePoint` | ||
* `String.prototype.padStart` | ||
* `String.prototype.padEnd` | ||
* `String.prototype.repeat` | ||
* `String.prototype.startsWith` | ||
* `String.prototype.endsWith` | ||
* `String.prototype.includes` | ||
* `Array.prototype.copyWithin` | ||
* `Array.prototype.find` | ||
* `Array.prototype.findIndex` | ||
* `Array.prototype.fill` | ||
* `Array.prototype.includes` | ||
* `Function.prototype.name` | ||
* `RegExp.prototype.flags` | ||
* `RegExp.prototype.match` | ||
* `RegExp.prototype.replace` | ||
* `RegExp.prototype.split` | ||
* `RegExp.prototype.search` | ||
* `String.prototype.codePointAt` | ||
* `String.prototype.fromCodePoint` | ||
* `String.prototype.padStart` | ||
* `String.prototype.padEnd` | ||
* `String.prototype.repeat` | ||
* `String.prototype.startsWith` | ||
* `String.prototype.endsWith` | ||
* `String.prototype.includes` |
23918
7
328
201
7