This guide presents a collection of best-practices and coding conventions for the CoffeeScript programming language.
This guide is intended to be community-driven, and contributions are highly encouraged.
Please note that this is a work-in-progress: there is much more that can be specified, and some of the guidelines that have been specified may not be deemed to be idiomatic by the community (in which case, these offending guidelines will be modified or removed, as appropriate).
The details in this guide have been very heavily inspired by several existing style guides and other resources. In particular:
- PEP-8: Style Guide for Python Code
- Bozhidar Batsov's Ruby Style Guide
- Google's JavaScript Style Guide
- Common CoffeeScript Idioms
- Thomas Reynolds' CoffeeScript-specific Style Guide
- Jeremy Ashkenas' code review of Spine
- The CoffeeScript FAQ
- The Weaver-Server Style Guide
- The CoffeeScript Style Guide
Use spaces only, with 2 spaces per indentation level. Never mix tabs and spaces.
Limit all lines to a maximum of 79 characters.
Separate top-level function and class definitions with a single blank line.
Separate method definitions inside of a class with a single blank line.
Use a single blank line within the bodies of methods or functions in cases where this improves readability (e.g., for the purpose of delineating logical sections).
Do not include trailing whitespace on any lines.
Include a newline at the end of every file.
Avoid the use of commas before newlines when properties or elements of an Object or Array are listed on separate lines.
# Yes
foo = [
'some'
'string'
'values'
]
bar:
label: 'test'
value: 87
# No
foo = [
'some',
'string',
'values'
]
bar:
label: 'test',
value: 87
When a line becomes too long, wrap it according to these rules:
callFunction(
argument1,
argument2,
argument3
) # Yes
callFunction(argument1,
argument2,
argument3) # No
callFunction(callNested(
argument1,
argument2
)) # Yes
callFunction(
callNested(
argument1,
argument2
)
) # Yes
# Indent an additional level for arguments lists of things that have a body so
# arguments and body are not confused. Also include the first element of the
# arguments on the same line as the opening paren, and the closing paren on
# the same line as the last argument
newFunction = (argument1,
argument2,
argument3) ->
argument1, + argument2 / argument3
console.log("This is
a long log statement"
) # Yes
console.log "This is
a long log statement as well
" # No, when wrapping long lines always use parentheses for function arguments
throw new Exception("this is too long") if somethingrather # Suppose this is too long
# Yes
if somethingrather
throw new Exception("this is no longer too long")
# No
throw new Exception(
"this is no longer too long"
) if somethingrather
UTF-8 is the preferred source file encoding.
If using a module system (CommonJS Modules, AMD, etc.), require
statements should be placed on separate lines.
require('lib/setup')
Backbone = require('backbone')
These statements should be grouped in the following order:
- Standard library imports (if a standard library exists)
- Third party library imports
- Local imports (imports specific to this application or library)
Avoid extraneous whitespace in the following situations:
-
Immediately inside parentheses, brackets or braces
callF('body') # Yes callF( 'body' ) # No
-
Immediately before a comma
console.log(x, y) # Yes console.log(x , y) # No
Additional recommendations:
-
Always surround these binary operators with a single space on either side
-
assignment:
=
-
Note that this also applies when indicating default parameter value(s) in a function declaration
test: (param = null) -> # Yes test: (param=null) -> # No
-
-
augmented assignment:
+=
,-=
, etc. -
comparisons:
==
,<
,>
,<=
,>=
,unless
, etc. -
arithmetic operators:
+
,-
,*
,/
, etc. -
(Do not use more than one space around these operators)
-
-
Align assingments when on the same level
# Yes x = 1 y = 1 fooBar = 3 # No x = 1 y = 1 fooBar = 3 # Also yes, but include a newline between the levels x = 1 xy = 2 other.a = b other.long = c
If modifying code that is described by an existing comment, update the comment such that it accurately reflects the new code. (Ideally, improve the code to obviate the need for the comment, and delete the comment entirely.)
The first word of the comment should be capitalized, unless the first word is an identifier that begins with a lower-case letter.
If a comment is short, the period at the end can be omitted.
Block comments apply to the block of code that follows them.
Each line of a block comment starts with a #
and a single space, and should be indented at the same level of the code that it describes.
Paragraphs inside of block comments are separated by a line containing a single #
.
# This is a block comment. Note that if this were a real block
# comment, we would actually be describing the proceeding code.
#
# This is the second paragraph of the same block comment. Note
# that this paragraph was separated from the previous paragraph
# by a line containing a single comment character.
init()
start()
stop()
Inline comments are placed on the line immediately above the statement that they are describing. If the inline comment is sufficiently short, it can be placed on the same line as the statement (separated by a single space from the end of the statement).
All inline comments should start with a #
and a single space.
The use of inline comments should be limited, because their existence is typically a sign of a code smell.
Do not use inline comments when they state the obvious:
# No
x = x + 1 # Increment x
However, inline comments can be useful in certain scenarios:
# Yes
x = x + 1 # Compensate for border
Use camelCase
(with a leading lowercase character) to name all variables, methods, and object properties.
Use CamelCase
(with a leading uppercase character) to name all classes. (This style is also commonly referred to as PascalCase
, CamelCaps
, or CapWords
, among other alternatives.)
(The official CoffeeScript convention is camelcase, because this simplifies interoperability with JavaScript. For more on this decision, see here.)
For constants, use all uppercase with underscores:
CONSTANT_LIKE_THIS
Methods and variables that are intended to be "private" should begin with a leading underscore:
_privateMethod: ->
(These guidelines also apply to the methods of a class.)
When declaring a function that takes arguments, always use a single space after the closing parenthesis of the arguments list:
foo = (arg1, arg2) -> # Yes
foo = (arg1, arg2)-> # No
Do not use parentheses when declaring functions that take no arguments:
bar = -> # Yes
bar = () -> # No
In cases where method calls are being chained and the code does not fit on a single line, each call should be placed on a separate line and (by default) indented by one level (i.e., two spaces), with a leading .
. Identing may be skipped as long as the code remains clear (in the case of builders this may be preferable).
[1..3]
.map((x) -> x * x)
.concat([10..12])
.filter((x) -> x < 11)
.reduce((x, y) -> x + y)
new SomethingBuilder()
.setThingOne(1)
.setThingTwo(2)
.build()
When calling functions, default to including parenthesis.
These can be ommitted to improve readability, but use this with great care. Logging and Mocha tests frequently omit them.
baz(12)
foo(4).bar(8)
obj.value(10, 20) / obj.value(20, 10)
new Tag(new Value(a, b), new Arg(c))
console.log "hi"
getLogger().log("hi")
describe 'a class', ->
it 'should do a thing', ->
expect thing to be 'done'
Do not use parentheses to group functions (instead of being used to group function parameters). Examples of using this style (hereafter referred to as the "function grouping style"):
($ '#selektor').addClass('klass') # No
(foo 4).bar(8) # No
Instead, use the more regular grouping of function parameters:
$('#selektor').addClass('klass') # Yes
foo(4).bar(8) # Yes
Use string interpolation instead of string concatenation:
"this is an #{adjective} string" # Yes
"this is an " + adjective + " string" # No
Prefer single quoted strings (''
) instead of double quoted (""
) strings, unless features like string interpolation are being used for the given string.
Favor unless
over if
for negative conditions.
Instead of using unless...else
, use if...else
:
# Yes
if true
...
else
...
# No
unless false
...
else
...
Multi-line if/else clauses should use indentation:
# Yes
if true
...
else
...
# No
if true then ...
else ...
Take advantage of comprehensions whenever possible:
# Yes
result = (item.name for item in array)
# No
results = []
for item in array
results.push item.name
To filter:
result = (item for item in array when item.name is "test")
To iterate over the keys and values of objects:
object = one: 1, two: 2
alert("#{key} = #{value}") for key, value of object
Do not modify native objects.
For example, do not modify Array.prototype
to introduce Array#forEach
.
Prefer a chain of promises over nested promises:
# Prefer this
someCall().then(->
someOtherCall()
).then(->
thirdCall()
)
# Over this
someCall().then(->
someOtherCall().then(->
thirdCall()
)
)
Do not use .catch
unless the rejection can actually be handled. Instead let the error "bubble up" the chain until
a point in the code where it can be handled properly, do not supress rejections.
Do not suppress exceptions.
Use annotations when necessary to describe a specific action that must be taken against the indicated block of code.
Write the annotation on the line immediately above the code that the annotation is describing.
The annotation keyword should be followed by a colon and a space, and a descriptive note.
# FIXME: The client's current state should *not* affect payload processing.
resetClientState()
processPayload()
If multiple lines are required by the description, indent subsequent lines with two spaces:
# TODO: Ensure that the value returned by this call falls within a certain
# range, or throw an exception.
analyze()
Annotation types:
TODO
: describe missing functionality that should be added at a later dateFIXME
: describe broken code that must be fixedOPTIMIZE
: describe code that is inefficient and may become a bottleneckHACK
: describe the use of a questionable (or ingenious) coding practiceREVIEW
: describe code that should be reviewed to confirm implementation
If a custom annotation is required, the annotation should be documented in the project's README.
Use thing?
to check wether something is defined.
Use thing ?= 1
to assign a value if none has been defined.
Use thing?.callF()
to call a function if something has been defined.
Use thing?.member?.other
to obtain nested values if unsure whether they are defined.
is
is preferred over ==
.
isnt
is preferred over !=
.
Prefer shorthand notation (::
) for accessing an object's prototype:
Array::slice # Yes
Array.prototype.slice # No
Prefer @property
over this.property
.
callF(@property) # Yes
callF(this.property) # No
Prefer object.property
over object['property']
callF(thing.name) # Yes
callF(thing['name']) # No
Avoid return
where not required, unless the explicit return increases clarity.
Use splats (...
) when working with functions that accept variable numbers of arguments:
console.log args... # Yes
(a, b, c, rest...) -> # Yes
Don't use fat arrows for functions when @ is not used.
Included in this repo is a coffeelint configuration file that should not be incompatible with this guide.
Run it by:
# Install it globally
yarn global add @fellow/coffeelint2
# Check files
coffeelint -f coffeelint.json <TARGET FILE/DIR>