1# Expo JavaScript Style Guide 2 3This guide explains style guidelines for writing JavaScript for Expo. It prioritizes readability for the team and also is meant to simplify small decisions when writing code. Most of this guide applies widely across the Expo repository but sometimes writing JavaScript differs between React, the web, and Node. 4 5# Modern JavaScript 6 7We generally use modern JavaScript on Expo, which means stable versions of the ES20xx specification with a few extensions, like JSX. We stay near the leading edge but away from the bleeding edge. 8 9# ESLint and Prettier 10 11ESLint reports errors and warnings for several style guidelines. Generally, the Expo ESLint configuration will report an error when it detects something that will prevent the code from working and a warning when it detects a style or formatting nit. The Expo configuration is written leniently and you should almost never have to use `/* eslint-disable */` comments. If you find yourself wanting to disable it, tell @ide so we can adjust the ESLint configuration to always be on. 12 13ESLint also uses Prettier, a code formatter, to check code formatting and to reformat code automatically; with Expo’s configuration, running ESLint runs Prettier too. 14 15ESLint has a `--fix` flag that tells it to fix errors and warnings when it can. Not all errors and warnings are automatically fixable but several are, including those reported by Prettier. 16 17## Editor Integration 18 19Many popular editors have ESLint plugins. Since the Expo ESLint configuration uses Prettier, if you configure your editor to use ESLint, it will use Prettier as well. These are some popular plugins: 20 21- **VS Code:** https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint 22- **Atom and Nuclide:** https://atom.io/packages/linter-eslint 23- **Sublime Text:** https://github.com/roadhump/SublimeLinter-eslint 24- **Emacs:** Flycheck with javascript-eslint 25 - Configure it to use the nearest available copy of ESLint by searching up `node_modules`: https://github.com/codesuki/add-node-modules-path 26- **Vim:** Syntastic: https://github.com/vim-syntastic/syntastic/blob/master/syntax_checkers/javascript/eslint.vim 27 - Configure it to use the nearest available copy of ESLint by searching up `node_modules` 28 29# Formatting 30 31## Prettier 32 33We use Prettier with Expo-specific settings for most of our code formatting. These settings are in `.prettierrc` in the Expo repository. Most small decisions about how to format code disappear with Prettier so we think less about formatting when writing and reviewing code. 34 35Sometimes Prettier makes code hard enough to read that we don’t want Prettier to format it. Add a `// prettier-ignore` comment above the expression whose formatting you want to preserve and let Prettier format the rest of the file. 36 37```js 38// prettier-ignore 39let matrix = [ 40 -c, 1, 1, 41 1, -c, 1, 42 1, 1, -c, 43]; 44``` 45 46If you would like Prettier to ignore the entire file rather than only a portion of it, add the file path to the `.prettierignore` file in the Expo repository. 47 48Since Prettier formats entire files (except ignored lines), we need to keep our files “pretty” so that the next person who runs Prettier on a file reformats only the lines they’re changing in their commit. We’ll talk more about Prettier later in this document. 49 50## Comments 51 52Use `// line` comments in most places. Use `/** block */` comments above classes, methods, and other structures and use `/* inline block */` comments in the middle of lines: 53 54```js 55// CORRECT 56/** 57 * Gets the latest version of Android that's been released. This is a version 58 * string like 7.1 instead of the code name Nougat. 59 */ 60function getLatestAndroidVersion() { 61 // Keep this logic in sync with Google's versioning scheme 62 return maxBy( 63 getAndroidVersions(/* includePrereleases */ false), 64 linearizeSemver 65 ); 66} 67``` 68 69Remove commented-out code before pushing it to GitHub. 70 71## Imports 72 73(Note: we don’t programmatically sort nor check the order of imports since there currently isn’t a linter plugin for these choices. This section is meant to be read as light guidance and not for code reviewers to spend much attention on.) 74 75Group and sort `import` statements and `require()` calls in this order: 76 77 781. `import` statements before `require()` calls 79 1. JavaScript hoists `import` statements; write the code to reflect that 802. Unassigned imports (`import 'side-effect'`) before assigned imports (`import React from 'react'`) 81 1. Unassigned imports almost always have side effects, which we usually want to apply earlier in the program’s lifetime. 823. External modules and Node.js built-in modules (`path`, `react`) before aliased internal modules (`www/module`) before relative modules (`../a/b`, `./c`) 83 84```js 85// CORRECT 86import 'side-effect'; 87 88import invariant from 'invariant'; 89import Expo, { Audio } from 'expo'; 90import path from 'path'; 91 92import HomeScreen from '../screens/HomeScreen'; 93import Colors from '../style/Colors'; 94import calculateViewport from '../style/calculateViewport'; 95import LoginButton './LoginButton'; 96 97const assert = require('assert'); 98``` 99 100Within each group, sort the statements by the names of the imported modules, not their assigned variables. Use ASCII order: uppercase before lowercase before scoped modules. 101 102```js 103// CORRECT 104import Z from 'Z'; 105import b from 'x'; 106import a from 'y'; 107import p from '@scope/package'; 108``` 109 110Write default imports before namespace imports before named imports: 111 112```js 113// CORRECT 114import a, * as b, { c } from 'module'; 115`` 116 117## React and JSX 118 119When writing React components, place your declarations and static methods near the top, followed by the constructor and lifecycle methods, followed by the render method and methods it calls, and other methods. 120 121Use Prettier to format JSX. 122 123```jsx 124// CORRECT 125type Props = { 126 title: string, 127 onPress?: event => void, 128}; 129 130type State = { 131 isPressed: boolean, 132}; 133 134class Button extends React.Component { 135 props: Props; 136 state: State = { 137 isPressed: true, 138 }; 139 140 constructor(props, context) { 141 super(props, context); 142 this.state = { 143 ...this.state, 144 bounce: new Animated.Value(1), 145 }; 146 } 147 148 componentWillUnmount() { 149 if (this.state.animation) { 150 this.state.animation.stop(); 151 } 152 } 153 154 render() { 155 return ( 156 <Animated.View 157 onPress={this._handlePress} 158 style={{ transform: [{ scale: this.state.bounce }] }}> 159 <Text> 160 {this.props.title} 161 </Text> 162 </Animated.View> 163 ); 164 } 165 166 _handlePress = event => { 167 this._bounce(); 168 if (this.props.onPress) { 169 this.props.onPress(event); 170 } 171 }; 172 173 _bounce() { 174 this.setState(state => { 175 state.bounce.setValue(0); 176 let animation = Animated.spring(state.bounce, { toValue: 1 }); 177 animation.start(({ finished }) => { 178 if (finished) { 179 this.setState(() => ({ animation: null })); 180 } 181 }); 182 return { animation }; 183 }); 184 } 185} 186``` 187 188# Naming 189 190Prioritize the reader when naming things. Choosing a greppable name tends to have a lot of benefits since it’s easier to find how the thing with the name is used, easier to rename and refactor, and is less context-sensitive. 191 192```js 193class TestPipeline { 194 // PREFERRED 195 runTests() { ... } 196 197 // DISFAVORED 198 run() { ... } 199} 200 201// "runTests" is a lot easier to grep for than "run". It also plainly communicates 202// more about what it does without being too wordy. 203``` 204 205## Classes, functions, and variables 206 207Use camel case for all names. Capitalize the names of classes and constructor functions. Start other names with lowercase. 208 209```js 210// CORRECT 211class Aquarium { 212 filterWater() {...} 213} 214 215function Fish() {...} 216Object.assign(Fish.prototype, ...); 217 218function populateAquarium(aquarium, school) {...} 219``` 220```js 221// INCORRECT 222class house { 223 CloseWindows() {...} 224} 225 226function EstimatePrice(house) {...} 227``` 228 229## Async functions 230 231Name async functions and other functions that return promises with “Async” at the end if they may complete asynchronously. This communicates that the function does work (often I/O) asynchronously and we need to await its result. 232 233```js 234// CORRECT 235async function fetchAccountAsync(accountId: ID): Promise<Account> { ... } 236``` 237 238It doesn’t matter how the function creates a promise for its asynchronous work. If the function isn’t defined with the `async` keyword but still looks like an async function from its call site, use the same naming convention. 239 240```js 241// CORRECT 242function readSettingsFileAsync(): Promise<string> { 243 return Promise((resolve, reject) => { 244 fs.readFile('settings.txt', 'utf8', ...); 245 }); 246} 247``` 248 249However, if a function does synchronous work but still returns a promise, it might make sense to omit the “Async” suffix. 250 251```jsx 252// OK 253function multiplexPromises(promises: Promise<*>[]): Promise<Array<* | Error>> { 254 // Given an array of promises, returns a promise that resolves to an array of 255 // promise results or errors. Semantically, this function doesn't do asynchronous 256 // work itself and the reader sees it operates on promises that do the actual work. 257} 258``` 259 260## Private variables 261 262Use an underscore to prefix instance variables that are intended to be private. This strikes a nice balance between communicating that the variable stores private data while keeping it accessible in a simple way for debugging, tests, and (sparingly) patches. 263 264```js 265// CORRECT 266class Counter { 267 _currentNumber = 0; 268 getNextNumber() { ... } 269} 270``` 271 272If it helps, use this same convention on variables that are internal to a module to make it clearer to readers which variables are defined and used only within the current module. 273 274```js 275// CORRECT 276export default function prettyPrintAll(values) { 277 for (let value of values) { 278 _prettyPrint(value); 279 } 280} 281 282function _prettyPrint(value) { ... } 283``` 284 285## Boolean names 286 287If it helps, consider naming Boolean variables with “is” or a similar verb at the beginning. Sometimes the names of Boolean variables can ambiguously describe an object (or program state) or reference an object, and using verbs like “is”, “was”, and “did” help communicate the variable’s purpose. 288 289```js 290// AMBIGUOUS 291console.log(history.deleted); 292 293// CLEAR 294console.log(history.isDeleted); 295console.log(history.deletedEntries); 296``` 297 298# Examples 299```js 300import Expo from 'expo'; 301import PropTypes from 'prop-types'; 302import React from 'react'; 303import { StyleSheet, Text } from 'react-native'; 304 305import Log from '../log/Log'; 306import Colors from '../style/Colors'; 307 308export default class GreetingText extends React.PureComponent { 309 static propTypes = { 310 greeting: PropTypes.string.isRequired, 311 ...Text.propTypes, 312 }; 313 314 componentDidUpdate() { 315 Log.info('The greeting was re-rendered'); 316 } 317 318 render() { 319 let { greeting, style, ...props } = this.props; 320 return ( 321 <Text 322 {...props} 323 onPress={this._handlePress} 324 style={[styles.greeting, style]}> 325 {greeting} 326 </Text> 327 ); 328 } 329 330 _handlePress = (event) => { 331 alert('Congratulations!'); 332 }; 333} 334 335const styles = StyleSheet.create({ 336 greeting: { 337 color: Colors.energetic, 338 fontSize: 30, 339 }, 340}); 341``` 342 343# Babel 344 345We use Babel to enable some of the newer JavaScript features that are sufficiently stable for us. This mostly includes transforms for features that are in a finalized version of the JavaScript standard. 346 347We use `babel-eslint`, which allows ESLint to use the Babel parser. In practice, with newer syntax extensions, Babel produces AST nodes that ESLint can’t consume; stable linter compatibility is another feature we look for in Babel plugins. 348 349