├── .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 ( );
691 | }
692 | }
693 |
694 | // bad (assigns static properties on the class declaration)
695 | class Button extends React.Component {
696 | render() {
697 | return ( );
698 | }
699 | }
700 | Button.propTypes = {
701 | type: React.PropTypes.string.isRequired
702 | };
703 | ```
704 |
705 | Both declarative property syntaxes are not a part of the ES2015 specification and are a in the midst of the ECMAScript proposal approval process. Currently they are sitting in Stage 3. For details, check out [ECMAScript Class Fields and Static Properties](https://github.com/jeffmo/es-class-fields-and-static-properties).
706 |
707 | When defining methods to be attached to event listeners, we recommend using the fat arrow syntax within classes to bind the current context without returning a new function each time.
708 |
709 | ```js
710 |
711 | // good: bind the current context using fat arrow syntax
712 | class myComponent extends React.PureComponent {
713 | _handleResize = () => {
714 | //handle resize window.
715 | }
716 |
717 | componentDidMount() {
718 | window.addEventListener('resize', this._handleResize);
719 | }
720 |
721 | componentWillUnmount() {
722 | window.removeEventListener('resize', this._handleResize);
723 | }
724 | }
725 |
726 | // good:
727 | class Button extends React.Component {
728 | _handleOnClick = () => {
729 | //handle button click.
730 | }
731 |
732 | render() {
733 | return (
734 |
735 | );
736 | }
737 | }
738 |
739 | // bad: use `Function.bind` in constructor
740 | class Button extends React.Component {
741 | constructor() {
742 | this._handleOnClick = this._handleOnClick.bind(this);
743 | }
744 |
745 | _handleOnClick() {
746 | //handle button click.
747 | }
748 |
749 | render() {
750 | return (
751 |
752 | );
753 | }
754 | }
755 |
756 | ```
757 |
758 | For more on classes, read [_Learning ES6: Classes_](http://www.eventbrite.com/engineering/learning-es6-classes/).
759 |
760 | **[⬆ back to top](#table-of-contents)**
761 |
762 | ## Modules
763 |
764 | Avoid importing from the same module in separate statements for better maintainability (eslint: [`no-duplicate-imports`](http://eslint.org/docs/rules/no-duplicate-imports)):
765 |
766 | ```js
767 | // good
768 | import React, {Component, PropTypes} from 'react';
769 |
770 | // bad (imports from same module are in
771 | // separate statements)
772 | import React from 'react';
773 | import {Component, PropTypes} from 'react';
774 | ```
775 |
776 | Only export constants (eslint: [`import/no-mutable-exports`](https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-mutable-exports.md)):
777 |
778 | ```js
779 | // good
780 | export const DEFAULT_LENGTH = 10;
781 |
782 | // bad (exports a `let` variable)
783 | export let DEFAULT_LENGTH = 10;
784 | ```
785 |
786 | Place all `import` statements at the beginning of a module (eslint: [`import/first`](https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/first.md)):
787 |
788 | ```js
789 | // good
790 | import React from 'react';
791 | import classNames from 'classnames';
792 | import {connect} form 'react-redux';
793 |
794 | const DEFAULT_LENGTH = 10;
795 |
796 |
797 | // bad (imports aren't all at the top)
798 | import React from 'react';
799 |
800 | const DEFAULT_LENGTH = 10;
801 |
802 | import classNames from 'classnames';
803 | import {connect} from 'react-redux';
804 | ```
805 |
806 | Avoid using webpack loader syntax in module `import` statements in order to decouple code from the module bundler (eslint: [`import/no-webpack-loader-syntax`](https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-webpack-loader-syntax.md)):
807 |
808 | ```js
809 | // good
810 | import someCss from 'some.css';
811 | import otherSass from 'other.scss';
812 |
813 | // bad (uses webpack loader syntax)
814 | import someCss from 'style!css!some.css';
815 | import otherSass from 'css!sass!other.scss';
816 | ```
817 |
818 | In order to avoid the webpack loader syntax in `import` statements, configure the loaders in `webpack.config.js`.
819 |
820 | For more on modules, read [_ES6 Modules (Final)_](http://www.2ality.com/2014/09/es6-modules-final.html).
821 |
822 | **[⬆ back to top](#table-of-contents)**
823 |
824 | ## Destructuring
825 |
826 | The unpacking of values from arrays or properties from objects, into distinct variables.
827 |
828 | In general whenever possible, it is strongly encouraged to use destructuring when accessing
829 | values. It allows for easier to maintain code, less if statements, and smaller margin
830 | of error.
831 |
832 | ### Objects
833 |
834 | Always destructure as far as you are able when accessing multiple properties of an object.
835 |
836 | *Why do we do this?*:
837 | It prevents the need for creating temporary references to those properties, and creates easier to maintain code.
838 |
839 | *Exception*:
840 | If the object represents empty values with something other than undefined (such as `null`), then use
841 | destructuring with caution as defaulting values will not work.
842 |
843 | ```javascript
844 | // good
845 | let {c137, c186} = ricks;
846 |
847 | console.log(`${c137}, ${c186}`);
848 |
849 |
850 | // bad (adds unnecessary references)
851 | let c137 = ricks.c137;
852 | let c186 = ricks.c186;
853 |
854 | console.log(`${c137}, ${c186}`);
855 | ```
856 |
857 | #### Object Destructuring Examples
858 |
859 | Examples showcasing all the different ways we can utilize destructuring
860 | to handle a variety of use cases.
861 |
862 | ##### Defaulting Properties
863 | ```javascript
864 | //good (defaults northernKingdom to empty object if undefined)
865 | let {northernKingdom = {}} = westerosFamilies;
866 |
867 | console.log(northernKingdom);
868 |
869 | //bad (doesn't take advantage of destructuring)
870 | let northernKingdom = westerosFamilies.northernKingdom;
871 |
872 | if (typeof northernKingdom === 'undefined') {
873 | northernKingdom = {};
874 | }
875 |
876 | console.log(northernKingdom);
877 | ```
878 |
879 | ##### Renaming Properties
880 | ```javascript
881 | //good (rename multiple properties in one go)
882 | let {
883 | northernKingdom: familiesInTheNorth,
884 | southernKingdom: familiesInTheSouth
885 | } = westerosFamilies;
886 |
887 | console.log(`${familiesInTheNorth}, ${familiesInTheSouth}`);
888 |
889 | //bad (multiple lets, duplication of code)
890 | let familiesInTheNorth = westerosFamilies.northernKingdom;
891 | let familiesInTheSouth = westerosFamilies.southernKingdom;
892 |
893 | console.log(`${familiesInTheNorth}, ${familiesInTheSouth}`);
894 | ```
895 |
896 | ##### Accessing Nested Properties
897 | ```javascript
898 | //good (allows for easy handling of undefined, renaming)
899 | let {
900 | northernKingdom: {
901 | stark: {
902 | robb
903 | }
904 | }
905 | } = westerosFamilies;
906 |
907 | console.log(robb);
908 |
909 | //bad (difficult to handle objects that may be undefined)
910 | let robb = westerosFamilies.northernKingdom.stark.robb;
911 |
912 | console.log(robb);
913 | ```
914 |
915 | Each of the examples above individually don't stand out too heavily
916 | from their "bad" counterparts; however, where destructuring is incredibly
917 | useful is how it allows you to combine all of these together seamlessly.
918 |
919 | ##### Combination Example
920 | ```javascript
921 | //good
922 | let {
923 | northernKingdom: {
924 | stark: {
925 | robb: kingInTheNorth
926 | } = {}
927 | }
928 | } = westerosFamilies;
929 |
930 | console.log(kingInTheNorth);
931 |
932 | //bad (adds unnecessary code, multiple extra references)
933 | let stark = westerosFamilies.northernKingdom.stark;
934 |
935 | if (typeof stark === 'undefined') {
936 | stark = {};
937 | }
938 |
939 | let kingInTheNorth = stark.robb;
940 |
941 | console.log(kingInTheNorth);
942 | ```
943 | For additional reading on object destructuring, check out our [blog post](https://www.eventbrite.com/engineering/learning-es6-destructuring/#object-destructurin) on the topic!
944 |
945 | ##### Accessing Functional Parameters
946 | It is also possible to do any of the examples listed above in the function call itself. However, in general it is only recommended to do so when the desstructuring would be relatively simple. If complex functional parameter destructuring is necessary, it may be worthwhile investigating the API of the function itself such that it could consume a simpler object.
947 |
948 | ```javascript
949 | //good (destructures the incoming object argument in the function call)
950 | const familyParser = ({northernKingdom, southernKingdom}) => {
951 | return `${northernKingdom}, ${southernKingdom}`;
952 | }
953 |
954 | //bad (adds unnecessary references)
955 | const familyParser = (westerosFamilies) => {
956 | let northernKingdom = westerosFamilies.northernKingdom;
957 | let southernKingdom = westerosFamilies.southernKingdom;
958 |
959 | return `${northernKingdom}, ${southernKingdom}`;
960 | }
961 | ```
962 | For additional reading on destructuring functional parameters specifically, check out our [blog post](https://www.eventbrite.com/engineering/learning-es6-destructuring/#destructured-parameters) on the topic!
963 |
964 | ### Arrays
965 |
966 | It is also possible to use destructuring to access values within arrays.
967 |
968 | ```javascript
969 | let characters = ['Mr PoopyButthole', 'Bird Person', 'Ants in My Eyes Johnson', 'Shrimply Pibbles'];
970 |
971 | // good
972 | let [mrPoopy, birdPerson] = characters;
973 |
974 | // bad (adds unnecessary code)
975 | let mrPoopy = characters[0];
976 | let birdPerson = characters[1];
977 |
978 | //good
979 | let [mrPoopy, _, antsInMyEyes] = characters;
980 |
981 | //bad
982 | let mrPoopy = characters[0];
983 | let antsInMyEyes = characters[2];
984 | ```
985 | For additional reading on destructuring arrays, check out our [blog post](https://www.eventbrite.com/engineering/learning-es6-destructuring/#array-destructuring) on the topic!
986 |
987 | **[⬆ back to top](#table-of-contents)**
988 |
--------------------------------------------------------------------------------
/general/README.md:
--------------------------------------------------------------------------------
1 | # Eventbrite JavaScript Coding Style Guide
2 |
3 | Eventbrite’s guidelines to ensure consistency in JavaScript code in any environment.
4 |
5 | ## Table of Contents
6 |
7 | 0. [Conditionals](#conditionals)
8 | 0. [Assignments](#assignments)
9 | 0. [Functions](#functions)
10 | 0. [Iterators](#iterators)
11 | 0. [Naming Conventions](#naming-conventions)
12 | 0. [Commas](#commas)
13 |
14 | ## Conditionals
15 |
16 | ### Complex conditional expressions
17 |
18 | Avoid complex conditional expressions within conditional statements. Simplification is necessary because you must fully understand an expression to process the overall code flow. Simplification also comes into play during the subsequent tasks of conducting code reviews or revisiting old code: you must understand what state the conditional expression represents to evaluate the logic in the code.
19 |
20 | To make things easier, store complex conditional expressions in state variables:
21 |
22 | ```js
23 | // good
24 | var shouldContinue = !options || (options.continue && options.hasWon);
25 |
26 | if (shouldContinue) {
27 |
28 | }
29 |
30 | // bad
31 | if (!options || (options.continue && options.hasWon)) {
32 |
33 | }
34 | ```
35 |
36 | **[⬆ back to top](#table-of-contents)**
37 |
38 | ### Negative conditional expressions
39 |
40 | Positive conditional expressions are generally easier to follow than negative ones. Try to flip the logic so that the positive case is considered first:
41 |
42 | ```js
43 | // good
44 | if (something && somethingElse) {
45 | /* do stuff*/
46 | } else {
47 | /* do other stuff */
48 | }
49 |
50 | // bad (negative case comes first)
51 |
52 | if (!something || !somethingElse ) {
53 | /* do stuff */
54 | } else {
55 | /* do other stuff */
56 | }
57 | ```
58 |
59 | If satisfying a negative condition should exit or throw, put the negative condition first. This generally improves readability especially for functions with more than one logical branching or large blocks.
60 |
61 | ```js
62 | // good
63 | const cond = () => {
64 | if (!something) {
65 | return;
66 | }
67 |
68 | /* do other stuff */
69 | }
70 |
71 | // bad
72 | const cond = () => {
73 | if (something) {
74 | /* do stuff */
75 | /* do more stuff */
76 | /* do even more stuff */
77 | /* keep doing stuff */
78 | /* never stop doing stuff... */
79 | } else {
80 | return;
81 | }
82 | }
83 | ```
84 |
85 | **[⬆ back to top](#table-of-contents)**
86 |
87 | ### Multiple `if-else`
88 |
89 | Creating a chain of conditional statements can make refactoring hard because the decisions are mixed with the actions. Instead, separate out the decisions into functions that can return simple actions:
90 |
91 | ```js
92 | // good
93 |
94 | // decision
95 | var whatToDoWithTheThing = this.getSomethingAction();
96 |
97 | // execution
98 | this[whatToDoWithTheThing]();
99 |
100 |
101 | // bad (mixed decisions and actions)
102 | if (this.something()) {
103 | /* do stuff */
104 | } else if (this.otherSomething()) {
105 | /* do other stuff */
106 | } else if (this.somethingEvenMoreCurious()) {
107 | /* yet another thing */
108 | } else {
109 | /* default something */
110 | }
111 | ```
112 |
113 | For more details, check out [_Replacing the `switch` statement for object literals_](https://toddmotto.com/deprecating-the-switch-statement-for-object-literals/).
114 |
115 | #### Case study
116 |
117 | **_First iteration_**: Multiple return statements and if-else statements that mix decision with execution.
118 | ```js
119 | var getType = function(model) {
120 | if (model % 2 !== 0) {
121 | return 'odd';
122 | } else if (model % 2 !== 0) {
123 | return 'even';
124 | } else if (model === 'Eventbrite') {
125 | return 'Eventbrite';
126 | }
127 | return 'default';
128 | };
129 | ```
130 |
131 | **_Second iteration_**: Use a lookup map to avoid multiple return statements inside a single function and to abstract conditional logic. This enables maintainability and readability, and boosts performance by using `.find`.
132 |
133 | ```js
134 | // good (use a lookup map)
135 | var helpers = {
136 | isOdd: function(value) {
137 | return value % 2 !== 0;
138 | },
139 | isEven: function(value) {
140 | return value % 2 === 0;
141 | },
142 | isEventbrite: function(value) {
143 | return value === 'Eventbrite';
144 | }
145 | }
146 |
147 | var types = {
148 | isOdd: 'odd',
149 | isEven: 'even',
150 | isEventbrite: 'eventbrite'
151 | };
152 |
153 | _getType = function(types, model) {
154 | var type = _.find(types, function(value, key) {
155 | // will return the first value of the key that evaluates to true;
156 | return helpers[key](model);
157 | });
158 | return type || 'default';
159 | }
160 | ```
161 |
162 | NOTE: We are using Underscore's .find method here, but with ES6, we can use find natively.
163 | For more info, visit [`Array.prototype.find`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find).
164 |
165 | **[⬆ back to top](#table-of-contents)**
166 |
167 | ### Ternary statements
168 |
169 | Simple ternary expressions are handy when conditionally assigning a value to a variable, when the condition is both `true` and `false`. However, for assigning the variable when the condition is `true`, avoid the temptation to use that ternary, returning `undefined`, `null`, `''` for the `false` case. Instead, declare the value without assigning it (the default value is `undefined`), and then use an `if` statement to assign to it when the condition is met:
170 |
171 | ```js
172 | // good
173 | var value;
174 |
175 | if (options.isSomethingTrue) {
176 | value = 'hey there';
177 | }
178 |
179 | return value;
180 |
181 | // bad (uses a ternary that returns undefined or null)
182 | var options.isSomethingTrue ? 'hey there' : undefined;
183 |
184 | ```
185 |
186 | This pattern uses the basic structures of the language in a more formal way, because the use of ternaries presumes that a value is required. Our suggested approach enables traceability when debugging and more robust maintenance over time. It also correlates with a defined state and lends itself to future alteration if required, instead of having a default state that is dynamic.
187 |
188 | #### Case study
189 |
190 | Let's look at a case study of how code can evolve over time. For this example we have created a fictional piece of code.
191 |
192 | **_First iteration_**: nothing fancy, just a method that needs to gather some information:
193 |
194 | ```js
195 | var isAllowed = hasParent && isOwner,
196 | parentEvent = isAllowed ? getParentEvent() : undefined;
197 |
198 | /* some use of these 2 variables later on... */
199 | ```
200 |
201 | **_Second iteration_**: a different developer comes around to add yet another condition following the current style, because a refactor is out of scope:
202 |
203 | ```js
204 | var isAllowed = hasParent && isOwner,
205 | parentEvent = isAllowed ? getParentEvent() : undefined,
206 | options = isAllowed ? undefined : getOptions();
207 |
208 | /* some use of these 3 variables later on... */
209 | ```
210 |
211 | **_Third iteration_**: another team comes in needing more functionality and adds even more variables:
212 |
213 | ``` js
214 | var isAllowed = hasParent && isOwner,
215 | parentEvent = isAllowed ? getParentEvent() : undefined,
216 | options = isAllowed ? undefined : getOptions(),
217 | childEventsTitles = isAllowed ? getEventChildTitles() : undefined,
218 | ownerAccount = isAllowed ? undefined : getOwnerAccount();
219 |
220 | /* some use of these 5 variables later on... */
221 | ```
222 |
223 | At this point, with all the possible states based on ternaries, determining the base state for this method is now much harder. Furthermore, having used the same `isAllowed` four times, we must now understand how all the state variables work before we can optimize the code. The code is too fragile.
224 |
225 | However, had this code adhered to our initial recommendation, it would not have degraded with added functionality.
226 |
227 | **_First iteration (revisited)_**:
228 |
229 | ```js
230 | var isAllowed = hasParent && isOwner,
231 | parentEvent;
232 |
233 | if (isAllowed) {
234 | parentEvent = getParentEvent();
235 | }
236 |
237 | /* some use of these 2 variables later on... */
238 | ```
239 |
240 | **_Second iteration (revisited)_**:
241 |
242 | ```js
243 | var isAllowed = hasParent && isOwner,
244 | parentEvent,
245 | options;
246 |
247 | if (isAllowed) {
248 | parentEvent = getParentEvent();
249 | } else {
250 | options = getOptions();
251 | }
252 |
253 | /* some use of these 3 variables later on... */
254 | ```
255 |
256 | **_Third iteration (revisited)_**:
257 |
258 | ```js
259 | var isAllowed = hasParent && isOwner,
260 | parentEvent,
261 | options,
262 | childEventsTitles,
263 | ownerAccount;
264 |
265 | if (isAllowed) {
266 | parentEvent = getParentEvent();
267 | childEventsTitles = getEventChildTitles();
268 | } else {
269 | options = getOptions();
270 | ownerAccount = getOwnerAccount();
271 | }
272 |
273 | /* some use of these 5 variables later on... */
274 | ```
275 |
276 | **[⬆ back to top](#table-of-contents)**
277 |
278 | ## Functions
279 |
280 | ### Immediately-invoked function expressions
281 |
282 | Avoid using IIFEs (immediately-invoked function expressions) if possible. The `bind` function is preferred when dealing with variable scope in closures (note that `bind` is only available in ES5+,
283 | though we use `es5-shim` so `bind` should always be in our codebase by default).
284 |
285 | ```js
286 | var purchaseTicketButtonSelector = '.goog-te-combo';
287 |
288 | // good (using .bind)
289 | buyTicketFunction = function(options) {
290 | buyTickets(_.pick(options, 'eventId', 'ticketId'));
291 | };
292 |
293 | $(purchaseTicketButtonSelector).click(buyTicketFunction.bind(null, options));
294 |
295 | // bad (using IIFEs)
296 | buyTicketFunction = (function(options) {
297 | return function() {
298 | buyTickets(_.pick(options, 'eventId', 'ticketId'));
299 | };
300 | })(options);
301 |
302 | $(purchaseTicketButtonSelector).click(function() {
303 | buyTicketFunction();
304 | });
305 | ```
306 |
307 | IIFEs tend to add unnecessary complexity (note that the "bad" IIFE example
308 | has three nested functions, while the "good" example has one) and is harder to
309 | change or extend.
310 |
311 | **[⬆ back to top](#table-of-contents)**
312 |
313 | ## Assignments
314 |
315 | ### Variable indirection
316 |
317 | Avoid generic names; instead, assign names that explain the content. If data must be changed as part of a process, create methods to produce these changes on demand whenever possible.
318 |
319 | ```js
320 | // good
321 | _handleEvent = function(e) {
322 | _doSomethingWithEvent(e.target.checked);
323 | }
324 |
325 | // bad
326 | _handleEvent = function(e) {
327 | var checked = e.target.checked;
328 |
329 | _doSomethingWithEvent(checked);
330 | }
331 | ```
332 |
333 | **[⬆ back to top](#table-of-contents)**
334 |
335 | ## Iterators
336 |
337 | We particularly try to avoid iterations that don't have predictable output e.g. `forEach`, `for` and `while` loops.
338 | Any need for an external element to store or transform the result of an output, is frowned upon.
339 |
340 | This enforces our [immutability](https://www.sitepoint.com/immutability-javascript/) rule and limits the temptation of putting logic inside a loop.
341 | It is easier to deal with pure functions that return values than dealing with there side effects.
342 |
343 | Use `map()` / `every()` / `filter()` / `find()` / `findIndex()` / `reduce()` / `some()` / ... to iterate over arrays, and `Object.keys()` / `Object.values()` / `Object.entries()` to produce arrays so you can iterate over objects.
344 |
345 | ```js
346 |
347 | /// Objects
348 |
349 | const peoplesFavoriteFruits = {
350 | 'vivian': 'mango',
351 | 'alby': 'banana',
352 | 'gago': 'tomato'
353 | };
354 |
355 | // good (use a functional approach) ES6
356 | const fruits = Object.keys(peoplesFavoriteFruits).map(key => peoplesFavoriteFruits[key]);
357 |
358 | // good (use a functional approach) ES5
359 | var fruits = Object.keys(peoplesFavoriteFruits).map(function(key) {
360 | return peoplesFavoriteFruits[key];
361 | });
362 |
363 | // bad
364 | var fruits = [];
365 | Object.keys(peoplesFavoriteFruits).forEach(function (index) {
366 | return fruits.push(peoplesFavoriteFruits[index]);
367 | });
368 |
369 | // very bad
370 | var fruits = [];
371 | for (var key in peoplesFavoriteFruits) {
372 | fruits.push(peoplesFavoriteFruits[key]);
373 | }
374 |
375 | /// Arrays
376 |
377 | const numbers = [1, 2, 3, 4, 5];
378 |
379 | // good (use a functional approach) ES6
380 | const sum = numbers.reduce((total, num) => total + num, 0);
381 | sum === 15;
382 |
383 | // good (use a functional approach) ES5
384 | var sum = numbers.reduce(function (total, num) {
385 | return total + num;
386 | }, 0);
387 | sum === 15;
388 |
389 | // bad
390 | var sum = 0;
391 | numbers.forEach(function (num) {
392 | return sum += num;
393 | });
394 | sum === 15;
395 |
396 | // very bad
397 | let sum = 0;
398 | for (let num of numbers) {
399 | sum += num;
400 | }
401 | sum === 15;
402 |
403 | // good (keeping it functional) ES6
404 | const increasedByOne = numbers.map(num => num + 1);
405 |
406 | // good (keeping it functional) ES5
407 | var increasedByOne = numbers.map(function (num) {
408 | return num + 1;
409 | });
410 |
411 | // bad
412 | var increasedByOne = [];
413 | numbers.forEach(function (num) {
414 | return increasedByOne.push(num + 1);
415 | });
416 |
417 | // very bad
418 | var increasedByOne = [];
419 | for (var i = 0; i < numbers.length; i++) {
420 | increasedByOne.push(numbers[i] + 1);
421 | }
422 | ```
423 |
424 | **[⬆ back to top](#table-of-contents)**
425 |
426 | ## Naming Conventions
427 | > There are only two hard things in Computer Science: cache invalidation and naming things. *Phil Karlton*
428 |
429 | ### Variables: Classes or newable functions
430 | Classes or newable functions (meant to be factories) should be PascalCase:
431 |
432 | ```js
433 | var groupTicket = new Ticket(id: 555);
434 | ```
435 | ### Variables: Helpers and common variables
436 |
437 | Any variable that's not a class or newable function, should be camelCase. This includes local variables, helper functions, modules, etc.
438 |
439 | ```js
440 | //good
441 | var pagination = require('pagination'),
442 | pages = pagination.getPages();
443 |
444 | //bad
445 | var Pagination = require('pagination'),
446 | pages = Pagination.getPages();
447 | ```
448 |
449 | ### Variables: Boolean
450 |
451 | Variables that will contain a boolean value should start with either `is` or `has`:
452 |
453 | ```js
454 | //good
455 | var isAvailable = true,
456 | hasTickets = false;
457 |
458 | //bad
459 | var available = true,
460 | IHaveTickets = false;
461 | ```
462 | ### Variables: jQuery elements
463 |
464 | Variables that contain jQuery elements should start with a dollar sign ($) so that we can quickly identify these types of variables by scanning the code (i.e. static analysis). This makes code review easier.
465 |
466 | ```js
467 | var $elements = $('someSelector'), // in case a global search
468 | $element = this.$('some selector'); // in case of backbone
469 | ```
470 | ### Variables: Deferred
471 |
472 | If a variable contains a jQuery.Deferred() or Promise, the variable should end in Dfd or Promise, respectively:
473 |
474 | ```js
475 | //good
476 | var fetchPromise = fetch('http://some/url'),
477 | requestDfd = new $.Deferred() // in case of jquery promises.
478 |
479 | //bad
480 | var request = fetch('http://some/url'),
481 | onResolve = new $.Deferred();
482 | ```
483 |
484 | ### Constants
485 |
486 | Constants should be all written in capital letters and in snake_case. (except config object.)
487 |
488 | ```js
489 | //good
490 | const DEFAULT_VALUE = 'none';
491 |
492 | var TIMEOUT = 3;
493 |
494 | var config = {};
495 |
496 | //bad
497 | const defaultValue = 'none';
498 |
499 | var timeout = 3;
500 |
501 | var someOtherStaticVariable = 'blah';
502 | ```
503 |
504 | ### Variables: objects, strings, integers
505 |
506 | Avoid names that are generic and instead, try to make your best effort to find a proper name in order to explain the content of it. If data needs to be changed, as part of a process, try to create methods to produce this changes on demand.
507 |
508 | ```js
509 |
510 | //good
511 | var attendeeNames = {},
512 | translatedGreeting = 'hola!',
513 | getDefaultPrice = parseInt(5, 10);
514 |
515 | //bad
516 | var propsObject = {},
517 | trimmedString = 'hello '.trim(),
518 | intPrice = parseInt(5, 10);
519 |
520 | ```
521 |
522 | ### Method: Private
523 |
524 | Prefix private method names with an underscore (`_`):
525 |
526 | ```js
527 |
528 | // good
529 | var _getInternalValue = function() {
530 | // code here.
531 | };
532 |
533 | // bad (does not begin with underscore)
534 | var getInternalValuePleaseDonot = function() {
535 | // code here.
536 | };
537 | ```
538 |
539 | The underscore prefix naming convention for private methods emphasizes the methods that are not part of the component's public API. This makes it easy for future developers to identify the methods they can refactor or even remove without affecting any users of the component. Upgrading to new technologies/tools/idioms is now simpler.
540 |
541 | The naming convention also helps code reviewers distinguish the methods we have added from those belonging to the framework's lifecycle (see: [React](https://facebook.github.io/react/docs/component-specs.html#lifecycle-methods) or [Marionette](https://marionette.gitbooks.io/marionette-guides/content/en/views/triggers.html)).
542 |
543 | ### Method: Public
544 |
545 | Should always begin with [camelCase](http://c2.com/cgi/wiki?LowerCamelCase).
546 |
547 | ```js
548 |
549 | // good
550 | getNode: function() {
551 | //code here.
552 | },
553 | setAttr: function() {
554 | // code here.
555 | };
556 |
557 | // bad (not camelCase)
558 | _superComplextName: function() {
559 | // code here.
560 | }
561 |
562 | UpdateAll: function() {
563 | // code here
564 | }
565 | ```
566 |
567 | **[⬆ back to top](#table-of-contents)**
568 |
569 | ## Commas
570 |
571 | (ES6) Trailing Commas are only enforced for multi-line objects and arrays. (see: [comma-dangle](http://eslint.org/docs/rules/comma-dangle#always-multiline))
572 |
573 | ```js
574 |
575 | // good
576 | const brunch = [
577 | eggs,
578 | chicken,
579 | spinach,
580 | hashbrowns,
581 | ];
582 |
583 | // bad
584 | const brunch = [
585 | eggs,
586 | chicken,
587 | spinach,
588 | hashbrowns
589 | ];
590 |
591 | // good
592 | const eventbrite = {
593 | age: 11,
594 | marketplace: 'global',
595 | color: 'sunrise-orange-500',
596 | hq: 'San Francisco',
597 | };
598 |
599 | // bad
600 | const eventbrite = {
601 | age: 11,
602 | marketplace: 'global',
603 | color: 'sunrise-orange-500',
604 | hq: 'San Francisco'
605 | };
606 | ```
607 | **[⬆ back to top](#table-of-contents)**
608 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "workspaces": [
4 | "packages/*"
5 | ],
6 | "scripts": {
7 | "test": "yarn workspace eslint-config-eventbrite-legacy test && yarn workspace eslint-config-eventbrite test && yarn workspace eslint-config-eventbrite-react test"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/packages/eslint-config-eventbrite-legacy/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./index.js"
3 | }
4 |
--------------------------------------------------------------------------------
/packages/eslint-config-eventbrite-legacy/.npmignore:
--------------------------------------------------------------------------------
1 | _scripts/
2 |
--------------------------------------------------------------------------------
/packages/eslint-config-eventbrite-legacy/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## v4.0.0 (May 24, 2018)
2 | - (major) Requires node >= 6
3 | - (major) Bumped `eslint` peer dependency to `^4.19.1`
4 | - (major) **New** erroring rules:
5 | * [accessor-pairs](https://eslint.org/docs/rules/accessor-pairs)
6 | * [func-name-matching](https://eslint.org/docs/rules/func-name-matching)
7 | * [no-catch-shadow](https://eslint.org/docs/rules/no-catch-shadow)
8 | * [no-extend-native](https://eslint.org/docs/rules/no-extend-native)
9 | * [no-lonely-if](http://eslint.org/docs/rules/no-lonely-if)
10 | * [no-multi-assign](http://eslint.org/docs/rules/no-multi-assign)
11 | * [no-shadow](https://eslint.org/docs/rules/no-shadow)
12 | * [no-shadow-restricted-names](https://eslint.org/docs/rules/no-shadow-restricted-names)
13 | * [no-trailing-spaces](http://eslint.org/docs/rules/no-trailing-spaces)
14 | * [no-unneeded-ternary](http://eslint.org/docs/rules/no-unneeded-ternary)
15 | * [no-use-before-define](https://eslint.org/docs/rules/no-use-before-define)
16 | * [no-useless-return](http://eslint.org/docs/rules/no-useless-return)
17 | * [semi-spacing](http://eslint.org/docs/rules/semi-spacing)
18 | - (major) Stronger exiting erroring rules:
19 | * [new-cap](http://eslint.org/docs/rules/new-cap) no longer w/ any exceptions
20 |
21 | ## v3.0.0 (February 10, 2017)
22 | - (major) remove `env` & `globals` directives
23 | - (major) reenable `no-unused-expressions` to be an error
24 |
25 | ## v2.0.0 (November 30, 2016)
26 | - (major) bump to eslint 3
27 |
28 | ## v1.1.1 (July 12, 2016)
29 | - (patch) `indent` allows for indentation of `switch` statements
30 | - (patch) turning off `operator-linebreak`
31 | - (patch) allowing for capitalized snake_case for `dot-notation`
32 |
33 | ## v1.1.0 (June 22, 2016)
34 | - (minor) turning off `no-unused-expressions` (temporarily)
35 |
36 | ## v1.0.0 (June 7, 2016)
37 | - Initial release
38 |
--------------------------------------------------------------------------------
/packages/eslint-config-eventbrite-legacy/README.md:
--------------------------------------------------------------------------------
1 | # eslint-config-eventbrite-legacy
2 |
3 | [](http://npm.im/eslint-config-eventbrite-legacy)
4 | [](http://npm-stat.com/charts.html?package=eslint-config-eventbrite-legacy&from=2016-05-27)
5 | [](https://github.com/eventbrite/javascript/pulse)
6 |
7 | [](http://makeapullrequest.com)
8 | [](http://spdx.org/licenses/MIT)
9 |
10 | Eventbrite's legacy ESLint config that lints ES5- and adheres to the [Eventbrite JavaScript Coding Style Guide](https://github.com/eventbrite/javascript).
11 |
12 | ## Usage
13 |
14 | This ESLint configuration requires [`eslint`](https://github.com/eslint/eslint).
15 |
16 | Install using [npm](https://www.npmjs.com/get-npm):
17 |
18 | ```sh
19 | npm install --save-dev eslint@^4.19.1 eslint-config-eventbrite-legacy
20 | ```
21 |
22 | ...or using [yarn](https://yarnpkg.com/):
23 |
24 | ```sh
25 | yarn add --dev eslint@^4.19.1 eslint-config-eventbrite-legacy
26 | ```
27 |
28 | Extend `eslint-config-eventbrite-legacy` in your [`.eslintrc.json`](http://eslint.org/docs/user-guide/configuring#extending-configuration-files):
29 |
30 | ```json
31 | {
32 | "extends": "eventbrite-legacy"
33 | }
34 | ```
35 |
36 | _NOTE:_ This configuration extends `eslint:recommended`.
37 |
38 | ## Contributing
39 |
40 | Thank you for your willingness to contribute! 😀
41 |
42 | Although `eslint-config-eventbrite-legacy` is a public package, its primary purpose is for linting Eventbrite's legacy JavaScript code written in ES5. Therefore, it is unlikely that we'll accept changes to rules as they may adversely affect the linting of our own code. However, we do welcome fixes for errors or additions for omissions.
43 |
44 | If you are uncertain as to whether your suggestion qualifies, go ahead and submit a [Pull Request](https://github.com/eventbrite/javascript/pulls) and we'll let you know. Thanks again!
45 |
46 | ## License
47 |
48 | The library is available as open source under the terms of the [MIT License](https://github.com/evenbrite/javascript/LICENSE).
49 |
50 | ## Thanks
51 |
52 | Many thanks to Airbnb and their example [`eslint-config-airbnb`](https://github.com/airbnb/javascript/tree/master/packages/eslint-config-airbnb).
53 |
--------------------------------------------------------------------------------
/packages/eslint-config-eventbrite-legacy/_scripts/git-tag-version.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-console */
2 | var execSync = require('child_process').execSync,
3 | packageInfo = require('../package.json'),
4 |
5 | name = packageInfo.name,
6 | version = packageInfo.version,
7 | tagName = name + '-v' + version,
8 |
9 | gitCommands = [
10 | 'git add package.json CHANGELOG.md',
11 | 'git commit --message "Release ' + tagName + '"',
12 | 'git tag ' + tagName
13 | ],
14 | shellCommand = gitCommands.join(' && ');
15 |
16 | // NOTE: The normal npm-version script creates a git tag that's in the form of
17 | // "v1.0.0" which is a problem because we have 3 NPM packages in this one git
18 | // repository. So instead, we'll disable `npm version` from creating that default
19 | // tag and this script will run to do it. This script prepends the package name
20 | // so that when looking at the tags in the repo they will be differentiated.
21 | // It also commits the package.json.
22 |
23 | console.log('Committing ' + tagName + ' of package.json & adding tag');
24 |
25 | execSync(shellCommand, function(error, stdout, stderr) {
26 | if (error) {
27 | console.error('execSync error: ' + error);
28 | return;
29 | }
30 |
31 | console.error(stdout);
32 | console.error(stderr);
33 | });
34 |
--------------------------------------------------------------------------------
/packages/eslint-config-eventbrite-legacy/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: ['eslint:recommended'].concat([
3 | './rules/best-practices',
4 | './rules/errors',
5 | './rules/node',
6 | './rules/style',
7 | './rules/variables'
8 | ].map(require.resolve))
9 | };
10 |
--------------------------------------------------------------------------------
/packages/eslint-config-eventbrite-legacy/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "eslint-config-eventbrite-legacy",
3 | "version": "4.0.0",
4 | "description": "Eventbrites's legacy ESLint config that lints ES5- and adheres to the Eventbrite JavaScript Coding Style Guide",
5 | "main": "index.js",
6 | "scripts": {
7 | "git-tag-version": "node ./_scripts/git-tag-version.js",
8 | "lint": "eslint rules",
9 | "test": "yarn run lint",
10 | "preversion": "yarn test",
11 | "version": "yarn run git-tag-version",
12 | "postversion": "git push && git push --tags",
13 | "version:patch": "npm version patch --no-git-tag-version",
14 | "version:minor": "npm version minor --no-git-tag-version",
15 | "version:major": "npm version major --no-git-tag-version"
16 | },
17 | "repository": {
18 | "type": "git",
19 | "url": "git+https://github.com/eventbrite/javascript.git"
20 | },
21 | "keywords": [
22 | "eslint",
23 | "eslintconfig",
24 | "config",
25 | "eventbrite",
26 | "javascript",
27 | "styleguide"
28 | ],
29 | "author": "Eventbrite ",
30 | "license": "MIT",
31 | "bugs": {
32 | "url": "https://github.com/eventbrite/javascript/issues"
33 | },
34 | "homepage": "https://github.com/eventbrite/javascript#readme",
35 | "devDependencies": {
36 | "eslint": "^4.19.1"
37 | },
38 | "peerDependencies": {
39 | "eslint": "^4.19.1"
40 | },
41 | "engines": {
42 | "node": ">=6"
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/packages/eslint-config-eventbrite-legacy/rules/best-practices.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | rules: {
3 | // enforce getter when setter is specified
4 | // https://eslint.org/docs/rules/accessor-pairs
5 | 'accessor-pairs': 'error',
6 |
7 | // enforces usage of return statement in callbacks of array’s methods
8 | // http://eslint.org/docs/rules/array-callback-return
9 | 'array-callback-return': 'error',
10 |
11 | // error when variables are used outside of the block in which they were defined
12 | // http://eslint.org/docs/rules/block-scoped-var
13 | 'block-scoped-var': 'error',
14 |
15 | // allow maximum cyclomatic complexity of 12
16 | // http://eslint.org/docs/rules/complexity
17 | 'complexity': ['warn', 12],
18 |
19 | // ensures all return statements either specify a value or don’t specify a value
20 | // http://eslint.org/docs/rules/consistent-return
21 | 'consistent-return': 'error',
22 |
23 | // all control statements must have curly braces
24 | // http://eslint.org/docs/rules/curly
25 | 'curly': 'error',
26 |
27 | // require default case in switch statements
28 | // http://eslint.org/docs/rules/default-case
29 | 'default-case': 'error',
30 |
31 | // requires the dot to be on the same line as the property
32 | // http://eslint.org/docs/rules/dot-location
33 | 'dot-location': ['error', 'property'],
34 |
35 | // force use of dot notation whenever possible, except for names w/
36 | // underscores in them
37 | // http://eslint.org/docs/rules/dot-notation
38 | 'dot-notation': ['error', {
39 | allowPattern: '^[a-zA-Z0-9]+(_[a-zA-Z0-9]+)+$'
40 | }],
41 |
42 | // require use of === and !==
43 | // http://eslint.org/docs/rules/eqeqeq
44 | 'eqeqeq': ['error', 'smart'],
45 |
46 | // ensure for-in loops have an if statement
47 | // http://eslint.org/docs/rules/guard-for-in
48 | 'guard-for-in': 'error',
49 |
50 | // warn when using alert, confirm & prompt
51 | // http://eslint.org/docs/rules/no-alert
52 | 'no-alert': 'warn',
53 |
54 | // disallow use of arguments.caller or arguments.callee
55 | // http://eslint.org/docs/rules/no-caller
56 | 'no-caller': 'error',
57 |
58 | // disallow use of multiple spaces
59 | // http://eslint.org/docs/rules/no-multi-spaces
60 | 'no-multi-spaces': 'error',
61 |
62 | // disallow else after a return in an if
63 | // http://eslint.org/docs/rules/no-else-return
64 | 'no-else-return': 'error',
65 |
66 | // disallow use of empty functions
67 | // http://eslint.org/docs/rules/no-empty-function
68 | 'no-empty-function': 'error',
69 |
70 | // disallow use of eval()
71 | // http://eslint.org/docs/rules/no-eval
72 | 'no-eval': 'error',
73 |
74 | // disallow extending native types
75 | // https://eslint.org/docs/rules/no-extend-native
76 | 'no-extend-native': 'error',
77 |
78 | // disallow unnecessary function binding
79 | // http://eslint.org/docs/rules/no-extra-bind
80 | 'no-extra-bind': 'error',
81 |
82 | // disallow the use of leading or trailing decimal points in numeric literals
83 | // http://eslint.org/docs/rules/no-floating-decimal
84 | 'no-floating-decimal': 'error',
85 |
86 | // allow the type conversions with shorter notations
87 | // http://eslint.org/docs/rules/no-implicit-coercion
88 | 'no-implicit-coercion': 'off',
89 |
90 | // disallow var and named functions in global scope
91 | // http://eslint.org/docs/rules/no-implicit-globals
92 | 'no-implicit-globals': 'error',
93 |
94 | // disallow use of eval()-like methods
95 | // http://eslint.org/docs/rules/no-implied-eval
96 | 'no-implied-eval': 'error',
97 |
98 | // disallow `this` keywords outside of classes or class-like objects
99 | // http://eslint.org/docs/rules/no-invalid-this
100 | 'no-invalid-this': 'error',
101 |
102 | // disallow usage of __iterator__ property
103 | // http://eslint.org/docs/rules/no-iterator
104 | 'no-iterator': 'error',
105 |
106 | // disallow use of labeled statements
107 | // http://eslint.org/docs/rules/no-labels
108 | 'no-labels': 'error',
109 |
110 | // disallow unnecessary nested blocks
111 | // http://eslint.org/docs/rules/no-lone-blocks
112 | 'no-lone-blocks': 'error',
113 |
114 | // disallow creation of functions within loops
115 | // http://eslint.org/docs/rules/no-loop-func
116 | 'no-loop-func': 'error',
117 |
118 | // disallow use of multiline strings
119 | // http://eslint.org/docs/rules/no-multi-str
120 | 'no-multi-str': 'error',
121 |
122 | // disallow reassignments of native objects
123 | // http://eslint.org/docs/rules/no-native-reassign
124 | 'no-native-reassign': 'error',
125 |
126 | // disallow use of the new operator when not part of an assignment or comparison
127 | // http://eslint.org/docs/rules/no-new
128 | 'no-new': 'error',
129 |
130 | // disallow use of new operator for Function object
131 | // http://eslint.org/docs/rules/no-new-func
132 | 'no-new-func': 'error',
133 |
134 | // disallow creating new instances of String,Number, and Boolean
135 | // http://eslint.org/docs/rules/no-new-wrappers
136 | 'no-new-wrappers': 'error',
137 |
138 | // disallow use of octal escape sequences in string literals
139 | // http://eslint.org/docs/rules/no-octal-escape
140 | 'no-octal-escape': 'error',
141 |
142 | // allow modifying properties of parameters
143 | // http://eslint.org/docs/rules/no-param-reassign
144 | 'no-param-reassign': 'off',
145 |
146 | // disallow usage of __proto__ property
147 | // http://eslint.org/docs/rules/no-proto
148 | 'no-proto': 'error',
149 |
150 | // disallow use of assignment in return statement
151 | // http://eslint.org/docs/rules/no-return-assign
152 | 'no-return-assign': 'error',
153 |
154 | // disallow use of javascript: urls
155 | // http://eslint.org/docs/rules/no-script-url
156 | 'no-script-url': 'error',
157 |
158 | // disallow comparisons where both sides are exactly the same
159 | // http://eslint.org/docs/rules/no-self-compare
160 | 'no-self-compare': 'error',
161 |
162 | // disallow use of the comma operator
163 | // http://eslint.org/docs/rules/no-sequences
164 | 'no-sequences': 'error',
165 |
166 | // only Error objects can be thrown
167 | // http://eslint.org/docs/rules/no-throw-literal
168 | 'no-throw-literal': 'error',
169 |
170 | // disallow unmodified conditions of loops
171 | // http://eslint.org/docs/rules/no-unmodified-loop-condition
172 | 'no-unmodified-loop-condition': 'error',
173 |
174 | // disallow usage of expressions in statement position
175 | // http://eslint.org/docs/rules/no-unused-expressions
176 | 'no-unused-expressions': 'error',
177 |
178 | // disallow unnecessary .call() and .apply()
179 | // http://eslint.org/docs/rules/no-useless-call
180 | 'no-useless-call': 'error',
181 |
182 | // disallow unnecessary concatenation of literals or template literals
183 | // http://eslint.org/docs/rules/no-useless-concat
184 | 'no-useless-concat': 'error',
185 |
186 | // disallow unnecessary usage of escape character
187 | // http://eslint.org/docs/rules/no-useless-escape
188 | 'no-useless-escape': 'error',
189 |
190 | // disallow redundant return statements
191 | // http://eslint.org/docs/rules/no-useless-return
192 | 'no-useless-return': 'error',
193 |
194 | // disallow use of the void operator
195 | // http://eslint.org/docs/rules/no-void
196 | 'no-void': 'error',
197 |
198 | // allow warning terms in comments (like TODO or FIXME)
199 | // http://eslint.org/docs/rules/no-warning-comments
200 | 'no-warning-comments': 'off',
201 |
202 | // disallow use of the with statement
203 | // http://eslint.org/docs/rules/no-with
204 | 'no-with': 'error',
205 |
206 | // require use of the second argument for parseInt()
207 | // http://eslint.org/docs/rules/radix
208 | 'radix': 'error',
209 |
210 | // require declaration of all vars at the top of their containing scope
211 | // http://eslint.org/docs/rules/vars-on-top
212 | 'vars-on-top': 'error',
213 |
214 | // require immediate function invocation to be wrapped in parentheses
215 | // http://eslint.org/docs/rules/wrap-iife
216 | 'wrap-iife': ['error', 'inside'],
217 |
218 | // disallow Yoda conditions
219 | // http://eslint.org/docs/rules/yoda
220 | 'yoda': 'error'
221 | }
222 | };
223 |
--------------------------------------------------------------------------------
/packages/eslint-config-eventbrite-legacy/rules/errors.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | rules: {
3 | // warn when using console
4 | // http://eslint.org/docs/rules/no-console
5 | 'no-console': 'warn',
6 |
7 | // disallow declaration of variables already declared in the outer scope
8 | // http://eslint.org/docs/rules/no-shadow
9 | 'no-shadow': 'error',
10 |
11 | // disallow use of variables before they are defined
12 | // http://eslint.org/docs/rules/no-use-before-define
13 | 'no-use-before-define': 'error'
14 | }
15 | };
16 |
--------------------------------------------------------------------------------
/packages/eslint-config-eventbrite-legacy/rules/node.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | env: {
3 | 'node': true
4 | },
5 | rules: {
6 | // TBD
7 | }
8 | };
9 |
--------------------------------------------------------------------------------
/packages/eslint-config-eventbrite-legacy/rules/style.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | rules: {
3 | // enforce one-true-brace style
4 | // http://eslint.org/docs/rules/brace-style
5 | 'brace-style': 'error',
6 |
7 | // require camel case names
8 | // http://eslint.org/docs/rules/camelcase
9 | 'camelcase': 'error',
10 |
11 | // disallow trailing commas
12 | // http://eslint.org/docs/rules/comma-dangle
13 | 'comma-dangle': 'error',
14 |
15 | // enforce spacing only after comma
16 | // http://eslint.org/docs/rules/comma-spacing
17 | 'comma-spacing': 'error',
18 |
19 | // enforces The standard comma style, in which commas are
20 | // placed at the end of the current line
21 | // http://eslint.org/docs/rules/comma-style
22 | 'comma-style': 'error',
23 |
24 | // If a variable is initialized or assigned the value `this`,
25 | // the name of the variable must be a 'self'.
26 | // http://eslint.org/docs/rules/consistent-this
27 | 'consistent-this': ['error', 'self'],
28 |
29 | // require function names to match the name of the variable or
30 | // property to which they are assigned
31 | // https://eslint.org/docs/rules/func-name-matching
32 | 'func-name-matching': 'error',
33 |
34 | // Enforce function declarations-only (except allow arrow
35 | // function expressions)
36 | // http://eslint.org/docs/rules/func-style
37 | 'func-style': ['error', 'declaration', {
38 | allowArrowFunctions: true
39 | }],
40 |
41 | // 4-space indentation
42 | // http://eslint.org/docs/rules/indent
43 | 'indent': ['error', 4, {
44 | SwitchCase: 1
45 | }],
46 |
47 | // space for values in object literals
48 | // http://eslint.org/docs/rules/key-spacing
49 | 'key-spacing': 'error',
50 |
51 | // require spacing before and after keywords
52 | // https://eslint.org/docs/rules/keyword-spacing
53 | 'keyword-spacing': 'error',
54 |
55 | // disallow inline comments after code
56 | // http://eslint.org/docs/rules/line-comment-position
57 | 'line-comment-position': 'error',
58 |
59 | // Require constructor function names to begin with a capital letter
60 | // Requires all `new` operators to be called with uppercase-started functions.
61 | // http://eslint.org/docs/rules/new-cap
62 | 'new-cap': 'error',
63 |
64 | // disallow the omission of parentheses when invoking a constructor with no arguments
65 | // http://eslint.org/docs/rules/new-parens
66 | 'new-parens': 'error',
67 |
68 | // require an empty newline after variable declarations
69 | // http://eslint.org/docs/rules/newline-after-var
70 | 'newline-after-var': 'error',
71 |
72 | // disallow use of chained assignment expressions
73 | // http://eslint.org/docs/rules/no-lonely-if
74 | 'no-lonely-if': 'error',
75 |
76 | // disallow if statements as the only statement in else blocks
77 | // http://eslint.org/docs/rules/no-multi-assign
78 | 'no-multi-assign': 'error',
79 |
80 | // Prevent more than 2 empty lines within a file,
81 | // 1 at the end, and 0 at the beginning
82 | // http://eslint.org/docs/rules/no-multiple-empty-lines
83 | 'no-multiple-empty-lines': ['error', {
84 | max: 2,
85 | maxEOF: 1,
86 | maxBOF: 0
87 | }],
88 |
89 | // disallows the use of nested ternary expressions
90 | // http://eslint.org/docs/rules/no-nested-ternary
91 | 'no-nested-ternary': 'error',
92 |
93 | // disallow trailing whitespace at the end of lines
94 | // http://eslint.org/docs/rules/no-trailing-spaces
95 | 'no-trailing-spaces': 'error',
96 |
97 | // allow underscores in identifiers
98 | // http://eslint.org/docs/rules/no-underscore-dangle
99 | 'no-underscore-dangle': 'off',
100 |
101 | // disallow ternary operators when simpler alternatives exist
102 | // http://eslint.org/docs/rules/no-unneeded-ternary
103 | 'no-unneeded-ternary': 'error',
104 |
105 | // disallow whitespace before properties
106 | // http://eslint.org/docs/rules/no-whitespace-before-property
107 | 'no-whitespace-before-property': 'error',
108 |
109 | // disallow padding within curly braces of object literals
110 | // http://eslint.org/docs/rules/object-curly-spacing
111 | 'object-curly-spacing': 'error',
112 |
113 | // enforce that `var` declarations are declared together
114 | // http://eslint.org/docs/rules/one-var
115 | 'one-var': ['error', {
116 | 'var': 'always'
117 | }],
118 |
119 | // require a newline around variable declarations
120 | // http://eslint.org/docs/rules/one-var-declaration-per-line
121 | 'one-var-declaration-per-line': 'error',
122 |
123 | // require operator assignment shorthand where possible
124 | // http://eslint.org/docs/rules/operator-assignment
125 | 'operator-assignment': 'error',
126 |
127 | // don't enforce placement of operators with line breaks
128 | // http://eslint.org/docs/rules/operator-linebreak
129 | 'operator-linebreak': 'off',
130 |
131 | // always use single quotes
132 | // http://eslint.org/docs/rules/quotes
133 | 'quotes': ['error', 'single'],
134 |
135 | // require use of semicolons
136 | // http://eslint.org/docs/rules/semi
137 | 'semi': 'error',
138 |
139 | // disallow space before semicolon
140 | // require space after semicolon
141 | // http://eslint.org/docs/rules/semi-spacing
142 | 'semi-spacing': 'error',
143 |
144 | // require a space before blocks
145 | // http://eslint.org/docs/rules/space-before-blocks
146 | 'space-before-blocks': 'error',
147 |
148 | // never have a space before function parentheses
149 | // http://eslint.org/docs/rules/space-before-function-paren
150 | 'space-before-function-paren': ['error', 'never']
151 | }
152 | };
153 |
--------------------------------------------------------------------------------
/packages/eslint-config-eventbrite-legacy/rules/variables.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | rules: {
3 | // disallow catch clause parameters from shadowing variables in the outer scope
4 | // https://eslint.org/docs/rules/no-catch-shadow
5 | 'no-catch-shadow': 'error',
6 |
7 | // disallow variable declarations from shadowing variables declared in the outer scope
8 | // https://eslint.org/docs/rules/no-shadow
9 | 'no-shadow': ['error', {builtinGlobals: true}],
10 |
11 | // disallow identifiers from shadowing restricted names
12 | // https://eslint.org/docs/rules/no-shadow-restricted-names
13 | 'no-shadow-restricted-names': 'error',
14 |
15 | // disallow the use of variables before they are defined
16 | // https://eslint.org/docs/rules/no-use-before-define
17 | 'no-use-before-define': 'error'
18 | }
19 | };
20 |
--------------------------------------------------------------------------------
/packages/eslint-config-eventbrite-react/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./index.js",
3 | "rules": {
4 | "import/unambiguous": "off",
5 | "import/no-commonjs": "off"
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/packages/eslint-config-eventbrite-react/.npmignore:
--------------------------------------------------------------------------------
1 | _scripts/
2 |
--------------------------------------------------------------------------------
/packages/eslint-config-eventbrite-react/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## v6.0.0 (May 24, 2018)
2 | - (major) Requires node >= 6
3 | - (major) bump `eslint-config-eventbrite-legacy` to `^4.0.0`
4 | - (major) Bumped `eslint` peer dependency to `^4.19.1`
5 | - (major) Bumped `babel-eslint` peer dependency to `^8.2.3`
6 | - (minor) Bumped `eslint-plugin-import` peer dependency to `^2.11.0`
7 | - (major) Added `eslint-plugin-babel` peer dependency at `^5.1.0`
8 | - (major) Added `eslint-plugin-jest` peer dependency at `^21.15.1`
9 | - (major) Bumped `esling-plugin-react` peer dependency to `^7.7.0`
10 | - (major) New erroring rules:
11 | * Everything within [plugin:jsx-a11y/recommended](https://github.com/evcohen/eslint-plugin-jsx-a11y/), particularly [jsx-a11y/no-onchange](https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/no-onchange.md)
12 | * [react/button-has-type](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/button-has-type.md)
13 | * [react/default-props-match-prop-types](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/default-props-match-prop-types.md)
14 | * [react/forbid-foreign-prop-types](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/forbid-foreign-prop-types.md)
15 | * [react/jsx-curly-brace-presence](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-curly-brace-presence.md)
16 | * [react/no-access-state-in-setstate](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-access-state-in-setstate.md)
17 | * [react/no-array-index-key](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-array-index-key.md)
18 | * [react/no-deprecated](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-deprecated.md)
19 | * [react/no-find-dom-node](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-find-dom-node.md)
20 | * [react/no-redundant-should-component-update](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-redundant-should-component-update.md)
21 | * [react/no-this-in-sfc](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-this-in-sfc.md)
22 | * [react/no-typos](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-typos.md)
23 | * [react/no-unescaped-entities](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-unescaped-entities.md)
24 | * [react/no-unused-state](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-unused-state.md)
25 | * [react/prop-types](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/prop-types.md)
26 | * [react/void-dom-elements-no-children](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/void-dom-elements-no-children.md)
27 | - (major) Stronger exiting erroring rules:
28 | * [react/jsx-no-bind](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-bind.md) prevents `.bind()` as well as arrow functions & refs in JSX props
29 | * [react/jsx-tag-spacing](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-tag-spacing.md)
30 | - (patch) Relaxed existing erroring rules:
31 | * [jsx-a11y/anchor-is-valid](https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/anchor-is-valid.md) to not complain about ` `
32 | * [jsx-a11y/label-has-for](https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/label-has-for.md) to support `` or `` with text contents
33 | * [react/jsx-first-prop-new-line](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-first-prop-new-line.md) only requires prop on separate line when there are multiple props AND the component spans multiple lines
34 | * [react/jsx-max-props-per-line](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-max-props-per-line.md) now allows 4 props per line (up from 3)
35 |
36 | ## v5.0.0 (July 5, 2017)
37 | - (major) Replace old `jsx-a11y` rules
38 |
39 | ## v4.0.0 (April 28, 2017)
40 | - (major) Add new `jsx-a11y` rules
41 |
42 | ## v3.0.0 (February 10, 2017)
43 | - (major) bump `eslint-config-eventbrite` to 4.0.0
44 |
45 | ## v2.0.1 (December 1, 2016)
46 | - (patch) Bump `eslint-config-eventbrite` dependency to 3.0.1
47 | - (dev-ops) Add `eslint-plugin-import` as a peer dependency
48 |
49 | ## v2.0.0 (November 30, 2016)
50 | - (patch) Bump `eslint-config-eventbrite` dependency to 3.0.0
51 | - (major) Bump to eslint 3
52 | - (patch) Bump to latest versions of `babel-eslint`, `eslint-plugin-react` & `eslint-plugin-jsx-a11y`
53 |
54 | ## v1.2.0 (July 12, 2016)
55 | - (minor) Change `react/jsx-handler-names` `eventHandlerPrefix` to `_handle` from `handle`
56 | - (patch) Bump `eslint-config-eventbrite` dependency to 2.1.1
57 |
58 | ## v1.1.2 (June 22, 2016)
59 | - (dev ops) Bump `eslint-config-eventbrite` dependency to 2.1.0
60 |
61 | ## v1.1.1 (June 22, 2016)
62 | - (dev ops) Bump `eslint-config-eventbrite` dependency to 2.0.0
63 |
64 | ## v1.1.0 (June 22, 2016)
65 | - (minor) turning off `react/prop-types` (temporarily)
66 | - (minor) validate `htmlFor` on `Label` component as well in `jsx-a11y/label-has-for`
67 |
68 | ## v1.0.0 (June 7, 2016)
69 | - Initial release
70 |
--------------------------------------------------------------------------------
/packages/eslint-config-eventbrite-react/README.md:
--------------------------------------------------------------------------------
1 | # eslint-config-eventbrite-react
2 |
3 | [](http://npm.im/eslint-config-eventbrite-react)
4 | [](http://npm-stat.com/charts.html?package=eslint-config-eventbrite-react&from=2016-05-27)
5 | [](https://github.com/eventbrite/javascript/pulse)
6 |
7 | [](http://makeapullrequest.com)
8 | [](http://spdx.org/licenses/MIT)
9 |
10 | Eventbrite's ESLint config that lints React & JSX, adhering to the [Eventbrite JavaScript Coding Style Guide](https://github.com/eventbrite/javascript).
11 |
12 | ## Usage
13 |
14 | This ESLint configuration requires [`eslint`](https://github.com/eslint/eslint), [`babel-eslint`](https://github.com/babel/babel-eslint), [`eslint-plugin-babel`](https://github.com/babel/eslint-plugin-babel), [`eslint-plugin-import`](https://github.com/benmosher/eslint-plugin-import), [`eslint-plugin-react`](https://github.com/yannickcr/eslint-plugin-react), [`eslint-plugin-jsx-a11y`](https://github.com/evcohen/eslint-plugin-jsx-a11y/) and [`eslint-plugin-jest`](https://github.com/jest-community/eslint-plugin-jest).
15 |
16 | Install using [npm](https://www.npmjs.com/get-npm):
17 |
18 | ```sh
19 | npm install --save-dev eslint@^4.19.1 babel-eslint@^8.2.3 eslint-plugin-babel@^5.1.0 eslint-plugin-import@^2.11.0 eslint-plugin-react@^7.7.0 eslint-plugin-jsx-a11y@^6.0.3 eslint-plugin-jest@^21.15.1 eslint-config-eventbrite-react
20 | ```
21 |
22 | ...or using [yarn](https://yarnpkg.com/):
23 |
24 | ```sh
25 | yarn add --dev eslint@^4.19.1 babel-eslint@^8.2.3 eslint-plugin-babel@^5.1.0 eslint-plugin-import@^2.11.0 eslint-plugin-react@^7.7.0 eslint-plugin-jsx-a11y@^6.0.3 eslint-plugin-jest@^21.15.1 eslint-config-eventbrite-react
26 | ```
27 |
28 | Extend `eslint-config-eventbrite-react` in your [`.eslintrc.json`](http://eslint.org/docs/user-guide/configuring#extending-configuration-files):
29 |
30 | ```json
31 | {
32 | "extends": "eventbrite-react"
33 | }
34 | ```
35 |
36 | _NOTE:_ This configuration extends [`eslint-config-eventbrite`](../eslint-config-eventbrite) and [`plugin:react/recommended'`](https://github.com/yannickcr/eslint-plugin-react#user-content-recommended-configuration).
37 |
38 | ## Contributing
39 |
40 | Thank you for your willingness to contribute! 😀
41 |
42 | Although `eslint-config-eventbrite-react` is a public package, its primary purpose is for linting Eventbrite's React JavaScript code. Therefore, it is unlikely that we'll accept changes to rules as they may adversely affect the linting of our own code. However, we do welcome fixes for errors or additions for omissions.
43 |
44 | If you are uncertain as to whether your suggestion qualifies, go ahead and submit a [Pull Request](https://github.com/eventbrite/javascript/pulls) and we'll let you know. Thanks again!
45 |
46 | ## License
47 |
48 | The library is available as open source under the terms of the [MIT License](https://github.com/evenbrite/javascript/LICENSE).
49 |
50 | ## Thanks
51 |
52 | Many thanks to Airbnb and their example [`eslint-config-airbnb`](https://github.com/airbnb/javascript/tree/master/packages/eslint-config-airbnb).
53 |
--------------------------------------------------------------------------------
/packages/eslint-config-eventbrite-react/_scripts/git-tag-version.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-console */
2 | const execSync = require('child_process').execSync;
3 | const packageInfo = require('../package.json');
4 |
5 | const name = packageInfo.name;
6 | const version = packageInfo.version;
7 | const tagName = `${name}-v${version}`;
8 |
9 | const gitCommands = [
10 | 'git add package.json CHANGELOG.md',
11 | `git commit --message "Release ${tagName}"`,
12 | `git tag ${tagName}`,
13 | ];
14 | const shellCommand = gitCommands.join(' && ');
15 |
16 | // NOTE: The normal npm-version script creates a git tag that's in the form of
17 | // "v1.0.0" which is a problem because we have 3 NPM packages in this one git
18 | // repository. So instead, we'll disable `npm version` from creating that default
19 | // tag and this script will run to do it. This script prepends the package name
20 | // so that when looking at the tags in the repo they will be differentiated.
21 | // It also commits the package.json.
22 |
23 | console.log(`Committing ${tagName} of package.json & adding tag`);
24 |
25 | execSync(shellCommand, (error, stdout, stderr) => {
26 | if (error) {
27 | console.error(`execSync error: ${error}`);
28 | return;
29 | }
30 |
31 | console.error(stdout);
32 | console.error(stderr);
33 | });
34 |
--------------------------------------------------------------------------------
/packages/eslint-config-eventbrite-react/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: ['eventbrite'].concat([
3 | './rules/react',
4 | './rules/react-a11y'
5 | ].map(require.resolve))
6 | };
7 |
--------------------------------------------------------------------------------
/packages/eslint-config-eventbrite-react/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "eslint-config-eventbrite-react",
3 | "version": "6.0.0",
4 | "description": "Eventbrites's ESLint config that lints React & JSX, adhering to the Eventbrite JavaScript Coding Style Guide",
5 | "main": "index.js",
6 | "scripts": {
7 | "git-tag-version": "node ./_scripts/git-tag-version.js",
8 | "lint": "eslint rules",
9 | "test": "yarn run lint",
10 | "preversion": "yarn test",
11 | "version": "yarn run git-tag-version",
12 | "postversion": "git push && git push --tags",
13 | "version:patch": "npm version patch --no-git-tag-version",
14 | "version:minor": "npm version minor --no-git-tag-version",
15 | "version:major": "npm version major --no-git-tag-version"
16 | },
17 | "repository": {
18 | "type": "git",
19 | "url": "git+https://github.com/eventbrite/javascript.git"
20 | },
21 | "keywords": [
22 | "eslint",
23 | "eslintconfig",
24 | "config",
25 | "eventbrite",
26 | "javascript",
27 | "styleguide"
28 | ],
29 | "author": "Eventbrite ",
30 | "license": "MIT",
31 | "bugs": {
32 | "url": "https://github.com/eventbrite/javascript/issues"
33 | },
34 | "homepage": "https://github.com/eventbrite/javascript#readme",
35 | "dependencies": {
36 | "eslint-config-eventbrite": "^5.0.0"
37 | },
38 | "devDependencies": {
39 | "babel-eslint": "^8.2.3",
40 | "eslint": "^4.19.1",
41 | "eslint-plugin-babel": "^5.1.0",
42 | "eslint-plugin-import": "^2.2.0",
43 | "eslint-plugin-jest": "^21.15.1",
44 | "eslint-plugin-jsx-a11y": "^6.0.0",
45 | "eslint-plugin-react": "^7.7.0"
46 | },
47 | "peerDependencies": {
48 | "babel-eslint": "^8.2.3",
49 | "eslint": "^4.19.1",
50 | "eslint-plugin-babel": "^5.1.0",
51 | "eslint-plugin-import": "^2.11.0",
52 | "eslint-plugin-jest": "^21.15.1",
53 | "eslint-plugin-jsx-a11y": "^6.0.0",
54 | "eslint-plugin-react": "^7.7.0"
55 | },
56 | "engines": {
57 | "node": ">=6"
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/packages/eslint-config-eventbrite-react/rules/react-a11y.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: ['jsx-a11y'],
3 | extends: [
4 | 'plugin:jsx-a11y/recommended',
5 | ],
6 |
7 | // View link below for docs on react a11y rules
8 | // https://github.com/evcohen/eslint-plugin-jsx-a11y/
9 | rules: {
10 | // Enforce an anchor element contains a valid `href` attribute and if it can be replaced by a button
11 | // (also checks react-router )
12 | // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/anchor-is-valid.md
13 | 'jsx-a11y/anchor-is-valid': ['error', {components: ['Link'], specialLink: ['to']}],
14 |
15 | // Enforce that `` & (custom) elements have the `htmlFor` prop
16 | // either the element is nested within or it has the `htmlFor` prop
17 | // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/label-has-for.md
18 | 'jsx-a11y/label-has-for': ['error', {
19 | components: ['Label'],
20 | required: {some: ['id', 'nesting']},
21 | allowChildren: true,
22 | }],
23 |
24 | // Enforce lang attribute has a valid value.
25 | // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/lang.md
26 | 'jsx-a11y/lang': 'error',
27 | },
28 | };
29 |
--------------------------------------------------------------------------------
/packages/eslint-config-eventbrite-react/rules/react.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: [
3 | 'react',
4 | ],
5 | env: {
6 | browser: true,
7 | },
8 | parser: 'babel-eslint',
9 | parserOptions: {
10 | ecmaVersion: 7,
11 | sourceType: 'module',
12 | ecmaFeatures: {
13 | jsx: true,
14 | },
15 | },
16 | extends: [
17 | 'plugin:react/recommended',
18 | ],
19 |
20 | // View link below for react rules documentation
21 | // https://github.com/yannickcr/eslint-plugin-react#list-of-supported-rules
22 | rules: {
23 | // Use double quotes for JSX
24 | // http://eslint.org/docs/rules/jsx-quotes
25 | 'jsx-quotes': 'error',
26 |
27 | // Forbid `` element without an explicit `type` attribute
28 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/button-has-type.md
29 | 'react/button-has-type': 'error',
30 |
31 | // Prevent extraneous `defaultProps` on components
32 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/default-props-match-prop-types.md
33 | 'react/default-props-match-prop-types': 'error',
34 |
35 | // Forbid propTypes: `any`, `array`, `object`
36 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/forbid-prop-types.md
37 | 'react/forbid-prop-types': 'error',
38 |
39 | // Forbid using another component's `propTypes` unless they are explicitly imported/exported
40 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/forbid-foreign-prop-types.md
41 | 'react/forbid-foreign-prop-types': 'error',
42 |
43 | // True boolean props must pass {true} value
44 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-boolean-value.md
45 | 'react/jsx-boolean-value': ['error', 'always'],
46 |
47 | // Enforce closing bracket location in JSX is aligned with opening tag
48 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-closing-bracket-location.md
49 | 'react/jsx-closing-bracket-location': 'error',
50 |
51 | // Enforce the closing tag for multiline elements with children are aligned with the opening tag on its own line
52 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-closing-tag-location.md
53 | 'react/jsx-closing-tag-location': 'error',
54 |
55 | // Disallow spaces within curly brackets
56 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-curly-spacing.md
57 | 'react/jsx-curly-spacing': 'error',
58 |
59 | // Disallow unnecessary curly braces in JSX props and/or children
60 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-curly-brace-presence.md
61 | 'react/jsx-curly-brace-presence': 'error',
62 |
63 | // Disallow spaces around equal sgns in JSX
64 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-equals-spacing.md
65 | 'react/jsx-equals-spacing': 'error',
66 |
67 | // Enforce that the first property should always be placed on a new line if the JSX tag takes
68 | // up multiple lines and there are multiple properties
69 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-first-prop-new-line.md
70 | 'react/jsx-first-prop-new-line': ['error', 'multiline-multiprop'],
71 |
72 | // Enforce event handler naming conventions in JSX: on* for props, _handle* from methods/functions
73 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-handler-names.md
74 | 'react/jsx-handler-names': ['error', {
75 | eventHandlerPrefix: '_handle',
76 | eventHandlerPropPrefix: 'on',
77 | }],
78 |
79 | // Enforce 4 space JSX tag indentation
80 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-indent.md
81 | 'react/jsx-indent': 'error',
82 |
83 | // Enforce 4 space JSX props indentation
84 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-indent-props.md
85 | 'react/jsx-indent-props': 'error',
86 |
87 | // Validate JSX has key prop when in array or iterator
88 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-key.md
89 | 'react/jsx-key': 'error',
90 |
91 | // Limit maximum of props on a single line in JSX to 3
92 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-max-props-per-line.md
93 | 'react/jsx-max-props-per-line': ['error', {maximum: 4}],
94 |
95 | // Prevent `.bind`, arrow functions & refs in a JSX prop
96 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-bind.md
97 | 'react/jsx-no-bind': 'error',
98 |
99 | // Prevent usage of unsafe target="_blank" w/o rel="noopener noreferrer"
100 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-target-blank.md
101 | 'react/jsx-no-target-blank': 'error',
102 |
103 | // Enforce PascalCase for user-defined JSX components
104 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-pascal-case.md
105 | 'react/jsx-pascal-case': 'error',
106 |
107 | // Forbid spaces after the opening bracket
108 | // Forbid spaces before the closing bracket
109 | // Enforce spaces before the closing bracket of self-closing elements
110 | // Forbid spaces between the angle bracket and slash of JSX closing or self-closing elements
111 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-tag-spacing.md
112 | 'react/jsx-tag-spacing': ['error', {
113 | afterOpening: 'never',
114 | beforeClosing: 'never',
115 | beforeSelfClosing: 'always',
116 | closingSlash: 'never',
117 | }],
118 |
119 | // Enforce multi-line JSX is wrapped in parentheses
120 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-wrap-multilines.md
121 | 'react/jsx-wrap-multilines': 'error',
122 |
123 | // Prevent usage of `this.state` inside `setState` calls
124 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-access-state-in-setstate.md
125 | 'react/no-access-state-in-setstate': 'error',
126 |
127 | // Warn if an element uses an Array index in its `key`
128 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-array-index-key.md
129 | 'react/no-array-index-key': 'warn',
130 |
131 | // Warn when using "dangerous" JSX properties
132 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-danger.md
133 | 'react/no-danger': 'warn',
134 |
135 | // Prevent usage of deprecated methods
136 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-deprecated.md
137 | 'react/no-deprecated': 'error',
138 |
139 | // Prevent usage of `findDOMNode` because Facebook will eventually deprecate it as it
140 | // blocks certain improvements in React in the future
141 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-find-dom-node.md
142 | 'react/no-find-dom-node': 'error',
143 |
144 | // Prevent usage of `shouldComponentUpdate` when extending `React.PureComponent`
145 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-redundant-should-component-update.md
146 | 'react/no-redundant-should-component-update': 'error',
147 |
148 | // Prevent using (legacy) string references in ref attribute
149 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-string-refs.md
150 | 'react/no-string-refs': 'error',
151 |
152 | // Prevent `this` from being used in stateless functional components
153 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-this-in-sfc.md
154 | 'react/no-this-in-sfc': 'error',
155 |
156 | // Prevents common typos made declaring static class properties and lifecycle methods
157 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-typos.md
158 | 'react/no-typos': 'error',
159 |
160 | // Prevent some characters from appearing in markup w/o being escaped
161 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-unescaped-entities.md
162 | 'react/no-unescaped-entities': ['error', {forbid: ['>', '}', '"']}],
163 |
164 | // Prevent definitions of unused state
165 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-unused-state.md
166 | 'react/no-unused-state': 'error',
167 |
168 | // Require ES6 class declarations over React.createClass
169 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/prefer-es6-class.md
170 | 'react/prefer-es6-class': 'error',
171 |
172 | // Do not enforce stateless React Components to be written as a pure function
173 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/prefer-stateless-function.md
174 | 'react/prefer-stateless-function': 'off',
175 |
176 | // Prevent missing props validation in a React component definition
177 | // Skips components where **no** prop types are defined (i.e. our helper components)
178 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/prop-types.md
179 | 'react/prop-types': ['error', {skipUndeclared: true}],
180 |
181 | // Enforce that ES6 class returns a value for `render()` method
182 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/require-render-return.md
183 | 'react/require-render-return': 'error',
184 |
185 | // Enforce that components are self-closed when they don't have content
186 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/self-closing-comp.md
187 | 'react/self-closing-comp': 'error',
188 |
189 | // Enforce default component methods order
190 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/sort-comp.md
191 | 'react/sort-comp': 'error',
192 |
193 | // Prevent void DOM elements (e.g. ` `, ` `) from receiving children
194 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/void-dom-elements-no-children.md
195 | 'react/void-dom-elements-no-children': 'error',
196 | },
197 | };
198 |
--------------------------------------------------------------------------------
/packages/eslint-config-eventbrite/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./index.js",
3 | "rules": {
4 | "import/unambiguous": "off",
5 | "import/no-commonjs": "off"
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/packages/eslint-config-eventbrite/.npmignore:
--------------------------------------------------------------------------------
1 | _scripts/
2 |
--------------------------------------------------------------------------------
/packages/eslint-config-eventbrite/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## v5.0.0 (May 24, 2018)
2 | - (major) Requires node >= 6
3 | - (major) bump `eslint-config-eventbrite-legacy` to `^4.0.0`
4 | - (major) Bumped `eslint` peer dependency to `^4.19.1`
5 | - (major) Bumped `babel-eslint` peer dependency to `^8.2.3`
6 | - (minor) Bumped `eslint-plugin-import` peer dependency to `^2.11.0`
7 | - (major) New erroring rules:
8 | * [import/default](https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/default.md)
9 | * [import/named](https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/named.md)
10 | * [import/namespace](https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/namespace.md)
11 | * [import/no-amd](https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-amd.md)
12 | * [import/no-commonjs](https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-commonjs.md)
13 | * [import/no-cycle](https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-cycle.md)
14 | * [import/no-named-as-default](https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-named-as-default.md)
15 | * [import/no-named-as-default-member](https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-named-as-default-member.md)
16 | * [import/no-named-default](https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-named-default.md)
17 | * [import/no-self-import](https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-self-import.md)
18 | * [import/no-useless-path-segments](https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-useless-path-segments.md)
19 | * [no-await-in-loop](https://eslint.org/docs/rules/no-await-in-loop)
20 | * [no-return-await](https://eslint.org/docs/rules/no-return-await)
21 | * [strict](https://eslint.org/docs/rules/strict)
22 | - (major) Stronger exiting erroring rules:
23 | * [comma-dangle](http://eslint.org/docs/rules/comma-dangle) is turned on for `import` & `export`
24 | * [prefer-const](http://eslint.org/docs/rules/prefer-const) is turned on enforce using `const` whenever possible
25 | - (major) Added `eslint-plugin-babel` peer dependency at `^5.1.0`
26 | * [babel/no-invalid-this](https://github.com/babel/eslint-plugin-babel/) replaces [no-invalid-this](http://eslint.org/docs/rules/no-invalid-this)
27 | - (major) Added `eslint-plugin-jest` peer dependency at `^21.15.1`
28 | * Everything within [plugin:jest/recommended](https://github.com/jest-community/eslint-plugin-jest#rules)
29 | * [jest/consistent-test-it](https://github.com/jest-community/eslint-plugin-jest/blob/master/docs/rules/consistent-test-it.md)
30 | * [jest/lowercase-name](https://github.com/jest-community/eslint-plugin-jest/blob/master/docs/rules/lowercase-name.md)
31 | * [jest/prefer-to-be-null](https://github.com/jest-community/eslint-plugin-jest/blob/master/docs/rules/prefer-to-be-null.md)
32 | * [jest/prefer-to-be-undefined](https://github.com/jest-community/eslint-plugin-jest/blob/master/docs/rules/prefer-to-be-undefined.md)
33 | * [jest/valid-describe](https://github.com/jest-community/eslint-plugin-jest/blob/master/docs/rules/valid-describe.md)
34 | * [jest/valid-expect-in-promise](https://github.com/jest-community/eslint-plugin-jest/blob/master/docs/rules/valid-expect-in-promise.md)
35 |
36 | ## v4.0.0 (February 10, 2017)
37 | - (major) bump `eslint-config-eventbrite-legacy` to 3.0.0
38 |
39 | ## v3.0.1 (December 1, 2016)
40 | - (patch) Bug fix: Need `babel-eslint@7` peer dependency
41 |
42 | ## v3.0.0 (November 30, 2016)
43 | - (patch) bump `eslint-config-eventbrite-legacy` to 2.0.0
44 | - (major) added `arrow-body-style`, `space-infix-ops`, `rest-spread-spacing`, and `import/*` rules
45 | - (major) bump to eslint 3
46 |
47 | ## v2.1.1 (July 12, 2016)
48 | - (patch) bump `eslint-config-eventbrite-legacy` to 1.1.1
49 |
50 | ## v2.1.0 (June 29, 2016)
51 | - (minor) turn off `no-confusing-arrow`
52 |
53 | ## v2.0.0 (June 22, 2016)
54 | - (dev ops) added `babel-eslint` dependency so ES2016+ can be linted
55 | - (minor) relaxed `comma-dangle` to allow dangling commas for multi-line
56 | - (breaking change) only function expressions (arrow functions) allowed in ES6+
57 | - (minor) ignore imports for `no-useless-rename`
58 | - (minor) relax `no-confusing-arrow` by setting `allowParens` to `true`
59 | - (dev ops) Bump `eslint-config-eventbrite-legacy` to 1.1.0
60 |
61 | ## v1.0.0 (June 7, 2016)
62 | - Initial release
63 |
--------------------------------------------------------------------------------
/packages/eslint-config-eventbrite/README.md:
--------------------------------------------------------------------------------
1 | # eslint-config-eventbrite
2 |
3 | [](http://npm.im/eslint-config-eventbrite)
4 | [](http://npm-stat.com/charts.html?package=eslint-config-eventbrite&from=2016-05-27)
5 | [](https://github.com/eventbrite/javascript/pulse)
6 |
7 | [](http://makeapullrequest.com)
8 | [](http://spdx.org/licenses/MIT)
9 |
10 | Eventbrite's base ESLint config that lints ES6+/ES2015+ and adheres to the [Eventbrite JavaScript Coding Style Guide](https://github.com/eventbrite/javascript).
11 |
12 | ## Usage
13 |
14 | This ESLint configuration requires [`eslint`](https://github.com/eslint/eslint), [`babel-eslint`](https://github.com/babel/babel-eslint), [`eslint-plugin-babel`](https://github.com/babel/eslint-plugin-babel), [`eslint-plugin-import`](https://github.com/benmosher/eslint-plugin-import), and [`eslint-plugin-jest`](https://github.com/jest-community/eslint-plugin-jest).
15 |
16 | Install using [npm](https://www.npmjs.com/get-npm):
17 |
18 | ```sh
19 | npm install --save-dev eslint@^4.19.1 babel-eslint@^8.2.3 eslint-plugin-babel@^5.1.0 eslint-plugin-import@^2.11.0 eslint-plugin-jest@^21.15.1 eslint-config-eventbrite
20 | ```
21 |
22 | ...or using [yarn](https://yarnpkg.com/):
23 |
24 | ```sh
25 | yarn add --dev eslint@^4.19.1 babel-eslint@^8.2.3 eslint-plugin-babel@^5.1.0 eslint-plugin-import@^2.11.0 eslint-plugin-jest@^21.15.1 eslint-config-eventbrite
26 | ```
27 |
28 | Extend `eslint-config-eventbrite` in your [`.eslintrc.json`](http://eslint.org/docs/user-guide/configuring#extending-configuration-files):
29 |
30 | ```json
31 | {
32 | "extends": "eventbrite"
33 | }
34 | ```
35 |
36 | _NOTE:_ This configuration extends [`eslint-config-eventbrite-legacy`](../eslint-config-eventbrite-legacy) and `plugin:import/errors`.
37 |
38 | ## Contributing
39 |
40 | Thank you for your willingness to contribute! 😀
41 |
42 | Although `eslint-config-eventbrite` is a public package, its primary purpose is for linting Eventbrite's JavaScript code. Therefore, it is unlikely that we'll accept changes to rules as they may adversely affect the linting of our own code. However, we do welcome fixes for errors or additions for omissions.
43 |
44 | If you are uncertain as to whether your suggestion qualifies, go ahead and submit a [Pull Request](https://github.com/eventbrite/javascript/pulls) and we'll let you know. Thanks again!
45 |
46 | ## License
47 |
48 | The library is available as open source under the terms of the [MIT License](https://github.com/evenbrite/javascript/LICENSE).
49 |
50 | ## Thanks
51 |
52 | Many thanks to Airbnb and their example [`eslint-config-airbnb`](https://github.com/airbnb/javascript/tree/master/packages/eslint-config-airbnb).
53 |
--------------------------------------------------------------------------------
/packages/eslint-config-eventbrite/_scripts/git-tag-version.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-console */
2 | const execSync = require('child_process').execSync;
3 | const packageInfo = require('../package.json');
4 |
5 | const name = packageInfo.name;
6 | const version = packageInfo.version;
7 | const tagName = `${name}-v${version}`;
8 |
9 | const gitCommands = [
10 | 'git add package.json CHANGELOG.md',
11 | `git commit --message "Release ${tagName}"`,
12 | `git tag ${tagName}`,
13 | ];
14 | const shellCommand = gitCommands.join(' && ');
15 |
16 | // NOTE: The normal npm-version script creates a git tag that's in the form of
17 | // "v1.0.0" which is a problem because we have 3 NPM packages in this one git
18 | // repository. So instead, we'll disable `npm version` from creating that default
19 | // tag and this script will run to do it. This script prepends the package name
20 | // so that when looking at the tags in the repo they will be differentiated.
21 | // It also commits the package.json.
22 |
23 | console.log(`Committing ${tagName} of package.json & adding tag`);
24 |
25 | execSync(shellCommand, (error, stdout, stderr) => {
26 | if (error) {
27 | console.error(`execSync error: ${error}`);
28 | return;
29 | }
30 |
31 | console.error(stdout);
32 | console.error(stderr);
33 | });
34 |
--------------------------------------------------------------------------------
/packages/eslint-config-eventbrite/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: ['eventbrite-legacy'].concat([
3 | './rules/best-practices',
4 | './rules/errors',
5 | './rules/es6',
6 | './rules/esnext',
7 | './rules/jest',
8 | './rules/strict',
9 | './rules/style',
10 | ].map(require.resolve)),
11 | };
12 |
--------------------------------------------------------------------------------
/packages/eslint-config-eventbrite/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "eslint-config-eventbrite",
3 | "version": "5.0.1",
4 | "description": "Eventbrites's base ESLint config that lints ES6+/ES2015+ and adheres to the Eventbrite JavaScript Coding Style Guide",
5 | "main": "index.js",
6 | "scripts": {
7 | "git-tag-version": "node ./_scripts/git-tag-version.js",
8 | "lint": "eslint rules",
9 | "test": "yarn run lint",
10 | "preversion": "yarn test",
11 | "version": "yarn run git-tag-version",
12 | "postversion": "git push && git push --tags",
13 | "version:patch": "npm version patch --no-git-tag-version",
14 | "version:minor": "npm version minor --no-git-tag-version",
15 | "version:major": "npm version major --no-git-tag-version"
16 | },
17 | "repository": {
18 | "type": "git",
19 | "url": "git+https://github.com/eventbrite/javascript.git"
20 | },
21 | "keywords": [
22 | "eslint",
23 | "eslintconfig",
24 | "config",
25 | "eventbrite",
26 | "javascript",
27 | "styleguide"
28 | ],
29 | "author": "Eventbrite ",
30 | "license": "MIT",
31 | "bugs": {
32 | "url": "https://github.com/eventbrite/javascript/issues"
33 | },
34 | "homepage": "https://github.com/eventbrite/javascript#readme",
35 | "dependencies": {
36 | "eslint-config-eventbrite-legacy": "^4.0.0"
37 | },
38 | "devDependencies": {
39 | "babel-eslint": "^8.2.3",
40 | "eslint": "^4.19.1",
41 | "eslint-plugin-babel": "^5.1.0",
42 | "eslint-plugin-import": "^2.2.0",
43 | "eslint-plugin-jest": "^21.15.1"
44 | },
45 | "peerDependencies": {
46 | "babel-eslint": "^8.2.3",
47 | "eslint": "^4.19.1",
48 | "eslint-plugin-babel": "^5.1.0",
49 | "eslint-plugin-import": "^2.11.0",
50 | "eslint-plugin-jest": "^21.15.1"
51 | },
52 | "engines": {
53 | "node": ">=6"
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/packages/eslint-config-eventbrite/rules/best-practices.js:
--------------------------------------------------------------------------------
1 | // The rules ultimately override any rules defined in legacy/rules/best-practices.js
2 | module.exports = {
3 | rules: {
4 | // disallow modifying properties of parameters
5 | // http://eslint.org/docs/rules/no-param-reassign
6 | 'no-param-reassign': ['error', {props: true}],
7 | },
8 | };
9 |
--------------------------------------------------------------------------------
/packages/eslint-config-eventbrite/rules/errors.js:
--------------------------------------------------------------------------------
1 | // The rules ultimately override any rules defined in legacy/rules/errors.js
2 | module.exports = {
3 | rules: {
4 | },
5 | };
6 |
--------------------------------------------------------------------------------
/packages/eslint-config-eventbrite/rules/es6.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | env: {
3 | es6: true,
4 | },
5 | parserOptions: {
6 | ecmaVersion: 6,
7 | sourceType: 'module',
8 | ecmaFeatures: {
9 | },
10 | },
11 | plugins: ['import'],
12 | extends: ['plugin:import/errors'],
13 | rules: {
14 | // require parentheses around arrow function parameters
15 | // http://eslint.org/docs/rules/arrow-parens
16 | 'arrow-parens': 'error',
17 |
18 | // require spacing before & after arrow function's arrow
19 | // http://eslint.org/docs/rules/arrow-spacing
20 | 'arrow-spacing': 'error',
21 |
22 | // enforce a default export is present, given a default import
23 | // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/default.md
24 | 'import/default': 'error',
25 |
26 | // disallow any invalid exports, i.e. re-export of the same name
27 | // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/export.md
28 | 'import/export': 'error',
29 |
30 | // enforce all imports appear before other statements
31 | // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/first.md
32 | 'import/first': 'error',
33 |
34 | // enforce a newline after import statements
35 | // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/newline-after-import.md
36 | 'import/newline-after-import': 'error',
37 |
38 | // enforce named imports correspond to a named export in the remote file
39 | // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/named.md
40 | 'import/named': 'error',
41 |
42 | // enforce imported namespaces contain dereferenced properties as they are dereferenced
43 | // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/namespace.md
44 | 'import/namespace': 'error',
45 |
46 | // disallow import of modules using absolute paths
47 | // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-absolute-path.md
48 | 'import/no-absolute-path': 'error',
49 |
50 | // disallow AMD `require` and `define` calls
51 | // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-amd.md
52 | 'import/no-amd': 'error',
53 |
54 | // disallow CommonJS require calls and `module.exports` or `exports.*`
55 | // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-commonjs.md
56 | 'import/no-commonjs': 'error',
57 |
58 | // disallow a module from importing a module with a dependency path back to itself
59 | // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-cycle.md
60 | 'import/no-cycle': 'error',
61 |
62 | // disallow require() calls with expressions
63 | // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-dynamic-require.md
64 | 'import/no-dynamic-require': 'error',
65 |
66 | // disallow repeated import of the same module in multiple places
67 | // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-duplicates.md
68 | 'import/no-duplicates': 'error',
69 |
70 | // disallow the use of extraneous packages
71 | // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-extraneous-dependencies.md
72 | 'import/no-extraneous-dependencies': 'error',
73 |
74 | // disallow the use of mutable exports with var or let
75 | // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-mutable-exports.md
76 | 'import/no-mutable-exports': 'error',
77 |
78 | // prevent use of exported name as identifier of default export
79 | // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-named-as-default.md
80 | 'import/no-named-as-default': 'error',
81 |
82 | // prevent use of exported name as property of default export
83 | // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-named-as-default-member.md
84 | 'import/no-named-as-default-member': 'error',
85 |
86 | // prevent named default exports
87 | // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-named-default.md
88 | 'import/no-named-default': 'error',
89 |
90 | // enforce imports point to a file/module that can be resolved
91 | // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-unresolved.md
92 | 'import/no-unresolved': 'error',
93 |
94 | // disallow a module from importing itself
95 | // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-self-import.md
96 | 'import/no-self-import': 'error',
97 |
98 | // prevent unnecessary path segments in import and require statements
99 | // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-useless-path-segments.md
100 | 'import/no-useless-path-segments': 'error',
101 |
102 | // disallow Webpack loader syntax in imports
103 | // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-webpack-loader-syntax.md
104 | 'import/no-webpack-loader-syntax': 'error',
105 |
106 | // disallow potentially ambiguous parse goal (script vs. module)
107 | // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/unambiguous.md
108 | 'import/unambiguous': 'error',
109 |
110 | // allow arrow functions where they could be confused with comparisons
111 | // http://eslint.org/docs/rules/no-confusing-arrow
112 | 'no-confusing-arrow': 'off',
113 |
114 | // disallow unnecessary computed property keys in object literals
115 | // http://eslint.org/docs/rules/no-useless-computed-key
116 | 'no-useless-computed-key': 'error',
117 |
118 | // disallow unnecessary (empty) constructors
119 | // http://eslint.org/docs/rules/no-useless-constructor
120 | 'no-useless-constructor': 'error',
121 |
122 | // disallow renaming export and destructured assignments to the same name
123 | // http://eslint.org/docs/rules/no-useless-rename
124 | 'no-useless-rename': 'error',
125 |
126 | // require use of let & const
127 | // http://eslint.org/docs/rules/no-var
128 | 'no-var': 'error',
129 |
130 | // require method & property shorthand for object literals
131 | // http://eslint.org/docs/rules/object-shorthand
132 | 'object-shorthand': 'error',
133 |
134 | // arrow functions should be used as callbacks
135 | // http://eslint.org/docs/rules/prefer-arrow-callback
136 | 'prefer-arrow-callback': 'error',
137 |
138 | // require const declarations for variables that are never reassigned after declared
139 | // http://eslint.org/docs/rules/prefer-const
140 | 'prefer-const': 'error',
141 |
142 | // use the rest operator instead of arguments
143 | // http://eslint.org/docs/rules/prefer-rest-params
144 | 'prefer-rest-params': 'error',
145 |
146 | // use the spread operator instead of apply
147 | // http://eslint.org/docs/rules/prefer-spread
148 | 'prefer-spread': 'error',
149 |
150 | // use template literals instead of string concatentation
151 | // http://eslint.org/docs/rules/prefer-template
152 | 'prefer-template': 'error',
153 |
154 | // prevents spacing between rest/spread operator (...)
155 | // and the expression
156 | // http://eslint.org/docs/rules/rest-spread-spacing
157 | 'rest-spread-spacing': 'error',
158 |
159 | // do not enforce any sorting of imports
160 | // http://eslint.org/docs/rules/sort-imports
161 | 'sort-imports': 'off',
162 |
163 | // require or disallow spacing around embedded expressions of template strings
164 | // http://eslint.org/docs/rules/template-curly-spacing
165 | 'template-curly-spacing': 'error',
166 |
167 | // disallow spacing in between `yield` & `*` and enforce a space after `*`
168 | // http://eslint.org/docs/rules/yield-star-spacing
169 | 'yield-star-spacing': 'error',
170 | },
171 | };
172 |
--------------------------------------------------------------------------------
/packages/eslint-config-eventbrite/rules/esnext.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | parser: 'babel-eslint',
3 | parserOptions: {
4 | ecmaVersion: 7,
5 | sourceType: 'module',
6 | ecmaFeatures: {
7 | },
8 | },
9 | plugins: ['babel'],
10 |
11 | rules: {
12 | // disallow await inside of loops
13 | // https://eslint.org/docs/rules/no-await-in-loop
14 | 'no-await-in-loop': 'error',
15 |
16 | // allow `this` keywords outside of classes or class-like objects
17 | // so that `babel/no-invalid-this` can disallow (see below)
18 | // http://eslint.org/docs/rules/no-invalid-this
19 | 'no-invalid-this': 'off',
20 |
21 | // disallow `this` keywords outside of classes or class-like objects
22 | // (but allow inside class properties)
23 | // https://github.com/babel/eslint-plugin-babel/
24 | 'babel/no-invalid-this': 'error',
25 |
26 | // disallows unnecessary return await
27 | // https://eslint.org/docs/rules/no-return-await
28 | 'no-return-await': 'error',
29 | },
30 | };
31 |
--------------------------------------------------------------------------------
/packages/eslint-config-eventbrite/rules/jest.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | env: {
3 | jest: true,
4 | 'jest/globals': true,
5 | },
6 | plugins: ['jest'],
7 | extends: ['plugin:jest/recommended'],
8 | rules: {
9 | // Enforces top level test to use `test` and all tests nested within `describe` to use `it`
10 | // https://github.com/jest-community/eslint-plugin-jest/blob/master/docs/rules/consistent-test-it.md
11 | 'jest/consistent-test-it': 'error',
12 |
13 | // Enforce lowercase test names
14 | // https://github.com/jest-community/eslint-plugin-jest/blob/master/docs/rules/lowercase-name.md
15 | 'jest/lowercase-name': 'error',
16 |
17 | // Enforce using `toBeNull()`
18 | // https://github.com/jest-community/eslint-plugin-jest/blob/master/docs/rules/prefer-to-be-null.md
19 | 'jest/prefer-to-be-null': 'error',
20 |
21 | // Enforce using `toBeUndefined()`
22 | // https://github.com/jest-community/eslint-plugin-jest/blob/master/docs/rules/prefer-to-be-undefined.md
23 | 'jest/prefer-to-be-undefined': 'error',
24 |
25 | // Enforce valid `describe()` callback
26 | // https://github.com/jest-community/eslint-plugin-jest/blob/master/docs/rules/valid-describe.md
27 | 'jest/valid-describe': 'error',
28 |
29 | // Enforce having return statement when testing with promises
30 | // https://github.com/jest-community/eslint-plugin-jest/blob/master/docs/rules/valid-expect-in-promise.md
31 | 'jest/valid-expect-in-promise': 'error',
32 | },
33 | };
34 |
--------------------------------------------------------------------------------
/packages/eslint-config-eventbrite/rules/strict.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | rules: {
3 | // require strict mode directives
4 | // https://eslint.org/docs/rules/strict
5 | 'strict': 'error',
6 | },
7 | };
8 |
--------------------------------------------------------------------------------
/packages/eslint-config-eventbrite/rules/style.js:
--------------------------------------------------------------------------------
1 | // The rules ultimately override any rules defined in legacy/rules/style.js
2 | module.exports = {
3 | rules: {
4 | // enforce dangling commas at the end of arrays, objects, imports & exports
5 | // http://eslint.org/docs/rules/comma-dangle
6 | 'comma-dangle': ['error', {
7 | 'arrays': 'always-multiline',
8 | 'objects': 'always-multiline',
9 | 'imports': 'always-multiline',
10 | 'exports': 'always-multiline',
11 | 'functions': 'ignore',
12 | }],
13 |
14 | // Enforce function expressions
15 | // http://eslint.org/docs/rules/func-style
16 | 'func-style': ['error', 'expression'],
17 |
18 | // enforce that `let` & `const` declarations are declared together
19 | // http://eslint.org/docs/rules/one-var
20 | 'one-var': ['error', 'never'],
21 |
22 | // enforce spacing around infix operators
23 | // http://eslint.org/docs/rules/space-infix-ops
24 | 'space-infix-ops': 'error',
25 |
26 | // enforce a space after async
27 | // const foo = async () => {}
28 | 'space-before-function-paren': ['error', {
29 | 'asyncArrow': 'always',
30 | }],
31 | },
32 | };
33 |
--------------------------------------------------------------------------------
/react/testing.md:
--------------------------------------------------------------------------------
1 | # Eventbrite React Testing Best Practices
2 |
3 | Guidelines and best practices used by Eventbrite to provide consistency and prevent errors in testing React components. This does not cover testing utility/helper functions (including Redux reducers) that are used in conjunction with React as those follow general testing guidelines.
4 |
5 | ## Table of Contents
6 |
7 | 0. [Testing environment](#testing-environment)
8 | 0. [Testing philosophy](#testing-philosophy)
9 | 0. [Writing a test case](#writing-a-test-case)
10 | 0. [Finding nodes](#finding-nodes)
11 | 0. [Finding components](#finding-components)
12 | 0. [Testing existence](#testing-existence)
13 | 0. [Assertion helpers](#assertion-helpers)
14 | 0. [Types of renderers](#types-of-renderers)
15 | 0. [Testing render](#testing-render)
16 | 0. [Testing events](#testing-events)
17 | 0. [Testing state](#testing-state)
18 | 0. [Testing updated props](#testing-updated-props)
19 |
20 | ## Testing environment
21 |
22 | Eventbrite uses [Jest](http://facebook.github.io/jest/) and [`enzyme`](http://airbnb.io/enzyme/) for unit testing React components. We also leverage [`jest-enzyme`](https://github.com/blainekasten/enzyme-matchers) assertion helpers. Enzyme wraps [`ReactTestUtils`](https://facebook.github.io/react/docs/test-utils.html), which contains a bunch of primitives for testing components.
23 |
24 | Don't use `ReactTestUtils` directly; use Enzyme!
25 |
26 | **[⬆ back to top](#table-of-contents)**
27 |
28 | ## Testing philosophy
29 |
30 | Unit testing React components can be a little tricky compared to testing the input/output of traditional JavaScript functions. But it's still doable! Just like with "normal" unit testing, we want to test all of the logic within the component via its public interface. The public _input_ to a component is its props. The public _output_ of a component is the combination of the elements it specifically renders (see [Testing render](#testing-render)) as well as the callback handlers it invokes (see [Testing events](#testing-events)). The goal is to render components with various configurations of their props, so that we can assert that what is rendered and what callbacks are invoked is as expected.
31 |
32 | **[⬆ back to top](#table-of-contents)**
33 |
34 | ## Writing a test case
35 |
36 | Use [arrow functions](https://www.eventbrite.com/engineering/learning-es6-arrow-functions/) to force functional test cases:
37 |
38 | ```js
39 | it('does what it is supposed to do', () => {
40 |
41 | });
42 | ```
43 |
44 | Using arrow functions prevents being able to use `beforeEach` & `afterEach` because `this` is now lexically scoped. In the past, data common to each test case was stored on `this` in `beforeEach` (and cleaned up in `afterEach`) so that each individual test case didn't have to generate the data itself. However, `beforeEach` devolved into a dumping ground for _anything_ that _may_ get used by more than one test case. As such way more data was generated than was needed, unnecessarily slowing down test execution.
45 |
46 | Instead, factor out helper data generation functions and call them as needed in the test cases:
47 |
48 | ```js
49 | const generateComponent = (additionalProps={}) => (
50 |
51 | );
52 |
53 | it('does what it is supposed to do', () => {
54 | let wrapper = mount(generateComponent());
55 | });
56 | ```
57 |
58 | **[⬆ back to top](#table-of-contents)**
59 |
60 | ## Finding nodes
61 |
62 | Search for nodes within a component by adding `data-spec` attributes to them. In the past, Eventbrite used special `js-*` CSS classes for references to nodes in JavaScript code. These `js-*` classes were used when testing as well. Now with React testing, instead of using special CSS classes, [refs](https://github.com/eventbrite/javascript/tree/master/react#refs), or attempting to traverse the DOM with Enzyme's [`find`](http://airbnb.io/enzyme/docs/api/ReactWrapper/find.html) helper, we use `data-spec` attributes.
63 |
64 | The `data-spec` attribute is specific to testing and not tied to presentation like CSS classes would be. If we decide to rename or remove a CSS class, the tests should not be impacted because there is no implicit link between styles and tests. We leverage a helper, `getSpecWrapper`, to find nodes with the `data-spec` attribute. Suppose we had the following (simplified) generated markup for a `Notification` component:
65 |
66 | ```html
67 |
68 |
X
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( );
197 | let selectOptionWrappers = wrapper.find(SelectOption);
198 |
199 | // assert that there are no found nodes
200 | expect(selectOptionWrappers).toBeEmpty();
201 |
202 | // assert that there are more than zero found nodes
203 | expect(selectOptionWrappers).toBePresent();
204 |
205 | // assert there to be a specific number of found nodes
206 | expect(selectOptionWrappers).toHaveLength(dummyValues.length);
207 | ```
208 |
209 | **[⬆ back to top](#table-of-contents)**
210 |
211 | ## Assertion helpers
212 |
213 | Whenever possible, use `jest-enzyme` assertion helpers in favor of the normal assertion helpers that just come with `jest`:
214 |
215 | ```js
216 | // good (leverages `.prop` from `jest-enzyme`)
217 | it('should render a checked checkbox if it is selected', () => {
218 | let wrapper = mount( );
219 | let checkboxWrapper = wrapper.find(Checkbox);
220 |
221 | expect(checkboxWrapper).toHaveProp('isChecked', true);
222 | });
223 |
224 | // bad (just uses `enzyme` with vanilla `jest`)
225 | it('should render a checked checkbox if it is selected', () => {
226 | let wrapper = mount( );
227 | let checkboxWrapper = wrapper.find(Checkbox);
228 |
229 | expect(checkboxWrapper.prop('isChecked')).toBe(true);
230 | });
231 | ```
232 |
233 | Functionally the "good" and "bad" assertions are the same. The assertions will both pass when the `isChecked` prop is `true` and both fail when it's `false`. The difference is in the reported error when they fail.
234 |
235 | When the "good" assertion (using `jest-enzyme`'s [`.toHaveProp`](https://github.com/blainekasten/enzyme-matchers#tobeempty) helper) fails, you'll receive an error such as:
236 |
237 | ```console
238 | AssertionError: expected the node in
to have a 'isChecked' prop with the value true, but the value was false
239 | ```
240 |
241 | However, when the "bad" assertion fails, you'll receive a more cryptic (and less helpful) error such as:
242 |
243 | ```console
244 | AssertionError: expected false to equal true
245 | ```
246 |
247 | The "good" example has significantly more context and should be significantly more helpful when looking through failed test logs.
248 |
249 | **[⬆ back to top](#table-of-contents)**
250 |
251 | ## Types of renderers
252 |
253 | Enzyme provides three types of renderers for testing React components:
254 |
255 | - [`mount`](http://airbnb.io/enzyme/docs/api/mount.html) - for components that may interact with DOM APIs, or may require the full lifecycle in order to fully test the component (i.e., `componentDidMount` etc.)
256 | - [`shallow`](http://airbnb.io/enzyme/docs/api/shallow.html) - performant renderer because it renders only single level of children (no descendants of those children) in order to ensure that tests aren't indirectly asserting on behavior of child components
257 | - [`render`](http://airbnb.io/enzyme/docs/api/render.html) - renders the components to traversable static HTML markup
258 |
259 | Eventbrite uses `mount` for rendering **all** components when testing.
260 |
261 | For components that just render markup ([atoms](http://bradfrost.com/blog/post/atomic-web-design/#atoms) in atomic web design), rendering with `mount` makes the most sense because they are the most likely to access the DOM API. Shallow rendering (via `shallow`) would be of little to no use.
262 |
263 | For components that are a mix of markup and small components ([molecules](http://bradfrost.com/blog/post/atomic-web-design/#molecules) in atomic web design), rendering with `mount` also makes the most sense because of all the markup that still exists. It's simpler to stay consistent without the test file and use `mount` for all tests.
264 |
265 | For components that are basically a composite of other components ([organisms](http://bradfrost.com/blog/post/atomic-web-design/#organisms) in atomic web design), we would ideally render with `shallow` because you're basically just testing that that the child components are receiving the correct props. Furthermore, it's faster to just render one level than render the entire markup tree, especially when the component is big. But in practice we make heavy use of [helper components](README.md#helper-components) in order to keep `render()` lean. As a result, what ends up being shallow rendered is not the actual child component, but an intermediary helper component. This means that if you wrote a test using `shallow` and then refactored the code to use helper components, your tests will break when the resultant render is actually still the same. Because of this nuance of when and where `shallow` can work, we've chosen to opt for `mount` because it always works. The trade-off is performance, which for now hasn't been a big enough issue.
266 |
267 | **[⬆ back to top](#table-of-contents)**
268 |
269 | ## Testing render
270 |
271 | When testing what a React component is rendering, _only test what the component itself renders_. Therefore if a parent component renders child components, such as a `TextFormField` component rendering `Label` & `TextInput` components, only test that the parent renders those child components and passes the appropriate props to them. **Do not** test the markup rendered by the child components because the tests for that child component will cover that.
272 |
273 | ```js
274 | // good
275 | it('displays label when `labelText` is specified', () => {
276 | let wrapper = mount( );
277 | let labelWrapper = wrapper.find(Label);
278 |
279 | // assert that when `labelText` is specified
280 | // the Label component is rendered
281 | expect(labelWrapper).toBePresent();
282 |
283 | // assuming that `labelText` gets passed like:
284 | // {labelText}
285 | // asserts that it's properly passed
286 | expect(labelWrapper).toHaveProp('children', 'Name');
287 | });
288 |
289 | // bad (assumes the markup the child component is rendering)
290 | it('displays label when `labelText` is specified', () => {
291 | let wrapper = mount( );
292 |
293 | expect(labelWrapper).toBePresent();
294 | expect(labelWrapper).toHaveText('Name');
295 | });
296 | ```
297 |
298 | The "bad" example assumes that the `Label` component is rendering a `` tag, but the `TextFormField` component shouldn't really know or care what `Label` renders. It treats `Label` as a black box in its implementation so the test should do the same. Imagine if the `Label` component changed to render a `` instead of a `
`. All of the tests for components using a `Label` component would now unnecessarily fail. On the other hand, the "good" example tests that the `TextFormField` properly renders the `` component and that the `labelText` prop is passed as its content (the `children` prop).
299 |
300 | The easiest way to test HTML elements and their attributes, is to use [Jest snapshots](http://facebook.github.io/jest/docs/snapshot-testing.html):
301 |
302 | ```js
303 | // good
304 | it('includes the disabled CSS class when `isDisabled` is `true`', () => {
305 | let wrapper = mount( );
306 |
307 | // assert that the current render matches the saved snapshot
308 | expect(wrapper).toMatchSnapshot();
309 | });
310 | ```
311 |
312 | While snapshot testing is very simple, that simplicity comes at a cost. The initial snapshot file is generated the first time the test is run, so you need to _visually_ inspect that the generated snapshot is correct, otherwise you could be saving a bad test case. Furthermore, the snapshot does not convey the intent of the test so you need to have a very verbose/descriptive test case title (the `it()`).
313 |
314 | Also because we use [`mount`](#types-of-renderers) for rendering, the **entire** component tree is in the snapshot, including any helper components, higher-order components, etc. The larger the component, the larger a snapshot will be. For _atoms_, you can use snapshots liberally because atoms are exclusively markup and are small. _Organisms_ are generally large components composed of several molecules and other smaller organisms; the component itself has very little markup making the snapshots bloated not very meaningful. As such, you should use snapshot testing sparingly and instead test that child components are rendered and get the appropriate props. _Molecules_ are somewhere in between and you should use your best judgment as to when to use snapshot testing.
315 |
316 | Lastly, since snapshot files are saved to disk, running the tests are slower than traditional means of unit testing.
317 |
318 | **[⬆ back to top](#table-of-contents)**
319 |
320 | ## Testing events
321 |
322 | As mentioned in our [Testing philosophy](#testing-philosophy), part of the output of your component are the callback handlers it invokes. These event callbacks are functions passed as props to your component and need to be tested.
323 |
324 | Test event callbacks by triggering the events that in turn will invoke the callback handler. The type of event triggered depends on whether the component contains HTML markup or child components.
325 |
326 | ### Testing events triggered by DOM
327 |
328 | If you are testing an event callback that is triggered by a DOM event (such as `onClick` of an `` node), you will need to simulate that DOM event. You will also need to stub the event callback prop to assert that it is being called with the correct arguments.
329 |
330 | Let's say that there is a `TextInput` component that wraps an ` ` 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 |
--------------------------------------------------------------------------------