├── .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 |
--------------------------------------------------------------------------------