├── .codeclimate.yml ├── javascript ├── eslintrc.es6.js ├── eslintrc.example.js ├── eslintrc.base.js └── README.md ├── ruby ├── rails_rubocop.yml ├── reek.yml ├── rubocop.yml └── README.md ├── html └── README.md ├── sass ├── .scss-lint.yml └── README.md ├── README.md ├── shell └── README.md └── haskell └── README.md /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | engines: 2 | markdownlint: 3 | enabled: true 4 | checks: 5 | MD024: # Allow multiple headings of the same name 6 | enabled: false 7 | ratings: 8 | paths: 9 | - README.md 10 | -------------------------------------------------------------------------------- /javascript/eslintrc.es6.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "rules": { 3 | "consistent-this": [2, "prefer-fat-arrow-over-reassigning-this"], 4 | "no-const-assign": 2, 5 | "no-var": 2, 6 | "prefer-const": 2, 7 | "prefer-spread": 2, 8 | "prefer-template": 2 9 | } 10 | }; 11 | -------------------------------------------------------------------------------- /ruby/rails_rubocop.yml: -------------------------------------------------------------------------------- 1 | inherit_from: base_rubocop.yml 2 | 3 | require: 4 | - rubocop-performance 5 | - rubocop-rails 6 | 7 | Rails: 8 | Enabled: true 9 | 10 | # Disabling ActiveRecord-related cops 11 | Rails/DynamicFindBy: 12 | Enabled: false 13 | Rails/FindBy: 14 | Enabled: false 15 | 16 | Rails/Present: 17 | Enabled: false 18 | -------------------------------------------------------------------------------- /html/README.md: -------------------------------------------------------------------------------- 1 | # HTML 2 | 3 | - Always use quotes when specifying attribute values. 4 | 5 | ```html 6 | 7 | 8 | 9 | 10 | 11 | 12 | ``` 13 | 14 | Reasoning: since quotes are required in some situations, quote consistently 15 | to minimize thought points + diffs. 16 | -------------------------------------------------------------------------------- /sass/.scss-lint.yml: -------------------------------------------------------------------------------- 1 | linters: 2 | ChainedClasses: 3 | enabled: false 4 | PropertySortOrder: 5 | # we sort properties alphabetically, which is not supported 6 | enabled: false 7 | SelectorFormat: 8 | # http://csswizardry.com/2013/01/mindbemding-getting-your-head-round-bem-syntax/ 9 | convention: hyphenated_BEM 10 | StringQuotes: 11 | style: double_quotes 12 | -------------------------------------------------------------------------------- /javascript/eslintrc.example.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "node": true, // When in a backend context 4 | "browser": true, // When in a web context 5 | "jquery": true, // When in a web context 6 | "es6": true, // When using ES6 features 7 | }, 8 | // It's recommended these files be pulled in via `codeclimate prepare` 9 | extends: [ 10 | ".eslintrc.base.js", 11 | ".eslintrc.es6.js" // only for ES6 projects 12 | ], 13 | /** 14 | * globals should be defined per file when possible. Use the directive here 15 | * when there are project-level globals (such as jquery) 16 | */ 17 | "globals": {}, 18 | }; 19 | -------------------------------------------------------------------------------- /javascript/eslintrc.base.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "rules": { 3 | "brace-style": [2, "1tbs", { "allowSingleLine": true }], 4 | "camelcase": [2, { "properties": "always" }], 5 | "comma-style": [2, "first", { exceptions: {ArrayExpression: true, ObjectExpression: true} }], 6 | "complexity": [2, 6], 7 | "curly": 2, 8 | "eqeqeq": [2, "allow-null"], 9 | "no-shadow-restricted-names": 2, 10 | "no-ternary": 2, 11 | "no-undef": 2, 12 | "no-unused-vars": [2, { "argsIgnorePattern": "^_" }], 13 | "no-use-before-define": 2, 14 | "quotes": [2, "double", "avoid-escape"], 15 | "radix": 2, 16 | "semi": 2, 17 | "space-infix-ops": 2, 18 | "strict": 0, 19 | }, 20 | }; 21 | -------------------------------------------------------------------------------- /ruby/reek.yml: -------------------------------------------------------------------------------- 1 | --- 2 | detectors: 3 | Attribute: 4 | enabled: false 5 | BooleanParameter: 6 | enabled: false 7 | ControlParameter: 8 | enabled: false 9 | DuplicateMethodCall: 10 | enabled: false 11 | FeatureEnvy: 12 | enabled: false 13 | InstanceVariableAssumption: 14 | enabled: false 15 | IrresponsibleModule: 16 | enabled: false 17 | LongParameterList: 18 | enabled: false 19 | NestedIterators: 20 | enabled: false 21 | MissingSafeMethod: 22 | enabled: false 23 | NilCheck: 24 | enabled: false 25 | TooManyConstants: 26 | enabled: false 27 | TooManyInstanceVariables: 28 | enabled: false 29 | TooManyMethods: 30 | enabled: false 31 | TooManyStatements: 32 | enabled: false 33 | UncommunicativeModuleName: 34 | enabled: false 35 | UncommunicativeVariableName: 36 | enabled: false 37 | UtilityFunction: 38 | enabled: false 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Code Climate Style Guides 2 | 3 | A consistent style is important in a code base. Inconsistency greatly increases 4 | the number of inconsequential decisions developers must make. Consistency is 5 | good for readability and a clean code base promotes keeping a code base clean 6 | (i.e. Broken Window Theory). It is more important, and ultimately will make you 7 | happier, to abide by a defined style and end up in a consistent code base, than 8 | to code in your own preferred style and not. 9 | 10 | Any PR comments regarding violations of the guidelines here should be addressed 11 | immediately and without discussion. If you disagree with a guideline, propose a 12 | change in a PR to this repo. Once the team has settled on a guideline, it must 13 | be followed. 14 | 15 | Existing code may be fixed for style if, and only if, it must be touched in the 16 | course of your current work. Do not change code *only* to fix style. 17 | 18 | ## Terminology 19 | 20 | - **Do**, **don't**, etc: these must be followed always 21 | - **Avoid**/**prefer**: if not following these, the burden is on you to convince 22 | your reviewer why 23 | 24 | ## Guides 25 | 26 | This repository includes written guides & relevant linter configuration for the 27 | following languages: 28 | 29 | - [Haskell](haskell/README.md) 30 | - [HTML](html/README.md) 31 | - [JavaScript](javascript/README.md) 32 | - [Ruby](ruby/README.md) 33 | - [Sass](sass/README.md) 34 | -------------------------------------------------------------------------------- /sass/README.md: -------------------------------------------------------------------------------- 1 | # [Sass](http://sass-lang.com) 2 | 3 | Prefer the `.scss` syntax over the `.sass` syntax. 4 | 5 | ## scss-lint 6 | 7 | Every project should start with the `.scss-lint.yml` present here. It hews 8 | fairly closely to the default configuration with a few exceptions. The default 9 | configuration is explained on the project's [linters page][0]. 10 | 11 | [0]: https://github.com/brigade/scss-lint/blob/master/lib/scss_lint/linter/README.md 12 | 13 | Notable exceptions: 14 | 15 | * use the [BEM][1] convention for selectors 16 | * prefer double quotes 17 | 18 | [1]: http://csswizardry.com/2013/01/mindbemding-getting-your-head-round-bem-syntax/ 19 | 20 | ## Whitespace 21 | 22 | Use space between neighboring nested blocks, but not before or after. 23 | 24 | ### Good 25 | 26 | ```scss 27 | .btn { 28 | display: inline-block; 29 | font-size: 15px; 30 | 31 | &:hover { 32 | box-shadow: 0 5px 7px $box-shadow-color; 33 | } 34 | 35 | &:active { 36 | box-shadow: 0 1px 3px $box-shadow-color; 37 | color: $green-dark; 38 | } 39 | } 40 | ``` 41 | 42 | ### Bad 43 | 44 | ```scss 45 | .btn { 46 | 47 | display: inline-block; 48 | font-size: 15px; 49 | 50 | &:hover { 51 | box-shadow: 0 5px 7px $box-shadow-color; 52 | } 53 | 54 | &:active { 55 | box-shadow: 0 1px 3px $box-shadow-color; 56 | color: $green-dark; 57 | } 58 | 59 | } 60 | ``` 61 | 62 | ### Bad 63 | 64 | ```scss 65 | .btn { 66 | display: inline-block; 67 | font-size: 15px; 68 | &:hover { 69 | box-shadow: 0 5px 7px $box-shadow-color; 70 | } 71 | &:active { 72 | box-shadow: 0 1px 3px $box-shadow-color; 73 | color: $green-dark; 74 | } 75 | } 76 | ``` 77 | -------------------------------------------------------------------------------- /javascript/README.md: -------------------------------------------------------------------------------- 1 | # JavaScript 2 | 3 | ## ESLint 4 | 5 | This repo contains an `eslintrc.base.js` file specifying rules useful for all 6 | projects, as well as an `eslintrc.es6.js` with rules specific to ES6 projects. 7 | 8 | We suggest creating an `.eslintrc.js` in your own project based on the 9 | `eslintrc.example.js` file here, using `codeclimate prepare` to pull in the base 10 | rules (and the ES6 rules if appropriate). 11 | 12 | ### Variable and Function Names 13 | 14 | Names of variables and functions should be CamelCased. This may not be 15 | practical to enforce automatically with ESLint on all projects, since code 16 | interacting with APIs will frequently need to snake\_case object properties for 17 | API queries and such. In those cases, only use snake\_case when interacting 18 | directly with the API, either querying or handling a response. If an API 19 | response is being transformed into a model-like object, key names should change 20 | to CamelCase at that boundary. E.g.: 21 | 22 | ``` 23 | api.getModel("id").then(function(data) { 24 | var model = new Model(); 25 | model.keyName = data.key_name; 26 | return model; 27 | }); 28 | ``` 29 | 30 | ### JQuery 31 | 32 | - Prefer `return false` over `event.preventDefault()` when you don't need the 33 | event to bubble up. 34 | 35 | ```javascript 36 | // Bad 37 | $(".js-item").click(function(event) { 38 | event.preventDefault(); 39 | $(this).hide(); 40 | }); 41 | 42 | // Good 43 | $(".js-item").click(function() { 44 | $(this).hide(); 45 | return false; 46 | }); 47 | ``` 48 | 49 | ### ECMAScript 6 50 | 51 | There is an additional set of ESLint rules we use for ES6 code: these are in 52 | `eslintrc.es6.js`, and can be included into your `.eslintrc.js` with an 53 | `"extends"` directive. 54 | 55 | The rules there are mostly self explanatory, with one perhaps requiring an 56 | explanatory note: `=>` functions are preferred over the `var self = this;` 57 | pattern, but not in all cases, as there are good reasons *not* to use `=>` 58 | sometimes. The `consistent-this` rule makes it feasible to flag these 59 | non-preferred usages without causing false positives on other cases, though 60 | this isn't the strictly intended purpose of the rule. This does not mean that 61 | `=>` should not be used in any other cases, only that whether to use it or not 62 | in other cases is a judgment call. 63 | -------------------------------------------------------------------------------- /shell/README.md: -------------------------------------------------------------------------------- 1 | # Shell 2 | 3 | ## ShellCheck 4 | 5 | [ShellCheck][] can catch many bug risks in shell scripts: we encourage running 6 | it against all scripts. There is also a Code Climate engine available wrapping 7 | ShellCheck: we recommend enabling this engine on repos that contain significant 8 | shell scripts. 9 | 10 | [ShellCheck]: https://github.com/koalaman/shellcheck 11 | 12 | ## Compatability 13 | 14 | `/bin/sh` should be preferred to `/bin/bash` for compatability unless Bash-only 15 | features are really needed. Be aware that many modern systems actually alias 16 | `/bin/bash` to `/bin/sh`, so if you use Bash-isms accidentally you may not 17 | realize it. If you want to be sure your shell is `sh`-compliant, you may want to 18 | install the [`dash`] shell & use that to test your scripts. 19 | 20 | By the same token, using POSIX flags & functionality that will work between most 21 | flavors of \*nix is preferred when possible. 22 | 23 | [dash]: https://en.wikipedia.org/wiki/Almquist_shell#dash:_Ubuntu.2C_Debian_and_POSIX_compliance_of_Linux_distributions 24 | 25 | ## Error reporting 26 | 27 | All scripts should call `set -e` to ensure the script will exit immediately if 28 | any intermediate command errors. 29 | 30 | ## Variable and function names 31 | 32 | Names of variables and functions should be snake\_cased. Variable names should 33 | not be UPPER\_CASED unless being exported to the `ENV`. 34 | 35 | ## Exit codes 36 | 37 | The general principle, of course, is that `0` indicates success and `1` 38 | indicates a general error. More complex scripts may want to define specific 39 | codes for different kinds of errors: please refer to the [Bash Scripting 40 | Guide][bsg_exitcodes] & [`sysexits`][man_sysexits] for reserved/defined codes. 41 | One common one we use in many scripts is `64` to indicate incorrect arguments 42 | passed to a script. 43 | 44 | [bsg_exitcodes]: http://www.tldp.org/LDP/abs/html/exitcodes.html 45 | [man_sysexits]: http://www.gsp.com/cgi-bin/man.cgi?topic=sysexits 46 | 47 | ## Calling binaries 48 | 49 | Long-form flags (e.g. `ls --all` instead of `ls -a`) are preferred: code is read 50 | more often than it's written. Long flags help keep scripts easy to understand & 51 | maintain, and written scripts don't have the same need for brevity that typing 52 | at an interactive terminal does. 53 | 54 | ## String interpolation for output 55 | 56 | Interpolating variables within strings passed to `echo` can have unexpected 57 | results: `printf` is preferred. 58 | 59 | ```shell 60 | # Good 61 | 62 | printf "Hello %s\n" "$name" 63 | 64 | # Bad 65 | 66 | echo "Hello $name" 67 | 68 | # This is fine: no variable involved 69 | 70 | echo "Hello world." 71 | ``` 72 | 73 | ## Resources 74 | 75 | * [Rich's sh Tricks](http://www.etalabs.net/sh_tricks.html) 76 | * [IEEE Std 1003.1-2001](http://pubs.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html#tag_02_05_03) 77 | * [Advanced Bash Scripting Guide](http://www.tldp.org/LDP/abs/html/index.html): 78 | a very in-depth guide to a wide range of shell scripting needs, though be 79 | aware that it is Bash-focused & doesn't clearly say what's Bash-specific and 80 | what's not. I wish there were a guide as good as this for POSIX `sh`. 81 | -------------------------------------------------------------------------------- /ruby/rubocop.yml: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | # Layout 3 | ################################################################################ 4 | 5 | Layout/ParameterAlignment: 6 | Enabled: false 7 | 8 | Layout/EmptyLinesAroundClassBody: 9 | Enabled: false 10 | 11 | Layout/MultilineMethodCallIndentation: 12 | EnforcedStyle: indented 13 | 14 | ################################################################################ 15 | # Metrics 16 | ################################################################################ 17 | 18 | Layout/LineLength: 19 | Enabled: false 20 | 21 | Metrics/AbcSize: 22 | Enabled: false 23 | 24 | Metrics/ModuleLength: 25 | Enabled: false 26 | 27 | Metrics/ClassLength: 28 | Enabled: false 29 | 30 | Metrics/MethodLength: 31 | Enabled: false 32 | 33 | Metrics/BlockLength: 34 | Enabled: false 35 | 36 | Metrics/CyclomaticComplexity: 37 | Enabled: false 38 | 39 | Metrics/ParameterLists: 40 | Enabled: false 41 | 42 | Metrics/PerceivedComplexity: 43 | Enabled: false 44 | 45 | ################################################################################ 46 | # Style 47 | ############################################################################### 48 | 49 | Style/BlockDelimiters: 50 | EnforcedStyle: semantic 51 | Exclude: 52 | - spec/**/* 53 | 54 | # Executables are conventionally named bin/foo-bar 55 | Naming/FileName: 56 | Exclude: 57 | - bin/**/* 58 | 59 | # Naming format tokens is often less readable, especially with time values. 60 | Style/FormatStringToken: 61 | Enabled: false 62 | 63 | # We don't (currently) document our code 64 | Style/Documentation: 65 | Enabled: false 66 | 67 | Style/StringLiterals: 68 | Enabled: false 69 | 70 | Style/StringLiteralsInInterpolation: 71 | EnforcedStyle: double_quotes 72 | 73 | # Use a trailing comma to keep diffs clean when elements are inserted or removed 74 | Style/TrailingCommaInArguments: 75 | EnforcedStyleForMultiline: comma 76 | 77 | Style/TrailingCommaInArrayLiteral: 78 | EnforcedStyleForMultiline: comma 79 | 80 | Style/TrailingCommaInHashLiteral: 81 | EnforcedStyleForMultiline: comma 82 | 83 | Layout/TrailingWhitespace: 84 | Enabled: false 85 | 86 | # We avoid GuardClause because it can result in "suprise return" 87 | Style/GuardClause: 88 | Enabled: false 89 | 90 | # We avoid IfUnlessModifier because it can result in "suprise if" 91 | Style/IfUnlessModifier: 92 | Enabled: false 93 | 94 | # We don't care about the fail/raise distinction 95 | Style/SignalException: 96 | EnforcedStyle: only_raise 97 | 98 | Layout/DotPosition: 99 | EnforcedStyle: trailing 100 | 101 | # Common globals we allow 102 | Style/GlobalVars: 103 | AllowedVariables: 104 | - "$statsd" 105 | - "$mongo" 106 | - "$rollout" 107 | 108 | # Using english names requires loading an extra module, which is annoying, so 109 | # we prefer the perl names for consistency. 110 | Style/SpecialGlobalVars: 111 | EnforcedStyle: use_perl_names 112 | 113 | # We have common cases where has_ and have_ make sense 114 | Naming/PredicateName: 115 | Enabled: true 116 | ForbiddenPrefixes: 117 | - is_ 118 | 119 | # We use %w[ ], not %w( ) because the former looks like an array 120 | Style/PercentLiteralDelimiters: 121 | PreferredDelimiters: 122 | "%i": "[]" 123 | "%I": "[]" 124 | "%w": "[]" 125 | "%W": "[]" 126 | 127 | # Allow "trivial" accessors when defined as a predicate? method 128 | Style/TrivialAccessors: 129 | AllowPredicates: true 130 | 131 | Style/Next: 132 | Enabled: false 133 | 134 | # We think it's OK to use the "extend self" module pattern 135 | Style/ModuleFunction: 136 | Enabled: false 137 | 138 | Layout/ExtraSpacing: 139 | Enabled: false 140 | 141 | # and/or in conditionals has no meaningful difference (only gotchas), so we 142 | # disallow them there. When used for control flow, the difference in precedence 143 | # can make for a less noisy expression, as in: 144 | # 145 | # x = find_x or raise XNotFound 146 | # 147 | Style/AndOr: 148 | EnforcedStyle: conditionals 149 | 150 | Style/MultilineBlockChain: 151 | Enabled: false 152 | 153 | Layout/MultilineOperationIndentation: 154 | EnforcedStyle: indented 155 | 156 | Layout/HashAlignment: 157 | EnforcedLastArgumentHashStyle: ignore_implicit 158 | 159 | # This has the behavior we want, but it has a bug in it which produces a lot of false positives 160 | # https://github.com/bbatsov/rubocop/issues/3462 161 | # MultilineMethodCallBraceLayout: 162 | # EnforcedStyle: new_line 163 | 164 | # We prefer alias_method. This cop's documentation actually indicates that's 165 | # what it enforces, but it seems to behave exactly the opposite. 166 | Style/Alias: 167 | Enabled: false 168 | 169 | Style/StderrPuts: 170 | Enabled: false 171 | 172 | Style/MissingElse: 173 | Enabled: true 174 | EnforcedStyle: case 175 | 176 | ################################################################################ 177 | # Performance 178 | ################################################################################ 179 | 180 | Performance/RedundantMerge: 181 | Enabled: false 182 | 183 | ################################################################################ 184 | # Rails - disabled because we're primarily non-Rails 185 | ################################################################################ 186 | 187 | Rails: 188 | Enabled: false 189 | 190 | ################################################################################ 191 | # Specs - be more lenient on length checks and block styles 192 | ################################################################################ 193 | 194 | Style/ClassAndModuleChildren: 195 | Exclude: 196 | - spec/**/* 197 | -------------------------------------------------------------------------------- /haskell/README.md: -------------------------------------------------------------------------------- 1 | # Haskell 2 | 3 | ## Safety 4 | 5 | - Avoid partial functions (`head`, `read`, etc) 6 | 7 | ```hs 8 | -- Bad 9 | getEmail :: User -> IO Email 10 | getEmail user = do 11 | emails <- requestApi $ "/users/" <> userId user <> "/emails" 12 | 13 | return $ head emails 14 | 15 | -- Good 16 | import Data.Maybe (listToMaybe) 17 | 18 | getEmail :: User -> IO (Maybe Email) 19 | getEmail user = do 20 | emails <- requestApi $ "/users/" <> userId user <> "/emails" 21 | 22 | return $ listToMaybe emails 23 | 24 | -- Better 25 | getEmail :: User -> IO (Either String Email) 26 | getEmail user = do 27 | -- `try` replaces `IO` exceptions with an `Either` result 28 | emails <- try $ requestApi $ "/users/" <> userId user <> "/emails" 29 | 30 | return $ case emails of 31 | Right (e:_) -> Right e 32 | Right _ -> Left "User had no emails" 33 | Left ex -> Left $ "IO error get emails: " <> show ex 34 | ``` 35 | 36 | - Compile with `-Wall -Werror` 37 | - Use `newtype`s when multiple concepts share primitive types 38 | 39 | ```hs 40 | -- Bad 41 | fetchWebPage :: String -> IO String 42 | fetchWebPage url = -- returns HTML body 43 | 44 | main = do 45 | body <- fetchWebPage "http://example.com" 46 | nonSense <- fetchWebPage body 47 | -- ^ Bug found at runtime 48 | 49 | -- Good 50 | newtype URL = URL { unURL :: String } 51 | newtype HTML = HTML { unHTML :: String } 52 | 53 | fetchWebPage :: URL -> IO HTML 54 | fetchWebPage = -- ... 55 | 56 | main = do 57 | body <- fetchWebPage $ URL "http://example.com" 58 | nonSense <- fetchWebPage body 59 | -- ^ Type error at compile time 60 | ``` 61 | 62 | ## Project Structure 63 | 64 | - Use [stack][], with the latest LTS snapshot resolver 65 | - Use a structure like the *hspec* template: 66 | - Source files under `src/` 67 | - HSpec-based tests under `test/` 68 | - Executable defined at `app/Main.hs` 69 | - For web projects, prefer [Yesod][] 70 | - For options parsing, prefer [optparse-applicative][] 71 | - Build docker images in two layers ([example][popeye-commit]) 72 | 73 | [stack]: http://docs.haskellstack.org/en/stable/README.html 74 | [yesod]: http://yesodweb.com 75 | [optparse-applicative]: https://hackage.haskell.org/package/optparse-applicative 76 | [popeye-commit]: https://github.com/codeclimate/popeye/commit/dd0daf131877ad5340571d81edc8c7c9e9588a82 77 | 78 | ## Formatting 79 | 80 | - Use four-space indentation except the `where` keyword which is indented two 81 | spaces 82 | 83 | ```hs 84 | outputStream commandId start = do 85 | outputs <- runDB $ selectList 86 | [OutputCommand ==. commandId] 87 | [Asc OutputCreatedAt, OffsetBy start] 88 | 89 | stop <- commandRunning 90 | 91 | unless stop $ do 92 | mapM_ sendText outputs 93 | 94 | outputStream commandId (start + length outputs) 95 | 96 | where 97 | commandRunning = runDB $ exists 98 | [ CommandId ==. commandId 99 | , CommandRunning ==. True 100 | ] 101 | ``` 102 | 103 | - Break long type signatures before separators 104 | 105 | ```hs 106 | exists 107 | :: ( MonadIO m 108 | , PersistQuery (PersistEntityBackend v) 109 | , PersistEntity v 110 | ) 111 | => [Filter v] 112 | -> ReaderT (PersistEntityBackend v) m Bool 113 | exists = fmap (> 0) . count 114 | ``` 115 | 116 | **Reasoning**: in general, one should be able to rename an identifier without 117 | having to adjust indentation. Notice the following: 118 | 119 | ```hs 120 | -- Bad 121 | foo :: String 122 | -> Int 123 | -> Int 124 | foo = undefined 125 | 126 | -- If I rename foo to longFoo, I have to adjust all four lines 127 | longFoo :: String 128 | -> Int 129 | -> Int 130 | longFoo = undefined 131 | 132 | -- Good 133 | foo 134 | :: String 135 | -> Int 136 | -> Int 137 | foo = undefined 138 | 139 | -- If I rename foo to longFoo, I only have to adjust the unavoidable two 140 | longFoo 141 | :: String 142 | -> Int 143 | -> Int 144 | longFoo = undefined 145 | ``` 146 | 147 | *Comma-first* style is recommended below for the same reason. 148 | 149 | - Use only one pragma statement per line. 150 | 151 | ```hs 152 | -- Bad 153 | {-# LANGUAGE OverloadedStrings, RecordWildCards #-} 154 | 155 | -- Also bad 156 | {-# LANGUAGE OverloadedStrings 157 | , RecordWildCards #-} 158 | 159 | -- Good 160 | {-# LANGUAGE OverloadedStrings #-} 161 | {-# LANGUAGE RecordWildCards #-} 162 | ``` 163 | 164 | - Use comma-first style exports, records, and lists. 165 | 166 | ```hs 167 | -- exports 168 | module Main 169 | ( MyType(..) 170 | , (&&^) 171 | , (||^) 172 | , exportedFunc1 173 | , exportedFunc2 174 | ) where 175 | 176 | -- imports 177 | import Network.HTTP.Conduit 178 | ( HttpException(..) 179 | , RequestBody(..) 180 | , Manager 181 | , httpLbs 182 | , parseUrl 183 | , responseBody 184 | , tlsManagerSettings 185 | ) 186 | 187 | -- record data types 188 | data Person = Person 189 | { personName :: String 190 | , personAge :: Int 191 | } 192 | deriving Show 193 | 194 | -- sum types 195 | data HTTPStatus 196 | = OK 197 | | BadRequest 198 | | ServerError 199 | deriving Eq 200 | 201 | -- lists 202 | mappedThings f = map f 203 | [ aThing 204 | , anotherThing 205 | , lastThing 206 | ] 207 | ``` 208 | 209 | - Order export and import lists as types, operators, then functions and 210 | alphabetize each group (the *comma-first* example follows this) 211 | -------------------------------------------------------------------------------- /ruby/README.md: -------------------------------------------------------------------------------- 1 | # Ruby 2 | 3 | ## RuboCop 4 | 5 | Every project should start with the `rubocop.yml` present here. It should 6 | enforce the styles defined here. 7 | 8 | It should be pulled in via [external configuration]. 9 | 10 | ### Non-Rails ruby code bases 11 | 12 | ``` 13 | prepare: 14 | fetch: 15 | - url: "https://raw.githubusercontent.com/codeclimate/styleguide/master/ruby/rubocop.yml" 16 | path: "base_rubocop.yml" 17 | 18 | engines: 19 | rubocop: 20 | enabled: true 21 | ``` 22 | 23 | And the project should have a `.rubocop.yml` that looks like: 24 | 25 | ``` 26 | inherit_from: base_rubocop.yml 27 | 28 | # project-specific configuration... 29 | ``` 30 | 31 | If a change or addition comes up in the course of that project that is not 32 | project-specific, it should be made in the styleguide. 33 | 34 | ### Rails code bases 35 | 36 | If the project is a Rails app, it should pull in the Rails config file as well: 37 | 38 | ``` 39 | prepare: 40 | fetch: 41 | - url: "https://raw.githubusercontent.com/codeclimate/styleguide/master/ruby/rubocop.yml" 42 | path: "base_rubocop.yml" 43 | - url: "https://raw.githubusercontent.com/codeclimate/styleguide/master/ruby/rails_rubocop.yml" 44 | path: "rails_rubocop.yml" 45 | 46 | engines: 47 | rubocop: 48 | enabled: true 49 | ``` 50 | 51 | And the project should have a `.rubocop.yml` that looks like: 52 | 53 | ``` 54 | inherit_from: rails_rubocop.yml 55 | 56 | # project-specific configuration... 57 | ``` 58 | 59 | ## Reek 60 | 61 | Every project should start with the `reek.yml` present here. It should 62 | enforce the styles defined here. 63 | 64 | It should be pulled in via [external configuration]. 65 | 66 | ``` 67 | prepare: 68 | fetch: 69 | - url: "https://raw.githubusercontent.com/codeclimate/styleguide/master/ruby/reek.yml" 70 | path: ".reek.yml" 71 | 72 | engines: 73 | reek: 74 | enabled: true 75 | ``` 76 | 77 | Reek does not support config inheritance, so you will not add a 78 | project-specific file. 79 | 80 | Reek has strong opinions about object-orientation that many find 81 | overaggressive in some parts of codebases where OO design is less 82 | important. You may find these common exclusions to be helpful: 83 | 84 | ``` 85 | engines: 86 | reek: 87 | enabled: true 88 | exclude_paths: 89 | - spec/ 90 | - app/helpers 91 | - app/controllers 92 | ``` 93 | 94 | ## General 95 | 96 | - Align `private`, `protected`, etc with other definitions (do not out-dent) 97 | - Avoid explicit `return`s 98 | - Avoid postfix conditionals 99 | - Avoid ternary operators 100 | - Define class methods with `def self.method_name` 101 | - Do not use an inner class outside of the class that defines it 102 | - Define classes with the following structure (comments are for the clarity of 103 | the example and are not required): 104 | 105 | Example 106 | 107 | ```rb 108 | class User 109 | # Constants 110 | TIME_ALLOWED_INACTIVE = 10.minutes 111 | 112 | # Class method calls / DSL calls 113 | attr_reader :name, :address 114 | 115 | # Class method definitions 116 | def self.create(attrs) 117 | # ... 118 | end 119 | 120 | # Instance methods 121 | def send_email(email) 122 | # ... 123 | end 124 | 125 | # protected methods 126 | protected 127 | 128 | def protected_call_here 129 | end 130 | 131 | # private methods 132 | private 133 | 134 | def private_call_here 135 | end 136 | 137 | # Inner classes 138 | FakeUserError = Class.new(StandardError) 139 | 140 | class InnerClassMagic 141 | end 142 | end 143 | ``` 144 | 145 | - Don't align tokens 146 | 147 | ```rb 148 | # Bad, adding, removing, or changing values will be both annoying and produce 149 | # a noisy diff if it requires re-alignment 150 | foo = "foo" 151 | bar_thing = "thing" 152 | other = "other" 153 | 154 | { 155 | foo: "foo" 156 | bar_thing: "thing" 157 | other: "other" 158 | } 159 | 160 | # Good 161 | foo = "foo" 162 | bar_thing = "thing" 163 | other = "other" 164 | 165 | { 166 | foo: "foo" 167 | bar_thing: "thing" 168 | other: "other" 169 | } 170 | ``` 171 | 172 | - Don't use `self` unless required (`self.class` or attribute assignment) 173 | - Don't use redundant braces when passing hash arguments 174 | 175 | ```rb 176 | # Bad 177 | Model.insert({ attr: "value" }) 178 | 179 | # Good 180 | Model.insert(attr: "value") 181 | ``` 182 | 183 | - Prefer `Hash#fetch` when a key is required, or defaulted 184 | 185 | ```rb 186 | # Bad, returns "bar" even for { bar: nil } or { bar: false } 187 | foo[:bar] || "bar" 188 | 189 | # Good, returns "bar" only if :bar is not present 190 | foo.fetch(:bar) { "bar" } 191 | 192 | # Bad, may result in a NoMethodError for NilClass far, far away 193 | foo[:bar] 194 | 195 | # Good, will result in a KeyError right here 196 | foo.fetch(:bar) 197 | ``` 198 | 199 | - Sort all lists at the time of declaration (array items, hash keys, `require` 200 | statements, etc.) 201 | - Use `%r{ }` for regular expressions containing more than one `/` 202 | - Use `%w[ ]` for word-arrays 203 | - Use `%{ }` for strings containing more than one double quote 204 | - Use `do`/`end` for procedural blocks and `{ }` for functional blocks, i.e. `{ }` if it returns a value `do`/`end` otherwise 205 | - Use a trailing comma in all lists 206 | 207 | ```rb 208 | # Bad, adding a new entry requires a modification and addition 209 | [ 210 | foo, 211 | bar, 212 | baz 213 | ] 214 | 215 | # Good, adding a new entry requires only an addition 216 | [ 217 | foo, 218 | bar, 219 | baz, 220 | ] 221 | ``` 222 | 223 | - Use double quotes for all strings 224 | - Use parentheses when calling methods with arguments, with the following 225 | exceptions: `puts`, `p`, `raise`, and class macros 226 | - Use parentheses when defining methods that take arguments 227 | - Don't use `unless` with an `else` branch. Switch the conditionals. 228 | - Do, or do not. There is no `try`. 229 | - Define error classes with `Class.new` where no subclass behavior is required: 230 | 231 | ```rb 232 | TerribleMorningException = Class.new(StandardError) 233 | ``` 234 | 235 | - Don't access instance variables directly outside of `#initialize`, (i.e define 236 | private `attr_reader`s as needed) 237 | 238 | The upcase forums has a good discussion [here][forum]. Ultimately, we choose 239 | this for the following: 240 | 241 | - Typo protection (typoed `@ivar` is `nil`, typoed `#ivar` is 242 | `NoMethodError`). Such a typo has actually caused a production incident. 243 | - Forcing mechanism against adding mutation by requiring an explicit writer or 244 | accessor be added to accomplish it. 245 | 246 | [forum]: https://forum.upcase.com/t/using-instance-variables-vs-attribute-accessors/1788/12 247 | 248 | - Break long argument lists between every argument 249 | - Break long method chains after the dot 250 | 251 | ## Namespaces 252 | 253 | - Services (a unit of code that runs as a process) should use a 254 | `CC::{service_name}` namespace 255 | - Library code which is domain specific (e.g. a Code Climate API or reads Code 256 | Climate config files) should use a `CC::{library_name}` namespace. 257 | - Other library code (a gem or helper library) generally does not need to be 258 | namespaced (e.g. `GitClient` or `Minidoc`) 259 | 260 | ## Project structure 261 | 262 | - Include a `.ruby_version` 263 | - Include a `bin/setup` script 264 | - Mirror `lib` in `spec`: `lib/foo/bar.rb => spec/foo/bar_spec.rb` 265 | 266 | ## Specs 267 | 268 | **A note about the RSpec DSL**: we have rules below about avoiding some of 269 | RSpec's DSL methods like `let`, `subject`, etc. These DSL methods can be easily 270 | abused to result in [obfuscated and brittle tests][lets-not], hence the 271 | *Avoid*/*Prefer* rules. That said, they can be fine to use in many 272 | circumstances, therefore some clarification of the nuance involved is 273 | worthwhile. 274 | 275 | [lets-not]: https://robots.thoughtbot.com/lets-not 276 | 277 | The overall guiding principle behind these rules is the following: 278 | 279 | A developer should be able to view any `it` block in isolation --without its 280 | `context`, without its `before`, without any `let`s it uses-- and understand 281 | **exactly** what's going on. If you can accomplish this while using the RSpec 282 | DSL methods, it's probably fine. 283 | 284 | - Avoid `let`, and `subject` (prefer factory methods) 285 | - Omit parenthesis with `#to` and `#not_to` 286 | 287 | ```rb 288 | # Good 289 | expect(x).to eq(y) 290 | 291 | # Bad, interrupts the English "to equal" phrase 292 | expect(x).to(eq(y)) 293 | 294 | # Bad, doesn't always parse correctly 295 | expect(x).to eq y 296 | ``` 297 | 298 | - Place `describe` within the namespace(s) for inner classes 299 | - Prefer `expect` syntax when possible 300 | - Prefer predicate matchers when it reads well 301 | 302 | ```rb 303 | # Good 304 | expect(config).to be_valid_for_analysis 305 | 306 | # Bad 307 | expect(config.valid_for_analysis?).to be true 308 | 309 | # But sometimes required 310 | expect(presenter.show_the_thing?).to be true 311 | 312 | # Because this doesn't work 313 | expect(presenter).to be_show_the_thing 314 | ``` 315 | 316 | - Prefer spies to mocks when possible (mocks put assertion before action) 317 | - Test only one thing per example in unit specs 318 | - Use `match_array` when order doesn't matter 319 | - Use `not_to` (not `to_not`, which creates a split-infinitive) 320 | - Use a nested `describe` for each method (named as `"#foo"`, or `".foo"`) 321 | - Write 4-phase tests with whitespace separating each phase 322 | 323 | ## Line length 324 | 325 | - Prefer lines no longer than 80 characters 326 | 327 | ## Whitespace 328 | 329 | - No trailing whitespace 330 | - Use 2-space indentation 331 | 332 | ## Editor configuration 333 | 334 | ### vimrc 335 | 336 | ```vim 337 | set colorcolumn=+1 338 | set textwidth=80 339 | set expandtab 340 | set list listchars=tab:»·,trail:· 341 | set shiftwidth=2 342 | ``` 343 | 344 | ### sublime 345 | 346 | ``` 347 | "rulers": [ 80 ] 348 | ``` 349 | 350 | [external configuration]: https://docs.codeclimate.com/v1.0/docs/configuring-the-prepare-step 351 | --------------------------------------------------------------------------------