autopolyfiller
Advanced tools
Comparing version 1.0.10 to 1.1.0
@@ -0,1 +1,7 @@ | ||
Version 1.1.0: | ||
* considerably increased speed of polyfill scan by removing `grasp-equery` | ||
* added ability to `.include()` & `.exclude()` polyfills #6 | ||
* improved polyfills scan. Now it can find square brackets expressions eg `""["trim"]()` #3 | ||
* improved polyfills scan. Now it can find padded expressions eg `new this.Promise()` or `window.atob()` #4 | ||
Version 1.0.10: | ||
@@ -2,0 +8,0 @@ * polyfills for `requestAnimationFrame()`, `cancelAnimationFrame()` |
@@ -66,3 +66,5 @@ var Command = require('commander').Command, | ||
createPolyFiller: function () { | ||
var browsers = this.program.browsers; | ||
var browsers = this.program.browsers, | ||
excludedPolyfills = this.program.exclude, | ||
includedPolyfills = this.program.include; | ||
@@ -76,3 +78,13 @@ this.log((function () { | ||
return autopolyfiller(browsers); | ||
if (includedPolyfills.length) { | ||
this.log('Adding extra polyfills: ' + [''].concat(includedPolyfills).join('\n * ')); | ||
} | ||
if (excludedPolyfills.length) { | ||
this.log('Ignoring polyfills: ' + [''].concat(excludedPolyfills).join('\n * ')); | ||
} | ||
return autopolyfiller(browsers) | ||
.exclude(excludedPolyfills) | ||
.include(includedPolyfills); | ||
}, | ||
@@ -86,2 +98,4 @@ | ||
.option('-b, --browsers <names>', 'generate polyfills for these browsers', this._list.bind(this), []) | ||
.option('-x, --exclude <polyfills>', 'exclude these polyfills', this._list.bind(this), []) | ||
.option('-i, --include <polyfills>', 'include these polyfills', this._list.bind(this), []) | ||
.option('-v, --verbose', 'verbose output') | ||
@@ -94,2 +108,3 @@ .on('--help', function(){ | ||
' $ autopolyfiller -o polyfills.js script.js', | ||
' $ autopolyfiller -o polyfills.js -i Promise -x Array.prototype.map script.js', | ||
' $ autopolyfiller -b "last 1 version, > 1%, Explorer 7" lib/*.js vendors/**/*.js', | ||
@@ -96,0 +111,0 @@ ' $ autopolyfiller lib/*.js !lib/lodash.js', |
@@ -17,5 +17,7 @@ var scan = require('./polyfill-scan'), | ||
* }) | ||
* .add('"".trim();Object.create();new Promise()') | ||
* .exclude(['Object.create']) | ||
* .include(['Array.prototype.map']) | ||
* .add('"".trim();Object.create();new Promise();') | ||
* .polyfills; | ||
* // ['Promise'] | ||
* // ['Promise', 'Array.prototype.map'] | ||
*/ | ||
@@ -25,2 +27,3 @@ function AutoPolyFiller(options) { | ||
this.polyfills = []; | ||
this.excluedPolyfills = []; | ||
} | ||
@@ -80,5 +83,42 @@ | ||
this.include(polyfills.concat(polyfillsOfPolyfills)); | ||
return this; | ||
}, | ||
/** | ||
* | ||
* @returns {string} code that polyfills all listed functions | ||
*/ | ||
toString: function () { | ||
return this.polyfills.map(function (polyfillName) { | ||
var polyfillCode = code(polyfillName); | ||
return wrap(polyfillCode, polyfillName); | ||
}).join(''); | ||
}, | ||
/** | ||
* Checks if `polyfill` is not in a `excluedPolyfills` list | ||
* | ||
* @param {String} polyfill | ||
* @returns {Boolean} | ||
* @private | ||
*/ | ||
_isPolyfillIncluded: function (polyfill) { | ||
return this.excluedPolyfills.indexOf(polyfill) === -1; | ||
}, | ||
/** | ||
* Adds `polyfills` to the list of required polyfills | ||
* | ||
* @param {String[]} polyfills | ||
* @returns {AutoPolyFiller} | ||
*/ | ||
include: function (polyfills) { | ||
this.polyfills = this.polyfills | ||
.concat(polyfills) | ||
.concat(polyfillsOfPolyfills) | ||
// Filter ignored polyfills | ||
.filter(this._isPolyfillIncluded.bind(this)) | ||
// Make unique polyfills | ||
@@ -89,2 +129,3 @@ .reduce(function (polyfills, polyfill) { | ||
} | ||
return polyfills; | ||
@@ -97,10 +138,15 @@ }, []); | ||
/** | ||
* Ignores `polyfills`, excluded their code from result | ||
* | ||
* @returns {string} code that polyfills all listed functions | ||
* @param {String[]} polyfills | ||
* @returns {AutoPolyFiller} | ||
*/ | ||
toString: function () { | ||
return this.polyfills.map(function (polyfillName) { | ||
var polyfillCode = code(polyfillName); | ||
return wrap(polyfillCode, polyfillName); | ||
}).join(''); | ||
exclude: function (polyfills) { | ||
this.excluedPolyfills.push.apply(this.excluedPolyfills, polyfills); | ||
// Filter ignored polyfills | ||
this.polyfills = this.polyfills | ||
.filter(this._isPolyfillIncluded.bind(this)); | ||
return this; | ||
} | ||
@@ -107,0 +153,0 @@ }; |
@@ -1,2 +0,4 @@ | ||
var astQuery = require('grasp-equery').query; | ||
var foldExpression = require('../../polyfill-expression-fold'); | ||
var grepExpressions = require('../grep-expression'); | ||
var matcher = require('../../polyfill-expression-matcher'); | ||
@@ -12,18 +14,26 @@ var constructors = { | ||
/** | ||
* @type {Function} | ||
*/ | ||
var testConstructor = matcher('constructor', { | ||
constructors: Object.keys(constructors) | ||
}); | ||
/** | ||
* | ||
* @param {Object} ast | ||
* @returns {String[]} list of polyfills in this ast | ||
*/ | ||
exports.test = function (ast) { | ||
var statements = astQuery('new __(_$)', ast); | ||
/*{ | ||
type: 'NewExpression', | ||
callee: { | ||
type: 'Identifier', | ||
name: 'Promise' | ||
}, | ||
arguments: [] | ||
}*/ | ||
return statements.reduce(function (polyfils, statement) { | ||
if (statement.callee && constructors.hasOwnProperty(statement.callee.name)) { | ||
polyfils.push(constructors[statement.callee.name]); | ||
} | ||
return polyfils; | ||
}, []); | ||
return grepExpressions(ast) | ||
.map(foldExpression) | ||
.reduce(function (polyfills, list) { | ||
var polyfill = constructors[testConstructor(list.join('.'))]; | ||
if (polyfill) { | ||
polyfills.push(polyfill); | ||
} | ||
return polyfills; | ||
}, []); | ||
}; |
@@ -1,2 +0,4 @@ | ||
var astQuery = require('grasp-equery').query; | ||
var foldExpression = require('../../polyfill-expression-fold'); | ||
var grepExpressions = require('../grep-expression'); | ||
var matcher = require('../../polyfill-expression-matcher'); | ||
@@ -11,250 +13,26 @@ var globalFunctions = { | ||
var functionMethods = { | ||
'bind': true, | ||
'apply': true, | ||
'call': true | ||
}; | ||
/** | ||
* @type {Function} | ||
*/ | ||
var testMethod = matcher('global', { | ||
methods: Object.keys(globalFunctions) | ||
}); | ||
function match_MemberExpression_property_Identifier(statement) { | ||
return statement.callee && | ||
statement.callee.type === 'MemberExpression' && | ||
/** | ||
* | ||
* @param {Object} ast | ||
* @returns {String[]} list of polyfills in this ast | ||
*/ | ||
exports.test = function (ast) { | ||
return grepExpressions(ast) | ||
.map(foldExpression) | ||
.reduce(function (polyfills, list) { | ||
var polyfill = globalFunctions[testMethod(list.join('.'))]; | ||
statement.callee.property && | ||
statement.callee.property.type === 'Identifier' && | ||
globalFunctions.hasOwnProperty(statement.callee.property.name); | ||
} | ||
function match_MemberExpression_object_Identifier(statement) { | ||
return statement.callee && | ||
statement.callee.type === 'MemberExpression' && | ||
statement.callee.object && | ||
statement.callee.object.type === 'Identifier' && | ||
globalFunctions.hasOwnProperty(statement.callee.object.name); | ||
} | ||
function match_MemberExpression_MemberExpression_property_Identifier(statement) { | ||
return statement.callee && | ||
statement.callee.type === 'MemberExpression' && | ||
statement.callee.object && | ||
statement.callee.object.type === 'MemberExpression' && | ||
statement.callee.object.property && | ||
statement.callee.object.property.type === 'Identifier' && | ||
globalFunctions.hasOwnProperty(statement.callee.object.property.name); | ||
} | ||
function match_CallExpression_functionMethod(statement) { | ||
return statement.callee && | ||
statement.callee.property && | ||
statement.callee.property.type === 'Identifier' && | ||
functionMethods.hasOwnProperty(statement.callee.property.name); | ||
} | ||
var matchers = [ | ||
/* | ||
btoa(); | ||
{ | ||
"type": "CallExpression", | ||
"callee": { | ||
"type": "Identifier", | ||
"name": "btoa" | ||
}, | ||
"arguments": [] | ||
} | ||
*/ | ||
function (statement) { | ||
if (statement.callee && | ||
statement.callee.type === 'Identifier' && | ||
globalFunctions.hasOwnProperty(statement.callee.name)) { | ||
return globalFunctions[statement.callee.name]; | ||
} | ||
}, | ||
/* | ||
btoa.{call,apply,bind}(); | ||
{ | ||
"type": "CallExpression", | ||
"callee": { | ||
"type": "MemberExpression", | ||
"object": { | ||
"type": "Identifier", | ||
"name": "btoa" | ||
}, | ||
"property": { | ||
"type": "Identifier", | ||
"name": "bind" | ||
}, | ||
"computed": false | ||
}, | ||
"arguments": [] | ||
} | ||
*/ | ||
function (statement) { | ||
if (match_MemberExpression_object_Identifier(statement) && | ||
match_CallExpression_functionMethod(statement)) { | ||
return globalFunctions[statement.callee.object.name]; | ||
} | ||
}, | ||
/* | ||
window.btoa(); | ||
{ | ||
"type": "CallExpression", | ||
"callee": { | ||
"type": "MemberExpression", | ||
"object": { | ||
"type": "Identifier", | ||
"name": "window" | ||
}, | ||
"property": { | ||
"type": "Identifier", | ||
"name": "btoa" | ||
}, | ||
"computed": false | ||
}, | ||
"arguments": [] | ||
} | ||
*/ | ||
function (statement) { | ||
if (match_MemberExpression_property_Identifier(statement) && | ||
statement.callee.object && | ||
statement.callee.object.type === 'Identifier' && | ||
statement.callee.object.name === 'window') { | ||
return globalFunctions[statement.callee.property.name]; | ||
} | ||
}, | ||
/* | ||
this.btoa(); | ||
{ | ||
"type": "CallExpression", | ||
"callee": { | ||
"type": "MemberExpression", | ||
"object": { | ||
"type": "ThisExpression" | ||
}, | ||
"property": { | ||
"type": "Identifier", | ||
"name": "btoa" | ||
}, | ||
"computed": false | ||
}, | ||
"arguments": [] | ||
} | ||
*/ | ||
function (statement) { | ||
if (match_MemberExpression_property_Identifier(statement) && | ||
statement.callee.object && | ||
statement.callee.object.type === 'ThisExpression') { | ||
return globalFunctions[statement.callee.property.name]; | ||
} | ||
}, | ||
/* | ||
window.btoa.{call,apply,bind}(); | ||
{ | ||
"type": "CallExpression", | ||
"callee": { | ||
"type": "MemberExpression", | ||
"object": { | ||
"type": "MemberExpression", | ||
"object": { | ||
"type": "Identifier", | ||
"name": "window" | ||
}, | ||
"property": { | ||
"type": "Identifier", | ||
"name": "btoa" | ||
}, | ||
"computed": false | ||
}, | ||
"property": { | ||
"type": "Identifier", | ||
"name": "call" | ||
}, | ||
"computed": false | ||
}, | ||
"arguments": [] | ||
} | ||
*/ | ||
function (statement) { | ||
if (match_MemberExpression_MemberExpression_property_Identifier(statement) && | ||
match_CallExpression_functionMethod(statement) && | ||
statement.callee.object.object.type === 'Identifier' && | ||
statement.callee.object.object.name === 'window') { | ||
return globalFunctions[statement.callee.object.property.name]; | ||
} | ||
}, | ||
/* | ||
this.btoa.{call,apply,bind}(); | ||
{ | ||
"type": "CallExpression", | ||
"callee": { | ||
"type": "MemberExpression", | ||
"object": { | ||
"type": "MemberExpression", | ||
"object": { | ||
"type": "ThisExpression" | ||
}, | ||
"property": { | ||
"type": "Identifier", | ||
"name": "btoa" | ||
}, | ||
"computed": false | ||
}, | ||
"property": { | ||
"type": "Identifier", | ||
"name": "call" | ||
}, | ||
"computed": false | ||
}, | ||
"arguments": [] | ||
} | ||
*/ | ||
function (statement) { | ||
if (match_MemberExpression_MemberExpression_property_Identifier(statement) && | ||
match_CallExpression_functionMethod(statement) && | ||
statement.callee.object.object.type === 'ThisExpression') { | ||
return globalFunctions[statement.callee.object.property.name]; | ||
} | ||
} | ||
]; | ||
var expressions = { | ||
CallExpression: function (polyfils, statement) { | ||
// Tll first match | ||
matchers.some(function (matcher) { | ||
var polyfill = matcher(statement); | ||
if (polyfill) { | ||
polyfils.push(polyfill); | ||
return true; | ||
polyfills.push(polyfill); | ||
} | ||
}); | ||
return polyfils; | ||
} | ||
return polyfills; | ||
}, []); | ||
}; | ||
exports.test = function (ast) { | ||
var statements = astQuery('__(_$)', ast); | ||
return statements.reduce(function (polyfils, statement) { | ||
return expressions[statement.type](polyfils, statement); | ||
}, []); | ||
}; |
@@ -1,2 +0,4 @@ | ||
var astQuery = require('grasp-equery').query; | ||
var foldExpression = require('../../polyfill-expression-fold'); | ||
var grepExpressions = require('../grep-expression'); | ||
var matcher = require('../../polyfill-expression-matcher'); | ||
@@ -37,84 +39,26 @@ var methods = { | ||
var functionMethods = { | ||
'bind': true, | ||
'apply': true, | ||
'call': true | ||
}; | ||
/** | ||
* @type {Function} | ||
*/ | ||
var testMethod = matcher('method', { | ||
methods: Object.keys(methods) | ||
}); | ||
function addTo(name, polyfils) { | ||
var polyfillName = methods.hasOwnProperty(name) ? methods[name] : void 0; | ||
if (polyfillName) { | ||
polyfils.push(polyfillName); | ||
} | ||
} | ||
/** | ||
* | ||
* @param {Object} ast | ||
* @returns {String[]} list of polyfills in this ast | ||
*/ | ||
exports.test = function (ast) { | ||
var statements = astQuery('__.__(_$)', ast); | ||
return grepExpressions(ast) | ||
.map(foldExpression) | ||
.reduce(function (polyfills, list) { | ||
var polyfill = methods[testMethod(list.join('.'))]; | ||
/*{ | ||
"type": "CallExpression", | ||
"callee": { | ||
"type": "MemberExpression", | ||
"object": { | ||
"type": "Literal", | ||
"value": "", | ||
"raw": "\"\"" | ||
}, | ||
"property": { | ||
"type": "Identifier", | ||
"name": "trim" | ||
}, | ||
"computed": false | ||
}, | ||
"arguments": [] | ||
} | ||
if (polyfill) { | ||
polyfills.push(polyfill); | ||
} | ||
OR | ||
{ | ||
"type": "CallExpression", | ||
"callee": { | ||
"type": "MemberExpression", | ||
"object": { | ||
"type": "MemberExpression", | ||
"object": { | ||
"type": "Literal", | ||
"value": "", | ||
"raw": "\"\"" | ||
}, | ||
"property": { | ||
"type": "Identifier", | ||
"name": "repeat" | ||
}, | ||
"computed": false | ||
}, | ||
"property": { | ||
"type": "Identifier", | ||
"name": "apply" | ||
}, | ||
"computed": false | ||
}, | ||
"arguments": [ | ||
] | ||
} | ||
*/ | ||
return statements.reduce(function (polyfils, statement) { | ||
var name = statement.callee && statement.callee.property && statement.callee.property.name; | ||
// in case of bind(); | ||
addTo(name, polyfils); | ||
// in case of .call() .apply() or .bind() change method name | ||
if (functionMethods.hasOwnProperty(name)) { | ||
name = statement.callee && | ||
statement.callee.object && | ||
statement.callee.object.property && | ||
statement.callee.object.property.name; | ||
addTo(name, polyfils); | ||
} | ||
return polyfils; | ||
}, []); | ||
return polyfills; | ||
}, []); | ||
}; |
@@ -1,2 +0,4 @@ | ||
var astQuery = require('grasp-equery').query; | ||
var foldExpression = require('../../polyfill-expression-fold'); | ||
var grepExpressions = require('../grep-expression'); | ||
var matcher = require('../../polyfill-expression-matcher'); | ||
@@ -63,113 +65,39 @@ var statics = { | ||
var functionMethods = { | ||
'bind': true, | ||
'apply': true, | ||
'call': true | ||
}; | ||
/** | ||
* @type {Function} | ||
*/ | ||
var testStatic = matcher('static', { | ||
objects: Object.keys(statics).reduce(function (objects, object) { | ||
objects[object] = Object.keys(statics[object]); | ||
return objects; | ||
}, {}) | ||
}); | ||
var expressions = { | ||
/* | ||
{ | ||
"type": "CallExpression", | ||
"callee": { | ||
"type": "MemberExpression", | ||
"object": { | ||
"type": "Identifier", | ||
"name": "Object" | ||
}, | ||
"property": { | ||
"type": "Identifier", | ||
"name": "create" | ||
}, | ||
"computed": false | ||
}, | ||
"arguments": [] | ||
} | ||
/** | ||
* @type {Object} | ||
*/ | ||
var expressionToPolyfillMap = Object.keys(statics).reduce(function (map, object) { | ||
return Object.keys(statics[object]).reduce(function (map, method) { | ||
map[object + '.' + method] = statics[object][method]; | ||
return map; | ||
}, map); | ||
}, {}); | ||
OR | ||
/** | ||
* | ||
* @param {Object} ast | ||
* @returns {String[]} list of polyfills in this ast | ||
*/ | ||
exports.test = function (ast) { | ||
return grepExpressions(ast) | ||
.map(foldExpression) | ||
.reduce(function (polyfills, list) { | ||
var polyfill = expressionToPolyfillMap[testStatic(list.join('.'))]; | ||
{ | ||
"type": "CallExpression", | ||
"callee": { | ||
"type": "MemberExpression", | ||
"object": { | ||
"type": "MemberExpression", | ||
"object": { | ||
"type": "Identifier", | ||
"name": "JSON" | ||
}, | ||
"property": { | ||
"type": "Identifier", | ||
"name": "parse" | ||
}, | ||
"computed": false | ||
}, | ||
"property": { | ||
"type": "Identifier", | ||
"name": "call" | ||
}, | ||
"computed": false | ||
}, | ||
"arguments": [ | ||
] | ||
} | ||
*/ | ||
CallExpression: function (polyfils, statement) { | ||
var staticMethodName = statement.callee && statement.callee.property && statement.callee.property.name, | ||
objectName = statement.callee && statement.callee.object && statement.callee.object.name; | ||
if (polyfill) { | ||
polyfills.push(polyfill); | ||
} | ||
// in case of .call() .apply() or .bind() change static method name and object name | ||
if (functionMethods.hasOwnProperty(staticMethodName)) { | ||
staticMethodName = statement.callee && | ||
statement.callee.object && | ||
statement.callee.object.property && | ||
statement.callee.object.property.name; | ||
objectName = statement.callee && | ||
statement.callee.object && | ||
statement.callee.object.object && | ||
statement.callee.object.object.name; | ||
} | ||
var polyFillName = statics.hasOwnProperty(objectName) && | ||
statics[objectName].hasOwnProperty(staticMethodName) && | ||
statics[objectName][staticMethodName]; | ||
if (polyFillName) { | ||
polyfils.push(polyFillName); | ||
} | ||
return polyfils; | ||
}, | ||
/* | ||
{ | ||
type: 'MemberExpression', | ||
object: { type: 'Identifier', name: 'Object' }, | ||
property: { type: 'Identifier', name: 'create' }, | ||
computed: false | ||
} | ||
*/ | ||
MemberExpression: function (polyfils, statement) { | ||
var staticMethodName = statement.property && statement.property.name, | ||
objectName = statement.object && statement.object.name; | ||
var polyFillName = statics.hasOwnProperty(objectName) && | ||
statics[objectName].hasOwnProperty(staticMethodName) && | ||
statics[objectName][staticMethodName]; | ||
if (polyFillName) { | ||
polyfils.push(polyFillName); | ||
} | ||
return polyfils; | ||
} | ||
return polyfills; | ||
}, []); | ||
}; | ||
exports.test = function (ast) { | ||
var statements = astQuery('__.__(_$)', ast).concat(astQuery('__.__', ast)); | ||
return statements.reduce(function (polyfils, statement) { | ||
return expressions[statement.type](polyfils, statement); | ||
}, []); | ||
}; |
@@ -5,3 +5,3 @@ { | ||
"keywords": ["polyfill", "polyfills", "dom", "ecmascript", "ecmascript5", "ecmascript6", "postprocessor"], | ||
"version" : "1.0.10", | ||
"version" : "1.1.0", | ||
"author" : "Mikhail Davydov <i@azproduction.ru>", | ||
@@ -23,3 +23,2 @@ "contributors" : [ | ||
"acorn": "~0.4.2", | ||
"grasp-equery": "~0.2.0", | ||
"autoprefixer": "1.1.x", | ||
@@ -32,3 +31,5 @@ "debug": "~0.7.4", | ||
"mkdirp": "~0.3.5", | ||
"semver": "~2.2.1" | ||
"semver": "~2.2.1", | ||
"estraverse": "1.5.0", | ||
"quotemeta": "0.0.0" | ||
}, | ||
@@ -46,3 +47,5 @@ "devDependencies" : { | ||
"mock-utf8-stream": "*", | ||
"uglify-js": "*" | ||
"uglify-js": "*", | ||
"glob": "~3.2.9", | ||
"grasp-equery": "~0.2.0" | ||
}, | ||
@@ -56,4 +59,5 @@ "bin": { | ||
"coverage": "make coverage", | ||
"clean": "make clean" | ||
"clean": "make clean", | ||
"benchmark": "node ./benchmark/scan" | ||
} | ||
} |
@@ -79,2 +79,16 @@ # Autopolyfiller — Precise polyfills | ||
**Excluding/including polyfills** | ||
```js | ||
var autopolyfiller = require('autopolyfiller'), | ||
autoprefixer = require('autopolyfiller'); | ||
autopolyfiller() | ||
.exclude(['Promise']) | ||
.include(['String.prototype.trim']) | ||
.add('new My.Promise();') | ||
.polyfills; | ||
// ['String.prototype.trim'] | ||
``` | ||
**Custom polyfill matchers** | ||
@@ -104,3 +118,3 @@ | ||
autopolyfiller() | ||
.add('Object.create();Object.newFeature();'); | ||
.add('Object.create();Object.newFeature();') | ||
.polyfills; | ||
@@ -110,5 +124,5 @@ // ['Object.create', 'Object.newFeature'] | ||
autopolyfiller('Chrome >= 20') | ||
.add('Object.create();Object.newFeature();'); | ||
.add('Object.create();Object.newFeature();') | ||
.polyfills; | ||
// [] | ||
``` |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
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
Native code
Supply chain riskContains native code (e.g., compiled binaries or shared libraries). Including native code can obscure malicious behavior.
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
522006
56
126
11
13
9519
13
4
+ Addedestraverse@1.5.0
+ Addedquotemeta@0.0.0
+ Addedestraverse@1.5.0(transitive)
+ Addedquotemeta@0.0.0(transitive)
- Removedgrasp-equery@~0.2.0
- Removedacorn@0.6.0(transitive)
- Removedgrasp-equery@0.2.3(transitive)
- Removedgrasp-syntax-javascript@0.1.0(transitive)
- Removedprelude-ls@1.0.31.1.2(transitive)