├── .gitignore ├── .travis.yml ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── backbone └── README.md ├── es5 └── README.md ├── es6 └── README.md ├── general └── README.md ├── package.json ├── packages ├── eslint-config-eventbrite-legacy │ ├── .eslintrc.json │ ├── .npmignore │ ├── CHANGELOG.md │ ├── README.md │ ├── _scripts │ │ └── git-tag-version.js │ ├── index.js │ ├── package.json │ └── rules │ │ ├── best-practices.js │ │ ├── errors.js │ │ ├── node.js │ │ ├── style.js │ │ └── variables.js ├── eslint-config-eventbrite-react │ ├── .eslintrc.json │ ├── .npmignore │ ├── CHANGELOG.md │ ├── README.md │ ├── _scripts │ │ └── git-tag-version.js │ ├── index.js │ ├── package.json │ └── rules │ │ ├── react-a11y.js │ │ └── react.js └── eslint-config-eventbrite │ ├── .eslintrc.json │ ├── .npmignore │ ├── CHANGELOG.md │ ├── README.md │ ├── _scripts │ └── git-tag-version.js │ ├── index.js │ ├── package.json │ └── rules │ ├── best-practices.js │ ├── errors.js │ ├── es6.js │ ├── esnext.js │ ├── jest.js │ ├── strict.js │ └── style.js ├── react ├── README.md └── testing.md └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | **/node_modules/* 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "node" 4 | - "10" 5 | - "8" 6 | - "6" 7 | 8 | cache: yarn 9 | install: yarn 10 | 11 | script: yarn test 12 | 13 | before_deploy: 14 | # Write the authentication token to .npmrc right before we're about to deploy. 15 | # We cannot check this file in because then it'll try to authenticate on yarn install. 16 | # And that is bad because $NPM_TOKEN isn't available in all PRs 17 | # (see https://docs.travis-ci.com/user/pull-requests/#Pull-Requests-and-Security-Restrictions). 18 | # We only need the token for publishing the package so we'll create the .npmrc file right before. 19 | - echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" >> .npmrc 20 | deploy: 21 | # Deploy new version of eslint-config-eventbrite-legacy (but only on the "node" test matrix) 22 | - provider: script 23 | skip_cleanup: true 24 | script: npm publish packages/eslint-config-eventbrite-legacy 25 | on: 26 | tags: true 27 | condition: $TRAVIS_TAG =~ ^eslint-config-eventbrite-legacy-v.+$ 28 | node: "node" 29 | 30 | # Deploy new version of eslint-config-eventbrite (but only on the "node" test matrix) 31 | - provider: script 32 | skip_cleanup: true 33 | script: npm publish packages/eslint-config-eventbrite 34 | on: 35 | tags: true 36 | condition: $TRAVIS_TAG =~ ^eslint-config-eventbrite-v.+$ 37 | node: "node" 38 | 39 | # Deploy new version of eslint-config-eventbrite-react (but only on the "node" test matrix) 40 | - provider: script 41 | skip_cleanup: true 42 | script: npm publish packages/eslint-config-eventbrite-react 43 | on: 44 | tags: true 45 | condition: $TRAVIS_TAG =~ ^eslint-config-eventbrite-react-v.+$ 46 | node: "node" 47 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | If you would like to make changes to this repo, please: 4 | 5 | 1. Make a GitHub Pull Request with the suggested change 6 | 2. Post the Pull Request in the Slack channel: #Frontend 7 | 3. Wait a day or two (at least 24 hours) for feedback 8 | 4. Ping the #Frontend channel again 9 | 5. If you have at least a few approvals, and no one is hard disapproving of your PR, merge it 10 | 6. If we can't come to an agreement, you may choose to either: 11 | - Close the pull request 12 | - Create a meeting to discuss the change (if it's going to take a lot of time) 13 | - Bring the change up in the next Frontend Guild meeting (if it won't take too much time) 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Eventbrite 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Eventbrite JavaScript Coding Style Guide 2 | 3 | Eventbrite's [ESLint](http://eslint.org/) guidelines to ensure consistency in JavaScript code. 4 | 5 | ## Intended Audience 6 | 7 | This coding style guide has been created _for_ Eventbrite developers, but is certainly applicable for the general JavaScript community as well. It is heavily inspired by [Airbnb's JavaScript Style Guide](https://github.com/airbnb/javascript). 8 | 9 | ## Table of Contents 10 | 11 | 0. [ESLint Configurations](#eslint-configurations) 12 | 0. [General guidelines](general/) 13 | 0. [ES6+](es6/) 14 | 0. [React & JSX](react/) 15 | 0. [Testing React](react/testing.md) 16 | 0. [Legacy ES5](es5/) 17 | 0. [Backbone & Marionette](backbone/) 18 | 0. [Resources](#resources) 19 | 0. [License](#license) 20 | 21 | ## ESLint Configurations 22 | 23 | Eventbrite has 3 ESLint configuration packages that you can extend: 24 | 25 | - [`eslint-config-eventbrite`](packages/eslint-config-eventbrite): base ESLint config that lints ES6+/ES2015+ 26 | - [`eslint-config-eventbrite-react`](packages/eslint-config-eventbrite-react): extends `eslint-config-eventbrite`, also linting React & JSX 27 | - [`eslint-config-eventbrite-legacy`](packages/eslint-config-eventbrite-legacy): ESLint config that lints legacy ES5- 28 | 29 | ## Resources 30 | 31 | Coming soon... 32 | 33 | ## License 34 | 35 | [MIT](LICENSE). Copyright (c) 2017 Eventbrite. 36 | -------------------------------------------------------------------------------- /backbone/README.md: -------------------------------------------------------------------------------- 1 | # Eventbrite Backbone & Marionette Coding Style Guide 2 | 3 | Eventbrite's [Backbone.js](backbonejs.org) and [Marionette.js](marionettejs.com) guidelines to ensure consistency in JavaScript code. 4 | 5 | Backbone and Marionette come with a rich API and also functions provided by [underscore](http://underscorejs.org/) (`_`) and [jquery]((http://api.jquery.com/category/version/1.7/)) (`$`). Although good and fast to use, these utilities can be hard to navigate or even challenging when building large-scale applications. Many times midway through development, we find that were used the tools incorrectly and have to change course, resulting in Frankenstein code. This guide will attempt to ease some of these problems. 6 | 7 | Backbone and Marionette come with rich APIs as well as functions provided by [underscore](http://underscorejs.org/) (`_`) and [jquery]((http://api.jquery.com/category/version/1.7/)) (`$`). Although good and fast to use, these utilities can be hard to navigate or even challenging when building large-scale applications. Many times, we have found midway through development that we had used the tools incorrectly and must change course, resulting in Frankenstein code. This guide aims to ease some of these problems. 8 | 9 | ## Table of Contents 10 | 11 | 0. [Backbone.js](#backbonejs) 12 | 0. [Marionette.js](#marionettejs) 13 | 0. [Additional plugins](#additional-plugins) 14 | 0. [Common terminology](#common-terminology) 15 | 0. [File structure](#file-structure) 16 | 0. [Ordering](#ordering) 17 | 0. [Statics](#statics) 18 | 0. [Styling](#styling) 19 | 0. [Context](#context) 20 | 0. [Function](#good-practices-functions) 21 | 0. [Hydrating apps](#good-practices-hydrating-apps) 22 | 0. [Marionette.Layout](#marionette-layout) 23 | 0. [Marionette.Views](#marionette-views) 24 | 0. [Backbone.Model](#backbonemodel) 25 | 0. [Backbone.Collection](#marionette-collection) 26 | 0. [Marionette Artifacts Life Cycle](#marionette-artifacts-life-cycle) 27 | 0. [Backbone Life Cycle](#) 28 | 0. [Architecting JS Apps at Eventbrite](#architecting-js-apps-at-eventbrite) 29 | 0. [Debugging common issues](#debugging-common-issues) 30 | 0. [Testable Modular JS with Backbone, Jasmine & Sinon](#testable-modular-js-with-backbone-jasmine--sinon) 31 | 32 | 33 | ## Backbone.js 34 | 35 | From the [Backbone.js](http://backbonejs.org/) docs: 36 | 37 | > Backbone.js gives structure to web applications by providing **models** with key-value binding and custom events, **collections** with a rich API of enumerable functions, **views** with declarative event handling, and connects it all to your existing API over a RESTful JSON interface. 38 | 39 | Eventbrite still uses v1.0.0 of Backbone. For more, see [Getting started with Backbone.js](http://backbonejs.org/#Getting-started). 40 | 41 | _NOTE:_ [`Backbone.View`](http://backbonejs.org/#View) is deprecated in favor of using [Marionette views](#marionette-views). 42 | 43 | **[⬆ back to top](#table-of-contents)** 44 | 45 | ## Marionette.js 46 | 47 | From the [Marionette.js](http://marionettejs.com/) docs: 48 | 49 | > Marionette simplifies your Backbone application code with robust views and architecture solutions. 50 | 51 | Eventbrite still uses v1.8.8 of Marionette.ItemView. For more, see [Marionette v1.8.8 docs](http://marionettejs.com/docs/v1.8.8/). 52 | 53 | _NOTE:_ [`Marionette.Application.module`](http://marionettejs.com/docs/v1.8.8/marionette.application.module.html) is deprecated in favor of [`Marionette.Layout`](http://marionettejs.com/docs/v1.8.8/marionette.layout.html). You will still see it used in certain parts of the product, such as in **Listings** or **My Contacts**. 54 | 55 | _NOTE:_ [`Marionette.Controller`](http://marionettejs.com/docs/v1.8.8/marionette.controller.html) is deprecated in favor of [`Marionette.Layout`](http://marionettejs.com/docs/v1.8.8/marionette.layout.html). [`Marionette.Object`](http://marionettejs.com/docs/v2.1.0/marionette.object.html) is also available. It was taken from a later version of Marionette and stitched in. 56 | 57 | **[⬆ back to top](#table-of-contents)** 58 | 59 | ## Additional plugins 60 | 61 | We have a couple of plugins/libraries to enhance and simplify our use of Backbone/Marionette: 62 | 63 | - [`Backbone.Advice`](https://github.com/rhysbrettbowen/Backbone.Advice): Adds functional mixin abilities for Backbone objects 64 | - [`dorsal`](https://github.com/eventbrite/dorsal): An HTML decorator library 65 | - [`Backbone.Stickit`](https://github.com/NYTimes/backbone.stickit): Backbone data binding plugin that binds Model attributes to View elements 66 | - [`Backbone.Validation`](https://github.com/thedersen/backbone.validation): A validation plugin for Backbone that validates both your model as well as form input 67 | - [`Backbone.Wreqr`](https://github.com/marionettejs/backbone.wreqr): Messaging patterns for Backbone applications 68 | 69 | **[⬆ back to top](#table-of-contents)** 70 | 71 | ## Common terminology 72 | 73 | - _context_ - 74 | - _hydrating_ - 75 | - _bootstrap_ - 76 | - _module_ - 77 | - _component_ - 78 | - _app_ - 79 | - _parameters_ - 80 | - _argument_ - 81 | - _config_ - 82 | - _artifact_ - 83 | - _helpers_ - 84 | - _mixins_ - 85 | - _base bundle_ - 86 | - _bundle_ - 87 | 88 | **[⬆ back to top](#table-of-contents)** 89 | 90 | ## Folder Structure 91 | 92 | We structure our Backbone projects like so: 93 | 94 | - js/src/require/component/feature_name/ 95 | - feature_name.js 96 | - model.js 97 | - model.spec.js 98 | - view.js 99 | - view.spec.js 100 | - sub_feature/ 101 | - feature_name.js 102 | - model.js 103 | - model.spec.js 104 | - view.js 105 | - view.spec.js 106 | - router.js 107 | 108 | `feature\_name.js` contains the code to initialize your module. 109 | 110 | Each model, each view, and the router gets its own file mirroring its JavaScript naming. For example, `EB.ProjectName.FirstModel` is in `eb/feature_name/first_model.js`. 111 | 112 | ## File structure 113 | 114 | A reference to `Marionette` can actually be retrieved from a reference to `Backbone`. However, we recommend requiring `Marionette` separately, so that if we try to simplify our stack, we don't have to change a considerable amount of code to remove the `Backbone` dependency/namespace: 115 | 116 | ```js 117 | // good 118 | var Marionette = require('marionette'); 119 | 120 | return Marionette.ItemView.extend({ /* do something here */ }); 121 | 122 | // bad (access Marionette from Backbone) 123 | var Backbone = require('backbone'); 124 | 125 | return Backbone.Marionette.ItemView.extend({ /* do something here */ }); 126 | ``` 127 | 128 | **[⬆ back to top](#table-of-contents)** 129 | 130 | Whenever possible, return only one [artifact](#common-terminology) per file: 131 | 132 | ```js 133 | // good 134 | 135 | //view_a.js 136 | 137 | var Marionette = require('marionette'); 138 | 139 | return Marionette.ItemView.extend({ /* do something here */ }); 140 | 141 | //view_b.js 142 | 143 | var Marionette = require('marionette'); 144 | 145 | return Marionette.ItemView.extend({ /* do something here */ }); 146 | 147 | 148 | // bad (returning multiple artifacts in one file) 149 | 150 | var Marionette = require('marionette'), 151 | ViewA = Marionette.ItemView.extend({ /* do something here */ }), 152 | ViewB = Marionette.ItemView.extend({ /* do something here */ }); 153 | 154 | return {ViewA: ViewA, ViewB: ViewB}; 155 | ``` 156 | 157 | Whenever possible, return the artifact immediately instead of assigning to a variable that just gets returned afterward: 158 | 159 | ```js 160 | // good 161 | var Marionette = require('marionette'); 162 | 163 | return Marionette.ItemView.extend({ /* do something here */ }); 164 | 165 | // bad (assigns the ItemView to a variable unnecessarily) 166 | var Marionette = require('marionette'), 167 | MyItemView; 168 | 169 | MyItemView = Marionette.ItemView.extend({ /* do something here */ }); 170 | 171 | return MyItemView; 172 | ``` 173 | 174 | **[⬆ back to top](#table-of-contents)** 175 | 176 | ## Ordering 177 | 178 | - **Outside** `Marionette.View` definition 179 | 180 | 0. `dependencies` requirement 181 | 0. `static` functions 182 | 0. `config` objects 183 | 184 | - Ordering for `Marionette.View`: 185 | 186 | 0. `el` 187 | 0. `template` 188 | 0. `tagName` 189 | 0. `itemView` *Composite View* 190 | 0. `itemViewContainer` *Composite View* 191 | 0. `className` 192 | 0. `ui` 193 | 0. `regions` *Layout* 194 | 0. `events` 195 | 0. `triggers` 196 | 0. `modelEvents` 197 | 0. `initialize` 198 | 0. `templateHelpers` 199 | 0. `onBeforeRender` 200 | 0. `render` 201 | 0. `onRender` 202 | 0. *all the remaining life cycle methods* 203 | 0. *clickHandlers or eventHandlers* 204 | 0. *getter methods* 205 | 206 | **[⬆ back to top](#table-of-contents)** 207 | 208 | ## Statics 209 | 210 | When we write views or models/collections, we tend to enclose all of our functions as methods on the artifact. However, sometimes these methods are really just static helpers that don't need context (i.e. not bound to `this`). In this case, it's better to extract out the function as a private helper, which also simplifies the API exposed by the artifact: 211 | 212 | ```js 213 | // good 214 | var Marionette = require('marionette'); 215 | 216 | function extractAttributes(options) { 217 | var attrs = {}; 218 | // do stuff 219 | return attrs; 220 | }; 221 | 222 | return Marionette.ItemView.extend({ 223 | initialize: function(options) { 224 | var attrs = extractAttributes(options); 225 | 226 | this.model = new Backbone.Model(attrs); 227 | }; 228 | }); 229 | 230 | // bad (extractAttributes is an additional method on the view unnecessarily) 231 | var Marionette = require('marionette'); 232 | 233 | return Marionette.ItemView.extend({ 234 | initialize: function(options) { 235 | var attrs = this.exractAttributes(options); 236 | 237 | this.model = new Backbone.Model(attrs); 238 | }, 239 | extracAttributes: function(options) { 240 | var attrs = {}; 241 | // do stuff 242 | return attrs; 243 | } 244 | }); 245 | ``` 246 | 247 | Oftentimes an artifact needs some static/constant data that never need to change. Instead of having magic numbers/strings in the code, or having a configuration object attached to each instance, we should store the configuration information in a const object variable: 248 | 249 | ```js 250 | // good 251 | var $ = require('jquery'), 252 | Marionette = require('marionette'), 253 | config = { 254 | selectorName: 'someDynamicSelector', 255 | isHiddenClass: 'is-hidden', 256 | timeout: 10 257 | }; 258 | 259 | return Marionette.ItemView.extend({ 260 | initialize: function(options) { 261 | $(config.selectorName).add(config.isHiddenClass); 262 | window.setTimeout(this.someCallback, config.timeout); 263 | } 264 | }); 265 | 266 | // ok (config objects exists as a property for each view instance) 267 | var $ = require('jquery'), 268 | Marionette = require('marionette'); 269 | 270 | return Marionette.ItemView.extend({ 271 | config: { 272 | selectorName: 'someDynamicSelector', 273 | isHiddenClass: 'is-hidden', 274 | timeout: 10 275 | }, 276 | initialize: function(options) { 277 | $(this.config.selectorName).addClass(this.config.isHiddenClass); 278 | window.setTimeout(this.someCallback, this.config.timeout); 279 | } 280 | }); 281 | 282 | // bad (uses magic numbers/strings) 283 | var $ = require('jquery'), 284 | Marionette = require('marionette'); 285 | 286 | return Marionette.ItemView.extend({ 287 | initialize: function(options) { 288 | $('someDynamicSelector').addClass('is-hidden'); 289 | window.setTimeout(this.someCallback, 10); 290 | } 291 | }); 292 | ``` 293 | 294 | **[⬆ back to top](#table-of-contents)** 295 | 296 | ## Styling 297 | 298 | To simplify searches when trying to find templates, put CSS classes in handlebars templates instead of coupling it with the view logic: 299 | 300 | ```js 301 | // good 302 | 303 | // some_view.handlebars 304 | 305 |
306 | 307 | // some_view.js 308 | 309 | var Marionette = require('marionette'), 310 | template = require('hb!./some_view.handlebars'); 311 | 312 | return Marionette.ItemView({ 313 | template: template 314 | }); 315 | 316 | 317 | // bad (CSS classes aren't separated out) 318 | var Marionette = require('marionette'); 319 | 320 | return Marionette.ItemView({ 321 | className: 'g-cell g-cell-12-12' 322 | }); 323 | ``` 324 | 325 | **[⬆ back to top](#table-of-contents)** 326 | 327 | ## Context 328 | 329 | ### Binding 330 | 331 | In order to use native JavaScript whenever possible, use [`Function.prototype.bind`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind) instead of [`_.bind`](http://underscorejs.org/#bind) and [`_.bindAll`](http://underscorejs.org/#bindAll) to bind callback handlers: 332 | 333 | ```js 334 | // good 335 | return Marionette.ItemView.extend({ 336 | initialize: function(options) { 337 | this.listenTo(channel.vent, 'someSignal', this.someMethod.bind(this)); 338 | this.listenTo(channel.vent, 'anotherSingle', this.anotherMethod.bind(this)); 339 | }, 340 | 341 | someMethod: function(options) { 342 | /* do something */ 343 | }, 344 | anotherMethod: function(options) { 345 | /* do something */ 346 | } 347 | }); 348 | 349 | // bad (uses _.bindAll) 350 | return Marionette.ItemView.extend({ 351 | initialize: function(options) { 352 | _.bindAll(this, 'someMethod', 'anotherMethod'); 353 | 354 | this.listenTo(channel.vent, 'someSignal', this.someMethod); 355 | this.listenTo(channel.vent, 'anotherSingle', this.anotherMethod); 356 | }, 357 | 358 | someMethod: function(options) { 359 | /* do something */ 360 | }, 361 | anotherMethod: function(options) { 362 | /* do something */ 363 | } 364 | }); 365 | 366 | // bad (uses _.bind) 367 | return Marionette.ItemView.extend({ 368 | initialize: function(options) { 369 | this.listenTo(channel.vent, 'someSignal', _.bind(this.someMethod)); 370 | this.listenTo(channel.vent, 'anotherSingle', _.bind(this.anotherMethod)); 371 | }, 372 | 373 | someMethod: function(options) { 374 | /* do something */ 375 | }, 376 | anotherMethod: function(options) { 377 | /* do something */ 378 | } 379 | }); 380 | ``` 381 | 382 | **[⬆ back to top](#table-of-contents)** 383 | 384 | ### Data 385 | 386 | Don't store derived/calculated data on the `this` context of a view. Doing so makes it fragile and error-prone, because nothing prevents that data from being modified. Furthermore, it complicates quality code review (AKA static analysis) because the reviewer must first investigate the origin of the instance property. 387 | 388 | Whenever possible, calculate the data on demand, either in the model or in the view: 389 | 390 | ```js 391 | // good 392 | return Marionette.ItemView.extend({ 393 | getComputedData: function() { 394 | return this.model.getComputedData(); 395 | } 396 | }); 397 | 398 | // ok (the View is doing data calculations that could be done by Model) 399 | return Marionette.ItemView.extend({ 400 | getComputedData: function() { 401 | return someDataTransformation(this.options); 402 | } 403 | }); 404 | 405 | // bad (storing computed data in the View context) 406 | return Marionette.ItemView.extend({ 407 | initialize: function(options) { 408 | this.computedData = someTransformation(options); 409 | } 410 | 411 | getComputedData: function() { 412 | return this.computedData; 413 | } 414 | }); 415 | ``` 416 | 417 | **[⬆ back to top](#table-of-contents)** 418 | -------------------------------------------------------------------------------- /es5/README.md: -------------------------------------------------------------------------------- 1 | # Eventbrite Legacy ES5 Coding Style Guide 2 | 3 | [ESLint](http://eslint.org/) rules and guidelines used by Eventbrite to provide consistency and prevent errors in legacy JavaScript code written in ES5. 4 | 5 | ## Table of Contents 6 | 7 | 0. [ES5 compatibility](#es5-compatibility) 8 | 9 | ## ES5 compatibility 10 | 11 | For browser, server, and compiler ES6 support, check out [kangax](https://twitter.com/kangax)'s [ES5 compatibility table](http://kangax.github.io/es5-compat-table/). 12 | -------------------------------------------------------------------------------- /es6/README.md: -------------------------------------------------------------------------------- 1 | # Eventbrite ES6+ Coding Style Guide 2 | 3 | Eventbrite’s [ESLint](http://eslint.org/) guidelines to ensure consistency in JavaScript code written in ES6 and later. 4 | 5 | ## Table of Contents 6 | 7 | 0. [ES6 compatibility](#es6-compatibility) 8 | 0. [Variables](#variables) 9 | 0. [Strings](#strings) 10 | 0. [Arrays](#arrays) 11 | 0. [Objects](#objects) 12 | 0. [Functions](#functions) 13 | 0. [Classes](#classes) 14 | 0. [Modules](#modules) 15 | 0. [Destructuring](#destructuring) 16 | 17 | ## ES6 compatibility 18 | 19 | For browser, server, and compiler ES6 support, check out [kangax](https://twitter.com/kangax)'s [ES6 compatibility table](http://kangax.github.io/compat-table/es6/). 20 | 21 | **[⬆ back to top](#table-of-contents)** 22 | 23 | ## Variables 24 | 25 | ### `let` vs. `var` 26 | 27 | Avoid using var for declaring local variables; instead use `let`, which provides [block scoping](https://www.eventbrite.com/engineering/learning-es6-block-level-scoping-with-let-and-const/) (eslint: [`no-var`](http://eslint.org/docs/rules/no-var)): 28 | 29 | ```js 30 | // good 31 | let x = 'y'; 32 | 33 | // bad (uses var) 34 | var x = 'y' 35 | ``` 36 | 37 | **[⬆ back to top](#table-of-contents)** 38 | 39 | ### `const` 40 | 41 | Use `const` for the following: 42 | 43 | - Actual [constants](#constants); i.e., variables that remain the same throughout the entire execution 44 | - [Arrow function](#arrow-functions) references 45 | 46 | ```js 47 | // good 48 | const DEFAULT_NAME = 'Eventbrite'; 49 | 50 | const generateGreeting = (name=DEFAULT_NAME) => { 51 | let formattedNow = new Date(); 52 | 53 | return `Hi, ${name} on ${formattedNow}`; 54 | } 55 | 56 | // bad (uses `let` for a constant & arrow function reference) 57 | let DEFAULT_NAME = 'Eventbrite'; 58 | 59 | let generateGreeting = (name=DEFAULT_NAME) => { 60 | let formattedNow = new Date(); 61 | 62 | return `Hi, ${name} on ${formattedNow}`; 63 | } 64 | 65 | ``` 66 | 67 | Name constants using `UPPER_SNAKE_CASE` for easy identification: 68 | 69 | ```js 70 | // good 71 | const MAX_ALLOWED = 7; 72 | 73 | // bad (uses snake_case) 74 | const max_allowed = 7; 75 | 76 | // bad (uses PascalCase) 77 | const MaxAllowed = 7; 78 | 79 | // bad (uses normal camelCase) 80 | const maxAllowed = 7; 81 | ``` 82 | 83 | If a given [module](#modules) has more than three constants, factor them out into a separate constants module, and then import that constants module as an object: 84 | 85 | ```js 86 | // good (imports constants as an object from 87 | // constants module) 88 | import * as constants from './constants'; 89 | 90 | // bad (has more than 3 constants w/in module) 91 | const FIRST_CONSTANT = 'foo'; 92 | const SECOND_CONSTANT = 'bar'; 93 | const THIRD_CONSTANT = 'baz'; 94 | const FOURTH_CONSTANT = 'qux'; 95 | const FIFTH_CONSTANT = 'quux'; 96 | const SIXTH_CONSTANT = 'corge'; 97 | ``` 98 | 99 | However, if a given module uses three or fewer constants, use individual named imports instead: 100 | 101 | ```js 102 | import {FIRST_CONSTANT, FIFTH_CONSTANT} from './constants'; 103 | ``` 104 | 105 | Avoid using `const` for local variables (eslint: [`prefer-const`](http://eslint.org/docs/rules/prefer-const)): 106 | 107 | ```js 108 | // good 109 | const generateGreeting = (name=DEFAULT_NAME) => { 110 | let formattedNow = new Date(); 111 | 112 | return `Hi, ${name} on ${formattedNow}`; 113 | } 114 | 115 | // bad (uses `const` for `formattedNow` local variable) 116 | const generateGreeting = (name=DEFAULT_NAME) => { 117 | const formattedNow = new Date(); 118 | 119 | return `Hi, ${name} on ${formattedNow}`; 120 | } 121 | ``` 122 | 123 | There is a pattern in the industry to use `const` if a variable is never going to be reassigned within a block (see eslint [`prefer-const`](http://eslint.org/docs/rules/prefer-const)). However, this is an abuse of `const` as it is intended for variables that are truly constant. The motivation for this practice is that it can help enforce "immutability" since a `const` variable cannot be reassigned. But immutability and `const` bindings are two separate things. For instance, an object declared `const` can still have its properties mutated. 124 | 125 | For more on constants, read [_Learning ES6: Block-level scoping with `let` and `const`_](http://www.eventbrite.com/engineering/learning-es6-block-level-scoping-with-let-and-const/). 126 | 127 | **[⬆ back to top](#table-of-contents)** 128 | 129 | ## Strings 130 | 131 | ### Template literals 132 | 133 | When building up a string, use a template literal instead of string concatenation (eslint: [`prefer-template`](http://eslint.org/docs/rules/prefer-template.html)): 134 | 135 | ```js 136 | // good 137 | const generateGreeting = (name=DEFAULT_NAME) => { 138 | const formattedNow = new Date(); 139 | 140 | return `Hi, ${name} on ${formattedNow}`; 141 | } 142 | 143 | // bad (uses string concatenation) 144 | const generateGreeting = (name=DEFAULT_NAME) => { 145 | const formattedNow = new Date(); 146 | 147 | return 'Hi, ' + name + ' on ' + formattedNow; 148 | } 149 | 150 | // bad (uses array join) 151 | const generateGreeting = (name=DEFAULT_NAME) => { 152 | const formattedNow = new Date(); 153 | 154 | return ['Hi, ', name, ' on ', formattedNow].join(); 155 | } 156 | ``` 157 | 158 | When using template literals, tokens should **not** be padded by spaces (eslint: [`template-curly-spacing`](http://eslint.org/docs/rules/template-curly-spacing)): 159 | 160 | ```js 161 | // good 162 | const generateGreeting = (name=DEFAULT_NAME) => { 163 | const formattedNow = new Date(); 164 | 165 | return `Hi, ${name} on ${formattedNow}`; 166 | } 167 | 168 | // bad (has extra padding around the curlies) 169 | const generateGreeting = (name=DEFAULT_NAME) => { 170 | const formattedNow = new Date(); 171 | 172 | return `Hi, ${ name } on ${ formattedNow }`; 173 | } 174 | ``` 175 | 176 | Don't use template literals when there is nothing to interpolate (eslint: [`no-useless-escape`](http://eslint.org/docs/rules/no-useless-concat)): 177 | 178 | ```js 179 | // good 180 | const COMPANY_NAME = 'Eventbrite'; 181 | 182 | // bad (uses template literal unnecessarily) 183 | const COMPANY_NAME = `Eventbrite`; 184 | ``` 185 | 186 | For more on template literals, read [_Learning ES6: Template literals & tagged templates_](http://www.eventbrite.com/engineering/learning-es6-template-literals-tagged-templates/). 187 | 188 | **[⬆ back to top](#table-of-contents)** 189 | 190 | ## Arrays 191 | 192 | ### Array + spread operator 193 | 194 | Use the spread operator (`...`) to create a shallow copy of an array: 195 | 196 | ```js 197 | // good 198 | let list = [1, 2, 3]; 199 | let listCopy = [...list]; 200 | 201 | // bad (uses `concat` to copy) 202 | let list = [1, 2, 3]; 203 | let listCopy = list.concat(); 204 | 205 | // bad (uses a map to create a copy) 206 | let list = [1, 2, 3]; 207 | let listCopy = list.map((item) => item); 208 | ``` 209 | 210 | Use the spread operator (`...`) to join multiple arrays together: 211 | 212 | ```js 213 | // good 214 | let start = ['do', 're', 'mi']; 215 | let end = ['la', 'ti']; 216 | let scale = [...start, 'fa', 'so', ...end]; 217 | 218 | // bad 219 | let start = ['do', 're', 'mi']; 220 | let end = ['la', 'ti']; 221 | let scale = start.concat(['fa', 'so']).concat(end); 222 | ``` 223 | 224 | Use the spread operator (`...`) to convert an array-like object into an `Array`: 225 | 226 | ```js 227 | // good 228 | 229 | // NodeList object 230 | let nodeList = document.querySelectorAll('p'); 231 | 232 | // Array 233 | let nodes = [...nodeList]; 234 | 235 | 236 | // bad (uses a loop convert to an array) 237 | 238 | // NodeList object 239 | let nodeList = document.querySelectorAll('p'); 240 | 241 | // Array 242 | let nodes = []; 243 | 244 | for (let i = 0; i < nodeList.length; i++) { 245 | nodes.push(nodeList[i]); 246 | } 247 | ``` 248 | 249 | For more on the spread operator, read [_Learning ES6: Rest & Spread Operators_](http://www.eventbrite.com/engineering/learning-es6-rest-spread-operators/#spread-operator). 250 | 251 | **[⬆ back to top](#table-of-contents)** 252 | 253 | ## Objects 254 | 255 | When a variable name matches the name of the object key in an object literal, use [property value shorthand](http://www.eventbrite.com/engineering/learning-es6-enhanced-object-literals/#property-value-shorthand) (eslint: [`object-shorthand`](http://eslint.org/docs/rules/object-shorthand)): 256 | 257 | ```js 258 | let name = 'Eventbrite'; 259 | 260 | // good 261 | let data = { 262 | name 263 | }; 264 | 265 | // bad (duplicates key and variable name) 266 | let data = { 267 | name: name 268 | }; 269 | ``` 270 | 271 | Group any object literal property value shorthands at the beginning of the object literal so that it's easier to see which properties are using the shorthand: 272 | 273 | ```js 274 | let name = 'Eventbrite'; 275 | let location = 'San Francisco, CA'; 276 | 277 | // good 278 | let data = { 279 | name, 280 | location, 281 | ceo: 'Julia Hartz', 282 | founders: ['Julia Hartz', 'Kevin Hartz', 'Renaud Visage'] 283 | }; 284 | 285 | // bad (shorthands aren't at the top) 286 | let data = { 287 | name, 288 | ceo: 'Julia Hartz', 289 | founders: ['Julia Hartz', 'Kevin Hartz', 'Renaud Visage'], 290 | location 291 | }; 292 | ``` 293 | 294 | When creating object literals with dynamic property names, use [computed property keys](http://www.eventbrite.com/engineering/learning-es6-enhanced-object-literals/#computed-property-keys) (eslint: [`object-shorthand`](http://eslint.org/docs/rules/object-shorthand)): 295 | 296 | ```js 297 | let name = 'Eventbrite'; 298 | let location = 'San Francisco, CA'; 299 | let leaderName = 'ceo'; 300 | 301 | // good 302 | let data = { 303 | name, 304 | location, 305 | [leaderName]: 'Julia Hartz', 306 | founders: ['Julia Hartz', 'Kevin Hartz', 'Renaud Visage'] 307 | }; 308 | 309 | // bad (doesn't leverage computed property keys) 310 | let data = { 311 | name, 312 | location, 313 | founders: ['Julia Hartz', 'Kevin Hartz', 'Renaud Visage'] 314 | }; 315 | 316 | data[leaderName] = 'Julia Hartz'; 317 | ``` 318 | 319 | When defining methods on an object literal, use [method definition shorthand](http://www.eventbrite.com/engineering/learning-es6-enhanced-object-literals/#method-definition-shorthand) (eslint: [`object-shorthand`](http://eslint.org/docs/rules/object-shorthand)): 320 | 321 | ```js 322 | let name = 'Eventbrite'; 323 | let location = 'San Francisco, CA'; 324 | let leaderName = 'ceo'; 325 | 326 | // good 327 | let data = { 328 | name, 329 | location, 330 | [leaderName]: 'Julia Hartz', 331 | founders: ['Julia Hartz', 'Kevin Hartz', 'Renaud Visage'], 332 | getDisplay() { 333 | return `${this.name} in ${this.location}`; 334 | } 335 | }; 336 | 337 | // bad (doesn't leverage method definition shorthand) 338 | let data = { 339 | name, 340 | location, 341 | [leaderName]: 'Julia Hartz', 342 | founders: ['Julia Hartz', 'Kevin Hartz', 'Renaud Visage'], 343 | getDisplay: function() { 344 | return `${this.name} in ${this.location}`; 345 | } 346 | }; 347 | ``` 348 | 349 | Use the [object spread operator](https://github.com/sebmarkbage/ecmascript-rest-spread/blob/master/Spread.md) instead of [`Object.assign`](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/assign) to create a shallow copy with source object properties merged in: 350 | 351 | ```js 352 | // good 353 | let warriors = {Steph: 95, Klay: 82, Draymond: 79}; 354 | let newWarriors = { 355 | ...warriors, 356 | Kevin: 97 357 | }; 358 | 359 | // bad (uses Object.assign instead) 360 | let warriors = {Steph: 95, Klay: 82, Draymond: 79}; 361 | let newWarriors = Object.assign({}, warriors, { 362 | Kevin: 97 363 | }); 364 | 365 | // terrible (mutates `warriors` variable) 366 | let warriors = {Steph: 95, Klay: 82, Draymond: 79}; 367 | let newWarriors = Object.assign(warriors, { 368 | Kevin: 97 369 | }); 370 | ``` 371 | 372 | The object spread operator (as well as `Object.assign`) only make shallow copies. As such they only work on a single level of nesting at a time. However, you need to merge into a level deeper than the top level, you can still make use of the object spread operator: 373 | 374 | ```js 375 | let teams = { 376 | warriors: {Steph: 95, Klay: 82, Draymond: 79}, 377 | cavs: {Lebron: 98, Kyrie: 87, Kevin: 80} 378 | }; 379 | let updatedTeams = { 380 | ...teams, 381 | warriors: { 382 | ...updatedTeams.warriors, 383 | Kevin: 97 384 | } 385 | }; 386 | ``` 387 | 388 | For more on enhanced object literals, read [_Learning ES6: Enhanced object literals_](http://www.eventbrite.com/engineering/learning-es6-enhanced-object-literals/). 389 | 390 | **[⬆ back to top](#table-of-contents)** 391 | 392 | ## Functions 393 | 394 | ### Arrow Functions 395 | 396 | When an arrow function expression is needed, use an arrow function in place of an anonymous function (eslint: [`prefer-arrow-callback`](http://eslint.org/docs/rules/prefer-arrow-callback)): 397 | 398 | ```js 399 | // good 400 | [1, 2, 3].map((x) => x * x); 401 | 402 | // bad (uses anonymous function) 403 | [1, 2, 3].map(function(x) { 404 | return x * x; 405 | }) 406 | ``` 407 | 408 | Include a single space around the arrow (`=>`) in an arrow function (eslint: [`arrow-spacing`](http://eslint.org/docs/rules/arrow-spacing)): 409 | 410 | ```js 411 | // good 412 | [1, 2, 3].map((x) => x * x); 413 | 414 | // bad (missing spaces around arrow) 415 | [1, 2, 3].map((x)=>x * x); 416 | ``` 417 | 418 | Always surround the parameters of an arrow function with parentheses (eslint: [`arrow-parens`](http://eslint.org/docs/rules/arrow-parens)): 419 | 420 | ```js 421 | // good 422 | [1, 2, 3].map((x) => x * x); 423 | 424 | // bad (missing parentheses surrounding parameters) 425 | [1, 2, 3].map(x => x * x); 426 | ``` 427 | 428 | When the function body is a single expression, omit the curly braces and use the implicit return syntax (eslint: [`arrow-body-style`](http://eslint.org/docs/rules/arrow-body-style)): 429 | 430 | ```js 431 | // good (uses implicit return for single expression) 432 | [1, 2, 3].map((x) => x * x); 433 | 434 | // bad (doesn't use implicit return for single expression) 435 | [1, 2, 3].map((x) => { 436 | return x * x; 437 | }); 438 | ``` 439 | 440 | When the function body is a single expression, but spans multiple lines, surround the function body in parentheses: 441 | 442 | ```js 443 | // good 444 | eventIds.forEach((eventId) => ( 445 | fetch(`EVENT_SAVE_URL/${eventId}`, { 446 | method: 'POST' 447 | }) 448 | )); 449 | 450 | // bad (missing parentheses surrounding function body) 451 | eventIds.forEach((eventId) => fetch(`EVENT_SAVE_URL/${eventId}`, { 452 | method: 'POST' 453 | })); 454 | ``` 455 | 456 | For more on arrow functions, read [_Learning ES6: Arrow Functions_](http://www.eventbrite.com/engineering/learning-es6-arrow-functions/). 457 | 458 | **[⬆ back to top](#table-of-contents)** 459 | 460 | ### Rest Parameters 461 | 462 | Use the rest operator (`...`) instead of the `arguments` object to handle an arbitrary number of function parameters (eslint [`prefer-rest-params`](http://eslint.org/docs/rules/prefer-rest-params)): 463 | 464 | ```js 465 | // good 466 | const join = (separator, ...values) => ( 467 | values.join(separator); 468 | ); 469 | 470 | // bad (uses arguments object) 471 | function join(separator) { 472 | var values = []; 473 | 474 | for (var argNo = 1; argNo < arguments.length; argNo++) { 475 | values.push(arguments[argNo]); 476 | } 477 | 478 | return values.join(separator); 479 | }; 480 | ``` 481 | 482 | The `arguments` object is problematic for many reasons. It's not an actual `Array` object, so methods like `slice` are unavailable to use. Because we have the `separator` parameter, we have to start at index `1` of `arguments`, which is pretty annoying. Also, just looking at our `join` function, it's not immediately discoverable that it actually takes more than one parameter, let alone that it supports an infinite number of them. Lastly `arguments` doesn't work with [arrow functions](#arrow-functions). 483 | 484 | There should be no spacing between the rest operator and its parameter (eslint: [`rest-spread-spacing`](http://eslint.org/docs/rules/rest-spread-spacing)): 485 | 486 | ```js 487 | 488 | // good 489 | const join = (separator, ...values) => ( 490 | values.join(separator); 491 | ); 492 | 493 | // bad (space between rest operator and param) 494 | const join = (separator, ... values) => ( 495 | values.join(separator); 496 | ); 497 | ``` 498 | 499 | For more on rest parameters, read [_Learning ES6: Rest & Spread Operators_](http://www.eventbrite.com/engineering/learning-es6-rest-spread-operators/#rest-operator). 500 | 501 | **[⬆ back to top](#table-of-contents)** 502 | 503 | ### Default Parameters 504 | 505 | Use default parameters in the function header instead of mutating parameters in the function body: 506 | 507 | ```js 508 | // good 509 | const getData = (options, useCache = true) => { 510 | let data; 511 | 512 | // get data based on whether we're using the 513 | // cache or not 514 | 515 | return data; 516 | } 517 | 518 | // bad (defaults the parameter in function body) 519 | const getData = (options, useCache) => { 520 | let data; 521 | 522 | if (useCache === undefined) { 523 | useCache = true; 524 | } 525 | 526 | // get data based on whether we're using the 527 | // cache or not 528 | 529 | return data; 530 | } 531 | ``` 532 | 533 | Put all default parameters at the end of the function header: 534 | 535 | ```js 536 | // good 537 | const getData = (options, useCache = true) => { 538 | let data; 539 | 540 | // get data based on whether we're using the 541 | // cache or not 542 | 543 | return data; 544 | } 545 | 546 | // bad (default parameter isn't at the end) 547 | const getData = (useCache = true, options) => { 548 | let data; 549 | 550 | // get data based on whether we're using the 551 | // cache or not 552 | 553 | return data; 554 | } 555 | ``` 556 | 557 | For more on default parameters, read [_Learning ES6: Default parameters_](http://www.eventbrite.com/engineering/learning-es6-default-parameters/). 558 | 559 | **[⬆ back to top](#table-of-contents)** 560 | 561 | ### Spread Operator 562 | 563 | Use the spread operator (`...`) instead of [`Function.prototype.apply`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/apply) when needing to pass elements of an array as arguments to a function call (eslint: [`prefer-spread`](http://eslint.org/docs/rules/prefer-spread)): 564 | 565 | ```js 566 | // good 567 | let maxValue = Math.max(...[3, 41, 17]); 568 | let today = new Date(...[2016, 11, 16]); 569 | 570 | // bad (uses `apply`) 571 | let maxValue = Math.max.apply(null, [3, 41, 17]); 572 | let today = new (Function.prototype.bind.apply(Date, [null, 2016, 11, 16])); 573 | ``` 574 | 575 | Using the spread operator is cleaner because you don't have to specify a context (first example). Furthermore you cannot easily combine `new` with `apply` (second example). 576 | 577 | There should be no spacing between the spread operator and its expression (eslint: [`rest-spread-spacing`](http://eslint.org/docs/rules/rest-spread-spacing)): 578 | 579 | ```js 580 | // good 581 | let maxValue = Math.max(...[3, 41, 17]); 582 | 583 | // bad (space between spread operator and 584 | // array literal) 585 | let maxValue = Math.max(... [3, 41, 17]); 586 | ``` 587 | 588 | For more on the spread operator, read [_Learning ES6: Rest & Spread Operators_](http://www.eventbrite.com/engineering/learning-es6-rest-spread-operators/#spread-operator). 589 | 590 | **[⬆ back to top](#table-of-contents)** 591 | 592 | ## Classes 593 | 594 | Avoid classes with an empty constructor because classes have a default constructor when one isn't specified (eslint: [`no-useless-constructor`](http://eslint.org/docs/rules/no-useless-constructor)): 595 | 596 | ```js 597 | // good 598 | class Person { 599 | speak(phrase) { 600 | 601 | } 602 | } 603 | class Child extends Person { 604 | speak(phrase) { 605 | 606 | } 607 | } 608 | 609 | // bad (has empty/unnecessary constructors) 610 | class Person { 611 | constructor() { 612 | 613 | } 614 | speak(phrase) { 615 | 616 | } 617 | } 618 | class Child extends Person { 619 | constructor() { 620 | super(); 621 | } 622 | speak(phrase) { 623 | 624 | } 625 | } 626 | ``` 627 | 628 | Avoid duplicate class members because the interpreter will (silently) use the last one (eslint [`no-dupe-class-members`](http://eslint.org/docs/rules/no-dupe-class-members)): 629 | 630 | ```js 631 | // good 632 | class Person { 633 | speak(phrase) { 634 | 635 | } 636 | } 637 | 638 | // bad (has duplicate methods) 639 | class Person { 640 | speak(phrase) { 641 | 642 | } 643 | speak(phrase, lang) { 644 | 645 | } 646 | } 647 | ``` 648 | 649 | Set default values for class properties using declarative syntax instead of defaulting within the `constructor` so that it's clear which properties the class supports instead of being hidden in code: 650 | 651 | ```js 652 | // good 653 | class TextInput extends React.Component { 654 | state = { 655 | value: '' 656 | } 657 | 658 | render() { 659 | return (
); 660 | } 661 | } 662 | 663 | // bad (defaults `state` within constructor instead 664 | // of using declarative syntax) 665 | class TextInput extends React.Component { 666 | constructor(props) { 667 | super(props); 668 | 669 | this.state = { 670 | value: '' 671 | }; 672 | } 673 | 674 | render() { 675 | return (
); 676 | } 677 | } 678 | ``` 679 | 680 | Initialize static class properties using declarative syntax instead of assigning to the class after the class declaration in order to keep everything within the class declaration: 681 | 682 | ```js 683 | // good 684 | class Button extends React.Component { 685 | static propTypes = { 686 | type: React.PropTypes.string.isRequired 687 | } 688 | 689 | render() { 690 | return ( 69 |

70 | You have successfully registered for this event! 71 |

72 | Browse all events 73 |
74 | ``` 75 | 76 | Tests using `getSpecWrapper` would look like: 77 | 78 | ```js 79 | // good 80 | it('has more link pointing to browse URL when `type` is browse', () => { 81 | let onMoreAction = jest.fn(); 82 | let wrapper = mount(); 83 | let moreLinkWrapper = getSpecWrapper(wrapper, 'notification-more-link'); 84 | 85 | moreLinkWrapper.simulate('click'); 86 | 87 | expect(onMoreAction).toHaveBeenCalled(); 88 | }); 89 | 90 | // bad (searches by tag name and CSS class) 91 | it('has more link pointing to browse URL when `type` is browse', () => { 92 | let onMoreAction = jest.fn(); 93 | let wrapper = mount(); 94 | let moreLinkWrapper = wrapper.find('a.notification__more-link'); 95 | 96 | moreLinkWrapper.simulate('click'); 97 | 98 | expect(onMoreAction).toHaveBeenCalled(); 99 | }); 100 | ``` 101 | 102 | As a reference, here are the implementations for `getSpecWrapper`: 103 | 104 | ```js 105 | // utils/unitTest.js 106 | 107 | export const DATA_SPEC_ATTRIBUTE_NAME = 'data-spec'; 108 | 109 | /** 110 | * Finds all instances of components in the rendered `componentWrapper` that are DOM components 111 | * with the `data-spec` attribute matching `name`. 112 | * @param {ReactWrapper} componentWrapper - Rendered componentWrapper (result of mount, shallow, or render) 113 | * @param {string} specName - Name of `data-spec` attribute value to find 114 | * @param {string|Function} typeFilter - (Optional) Expected type of the wrappers (defaults to all HTML tags) 115 | * @returns {ReactComponent[]} All matching DOM components 116 | */ 117 | export const getSpecWrapper = (componentWrapper, specName, typeFilter) => { 118 | let specWrappers; 119 | 120 | if (!typeFilter) { 121 | specWrappers = componentWrapper.find(`[${DATA_SPEC_ATTRIBUTE_NAME}="${specName}"]`); 122 | } else { 123 | specWrappers = componentWrapper.findWhere((wrapper) => ( 124 | wrapper.prop(DATA_SPEC_ATTRIBUTE_NAME) === specName && wrapper.type() === typeFilter 125 | )); 126 | } 127 | 128 | return specWrappers; 129 | }; 130 | ``` 131 | 132 | **[⬆ back to top](#table-of-contents)** 133 | 134 | ## Finding components 135 | 136 | You can find a component simply by using Enzyme's [`find`](http://airbnb.io/enzyme/docs/api/ReactWrapper/find.html) and passing the component class: 137 | 138 | ```js 139 | it('should render a checked checkbox if it is selected', () => { 140 | let wrapper = mount(); 141 | let checkboxWrapper = wrapper.find(Checkbox); 142 | 143 | expect(checkboxWrapper).toHaveProp('isChecked', true); 144 | }); 145 | ``` 146 | 147 | This works as long as there's only one `Checkbox` rendered within `Component`. If there are multiple `Checkbox` components within `Component`, `checkboxWrapper` would have multiple elements in it. Instead you can add a `data-spec` attribute to the specific `Checkbox` and use `getSpecWrapper`: 148 | 149 | ```js 150 | // good 151 | it('should render a checked checkbox if it is selected', () => { 152 | let wrapper = mount(); 153 | 154 | // pass the component class as the third parameter to `getSpecWrapper` 155 | let selectAllCheckboxWrapper = getSpecWrapper(wrapper, 'component-selectAll', Checkbox); 156 | 157 | expect(selectAllCheckboxWrapper).toHaveProp('isChecked', true); 158 | }); 159 | 160 | // bad (finds the appropriate Checkbox based on source order) 161 | it('should render a checked checkbox if it is selected', () => { 162 | let wrapper = mount(); 163 | let selectAllCheckboxWrapper = wrapper.find(Checkbox).at(2); 164 | 165 | expect(selectAllCheckboxWrapper).toHaveProp('isChecked', true); 166 | }); 167 | ``` 168 | 169 | The key in the "good" example is the third parameter passed to `getSpecWrapper`. By default `getSpecWrapper` will try to find a node with the specified `data-spec`. But if you specify the component class (`Checkbox` in this case), it'll return a reference to the component wrapper. 170 | 171 | **[⬆ back to top](#table-of-contents)** 172 | 173 | ## Testing existence 174 | 175 | ### Testing node existence 176 | 177 | To [find nodes](#finding-nodes) you use the `getSpecWrapper` helper and use the `jest-enzyme` [`.toBePresent`](https://github.com/blainekasten/enzyme-matchers#tobepresent) and [`.toBeEmpty`](https://github.com/blainekasten/enzyme-matchers#tobeempty) assertion matchers: 178 | 179 | ```js 180 | let wrapper = mount(); 181 | 182 | // assert that node exists (doesn't throw an Error) 183 | expect(wrapper).toBePresent(); 184 | 185 | // assert that node doesn't exist (throws an Error) 186 | expect(wrapper).toBeEmpty(); 187 | ``` 188 | 189 | **[⬆ back to top](#table-of-contents)** 190 | 191 | ### Testing component existence 192 | 193 | Typically, you'll [find components](#finding-components) by using Enzyme's `find` method which returns an an Enzyme [`ReactWrapper`](https://github.com/airbnb/enzyme/tree/master/docs/api/ReactWrapper) and the `jest-enzyme` [`.toBePresent`](https://github.com/blainekasten/enzyme-matchers#tobepresent) and [`.toBeEmpty`](https://github.com/blainekasten/enzyme-matchers#tobeempty) assertion matchers: 194 | 195 | ```js 196 | let wrapper = mount(` DOM node. The `TextInput` has an `onChange` prop that gets called whenever the input field value changes. The `onChange` prop is also called with the current value that's in the input field. The test case would be set up like: 331 | 332 | ```js 333 | it('properly fires `onChange` when input changes', () => { 334 | let onChange = jest.fn(); 335 | 336 | // pass mock function to component as `onChange` prop 337 | let wrapper = mount(); 338 | let inputWrapper = getSpecWrapper(wrapper, 'text-input'); 339 | let inputValue = 'Here is a value'; 340 | 341 | // Create a fake event with the properties needed by the component 342 | let mockEvent = { 343 | target: { 344 | value: inputValue 345 | } 346 | }; 347 | 348 | // simulate onChange event on input DOM 349 | inputWrapper.simulate('change', mockEvent); 350 | 351 | // assert that the stubbed function was called with the 352 | // expected value 353 | expect(onChange).toHaveBeenCalledWith(inputValue); 354 | }); 355 | ``` 356 | 357 | The test case above uses [`jest.fn()`](http://facebook.github.io/jest/docs/mock-function-api.html) to create a mock function. The mock is passed as the `TextInput` component's `onChange` prop so that we can make assertions on it at the end. After [finding a reference](#finding-nodes) to the input field, we simulate a fake `onChange` DOM event on the input field (using Enzyme's [`.simulate`](https://github.com/airbnb/enzyme/blob/master/docs/api/ReactWrapper/simulate.md) helper). Because the `TextInput` implementation expects to read `e.target.value` from an actual DOM event when it's running the browser, we have to mock that event with an object of the same structure. We don't need a full mock DOM event; we only need to mock what the code is actually calling. 358 | 359 | Simulating the fake event on the input field will ultimately call our `onChange` with its current value. Therefore, our assertion is that `onChange` was not only called, but also called with the expected input value. This assertion leverages the `.toHaveBeenCalledWith` assertion helper from `jest-enzyme`. 360 | 361 | **[⬆ back to top](#table-of-contents)** 362 | 363 | ### Testing events triggered by child components 364 | 365 | More than likely instead of your component adding event handlers directly to DOM nodes, it will be adding handlers to child components. Therefore instead of simulating a DOM event, simulate the child component's event handler being invoked. 366 | 367 | Let's say you have an `AutocompleteField` component that has a child `TextInput`. The `AutocompleteField` has an `onChange` prop that is invoked whenever its child `TextInput`'s `onChange` event is invoked. The `AutocompleteField`'s `onChange` prop also passes the current input value. The test case would be set up like: 368 | 369 | ```js 370 | it('properly fires `onChange` when input changes', () => { 371 | let onChange = jest.fn(); 372 | 373 | // pass stubbed function to component as `onChange` prop 374 | let wrapper = mount(); 375 | let textInputWrapper = wrapper.find(TextInput); 376 | let inputValue = 'Here is a value'; 377 | 378 | // We don't want to make any assumptions about the markup of `TextInput`. The 379 | // `AutocompleteField` component handles `onChange` of `TextInput`, so all we need to 380 | // do is call the prop directly like `TextInput` would and ensure we get the appropriate 381 | // value 382 | textInputWrapper.prop('onChange')(inputValue); 383 | 384 | // assert that the stubbed function was called with the 385 | // expected value 386 | expect(onChange).toHaveBeenCalledWith(inputValue); 387 | }); 388 | ``` 389 | 390 | The test case above uses [`jest.fn()`](http://facebook.github.io/jest/docs/mock-function-api.html) to create a mock function. The mock is passed as the `AutocompleteField` component's `onChange` prop so that we can make assertions on it at the end. After [finding a reference](#finding-components) to the `TextInput`, we simulate how `TextInput` would invoke its `onChange` callback prop. We get a reference to the prop using Enzyme's [`.prop`](https://github.com/airbnb/enzyme/blob/master/docs/api/ReactWrapper/prop.md) helper and call the function with the `inputValue`. This exactly how `TextInput` would call it when its DOM input field changes. However, because we don't want to make any assumptions about the markup of `TextInput` we simulate its `onChange` prop instead of digging into it in order to simulate its DOM. 391 | 392 | Invoking the `onChange` prop will ultimately call our `onChange` with the value. Therefore, our assertion is that `onChange` was not only called, but also called with the expected input value. This assertion leverages the `.toHaveBeenCalledWith` assertion helper from `jest-enzyme`. 393 | 394 | **[⬆ back to top](#table-of-contents)** 395 | 396 | ## Testing state 397 | 398 | Although `jest-enzyme` provides a [`.toHaveState()`](https://github.com/blainekasten/enzyme-matchers#tohavestate) helper method for asserting component state, it shouldn't be used in tests because the component's state is internal (and shouldn't be tested). Based on our [testing philosophy](#testing-philosophy), we only want to test the public API of the component. 399 | 400 | When a component's state changes, the component is re-rendered, resulting in a change in markup. By testing only the changed markup (part of the component's public output), instead of the component's internal state, we can refactor the component's internals and have all of our test cases still pass. In sum, our test cases are a little less fragile. 401 | 402 | Let's say for instance we had a component that has a `Checkbox` child component that toggles the component between inactive and active states. The active state is publicly represented by an `isActive` class added to the root DOM node. The test case could look something like: 403 | 404 | ```js 405 | // good (tests internal state *indirectly* via re-rendered markup) 406 | it('toggles active state when checkbox is toggled', () => { 407 | let wrapper = mount(); 408 | let checkboxWrapper = wrapper.find(Checkbox); 409 | 410 | // first assert that by default the active class is *not* present 411 | expect(wrapper).toMatchSnapshot(); 412 | 413 | // simulate toggling the checkbox on by calling its 414 | // onChange callback handler passing `true` for 415 | // checked state 416 | checkboxWrapper.prop('onChange')(true); 417 | 418 | // now assert that the active class *is* present 419 | expect(wrapper).toMatchSnapshot(); 420 | 421 | // simulate toggling the checkbox back off 422 | checkboxWrapper.prop('onChange')(false); 423 | 424 | // finally assert once again that active class is *not* 425 | // present 426 | expect(wrapper).toMatchSnapshot(); 427 | }); 428 | 429 | // bad (tests internal state directly) 430 | it('toggles active state when checkbox is toggled', () => { 431 | let wrapper = mount(); 432 | let checkboxWrapper = wrapper.find(Checkbox); 433 | 434 | // assert that component's `isActive` internal state is 435 | // initially false 436 | expect(wrapper).toHaveState('isActive', false); 437 | 438 | // simulate toggling the checkbox on by calling its 439 | // onChange callback handler passing `true` for 440 | // checked state 441 | checkboxWrapper.prop('onChange')(true); 442 | 443 | // now assert that the `isActive` internal state is 444 | // true 445 | expect(wrapper).toHaveState('isActive', true); 446 | 447 | // simulate toggling the checkbox back off 448 | checkboxWrapper.prop('onChange')(false); 449 | 450 | // finally assert once again that `isActive` internal 451 | // state is false 452 | expect(wrapper).toHaveState('isActive', false); 453 | }); 454 | ``` 455 | 456 | Both the "good" and "bad" test cases are basically the same. The only difference is what is asserted. Ultimately, what we care about is that the root node has the appropriate CSS class; the changing of the internal `isActive` state just happens to be the mechanism that we accomplish it. This is what makes the "good" example better. 457 | 458 | See [Testing events triggered by child components](#testing-events-triggered-by-child-components) for more on simulating child component events. 459 | 460 | **[⬆ back to top](#table-of-contents)** 461 | 462 | ## Testing updated props 463 | 464 | Typically components are stateless, meaning that what is rendered by the component is 100% based upon the props that are based in. In these cases creating a component with initial props when [testing render](#testing-render) and [testing events](#testing-events) as explained above should suffice. There shouldn't be a need to test the re-render of a component receiving new props. 465 | 466 | However, when a component leverages internal state and its props are changed, what will be rendered will be based on a combination of those updated props and the existing state. In this case, test that the new markup is as it should be, indirectly verifying that the updated prop(s) either have or have not overridden the existing state. 467 | 468 | Let's say we have a `TextInput` component. It has `initialValue` & `value` props (among many others). The `initialValue` prop will initialize the `TextInput` component's underlying `` node's value, but won't override the node if the prop is later updated. However, the `value` prop will both initialize the `` as well as override its value. 469 | 470 | To test the `initialValue` prop behavior: 471 | 472 | ```js 473 | it('does NOT allow `initialValue` to override existing value', () => { 474 | let initialValue = 'react'; 475 | let newValue = 'enzyme'; 476 | let wrapper = mount(); 477 | 478 | // ensure that the `initialValue` is properly reflected 479 | // by checking the node 480 | expect(wrapper).toMatchSnapshot(); 481 | 482 | // update the TextInput's props 483 | wrapper.setProps({initialValue: newValue}); 484 | 485 | // ensure that the node's value hasn't changed 486 | expect(wrapper).toMatchSnapshot(); 487 | }); 488 | ``` 489 | 490 | To test the `value` prop behavior: 491 | 492 | ```js 493 | it('DOES allow `value` to override existing value', () => { 494 | let initialValue = 'react'; 495 | let newValue = 'enzyme'; 496 | let wrapper = mount(); 497 | 498 | // ensure that the `initialValue` is properly reflected 499 | // by checking the node 500 | expect(wrapper).toMatchSnapshot(); 501 | 502 | // update the TextInput's props 503 | wrapper.setProps({value: newValue}); 504 | 505 | // ensure that the node's value has changed 506 | expect(wrapper).toMatchSnapshot(); 507 | }); 508 | ``` 509 | 510 | The key to passing new props to the existing `TextInput` component is the [`setProps`](https://github.com/airbnb/enzyme/blob/master/docs/api/mount.md#setpropsnextprops--reactwrapper) helper method. It will cause a re-render, which will allow us to assert that the new markup is as it should be. 511 | 512 | **[⬆ back to top](#table-of-contents)** 513 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@babel/code-frame@7.0.0-beta.44": 6 | version "7.0.0-beta.44" 7 | resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0-beta.44.tgz#2a02643368de80916162be70865c97774f3adbd9" 8 | dependencies: 9 | "@babel/highlight" "7.0.0-beta.44" 10 | 11 | "@babel/generator@7.0.0-beta.44": 12 | version "7.0.0-beta.44" 13 | resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.0.0-beta.44.tgz#c7e67b9b5284afcf69b309b50d7d37f3e5033d42" 14 | dependencies: 15 | "@babel/types" "7.0.0-beta.44" 16 | jsesc "^2.5.1" 17 | lodash "^4.2.0" 18 | source-map "^0.5.0" 19 | trim-right "^1.0.1" 20 | 21 | "@babel/helper-function-name@7.0.0-beta.44": 22 | version "7.0.0-beta.44" 23 | resolved "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.0.0-beta.44.tgz#e18552aaae2231100a6e485e03854bc3532d44dd" 24 | dependencies: 25 | "@babel/helper-get-function-arity" "7.0.0-beta.44" 26 | "@babel/template" "7.0.0-beta.44" 27 | "@babel/types" "7.0.0-beta.44" 28 | 29 | "@babel/helper-get-function-arity@7.0.0-beta.44": 30 | version "7.0.0-beta.44" 31 | resolved "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0-beta.44.tgz#d03ca6dd2b9f7b0b1e6b32c56c72836140db3a15" 32 | dependencies: 33 | "@babel/types" "7.0.0-beta.44" 34 | 35 | "@babel/helper-split-export-declaration@7.0.0-beta.44": 36 | version "7.0.0-beta.44" 37 | resolved "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.0.0-beta.44.tgz#c0b351735e0fbcb3822c8ad8db4e583b05ebd9dc" 38 | dependencies: 39 | "@babel/types" "7.0.0-beta.44" 40 | 41 | "@babel/highlight@7.0.0-beta.44": 42 | version "7.0.0-beta.44" 43 | resolved "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0-beta.44.tgz#18c94ce543916a80553edcdcf681890b200747d5" 44 | dependencies: 45 | chalk "^2.0.0" 46 | esutils "^2.0.2" 47 | js-tokens "^3.0.0" 48 | 49 | "@babel/template@7.0.0-beta.44": 50 | version "7.0.0-beta.44" 51 | resolved "https://registry.npmjs.org/@babel/template/-/template-7.0.0-beta.44.tgz#f8832f4fdcee5d59bf515e595fc5106c529b394f" 52 | dependencies: 53 | "@babel/code-frame" "7.0.0-beta.44" 54 | "@babel/types" "7.0.0-beta.44" 55 | babylon "7.0.0-beta.44" 56 | lodash "^4.2.0" 57 | 58 | "@babel/traverse@7.0.0-beta.44": 59 | version "7.0.0-beta.44" 60 | resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.0.0-beta.44.tgz#a970a2c45477ad18017e2e465a0606feee0d2966" 61 | dependencies: 62 | "@babel/code-frame" "7.0.0-beta.44" 63 | "@babel/generator" "7.0.0-beta.44" 64 | "@babel/helper-function-name" "7.0.0-beta.44" 65 | "@babel/helper-split-export-declaration" "7.0.0-beta.44" 66 | "@babel/types" "7.0.0-beta.44" 67 | babylon "7.0.0-beta.44" 68 | debug "^3.1.0" 69 | globals "^11.1.0" 70 | invariant "^2.2.0" 71 | lodash "^4.2.0" 72 | 73 | "@babel/types@7.0.0-beta.44": 74 | version "7.0.0-beta.44" 75 | resolved "https://registry.npmjs.org/@babel/types/-/types-7.0.0-beta.44.tgz#6b1b164591f77dec0a0342aca995f2d046b3a757" 76 | dependencies: 77 | esutils "^2.0.2" 78 | lodash "^4.2.0" 79 | to-fast-properties "^2.0.0" 80 | 81 | acorn-jsx@^3.0.0: 82 | version "3.0.1" 83 | resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz#afdf9488fb1ecefc8348f6fb22f464e32a58b36b" 84 | dependencies: 85 | acorn "^3.0.4" 86 | 87 | acorn@^3.0.4: 88 | version "3.3.0" 89 | resolved "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz#45e37fb39e8da3f25baee3ff5369e2bb5f22017a" 90 | 91 | acorn@^5.5.0: 92 | version "5.5.3" 93 | resolved "https://registry.npmjs.org/acorn/-/acorn-5.5.3.tgz#f473dd47e0277a08e28e9bec5aeeb04751f0b8c9" 94 | 95 | ajv-keywords@^2.1.0: 96 | version "2.1.1" 97 | resolved "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-2.1.1.tgz#617997fc5f60576894c435f940d819e135b80762" 98 | 99 | ajv@^5.2.3, ajv@^5.3.0: 100 | version "5.5.2" 101 | resolved "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz#73b5eeca3fab653e3d3f9422b341ad42205dc965" 102 | dependencies: 103 | co "^4.6.0" 104 | fast-deep-equal "^1.0.0" 105 | fast-json-stable-stringify "^2.0.0" 106 | json-schema-traverse "^0.3.0" 107 | 108 | ansi-escapes@^3.0.0: 109 | version "3.1.0" 110 | resolved "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz#f73207bb81207d75fd6c83f125af26eea378ca30" 111 | 112 | ansi-regex@^2.0.0: 113 | version "2.1.1" 114 | resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" 115 | 116 | ansi-regex@^3.0.0: 117 | version "3.0.0" 118 | resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" 119 | 120 | ansi-styles@^2.2.1: 121 | version "2.2.1" 122 | resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" 123 | 124 | ansi-styles@^3.2.1: 125 | version "3.2.1" 126 | resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" 127 | dependencies: 128 | color-convert "^1.9.0" 129 | 130 | argparse@^1.0.7: 131 | version "1.0.10" 132 | resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" 133 | dependencies: 134 | sprintf-js "~1.0.2" 135 | 136 | aria-query@^0.7.0: 137 | version "0.7.1" 138 | resolved "https://registry.npmjs.org/aria-query/-/aria-query-0.7.1.tgz#26cbb5aff64144b0a825be1846e0b16cfa00b11e" 139 | dependencies: 140 | ast-types-flow "0.0.7" 141 | commander "^2.11.0" 142 | 143 | array-includes@^3.0.3: 144 | version "3.0.3" 145 | resolved "https://registry.npmjs.org/array-includes/-/array-includes-3.0.3.tgz#184b48f62d92d7452bb31b323165c7f8bd02266d" 146 | dependencies: 147 | define-properties "^1.1.2" 148 | es-abstract "^1.7.0" 149 | 150 | array-union@^1.0.1: 151 | version "1.0.2" 152 | resolved "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39" 153 | dependencies: 154 | array-uniq "^1.0.1" 155 | 156 | array-uniq@^1.0.1: 157 | version "1.0.3" 158 | resolved "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6" 159 | 160 | arrify@^1.0.0: 161 | version "1.0.1" 162 | resolved "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" 163 | 164 | asap@~2.0.3: 165 | version "2.0.6" 166 | resolved "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" 167 | 168 | ast-types-flow@0.0.7: 169 | version "0.0.7" 170 | resolved "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz#f70b735c6bca1a5c9c22d982c3e39e7feba3bdad" 171 | 172 | axobject-query@^0.1.0: 173 | version "0.1.0" 174 | resolved "https://registry.npmjs.org/axobject-query/-/axobject-query-0.1.0.tgz#62f59dbc59c9f9242759ca349960e7a2fe3c36c0" 175 | dependencies: 176 | ast-types-flow "0.0.7" 177 | 178 | babel-code-frame@^6.22.0: 179 | version "6.26.0" 180 | resolved "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b" 181 | dependencies: 182 | chalk "^1.1.3" 183 | esutils "^2.0.2" 184 | js-tokens "^3.0.2" 185 | 186 | babel-eslint@^8.2.3: 187 | version "8.2.3" 188 | resolved "https://registry.npmjs.org/babel-eslint/-/babel-eslint-8.2.3.tgz#1a2e6681cc9bc4473c32899e59915e19cd6733cf" 189 | dependencies: 190 | "@babel/code-frame" "7.0.0-beta.44" 191 | "@babel/traverse" "7.0.0-beta.44" 192 | "@babel/types" "7.0.0-beta.44" 193 | babylon "7.0.0-beta.44" 194 | eslint-scope "~3.7.1" 195 | eslint-visitor-keys "^1.0.0" 196 | 197 | babylon@7.0.0-beta.44: 198 | version "7.0.0-beta.44" 199 | resolved "https://registry.npmjs.org/babylon/-/babylon-7.0.0-beta.44.tgz#89159e15e6e30c5096e22d738d8c0af8a0e8ca1d" 200 | 201 | balanced-match@^1.0.0: 202 | version "1.0.0" 203 | resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" 204 | 205 | brace-expansion@^1.1.7: 206 | version "1.1.11" 207 | resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" 208 | dependencies: 209 | balanced-match "^1.0.0" 210 | concat-map "0.0.1" 211 | 212 | buffer-from@^1.0.0: 213 | version "1.0.0" 214 | resolved "https://registry.npmjs.org/buffer-from/-/buffer-from-1.0.0.tgz#4cb8832d23612589b0406e9e2956c17f06fdf531" 215 | 216 | builtin-modules@^1.0.0: 217 | version "1.1.1" 218 | resolved "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" 219 | 220 | caller-path@^0.1.0: 221 | version "0.1.0" 222 | resolved "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz#94085ef63581ecd3daa92444a8fe94e82577751f" 223 | dependencies: 224 | callsites "^0.2.0" 225 | 226 | callsites@^0.2.0: 227 | version "0.2.0" 228 | resolved "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz#afab96262910a7f33c19a5775825c69f34e350ca" 229 | 230 | chalk@^1.1.3: 231 | version "1.1.3" 232 | resolved "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" 233 | dependencies: 234 | ansi-styles "^2.2.1" 235 | escape-string-regexp "^1.0.2" 236 | has-ansi "^2.0.0" 237 | strip-ansi "^3.0.0" 238 | supports-color "^2.0.0" 239 | 240 | chalk@^2.0.0, chalk@^2.1.0: 241 | version "2.4.1" 242 | resolved "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz#18c49ab16a037b6eb0152cc83e3471338215b66e" 243 | dependencies: 244 | ansi-styles "^3.2.1" 245 | escape-string-regexp "^1.0.5" 246 | supports-color "^5.3.0" 247 | 248 | chardet@^0.4.0: 249 | version "0.4.2" 250 | resolved "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz#b5473b33dc97c424e5d98dc87d55d4d8a29c8bf2" 251 | 252 | circular-json@^0.3.1: 253 | version "0.3.3" 254 | resolved "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz#815c99ea84f6809529d2f45791bdf82711352d66" 255 | 256 | cli-cursor@^2.1.0: 257 | version "2.1.0" 258 | resolved "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5" 259 | dependencies: 260 | restore-cursor "^2.0.0" 261 | 262 | cli-width@^2.0.0: 263 | version "2.2.0" 264 | resolved "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639" 265 | 266 | co@^4.6.0: 267 | version "4.6.0" 268 | resolved "https://registry.npmjs.org/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" 269 | 270 | color-convert@^1.9.0: 271 | version "1.9.1" 272 | resolved "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz#c1261107aeb2f294ebffec9ed9ecad529a6097ed" 273 | dependencies: 274 | color-name "^1.1.1" 275 | 276 | color-name@^1.1.1: 277 | version "1.1.3" 278 | resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" 279 | 280 | commander@^2.11.0: 281 | version "2.15.1" 282 | resolved "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz#df46e867d0fc2aec66a34662b406a9ccafff5b0f" 283 | 284 | concat-map@0.0.1: 285 | version "0.0.1" 286 | resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" 287 | 288 | concat-stream@^1.6.0: 289 | version "1.6.2" 290 | resolved "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" 291 | dependencies: 292 | buffer-from "^1.0.0" 293 | inherits "^2.0.3" 294 | readable-stream "^2.2.2" 295 | typedarray "^0.0.6" 296 | 297 | contains-path@^0.1.0: 298 | version "0.1.0" 299 | resolved "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz#fe8cf184ff6670b6baef01a9d4861a5cbec4120a" 300 | 301 | core-js@^1.0.0: 302 | version "1.2.7" 303 | resolved "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636" 304 | 305 | core-util-is@~1.0.0: 306 | version "1.0.2" 307 | resolved "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" 308 | 309 | cross-spawn@^5.1.0: 310 | version "5.1.0" 311 | resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449" 312 | dependencies: 313 | lru-cache "^4.0.1" 314 | shebang-command "^1.2.0" 315 | which "^1.2.9" 316 | 317 | damerau-levenshtein@^1.0.0: 318 | version "1.0.4" 319 | resolved "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.4.tgz#03191c432cb6eea168bb77f3a55ffdccb8978514" 320 | 321 | debug@^2.6.8, debug@^2.6.9: 322 | version "2.6.9" 323 | resolved "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" 324 | dependencies: 325 | ms "2.0.0" 326 | 327 | debug@^3.1.0: 328 | version "3.1.0" 329 | resolved "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" 330 | dependencies: 331 | ms "2.0.0" 332 | 333 | deep-is@~0.1.3: 334 | version "0.1.3" 335 | resolved "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" 336 | 337 | define-properties@^1.1.2: 338 | version "1.1.2" 339 | resolved "https://registry.npmjs.org/define-properties/-/define-properties-1.1.2.tgz#83a73f2fea569898fb737193c8f873caf6d45c94" 340 | dependencies: 341 | foreach "^2.0.5" 342 | object-keys "^1.0.8" 343 | 344 | del@^2.0.2: 345 | version "2.2.2" 346 | resolved "https://registry.npmjs.org/del/-/del-2.2.2.tgz#c12c981d067846c84bcaf862cff930d907ffd1a8" 347 | dependencies: 348 | globby "^5.0.0" 349 | is-path-cwd "^1.0.0" 350 | is-path-in-cwd "^1.0.0" 351 | object-assign "^4.0.1" 352 | pify "^2.0.0" 353 | pinkie-promise "^2.0.0" 354 | rimraf "^2.2.8" 355 | 356 | doctrine@1.5.0: 357 | version "1.5.0" 358 | resolved "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz#379dce730f6166f76cefa4e6707a159b02c5a6fa" 359 | dependencies: 360 | esutils "^2.0.2" 361 | isarray "^1.0.0" 362 | 363 | doctrine@^2.0.2, doctrine@^2.1.0: 364 | version "2.1.0" 365 | resolved "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" 366 | dependencies: 367 | esutils "^2.0.2" 368 | 369 | emoji-regex@^6.1.0: 370 | version "6.5.1" 371 | resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-6.5.1.tgz#9baea929b155565c11ea41c6626eaa65cef992c2" 372 | 373 | encoding@^0.1.11: 374 | version "0.1.12" 375 | resolved "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz#538b66f3ee62cd1ab51ec323829d1f9480c74beb" 376 | dependencies: 377 | iconv-lite "~0.4.13" 378 | 379 | error-ex@^1.2.0: 380 | version "1.3.1" 381 | resolved "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz#f855a86ce61adc4e8621c3cda21e7a7612c3a8dc" 382 | dependencies: 383 | is-arrayish "^0.2.1" 384 | 385 | es-abstract@^1.7.0: 386 | version "1.11.0" 387 | resolved "https://registry.npmjs.org/es-abstract/-/es-abstract-1.11.0.tgz#cce87d518f0496893b1a30cd8461835535480681" 388 | dependencies: 389 | es-to-primitive "^1.1.1" 390 | function-bind "^1.1.1" 391 | has "^1.0.1" 392 | is-callable "^1.1.3" 393 | is-regex "^1.0.4" 394 | 395 | es-to-primitive@^1.1.1: 396 | version "1.1.1" 397 | resolved "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.1.1.tgz#45355248a88979034b6792e19bb81f2b7975dd0d" 398 | dependencies: 399 | is-callable "^1.1.1" 400 | is-date-object "^1.0.1" 401 | is-symbol "^1.0.1" 402 | 403 | escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: 404 | version "1.0.5" 405 | resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" 406 | 407 | eslint-import-resolver-node@^0.3.1: 408 | version "0.3.2" 409 | resolved "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.2.tgz#58f15fb839b8d0576ca980413476aab2472db66a" 410 | dependencies: 411 | debug "^2.6.9" 412 | resolve "^1.5.0" 413 | 414 | eslint-module-utils@^2.2.0: 415 | version "2.2.0" 416 | resolved "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.2.0.tgz#b270362cd88b1a48ad308976ce7fa54e98411746" 417 | dependencies: 418 | debug "^2.6.8" 419 | pkg-dir "^1.0.0" 420 | 421 | eslint-plugin-babel@^5.1.0: 422 | version "5.1.0" 423 | resolved "https://registry.npmjs.org/eslint-plugin-babel/-/eslint-plugin-babel-5.1.0.tgz#9c76e476162041e50b6ba69aa4eae3bdd6a4e1c3" 424 | dependencies: 425 | eslint-rule-composer "^0.3.0" 426 | 427 | eslint-plugin-import@^2.2.0: 428 | version "2.11.0" 429 | resolved "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.11.0.tgz#15aeea37a67499d848e8e981806d4627b5503816" 430 | dependencies: 431 | contains-path "^0.1.0" 432 | debug "^2.6.8" 433 | doctrine "1.5.0" 434 | eslint-import-resolver-node "^0.3.1" 435 | eslint-module-utils "^2.2.0" 436 | has "^1.0.1" 437 | lodash "^4.17.4" 438 | minimatch "^3.0.3" 439 | read-pkg-up "^2.0.0" 440 | resolve "^1.6.0" 441 | 442 | eslint-plugin-jest@^21.15.1: 443 | version "21.15.1" 444 | resolved "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-21.15.1.tgz#662a3f0888002878f0f388efd09c190a95c33d82" 445 | 446 | eslint-plugin-jsx-a11y@^6.0.0: 447 | version "6.0.3" 448 | resolved "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.0.3.tgz#54583d1ae442483162e040e13cc31865465100e5" 449 | dependencies: 450 | aria-query "^0.7.0" 451 | array-includes "^3.0.3" 452 | ast-types-flow "0.0.7" 453 | axobject-query "^0.1.0" 454 | damerau-levenshtein "^1.0.0" 455 | emoji-regex "^6.1.0" 456 | jsx-ast-utils "^2.0.0" 457 | 458 | eslint-plugin-react@^7.7.0: 459 | version "7.7.0" 460 | resolved "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.7.0.tgz#f606c719dbd8a1a2b3d25c16299813878cca0160" 461 | dependencies: 462 | doctrine "^2.0.2" 463 | has "^1.0.1" 464 | jsx-ast-utils "^2.0.1" 465 | prop-types "^15.6.0" 466 | 467 | eslint-rule-composer@^0.3.0: 468 | version "0.3.0" 469 | resolved "https://registry.npmjs.org/eslint-rule-composer/-/eslint-rule-composer-0.3.0.tgz#79320c927b0c5c0d3d3d2b76c8b4a488f25bbaf9" 470 | 471 | eslint-scope@^3.7.1, eslint-scope@~3.7.1: 472 | version "3.7.1" 473 | resolved "https://registry.npmjs.org/eslint-scope/-/eslint-scope-3.7.1.tgz#3d63c3edfda02e06e01a452ad88caacc7cdcb6e8" 474 | dependencies: 475 | esrecurse "^4.1.0" 476 | estraverse "^4.1.1" 477 | 478 | eslint-visitor-keys@^1.0.0: 479 | version "1.0.0" 480 | resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#3f3180fb2e291017716acb4c9d6d5b5c34a6a81d" 481 | 482 | eslint@^4.19.1: 483 | version "4.19.1" 484 | resolved "https://registry.npmjs.org/eslint/-/eslint-4.19.1.tgz#32d1d653e1d90408854bfb296f076ec7e186a300" 485 | dependencies: 486 | ajv "^5.3.0" 487 | babel-code-frame "^6.22.0" 488 | chalk "^2.1.0" 489 | concat-stream "^1.6.0" 490 | cross-spawn "^5.1.0" 491 | debug "^3.1.0" 492 | doctrine "^2.1.0" 493 | eslint-scope "^3.7.1" 494 | eslint-visitor-keys "^1.0.0" 495 | espree "^3.5.4" 496 | esquery "^1.0.0" 497 | esutils "^2.0.2" 498 | file-entry-cache "^2.0.0" 499 | functional-red-black-tree "^1.0.1" 500 | glob "^7.1.2" 501 | globals "^11.0.1" 502 | ignore "^3.3.3" 503 | imurmurhash "^0.1.4" 504 | inquirer "^3.0.6" 505 | is-resolvable "^1.0.0" 506 | js-yaml "^3.9.1" 507 | json-stable-stringify-without-jsonify "^1.0.1" 508 | levn "^0.3.0" 509 | lodash "^4.17.4" 510 | minimatch "^3.0.2" 511 | mkdirp "^0.5.1" 512 | natural-compare "^1.4.0" 513 | optionator "^0.8.2" 514 | path-is-inside "^1.0.2" 515 | pluralize "^7.0.0" 516 | progress "^2.0.0" 517 | regexpp "^1.0.1" 518 | require-uncached "^1.0.3" 519 | semver "^5.3.0" 520 | strip-ansi "^4.0.0" 521 | strip-json-comments "~2.0.1" 522 | table "4.0.2" 523 | text-table "~0.2.0" 524 | 525 | espree@^3.5.4: 526 | version "3.5.4" 527 | resolved "https://registry.npmjs.org/espree/-/espree-3.5.4.tgz#b0f447187c8a8bed944b815a660bddf5deb5d1a7" 528 | dependencies: 529 | acorn "^5.5.0" 530 | acorn-jsx "^3.0.0" 531 | 532 | esprima@^4.0.0: 533 | version "4.0.1" 534 | resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" 535 | 536 | esquery@^1.0.0: 537 | version "1.0.1" 538 | resolved "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz#406c51658b1f5991a5f9b62b1dc25b00e3e5c708" 539 | dependencies: 540 | estraverse "^4.0.0" 541 | 542 | esrecurse@^4.1.0: 543 | version "4.2.1" 544 | resolved "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz#007a3b9fdbc2b3bb87e4879ea19c92fdbd3942cf" 545 | dependencies: 546 | estraverse "^4.1.0" 547 | 548 | estraverse@^4.0.0, estraverse@^4.1.0, estraverse@^4.1.1: 549 | version "4.2.0" 550 | resolved "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13" 551 | 552 | esutils@^2.0.2: 553 | version "2.0.2" 554 | resolved "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b" 555 | 556 | external-editor@^2.0.4: 557 | version "2.2.0" 558 | resolved "https://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz#045511cfd8d133f3846673d1047c154e214ad3d5" 559 | dependencies: 560 | chardet "^0.4.0" 561 | iconv-lite "^0.4.17" 562 | tmp "^0.0.33" 563 | 564 | fast-deep-equal@^1.0.0: 565 | version "1.1.0" 566 | resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz#c053477817c86b51daa853c81e059b733d023614" 567 | 568 | fast-json-stable-stringify@^2.0.0: 569 | version "2.0.0" 570 | resolved "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2" 571 | 572 | fast-levenshtein@~2.0.4: 573 | version "2.0.6" 574 | resolved "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" 575 | 576 | fbjs@^0.8.16: 577 | version "0.8.16" 578 | resolved "https://registry.npmjs.org/fbjs/-/fbjs-0.8.16.tgz#5e67432f550dc41b572bf55847b8aca64e5337db" 579 | dependencies: 580 | core-js "^1.0.0" 581 | isomorphic-fetch "^2.1.1" 582 | loose-envify "^1.0.0" 583 | object-assign "^4.1.0" 584 | promise "^7.1.1" 585 | setimmediate "^1.0.5" 586 | ua-parser-js "^0.7.9" 587 | 588 | figures@^2.0.0: 589 | version "2.0.0" 590 | resolved "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962" 591 | dependencies: 592 | escape-string-regexp "^1.0.5" 593 | 594 | file-entry-cache@^2.0.0: 595 | version "2.0.0" 596 | resolved "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz#c392990c3e684783d838b8c84a45d8a048458361" 597 | dependencies: 598 | flat-cache "^1.2.1" 599 | object-assign "^4.0.1" 600 | 601 | find-up@^1.0.0: 602 | version "1.1.2" 603 | resolved "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f" 604 | dependencies: 605 | path-exists "^2.0.0" 606 | pinkie-promise "^2.0.0" 607 | 608 | find-up@^2.0.0: 609 | version "2.1.0" 610 | resolved "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" 611 | dependencies: 612 | locate-path "^2.0.0" 613 | 614 | flat-cache@^1.2.1: 615 | version "1.3.0" 616 | resolved "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.0.tgz#d3030b32b38154f4e3b7e9c709f490f7ef97c481" 617 | dependencies: 618 | circular-json "^0.3.1" 619 | del "^2.0.2" 620 | graceful-fs "^4.1.2" 621 | write "^0.2.1" 622 | 623 | foreach@^2.0.5: 624 | version "2.0.5" 625 | resolved "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99" 626 | 627 | fs.realpath@^1.0.0: 628 | version "1.0.0" 629 | resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" 630 | 631 | function-bind@^1.0.2, function-bind@^1.1.1: 632 | version "1.1.1" 633 | resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" 634 | 635 | functional-red-black-tree@^1.0.1: 636 | version "1.0.1" 637 | resolved "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" 638 | 639 | glob@^7.0.3, glob@^7.0.5, glob@^7.1.2: 640 | version "7.1.2" 641 | resolved "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" 642 | dependencies: 643 | fs.realpath "^1.0.0" 644 | inflight "^1.0.4" 645 | inherits "2" 646 | minimatch "^3.0.4" 647 | once "^1.3.0" 648 | path-is-absolute "^1.0.0" 649 | 650 | globals@^11.0.1, globals@^11.1.0: 651 | version "11.5.0" 652 | resolved "https://registry.npmjs.org/globals/-/globals-11.5.0.tgz#6bc840de6771173b191f13d3a9c94d441ee92642" 653 | 654 | globby@^5.0.0: 655 | version "5.0.0" 656 | resolved "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz#ebd84667ca0dbb330b99bcfc68eac2bc54370e0d" 657 | dependencies: 658 | array-union "^1.0.1" 659 | arrify "^1.0.0" 660 | glob "^7.0.3" 661 | object-assign "^4.0.1" 662 | pify "^2.0.0" 663 | pinkie-promise "^2.0.0" 664 | 665 | graceful-fs@^4.1.2: 666 | version "4.1.11" 667 | resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" 668 | 669 | has-ansi@^2.0.0: 670 | version "2.0.0" 671 | resolved "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" 672 | dependencies: 673 | ansi-regex "^2.0.0" 674 | 675 | has-flag@^3.0.0: 676 | version "3.0.0" 677 | resolved "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" 678 | 679 | has@^1.0.1: 680 | version "1.0.1" 681 | resolved "https://registry.npmjs.org/has/-/has-1.0.1.tgz#8461733f538b0837c9361e39a9ab9e9704dc2f28" 682 | dependencies: 683 | function-bind "^1.0.2" 684 | 685 | hosted-git-info@^2.1.4: 686 | version "2.8.9" 687 | resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9" 688 | 689 | iconv-lite@^0.4.17, iconv-lite@~0.4.13: 690 | version "0.4.23" 691 | resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz#297871f63be507adcfbfca715d0cd0eed84e9a63" 692 | dependencies: 693 | safer-buffer ">= 2.1.2 < 3" 694 | 695 | ignore@^3.3.3: 696 | version "3.3.8" 697 | resolved "https://registry.npmjs.org/ignore/-/ignore-3.3.8.tgz#3f8e9c35d38708a3a7e0e9abb6c73e7ee7707b2b" 698 | 699 | imurmurhash@^0.1.4: 700 | version "0.1.4" 701 | resolved "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" 702 | 703 | inflight@^1.0.4: 704 | version "1.0.6" 705 | resolved "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" 706 | dependencies: 707 | once "^1.3.0" 708 | wrappy "1" 709 | 710 | inherits@2, inherits@^2.0.3, inherits@~2.0.3: 711 | version "2.0.3" 712 | resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" 713 | 714 | inquirer@^3.0.6: 715 | version "3.3.0" 716 | resolved "https://registry.npmjs.org/inquirer/-/inquirer-3.3.0.tgz#9dd2f2ad765dcab1ff0443b491442a20ba227dc9" 717 | dependencies: 718 | ansi-escapes "^3.0.0" 719 | chalk "^2.0.0" 720 | cli-cursor "^2.1.0" 721 | cli-width "^2.0.0" 722 | external-editor "^2.0.4" 723 | figures "^2.0.0" 724 | lodash "^4.3.0" 725 | mute-stream "0.0.7" 726 | run-async "^2.2.0" 727 | rx-lite "^4.0.8" 728 | rx-lite-aggregates "^4.0.8" 729 | string-width "^2.1.0" 730 | strip-ansi "^4.0.0" 731 | through "^2.3.6" 732 | 733 | invariant@^2.2.0: 734 | version "2.2.4" 735 | resolved "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" 736 | dependencies: 737 | loose-envify "^1.0.0" 738 | 739 | is-arrayish@^0.2.1: 740 | version "0.2.1" 741 | resolved "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" 742 | 743 | is-builtin-module@^1.0.0: 744 | version "1.0.0" 745 | resolved "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz#540572d34f7ac3119f8f76c30cbc1b1e037affbe" 746 | dependencies: 747 | builtin-modules "^1.0.0" 748 | 749 | is-callable@^1.1.1, is-callable@^1.1.3: 750 | version "1.1.3" 751 | resolved "https://registry.npmjs.org/is-callable/-/is-callable-1.1.3.tgz#86eb75392805ddc33af71c92a0eedf74ee7604b2" 752 | 753 | is-date-object@^1.0.1: 754 | version "1.0.1" 755 | resolved "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz#9aa20eb6aeebbff77fbd33e74ca01b33581d3a16" 756 | 757 | is-fullwidth-code-point@^2.0.0: 758 | version "2.0.0" 759 | resolved "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" 760 | 761 | is-path-cwd@^1.0.0: 762 | version "1.0.0" 763 | resolved "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz#d225ec23132e89edd38fda767472e62e65f1106d" 764 | 765 | is-path-in-cwd@^1.0.0: 766 | version "1.0.1" 767 | resolved "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz#5ac48b345ef675339bd6c7a48a912110b241cf52" 768 | dependencies: 769 | is-path-inside "^1.0.0" 770 | 771 | is-path-inside@^1.0.0: 772 | version "1.0.1" 773 | resolved "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz#8ef5b7de50437a3fdca6b4e865ef7aa55cb48036" 774 | dependencies: 775 | path-is-inside "^1.0.1" 776 | 777 | is-promise@^2.1.0: 778 | version "2.1.0" 779 | resolved "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa" 780 | 781 | is-regex@^1.0.4: 782 | version "1.0.4" 783 | resolved "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz#5517489b547091b0930e095654ced25ee97e9491" 784 | dependencies: 785 | has "^1.0.1" 786 | 787 | is-resolvable@^1.0.0: 788 | version "1.1.0" 789 | resolved "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz#fb18f87ce1feb925169c9a407c19318a3206ed88" 790 | 791 | is-stream@^1.0.1: 792 | version "1.1.0" 793 | resolved "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" 794 | 795 | is-symbol@^1.0.1: 796 | version "1.0.1" 797 | resolved "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.1.tgz#3cc59f00025194b6ab2e38dbae6689256b660572" 798 | 799 | isarray@^1.0.0, isarray@~1.0.0: 800 | version "1.0.0" 801 | resolved "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" 802 | 803 | isexe@^2.0.0: 804 | version "2.0.0" 805 | resolved "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" 806 | 807 | isomorphic-fetch@^2.1.1: 808 | version "2.2.1" 809 | resolved "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9" 810 | dependencies: 811 | node-fetch "^1.0.1" 812 | whatwg-fetch ">=0.10.0" 813 | 814 | js-tokens@^3.0.0, js-tokens@^3.0.2: 815 | version "3.0.2" 816 | resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" 817 | 818 | js-yaml@^3.9.1: 819 | version "3.13.1" 820 | resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" 821 | dependencies: 822 | argparse "^1.0.7" 823 | esprima "^4.0.0" 824 | 825 | jsesc@^2.5.1: 826 | version "2.5.1" 827 | resolved "https://registry.npmjs.org/jsesc/-/jsesc-2.5.1.tgz#e421a2a8e20d6b0819df28908f782526b96dd1fe" 828 | 829 | json-schema-traverse@^0.3.0: 830 | version "0.3.1" 831 | resolved "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz#349a6d44c53a51de89b40805c5d5e59b417d3340" 832 | 833 | json-stable-stringify-without-jsonify@^1.0.1: 834 | version "1.0.1" 835 | resolved "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" 836 | 837 | jsx-ast-utils@^2.0.0, jsx-ast-utils@^2.0.1: 838 | version "2.0.1" 839 | resolved "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.0.1.tgz#e801b1b39985e20fffc87b40e3748080e2dcac7f" 840 | dependencies: 841 | array-includes "^3.0.3" 842 | 843 | levn@^0.3.0, levn@~0.3.0: 844 | version "0.3.0" 845 | resolved "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" 846 | dependencies: 847 | prelude-ls "~1.1.2" 848 | type-check "~0.3.2" 849 | 850 | load-json-file@^2.0.0: 851 | version "2.0.0" 852 | resolved "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz#7947e42149af80d696cbf797bcaabcfe1fe29ca8" 853 | dependencies: 854 | graceful-fs "^4.1.2" 855 | parse-json "^2.2.0" 856 | pify "^2.0.0" 857 | strip-bom "^3.0.0" 858 | 859 | locate-path@^2.0.0: 860 | version "2.0.0" 861 | resolved "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" 862 | dependencies: 863 | p-locate "^2.0.0" 864 | path-exists "^3.0.0" 865 | 866 | lodash@^4.17.4, lodash@^4.2.0, lodash@^4.3.0: 867 | version "4.17.21" 868 | resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" 869 | 870 | loose-envify@^1.0.0, loose-envify@^1.3.1: 871 | version "1.3.1" 872 | resolved "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz#d1a8ad33fa9ce0e713d65fdd0ac8b748d478c848" 873 | dependencies: 874 | js-tokens "^3.0.0" 875 | 876 | lru-cache@^4.0.1: 877 | version "4.1.3" 878 | resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.3.tgz#a1175cf3496dfc8436c156c334b4955992bce69c" 879 | dependencies: 880 | pseudomap "^1.0.2" 881 | yallist "^2.1.2" 882 | 883 | mimic-fn@^1.0.0: 884 | version "1.2.0" 885 | resolved "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022" 886 | 887 | minimatch@^3.0.2, minimatch@^3.0.3, minimatch@^3.0.4: 888 | version "3.0.4" 889 | resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" 890 | dependencies: 891 | brace-expansion "^1.1.7" 892 | 893 | minimist@0.0.8: 894 | version "0.0.8" 895 | resolved "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" 896 | 897 | mkdirp@^0.5.1: 898 | version "0.5.1" 899 | resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" 900 | dependencies: 901 | minimist "0.0.8" 902 | 903 | ms@2.0.0: 904 | version "2.0.0" 905 | resolved "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" 906 | 907 | mute-stream@0.0.7: 908 | version "0.0.7" 909 | resolved "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" 910 | 911 | natural-compare@^1.4.0: 912 | version "1.4.0" 913 | resolved "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" 914 | 915 | node-fetch@^1.0.1: 916 | version "1.7.3" 917 | resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef" 918 | dependencies: 919 | encoding "^0.1.11" 920 | is-stream "^1.0.1" 921 | 922 | normalize-package-data@^2.3.2: 923 | version "2.4.0" 924 | resolved "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz#12f95a307d58352075a04907b84ac8be98ac012f" 925 | dependencies: 926 | hosted-git-info "^2.1.4" 927 | is-builtin-module "^1.0.0" 928 | semver "2 || 3 || 4 || 5" 929 | validate-npm-package-license "^3.0.1" 930 | 931 | object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: 932 | version "4.1.1" 933 | resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" 934 | 935 | object-keys@^1.0.8: 936 | version "1.0.11" 937 | resolved "https://registry.npmjs.org/object-keys/-/object-keys-1.0.11.tgz#c54601778ad560f1142ce0e01bcca8b56d13426d" 938 | 939 | once@^1.3.0: 940 | version "1.4.0" 941 | resolved "https://registry.npmjs.org/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" 942 | dependencies: 943 | wrappy "1" 944 | 945 | onetime@^2.0.0: 946 | version "2.0.1" 947 | resolved "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4" 948 | dependencies: 949 | mimic-fn "^1.0.0" 950 | 951 | optionator@^0.8.2: 952 | version "0.8.2" 953 | resolved "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz#364c5e409d3f4d6301d6c0b4c05bba50180aeb64" 954 | dependencies: 955 | deep-is "~0.1.3" 956 | fast-levenshtein "~2.0.4" 957 | levn "~0.3.0" 958 | prelude-ls "~1.1.2" 959 | type-check "~0.3.2" 960 | wordwrap "~1.0.0" 961 | 962 | os-tmpdir@~1.0.2: 963 | version "1.0.2" 964 | resolved "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" 965 | 966 | p-limit@^1.1.0: 967 | version "1.2.0" 968 | resolved "https://registry.npmjs.org/p-limit/-/p-limit-1.2.0.tgz#0e92b6bedcb59f022c13d0f1949dc82d15909f1c" 969 | dependencies: 970 | p-try "^1.0.0" 971 | 972 | p-locate@^2.0.0: 973 | version "2.0.0" 974 | resolved "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" 975 | dependencies: 976 | p-limit "^1.1.0" 977 | 978 | p-try@^1.0.0: 979 | version "1.0.0" 980 | resolved "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" 981 | 982 | parse-json@^2.2.0: 983 | version "2.2.0" 984 | resolved "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9" 985 | dependencies: 986 | error-ex "^1.2.0" 987 | 988 | path-exists@^2.0.0: 989 | version "2.1.0" 990 | resolved "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz#0feb6c64f0fc518d9a754dd5efb62c7022761f4b" 991 | dependencies: 992 | pinkie-promise "^2.0.0" 993 | 994 | path-exists@^3.0.0: 995 | version "3.0.0" 996 | resolved "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" 997 | 998 | path-is-absolute@^1.0.0: 999 | version "1.0.1" 1000 | resolved "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" 1001 | 1002 | path-is-inside@^1.0.1, path-is-inside@^1.0.2: 1003 | version "1.0.2" 1004 | resolved "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" 1005 | 1006 | path-parse@^1.0.5: 1007 | version "1.0.5" 1008 | resolved "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz#3c1adf871ea9cd6c9431b6ea2bd74a0ff055c4c1" 1009 | 1010 | path-type@^2.0.0: 1011 | version "2.0.0" 1012 | resolved "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz#f012ccb8415b7096fc2daa1054c3d72389594c73" 1013 | dependencies: 1014 | pify "^2.0.0" 1015 | 1016 | pify@^2.0.0: 1017 | version "2.3.0" 1018 | resolved "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" 1019 | 1020 | pinkie-promise@^2.0.0: 1021 | version "2.0.1" 1022 | resolved "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa" 1023 | dependencies: 1024 | pinkie "^2.0.0" 1025 | 1026 | pinkie@^2.0.0: 1027 | version "2.0.4" 1028 | resolved "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" 1029 | 1030 | pkg-dir@^1.0.0: 1031 | version "1.0.0" 1032 | resolved "https://registry.npmjs.org/pkg-dir/-/pkg-dir-1.0.0.tgz#7a4b508a8d5bb2d629d447056ff4e9c9314cf3d4" 1033 | dependencies: 1034 | find-up "^1.0.0" 1035 | 1036 | pluralize@^7.0.0: 1037 | version "7.0.0" 1038 | resolved "https://registry.npmjs.org/pluralize/-/pluralize-7.0.0.tgz#298b89df8b93b0221dbf421ad2b1b1ea23fc6777" 1039 | 1040 | prelude-ls@~1.1.2: 1041 | version "1.1.2" 1042 | resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" 1043 | 1044 | process-nextick-args@~2.0.0: 1045 | version "2.0.0" 1046 | resolved "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa" 1047 | 1048 | progress@^2.0.0: 1049 | version "2.0.0" 1050 | resolved "https://registry.npmjs.org/progress/-/progress-2.0.0.tgz#8a1be366bf8fc23db2bd23f10c6fe920b4389d1f" 1051 | 1052 | promise@^7.1.1: 1053 | version "7.3.1" 1054 | resolved "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf" 1055 | dependencies: 1056 | asap "~2.0.3" 1057 | 1058 | prop-types@^15.6.0: 1059 | version "15.6.1" 1060 | resolved "https://registry.npmjs.org/prop-types/-/prop-types-15.6.1.tgz#36644453564255ddda391191fb3a125cbdf654ca" 1061 | dependencies: 1062 | fbjs "^0.8.16" 1063 | loose-envify "^1.3.1" 1064 | object-assign "^4.1.1" 1065 | 1066 | pseudomap@^1.0.2: 1067 | version "1.0.2" 1068 | resolved "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" 1069 | 1070 | read-pkg-up@^2.0.0: 1071 | version "2.0.0" 1072 | resolved "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz#6b72a8048984e0c41e79510fd5e9fa99b3b549be" 1073 | dependencies: 1074 | find-up "^2.0.0" 1075 | read-pkg "^2.0.0" 1076 | 1077 | read-pkg@^2.0.0: 1078 | version "2.0.0" 1079 | resolved "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz#8ef1c0623c6a6db0dc6713c4bfac46332b2368f8" 1080 | dependencies: 1081 | load-json-file "^2.0.0" 1082 | normalize-package-data "^2.3.2" 1083 | path-type "^2.0.0" 1084 | 1085 | readable-stream@^2.2.2: 1086 | version "2.3.6" 1087 | resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" 1088 | dependencies: 1089 | core-util-is "~1.0.0" 1090 | inherits "~2.0.3" 1091 | isarray "~1.0.0" 1092 | process-nextick-args "~2.0.0" 1093 | safe-buffer "~5.1.1" 1094 | string_decoder "~1.1.1" 1095 | util-deprecate "~1.0.1" 1096 | 1097 | regexpp@^1.0.1: 1098 | version "1.1.0" 1099 | resolved "https://registry.npmjs.org/regexpp/-/regexpp-1.1.0.tgz#0e3516dd0b7904f413d2d4193dce4618c3a689ab" 1100 | 1101 | require-uncached@^1.0.3: 1102 | version "1.0.3" 1103 | resolved "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz#4e0d56d6c9662fd31e43011c4b95aa49955421d3" 1104 | dependencies: 1105 | caller-path "^0.1.0" 1106 | resolve-from "^1.0.0" 1107 | 1108 | resolve-from@^1.0.0: 1109 | version "1.0.1" 1110 | resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz#26cbfe935d1aeeeabb29bc3fe5aeb01e93d44226" 1111 | 1112 | resolve@^1.5.0, resolve@^1.6.0: 1113 | version "1.7.1" 1114 | resolved "https://registry.npmjs.org/resolve/-/resolve-1.7.1.tgz#aadd656374fd298aee895bc026b8297418677fd3" 1115 | dependencies: 1116 | path-parse "^1.0.5" 1117 | 1118 | restore-cursor@^2.0.0: 1119 | version "2.0.0" 1120 | resolved "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf" 1121 | dependencies: 1122 | onetime "^2.0.0" 1123 | signal-exit "^3.0.2" 1124 | 1125 | rimraf@^2.2.8: 1126 | version "2.6.2" 1127 | resolved "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz#2ed8150d24a16ea8651e6d6ef0f47c4158ce7a36" 1128 | dependencies: 1129 | glob "^7.0.5" 1130 | 1131 | run-async@^2.2.0: 1132 | version "2.3.0" 1133 | resolved "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz#0371ab4ae0bdd720d4166d7dfda64ff7a445a6c0" 1134 | dependencies: 1135 | is-promise "^2.1.0" 1136 | 1137 | rx-lite-aggregates@^4.0.8: 1138 | version "4.0.8" 1139 | resolved "https://registry.npmjs.org/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz#753b87a89a11c95467c4ac1626c4efc4e05c67be" 1140 | dependencies: 1141 | rx-lite "*" 1142 | 1143 | rx-lite@*, rx-lite@^4.0.8: 1144 | version "4.0.8" 1145 | resolved "https://registry.npmjs.org/rx-lite/-/rx-lite-4.0.8.tgz#0b1e11af8bc44836f04a6407e92da42467b79444" 1146 | 1147 | safe-buffer@~5.1.0, safe-buffer@~5.1.1: 1148 | version "5.1.2" 1149 | resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" 1150 | 1151 | "safer-buffer@>= 2.1.2 < 3": 1152 | version "2.1.2" 1153 | resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" 1154 | 1155 | "semver@2 || 3 || 4 || 5", semver@^5.3.0: 1156 | version "5.5.0" 1157 | resolved "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab" 1158 | 1159 | setimmediate@^1.0.5: 1160 | version "1.0.5" 1161 | resolved "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" 1162 | 1163 | shebang-command@^1.2.0: 1164 | version "1.2.0" 1165 | resolved "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" 1166 | dependencies: 1167 | shebang-regex "^1.0.0" 1168 | 1169 | shebang-regex@^1.0.0: 1170 | version "1.0.0" 1171 | resolved "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" 1172 | 1173 | signal-exit@^3.0.2: 1174 | version "3.0.2" 1175 | resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" 1176 | 1177 | slice-ansi@1.0.0: 1178 | version "1.0.0" 1179 | resolved "https://registry.npmjs.org/slice-ansi/-/slice-ansi-1.0.0.tgz#044f1a49d8842ff307aad6b505ed178bd950134d" 1180 | dependencies: 1181 | is-fullwidth-code-point "^2.0.0" 1182 | 1183 | source-map@^0.5.0: 1184 | version "0.5.7" 1185 | resolved "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" 1186 | 1187 | spdx-correct@^3.0.0: 1188 | version "3.0.0" 1189 | resolved "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.0.0.tgz#05a5b4d7153a195bc92c3c425b69f3b2a9524c82" 1190 | dependencies: 1191 | spdx-expression-parse "^3.0.0" 1192 | spdx-license-ids "^3.0.0" 1193 | 1194 | spdx-exceptions@^2.1.0: 1195 | version "2.1.0" 1196 | resolved "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.1.0.tgz#2c7ae61056c714a5b9b9b2b2af7d311ef5c78fe9" 1197 | 1198 | spdx-expression-parse@^3.0.0: 1199 | version "3.0.0" 1200 | resolved "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz#99e119b7a5da00e05491c9fa338b7904823b41d0" 1201 | dependencies: 1202 | spdx-exceptions "^2.1.0" 1203 | spdx-license-ids "^3.0.0" 1204 | 1205 | spdx-license-ids@^3.0.0: 1206 | version "3.0.0" 1207 | resolved "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.0.tgz#7a7cd28470cc6d3a1cfe6d66886f6bc430d3ac87" 1208 | 1209 | sprintf-js@~1.0.2: 1210 | version "1.0.3" 1211 | resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" 1212 | 1213 | string-width@^2.1.0, string-width@^2.1.1: 1214 | version "2.1.1" 1215 | resolved "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" 1216 | dependencies: 1217 | is-fullwidth-code-point "^2.0.0" 1218 | strip-ansi "^4.0.0" 1219 | 1220 | string_decoder@~1.1.1: 1221 | version "1.1.1" 1222 | resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" 1223 | dependencies: 1224 | safe-buffer "~5.1.0" 1225 | 1226 | strip-ansi@^3.0.0: 1227 | version "3.0.1" 1228 | resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" 1229 | dependencies: 1230 | ansi-regex "^2.0.0" 1231 | 1232 | strip-ansi@^4.0.0: 1233 | version "4.0.0" 1234 | resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" 1235 | dependencies: 1236 | ansi-regex "^3.0.0" 1237 | 1238 | strip-bom@^3.0.0: 1239 | version "3.0.0" 1240 | resolved "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" 1241 | 1242 | strip-json-comments@~2.0.1: 1243 | version "2.0.1" 1244 | resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" 1245 | 1246 | supports-color@^2.0.0: 1247 | version "2.0.0" 1248 | resolved "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" 1249 | 1250 | supports-color@^5.3.0: 1251 | version "5.4.0" 1252 | resolved "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz#1c6b337402c2137605efe19f10fec390f6faab54" 1253 | dependencies: 1254 | has-flag "^3.0.0" 1255 | 1256 | table@4.0.2: 1257 | version "4.0.2" 1258 | resolved "https://registry.npmjs.org/table/-/table-4.0.2.tgz#a33447375391e766ad34d3486e6e2aedc84d2e36" 1259 | dependencies: 1260 | ajv "^5.2.3" 1261 | ajv-keywords "^2.1.0" 1262 | chalk "^2.1.0" 1263 | lodash "^4.17.4" 1264 | slice-ansi "1.0.0" 1265 | string-width "^2.1.1" 1266 | 1267 | text-table@~0.2.0: 1268 | version "0.2.0" 1269 | resolved "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" 1270 | 1271 | through@^2.3.6: 1272 | version "2.3.8" 1273 | resolved "https://registry.npmjs.org/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" 1274 | 1275 | tmp@^0.0.33: 1276 | version "0.0.33" 1277 | resolved "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" 1278 | dependencies: 1279 | os-tmpdir "~1.0.2" 1280 | 1281 | to-fast-properties@^2.0.0: 1282 | version "2.0.0" 1283 | resolved "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" 1284 | 1285 | trim-right@^1.0.1: 1286 | version "1.0.1" 1287 | resolved "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003" 1288 | 1289 | type-check@~0.3.2: 1290 | version "0.3.2" 1291 | resolved "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" 1292 | dependencies: 1293 | prelude-ls "~1.1.2" 1294 | 1295 | typedarray@^0.0.6: 1296 | version "0.0.6" 1297 | resolved "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" 1298 | 1299 | ua-parser-js@^0.7.9: 1300 | version "0.7.28" 1301 | resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.28.tgz#8ba04e653f35ce210239c64661685bf9121dec31" 1302 | 1303 | util-deprecate@~1.0.1: 1304 | version "1.0.2" 1305 | resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" 1306 | 1307 | validate-npm-package-license@^3.0.1: 1308 | version "3.0.3" 1309 | resolved "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.3.tgz#81643bcbef1bdfecd4623793dc4648948ba98338" 1310 | dependencies: 1311 | spdx-correct "^3.0.0" 1312 | spdx-expression-parse "^3.0.0" 1313 | 1314 | whatwg-fetch@>=0.10.0: 1315 | version "2.0.4" 1316 | resolved "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz#dde6a5df315f9d39991aa17621853d720b85566f" 1317 | 1318 | which@^1.2.9: 1319 | version "1.3.0" 1320 | resolved "https://registry.npmjs.org/which/-/which-1.3.0.tgz#ff04bdfc010ee547d780bec38e1ac1c2777d253a" 1321 | dependencies: 1322 | isexe "^2.0.0" 1323 | 1324 | wordwrap@~1.0.0: 1325 | version "1.0.0" 1326 | resolved "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" 1327 | 1328 | wrappy@1: 1329 | version "1.0.2" 1330 | resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" 1331 | 1332 | write@^0.2.1: 1333 | version "0.2.1" 1334 | resolved "https://registry.npmjs.org/write/-/write-0.2.1.tgz#5fc03828e264cea3fe91455476f7a3c566cb0757" 1335 | dependencies: 1336 | mkdirp "^0.5.1" 1337 | 1338 | yallist@^2.1.2: 1339 | version "2.1.2" 1340 | resolved "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" 1341 | --------------------------------------------------------------------------------