XUtils

Airbnb

Airbnb's Official Style Guide.


Airbnb Swift Style Guide

Goals

Following this style guide should:

  • Make it easier to read and begin understanding unfamiliar code.
  • Make code easier to maintain.
  • Reduce simple programmer errors.
  • Reduce cognitive load while coding.
  • Keep discussions on diffs focused on the code’s logic rather than its style.

Note that brevity is not a primary goal. Code should be made more concise only if other good code qualities (such as readability, simplicity, and clarity) remain equal or are improved.

Guiding Tenets

  • This guide is in addition to the official Swift API Design Guidelines. These rules should not contradict that document.
  • These rules should not fight Xcode’s ^ + I indentation behavior.
  • We strive to make every rule lintable:
    • If a rule changes the format of the code, it needs to be able to be reformatted automatically (either using SwiftFormat or SwiftLint autocorrect).
    • For rules that don’t directly change the format of the code, we should have a lint rule that throws a warning.
    • Exceptions to these rules should be rare and heavily justified.

When using the Xcode 13 toolchain, or a noninteractive shell, you must use:

$ swift package –allow-writing-to-package-directory format

To just lint without reformatting, you can use --lint:

$ swift package format –lint

By default the command plugin runs on the entire package directory.

You can exclude directories using exclude:

$ swift package format –exclude Tests

Alternatively you can explicitly list the set of paths and/or SPM targets:

\( swift package format --paths Sources Tests Package.swift \) swift package format –targets AirbnbSwiftFormatTool

The plugin infers your package’s minimum Swift version from the swift-tools-version

in your Package.swift, but you can provide a custom value with --swift-version:

$ swift package format –swift-version 5.3


The package plugin returns a non-zero exit code if there is a lint failure that requires attention.
 - In `--lint` mode, any lint failure from any tool will result in a non-zero exit code.
 - In standard autocorrect mode without `--lint`, only failures from SwiftLint lint-only rules will result in a non-zero exit code.

</details>

## Table of Contents

1. [Xcode Formatting](#xcode-formatting)
1. [Naming](#naming)
1. [Style](#style)
    1. [Functions](#functions)
    1. [Closures](#closures)
    1. [Operators](#operators)
1. [Patterns](#patterns)
1. [File Organization](#file-organization)
1. [Objective-C Interoperability](#objective-c-interoperability)
1. [Contributors](#contributors)
1. [Amendments](#amendments)

## Xcode Formatting

_You can enable the following settings in Xcode by running [this script](resources/xcode_settings.bash), e.g. as part of a "Run Script" build phase._

* <a id='column-width'></a>(<a href='#column-width'>link</a>) **Each line should have a maximum column width of 100 characters.** [![SwiftFormat: wrap](https://img.shields.io/badge/SwiftFormat-wrap-7B0051.svg)](https://github.com/nicklockwood/SwiftFormat/blob/master/Rules.md#wrap)

  <details>

  #### Why?
  Due to larger screen sizes, we have opted to choose a page guide greater than 80.

  We currently only "strictly enforce" (lint / auto-format) a maximum column width of 130 characters to limit the cases where manual clean up is required for reformatted lines that fall slightly above the threshold.

  </details>

* <a id='spaces-over-tabs'></a>(<a href='#spaces-over-tabs'>link</a>) **Use 2 spaces to indent lines.** [![SwiftFormat: indent](https://img.shields.io/badge/SwiftFormat-indent-7B0051.svg)](https://github.com/nicklockwood/SwiftFormat/blob/master/Rules.md#indent)

* <a id='trailing-whitespace'></a>(<a href='#trailing-whitespace'>link</a>) **Trim trailing whitespace in all lines.** [![SwiftFormat: trailingSpace](https://img.shields.io/badge/SwiftFormat-trailingSpace-7B0051.svg)](https://github.com/nicklockwood/SwiftFormat/blob/master/Rules.md#trailingSpace)

**[⬆ back to top](#table-of-contents)**

## Naming

* <a id='use-camel-case'></a>(<a href='#use-camel-case'>link</a>) **Use PascalCase for type and protocol names, and lowerCamelCase for everything else.**

  <details>

  ```swift
  protocol SpaceThing {
    // ...
  }

  class SpaceFleet: SpaceThing {

    enum Formation {
      // ...
    }

    class Spaceship {
      // ...
    }

    var ships: [Spaceship] = []
    static let worldName: String = "Earth"

    func addShip(_ ship: Spaceship) {
      // ...
    }
  }

  let myFleet = SpaceFleet()

Exception: You may prefix a private property with an underscore if it is backing an identically-named property or method with a higher access level.

#### Why? There are specific scenarios where a backing property or method that is prefixed with an underscore could be easier to read than using a more descriptive name.

  • Type erasure
  public final class AnyRequester<ModelType>: Requester {

    public init<T: Requester>(_ requester: T) where T.ModelType == ModelType {
      _executeRequest = requester.executeRequest
    }

    @discardableResult
    public func executeRequest(
      _ request: URLRequest,
      onSuccess: @escaping (ModelType, Bool) -> Void,
      onFailure: @escaping (Error) -> Void)
      -> URLSessionCancellable
    {
      return _executeRequest(request, onSuccess, onFailure)
    }

    private let _executeRequest: (
      URLRequest,
      @escaping (ModelType, Bool) -> Void,
      @escaping (Error) -> Void)
      -> URLSessionCancellable
  }
  • Backing a less specific type with a more specific type
  final class ExperiencesViewController: UIViewController {
    // We can't name this view since UIViewController has a view: UIView property.
    private lazy var _view = CustomView()

    loadView() {
      self.view = _view
    }
  }

  • (link) Name booleans like isSpaceship, hasSpacesuit, etc. This makes it clear that they are booleans and not other types.

  • (link) Acronyms in names (e.g. URL) should be all-caps except when it’s the start of a name that would otherwise be lowerCamelCase, in which case it should be uniformly lower-cased.

  // WRONG
  class UrlValidator {

    func isValidUrl(_ URL: URL) -> Bool {
      // ...
    }

    func isProfileUrl(_ URL: URL, for userId: String) -> Bool {
      // ...
    }
  }

  let URLValidator = UrlValidator()
  let isProfile = URLValidator.isProfileUrl(URLToTest, userId: IDOfUser)

  // RIGHT
  class URLValidator {

    func isValidURL(_ url: URL) -> Bool {
      // ...
    }

    func isProfileURL(_ url: URL, for userID: String) -> Bool {
      // ...
    }
  }

  let urlValidator = URLValidator()
  let isProfile = urlValidator.isProfileURL(urlToTest, userID: idOfUser)

  • (link) Names should be written with their most general part first and their most specific part last. The meaning of “most general” depends on context, but should roughly mean “that which most helps you narrow down your search for the item you’re looking for.” Most importantly, be consistent with how you order the parts of your name.

  // WRONG
  let rightTitleMargin: CGFloat
  let leftTitleMargin: CGFloat
  let bodyRightMargin: CGFloat
  let bodyLeftMargin: CGFloat

  // RIGHT
  let titleMarginRight: CGFloat
  let titleMarginLeft: CGFloat
  let bodyMarginRight: CGFloat
  let bodyMarginLeft: CGFloat

  • (link) Include a hint about type in a name if it would otherwise be ambiguous.

  // WRONG
  let title: String
  let cancel: UIButton

  // RIGHT
  let titleText: String
  let cancelButton: UIButton

  • (link) Event-handling functions should be named like past-tense sentences. The subject can be omitted if it’s not needed for clarity.

  // WRONG
  class ExperiencesViewController {

    private func handleBookButtonTap() {
      // ...
    }

    private func modelChanged() {
      // ...
    }
  }

  // RIGHT
  class ExperiencesViewController {

    private func didTapBookButton() {
      // ...
    }

    private func modelDidChange() {
      // ...
    }
  }

  • (link) Avoid Objective-C-style acronym prefixes. This is no longer needed to avoid naming conflicts in Swift.

  // WRONG
  class AIRAccount {
    // ...
  }

  // RIGHT
  class Account {
    // ...
  }

  • (link) Avoid *Controller in names of classes that aren’t view controllers.

#### Why? Controller is an overloaded suffix that doesn’t provide information about the responsibilities of the class.

⬆ back to top

Functions

  • (link) Omit Void return types from function definitions. SwiftFormat: redundantVoidReturnType

  // WRONG
  func doSomething() -> Void {
    ...
  }

  // RIGHT
  func doSomething() {
    ...
  }

  • (link) Separate long function declarations with line breaks before each argument label, and before the return signature or any effects (async, throws). Put the open curly brace on the next line so the first executable line doesn’t look like it’s another parameter. SwiftFormat: wrapArguments SwiftFormat: braces

  class Universe {

    // WRONG
    func generateStars(at location: Point, count: Int, color: StarColor, withAverageDistance averageDistance: Float) -> String {
      // This is too long and will probably auto-wrap in a weird way
    }

    // WRONG
    func generateStars(at location: Point,
                       count: Int,
                       color: StarColor,
                       withAverageDistance averageDistance: Float) -> String
    {
      // Xcode indents all the arguments
    }

    // WRONG
    func generateStars(
      at location: Point,
      count: Int,
      color: StarColor,
      withAverageDistance averageDistance: Float) -> String {
      populateUniverse() // this line blends in with the argument list
    }

    // WRONG
    func generateStars(
      at location: Point,
      count: Int,
      color: StarColor,
      withAverageDistance averageDistance: Float) throws
      -> String {
      populateUniverse() // this line blends in with the argument list
    }

    // WRONG
    func generateStars(
      at location: Point,
      count: Int,
      color: StarColor,
      withAverageDistance averageDistance: Float) async throws // these effects are easy to miss since they're visually associated with the last parameter
      -> String
    {
      populateUniverse()
    }

    // RIGHT
    func generateStars(
      at location: Point,
      count: Int,
      color: StarColor,
      withAverageDistance averageDistance: Float)
      -> String
    {
      populateUniverse()
    }

    // RIGHT
    func generateStars(
      at location: Point,
      count: Int,
      color: StarColor,
      withAverageDistance averageDistance: Float)
      async throws -> String
    {
      populateUniverse()
    }
  }

  • (link) Long function invocations should also break on each argument. Put the closing parenthesis on the last line of the invocation. SwiftFormat: wrapArguments

  // WRONG
  universe.generateStars(at: location, count: 5, color: starColor, withAverageDistance: 4)

  // WRONG
  universe.generateStars(at: location,
                         count: 5,
                         color: starColor,
                         withAverageDistance: 4)

  // WRONG
  universe.generateStars(
    at: location,
    count: 5,
    color: starColor,
    withAverageDistance: 4
  )

  // WRONG
  universe.generate(5,
    .stars,
    at: location)

  // RIGHT
  universe.generateStars(
    at: location,
    count: 5,
    color: starColor,
    withAverageDistance: 4)

  // RIGHT
  universe.generate(
    5,
    .stars,
    at: location)

  • (link) Name unused function parameters as underscores (_). SwiftFormat: unusedArguments

    #### Why? Naming unused function parameters as underscores makes it more clear when the parameter is unused within the function body. This can make it easier to catch subtle logical errors, and can highlight opportunities to simplify method signatures. ```swift // WRONG // In this method, the `newCondition` parameter is unused. // This is actually a logical error, and is easy to miss, but compiles without warning. func updateWeather(_ newCondition: WeatherCondition) -> Weather { var updatedWeather = self updatedWeather.condition = condition // this mistake inadvertently makes this method unable to change the weather condition return updatedWeather } // In this method, the `color` parameter is unused. // Is this a logical error (e.g. should it be passed through to the `universe.generateStars` method call), // or is this an unused argument that should be removed from the method signature? func generateUniverseWithStars( at location: Point, count: Int, color: StarColor, withAverageDistance averageDistance: Float) { let universe = generateUniverse() universe.generateStars( at: location, count: count, withAverageDistance: averageDistance) } ``` ```swift // RIGHT // Automatically reformatting the unused parameter to be an underscore // makes it more clear that the parameter is unused, which makes it // easier to spot the logical error. func updateWeather(_: WeatherCondition) -> Weather { var updatedWeather = self updatedWeather.condition = condition return updatedWeather } // The underscore makes it more clear that the `color` parameter is unused. // This method argument can either be removed if truly unnecessary, // or passed through to `universe.generateStars` to correct the logical error. func generateUniverseWithStars( at location: Point, count: Int, color _: StarColor, withAverageDistance averageDistance: Float) { let universe = generateUniverse() universe.generateStars( at: location, count: count, withAverageDistance: averageDistance) } ```
  • (link) Remove blank lines between chained functions. SwiftFormat: blanklinesbetweenchainedfunctions

#### Why?

Improves readability and maintainability, making it easier to see the sequence of functions that are applied to the object.

  // WRONG
  var innerPlanetNames: [String] {
    planets
      .filter { $0.isInnerPlanet }

      .map { $0.name }
  }

  // WRONG
  var innerPlanetNames: [String] {
    planets
      .filter { $0.isInnerPlanet }

      // Gets the name of the inner planet
      .map { $0.name }
  }

  // RIGHT
  var innerPlanetNames: [String] {
    planets
      .filter { $0.isInnerPlanet }
      .map { $0.name }
  }

  // RIGHT
  var innerPlanetNames: [String] {
    planets
      .filter { $0.isInnerPlanet }
      // Gets the name of the inner planet
      .map { $0.name }
  }

Closures

  • (link) Favor Void return types over () in closure declarations. If you must specify a Void return type in a function declaration, use Void rather than () to improve readability. SwiftLint: void_return

  // WRONG
  func method(completion: () -> ()) {
    ...
  }

  // RIGHT
  func method(completion: () -> Void) {
    ...
  }

  • (link) Name unused closure parameters as underscores (_). SwiftFormat: unusedArguments

    #### Why? Naming unused closure parameters as underscores reduces the cognitive overhead required to read closures by making it obvious which parameters are used and which are unused. ```swift // WRONG someAsyncThing() { argument1, argument2, argument3 in print(argument3) } // RIGHT someAsyncThing() { _, _, argument3 in print(argument3) } ```
  • (link) Closures should have a single space or newline inside each brace. Trailing closures should additionally have a single space or newline outside each brace. SwiftFormat: spaceInsideBraces SwiftFormat: spaceAroundBraces

  // WRONG
  let evenSquares = numbers.filter{$0.isMultiple(of: 2)}.map{  $0 * $0  }

  // RIGHT
  let evenSquares = numbers.filter { $0.isMultiple(of: 2) }.map { $0 * $0 }

  // WRONG
  let evenSquares = numbers.filter( { $0.isMultiple(of: 2) } ).map( { $0 * $0 } )

  // RIGHT
  let evenSquares = numbers.filter({ $0.isMultiple(of: 2) }).map({ $0 * $0 })

  // WRONG
  let evenSquares = numbers
    .filter{
      $0.isMultiple(of: 2)
    }
    .map{
      $0 * $0
    }

  // RIGHT
  let evenSquares = numbers
    .filter {
      $0.isMultiple(of: 2)
    }
    .map {
      $0 * $0
    }

  • (link) Omit Void return types from closure expressions. SwiftFormat: redundantVoidReturnType

  // WRONG
  someAsyncThing() { argument -> Void in
    ...
  }

  // RIGHT
  someAsyncThing() { argument in
    ...
  }

  • (link) Prefer trailing closure syntax for closure arguments with no parameter name. SwiftFormat: trailingClosures

  // WRONG
  planets.map({ $0.name })

  // RIGHT
  planets.map { $0.name }

  // ALSO RIGHT, since this closure has a parameter name
  planets.first(where: { $0.isGasGiant })

  // ALSO FINE. Trailing closure syntax is still permitted for closures
  // with parameter names. However, consider using non-trailing syntax
  // in cases where the parameter name is semantically meaningful.
  planets.first { $0.isGasGiant }

  • (link) Avoid using unowned captures. Instead prefer safer alternatives like weak captures, or capturing variables directly. SwiftLint: unowned_variable_capture

unowned captures are unsafe because they will cause the application to crash if the referenced object has been deallocated.

  // WRONG: Crashes if `self` has been deallocated when closures are called.
  final class SpaceshipNavigationService {
    let spaceship: Spaceship
    let planet: Planet
    
    func colonizePlanet() {
      spaceship.travel(to: planet, onArrival: { [unowned self] in
        planet.colonize()
      })
    }
    
    func exploreSystem() {
      spaceship.travel(to: planet, nextDestination: { [unowned self] in
        planet.moons?.first
      })
    }
  }

weak captures are safer because they require the author to explicitly handle the case where the referenced object no longer exists.

  // RIGHT: Uses a `weak self` capture and explicitly handles the case where `self` has been deallocated
  final class SpaceshipNavigationService {
    let spaceship: Spaceship
    let planet: Planet
    
    func colonizePlanet() {
      spaceship.travel(to: planet, onArrival: { [weak self] in
        guard let self else { return }
        planet.colonize()
      })
    }
    
    func exploreSystem() {
      spaceship.travel(to: planet, nextDestination: { [weak self] in
        guard let self else { return nil }
        return planet.moons?.first
      })
    }
  }

Alternatively, consider directly capturing the variables that are used in the closure. This lets you avoid having to handle the case where self is nil, since you don’t even need to reference self:

  // RIGHT: Explicitly captures `planet` instead of capturing `self`
  final class SpaceshipNavigationService {
    let spaceship: Spaceship
    let planet: Planet
    
    func colonizePlanet() {
      spaceship.travel(to: planet, onArrival: { [planet] in
        planet.colonize()
      })
    }
    
    func exploreSystem() {
      spaceship.travel(to: planet, nextDestination: { [planet] in
        planet.moons?.first
      })
    }
  }

File Organization

  • (link) Alphabetize and deduplicate module imports within a file. Place all imports at the top of the file below the header comments. Do not add additional line breaks between import statements. Add a single empty line before the first import and after the last import. SwiftFormat: sortedImports SwiftFormat: duplicateImports

#### Why?

  • A standard organization method helps engineers more quickly determine which modules a file depends on.
  • Duplicated import statements have no effect and should be removed for clarity.
  // WRONG

  //  Copyright © 2018 Airbnb. All rights reserved.
  //
  import DLSPrimitives
  import Constellation
  import Constellation
  import Epoxy

  import Foundation

  // RIGHT

  //  Copyright © 2018 Airbnb. All rights reserved.
  //

  import Constellation
  import DLSPrimitives
  import Epoxy
  import Foundation

Exception: @testable import should be grouped after the regular import and separated by an empty line.

  // WRONG

  //  Copyright © 2018 Airbnb. All rights reserved.
  //

  import DLSPrimitives
  @testable import Epoxy
  import Foundation
  import Nimble
  import Quick

  // RIGHT

  //  Copyright © 2018 Airbnb. All rights reserved.
  //

  import DLSPrimitives
  import Foundation
  import Nimble
  import Quick

  @testable import Epoxy

  • (link) Limit consecutive whitespace to one blank line or space (excluding indentation). Favor the following formatting guidelines over whitespace of varying heights or widths. SwiftLint: vertical_whitespace SwiftFormat: consecutiveSpaces

  // WRONG
  struct Planet {

    let mass:          Double
    let hasAtmosphere: Bool


    func distance(to: Planet) { }

  }

  // RIGHT
  struct Planet {

    let mass: Double
    let hasAtmosphere: Bool

    func distance(to: Planet) { }

  }

  • (link) Files should end in a newline. SwiftLint: trailing_newline

  • (link) Declarations that include scopes spanning multiple lines should be separated from adjacent declarations in the same scope by a newline. Insert a single blank line between multi-line scoped declarations (e.g. types, extensions, functions, computed properties, etc.) and other declarations at the same indentation level. SwiftFormat: blankLinesBetweenScopes

#### Why? Dividing scoped declarations from other declarations at the same scope visually separates them, making adjacent declarations easier to differentiate from the scoped declaration.

  // WRONG
  struct SolarSystem {
    var numberOfPlanets: Int {
      …
    }
    func distance(to: SolarSystem) -> AstronomicalUnit {
      …
    }
  }
  struct Galaxy {
    func distance(to: Galaxy) -> AstronomicalUnit {
      …
    }
    func contains(_ solarSystem: SolarSystem) -> Bool {
      …
    }
  }

  // RIGHT
  struct SolarSystem {
    var numberOfPlanets: Int {
      …
    }

    func distance(to: SolarSystem) -> AstronomicalUnit {
      …
    }
  }

  struct Galaxy {
    func distance(to: Galaxy) -> AstronomicalUnit {
      …
    }

    func contains(_ solarSystem: SolarSystem) -> Bool {
      …
    }
  }

  • (link) Remove blank lines at the top and bottom of scopes, excluding type bodies which can optionally include blank lines. SwiftFormat: blankLinesAtStartOfScope SwiftFormat: blankLinesAtEndOfScope

  // WRONG
  class Planet {
    func terraform() {

      generateAtmosphere()
      generateOceans()

    }
  }

  // RIGHT
  class Planet {
    func terraform() {
      generateAtmosphere()
      generateOceans()
    }
  }

  // Also fine!
  class Planet {

    func terraform() {
      generateAtmosphere()
      generateOceans()
    }

  }

  • (link) Each type and extension which implements a conformance should be preceded by a MARK comment. SwiftFormat: markTypes
    • Types should be preceded by a // MARK: - TypeName comment.
    • Extensions that add a conformance should be preceded by a // MARK: - TypeName + ProtocolName comment.
    • Extensions that immediately follow the type being extended should omit that type’s name and instead use // MARK: ProtocolName.
    • If there is only one type or extension in a file, the MARK comment can be omitted.
    • If the extension in question is empty (e.g. has no declarations in its body), the MARK comment can be omitted.
    • For extensions that do not add new conformances, consider adding a MARK with a descriptive comment.

  // MARK: - GalaxyView

  final class GalaxyView: UIView { … }

  // MARK: ContentConfigurableView

  extension GalaxyView: ContentConfigurableView { … }

  // MARK: - Galaxy + SpaceThing, NamedObject

  extension Galaxy: SpaceThing, NamedObject { … }

  • (link) Use // MARK: to separate the contents of type definitions and extensions into the sections listed below, in order. All type definitions and extensions should be divided up in this consistent way, allowing a reader of your code to easily jump to what they are interested in. SwiftFormat: organizeDeclarations

    • // MARK: Lifecycle for init and deinit methods.
    • // MARK: Open for open properties and methods.
    • // MARK: Public for public properties and methods.
    • // MARK: Package for package properties and methods.
    • // MARK: Internal for internal properties and methods.
    • // MARK: Fileprivate for fileprivate properties and methods.
    • // MARK: Private for private properties and methods.
    • If the type in question is an enum, its cases should go above the first // MARK:.
    • Do not subdivide each of these sections into subsections, as it makes the method dropdown more cluttered and therefore less useful. Instead, group methods by functionality and use smart naming to make clear which methods are related. If there are enough methods that sub-sections seem necessary, consider refactoring your code into multiple types.
    • If all of the type or extension’s definitions belong to the same category (e.g. the type or extension only consists of internal properties), it is OK to omit the // MARK:s.
    • If the type in question is a simple value type (e.g. fewer than 20 lines), it is OK to omit the // MARK:s, as it would hurt legibility.
  • (link) Within each top-level section, place content in the following order. This allows a new reader of your code to more easily find what they are looking for. SwiftFormat: organizeDeclarations

    • Nested types and type aliases
    • Static properties
    • Class properties
    • Instance properties
    • Static methods
    • Class methods
    • Instance methods
  • (link) Add empty lines between property declarations of different kinds. (e.g. between static properties and instance properties.) SwiftFormat: organizeDeclarations

  // WRONG
  static let gravityEarth: CGFloat = 9.8
  static let gravityMoon: CGFloat = 1.6
  var gravity: CGFloat

  // RIGHT
  static let gravityEarth: CGFloat = 9.8
  static let gravityMoon: CGFloat = 1.6

  var gravity: CGFloat

  • (link) Computed properties and properties with property observers should appear at the end of the set of declarations of the same kind. (e.g. instance properties.) SwiftFormat: organizeDeclarations

  // WRONG
  var atmosphere: Atmosphere {
    didSet {
      print("oh my god, the atmosphere changed")
    }
  }
  var gravity: CGFloat

  // RIGHT
  var gravity: CGFloat
  var atmosphere: Atmosphere {
    didSet {
      print("oh my god, the atmosphere changed")
    }
  }

⬆ back to top

Objective-C Interoperability

  • (link) Prefer pure Swift classes over subclasses of NSObject. If your code needs to be used by some Objective-C code, wrap it to expose the desired functionality. Use @objc on individual methods and variables as necessary rather than exposing all API on a class to Objective-C via @objcMembers.

  class PriceBreakdownViewController {

    private let acceptButton = UIButton()

    private func setUpAcceptButton() {
      acceptButton.addTarget(
        self,
        action: #selector(didTapAcceptButton),
        forControlEvents: .touchUpInside)
    }

    @objc
    private func didTapAcceptButton() {
      // ...
    }
  }

⬆ back to top

Contributors

⬆ back to top

Amendments

We encourage you to fork this guide and change the rules to fit your team’s style guide. Below, you may list some amendments to the style guide. This allows you to periodically update your style guide without having to deal with merge conflicts.

⬆ back to top


Articles

  • coming soon...