As of April 2018, Higher Logic Vanilla (Vanilla) will follow the RFC 2119 coding standard for all frontend scripts. Existing code should not (and cannot) be mass-updated, but all new code must follow this standard.
📝 NOTE: Key words in this document (“MUST”, “SHOULD”, etc.) are used as described in RFC 2119.
Language features
All typescript features up to typescript version 3.7 are supported. This includes everything found in the Typescript documentation. For example:
Browser support
Whether the final JavaScript that runs in the browser has been transpiled or not, all JavaScript must support the following browsers:
- IE11
- The last 2 versions of the following browsers
- Edge
- Firefox
- Chrome
- Chrome for Android
- Safari
- iOS Safari
Validating your Typescript
Vanilla uses Prettier and TSLint to enforce coding standards on Typescript.
- Prettier will automatically format your code to conform to style standards.
- TSLint will perform static analysis to check code for readability, maintainability, and functionality errors.
There are multiple ways to use these tools, as outlined below.
IDE Integration
Prettier and TSLint have integrations with many popular integrated development environments (IDE) and editors.
Prettier
Prettier IDE integrations generally allow automatic formatting on save, on input, or manually.
All repositories in the vanilla
organization are meant to be developed in the context of a vanilla/vanilla installation. Vanilla core provides all dependencies required to run these tools.
TSLint
Command line validation
The Vanilla build process includes some command line tools that you can run to check some of our code standards.
You can run our linter with the following command:
yarn run lint
You can also invoke the type checker with the following command:
yarn check-types
1. Overview
- All new files in the
vanilla/vanilla
repo MUST be in Typescript. - New code MUST NOT use JQuery. Instead, native browser APIs and utility functions from
@core/dom
may be used. - All files MUST be formatted with Prettier.
- Files SHOULD NOT declare more than one class in a single file.
- Files with a default export MUST be named equivalently to the symbol (class, function, interface, constant) that they export.
- Interfaces for code defined inside of Vanilla Forums code MUST be named beginning with the character
I
(e.g., IThing
, IButtonOptions
). This rule does not apply to type definitions for dependencies. - Method names SHOULD be declared in
camelCase
. - Static class properties MUST be declared in all upper case with underscore separators.
- Class names MUST be declared in
PascalCase
. const
MUST be used where possible. Otherwise let
MUST be used. var
MUST NOT be used.===
MUST be used instead of ==
. An exception is made for null checks, specifically someVar == null
.- A file MUST NOT contain unused imports.
- Test files MUST be located in a directory
__tests__
and end with the extension .test.ts
or .test.js
. console.log
and other built-in logging functions MUST NOT be used. Instead, logging functions from @core/utility
may be used.
1.1. Styling rules
All files MUST be formatted with Prettier. This is to ensure consistent formatting, and to prevent overly large diffs when someone else formats a file. This encompasses all spacing and formatting rules. The following rules, among others, will all be automatically enforced by formatting with Prettier:
- Code MUST use four spaces for indenting, not tabs.
- Opening braces for classes and functions MUST be on the same line.
- Control structure keywords MUST have one space after them; method and function calls MUST NOT.
- Opening braces for control structures MUST go on the same line, and closing braces MUST go on the next line after the body.
- Opening parentheses for control structures MUST NOT have a space after them, and closing parentheses for control structures MUST NOT have a space before.
- Lines MUST be 120 characters or less.
- Semicolons are REQUIRED.
- Strings SHOULD use double quotes
"
or Backtick quotes \
`. - Colons in object and interface declarations MUST NOT be preceded by a space and MUST be followed by a space.
- Object and array declarations MUST contain a trailing comma, if it is declared on multiple lines.
1.2. Example
This example encompasses some of the rules above as a quick overview:
/**
* @copyright 2009-2018 Vanilla Forums Inc.
* @license http://www.opensource.org/licenses/gpl-2.0.php GPLv2
*/
/**
* The is the foo class that does foo.
*
* This is a longer description that spans multiple
* lines.
*/
export default class SomeClass extends ParentClass implements ISome {
/**
* The is a method that does a thing.
*
* This is a longer description that spans multiple
* lines.
*
* @param a Must be a full sentence if provided.
* @param b Must be a full sentence if provided.
*
* @returns Must be a full sentence if provided.
*/
public function sampleFunction(a: string, b?: = string): boolean{
if (a === b) {
return bar();
} else if (a > b) {
return foo->bar(a);
} else {
return BazClass.bar(a, b);
}
}
}
2. Isolating legacy code
New code MUST NOT use JQuery. Instead, native browser APIs and utility functions from @vanilla/dom-utils
may be used.
An exception is made to this rule for code being gradually ported into the new code base, but does not have a long-term future. This code MUST be contained in a directory called legacy
. At some point in the future, legacy code will be completely removed; anything important enough to save SHOULD be migrated into either @core/application
, @core/utility
, or @core/dom
without a dependency on JQuery.
New code MUST NOT access methods or properties in the global Vanilla
or gdn
objects. Instead, functions from @vanilla/utils
and @core/application
may be used.
3. General
3.1. Files
- All files MUST use the Unix LF (linefeed) line ending.
- All files MUST end with a single blank line.
- A file with a default export MUST be named the same as the export.
3.2. Character encoding
- Code MUST use only UTF-8 without BOM.
3.3. Indenting
- Code MUST use an indent of four spaces, and MUST NOT use tabs for indenting.
- Using only spaces, and not mixing spaces with tabs, helps to avoid problems with diffs, patches, history, and annotations.
3.4. Single and double quotes
- All strings will automatically have their quotes adjusted by Prettier.
- Strings SHOULD use double quotes
"
or back-tick quotes `
. - Strings MAY use double quotes if there are double quotes that would have to otherwise be escaped.
// Good
"Something"
"OMG she's using double quotes!"
`This one uses backtick quotes and has ${numberOfVars} variables.`
'"Single quotes can work sometimes too!", he excaimed.'
// Bad
'Single quotes with no escaped characters'
'Definitely not single quotes if there\'s single quotes that need to be escaped.'
4. Namespaces, types, and interfaces
Namespaces
Typescript namespaces MUST NOT be used; ES Modules MUST be used instead.
4.1. Types and Interfaces
- Interface names MUST be prefixed with an uppercase
I
(e.g., IProps
, IState
). - Interfaces MUST be used instead of type literals.
// Good
interface IThing {
foo: number;
}
// Bad
type IOtherThing = {
foo: number;
}
4.2. Type casting
Casting should generally be avoided whenever possible, but when used it should obey the following rules.
- When casting a type the
variable as IType
syntax MUST be used. - When casting a type the
<IType>variable
syntax MUST NOT be used.
interface IFoo {
foo: string;
}
// Good
(getFooLikeStructure() as IFoo).foo;
// Bad
(<IFoo>getFooLikeStructure()).foo;
4.3. Forbidden types
The following types MUST not be used; instead, their alternatives should be used.
5. Class constants, properties, and methods
5.1. Static class properties
- Static class properties MUST be declared in all upper case with underscore separators.
class Foo {
public static VERSION = '1.0';
public static DATE_APPROVED = '2012-06-01';
}
5.2. Extends and Implements
- The
extends
and implements
keywords MUST be declared on the same line as the class name. - The opening brace for the class MUST go on the same line as the class name; the closing brace for the class MUST go on the next line after the body.
class ClassName extends ParentClass implements ArrayAccess, Countable {
// constants, properties, methods
}
- Lists of
implements
MAY be split across multiple lines, where each subsequent line is indented once. When doing so, the first item in the list MUST be on the next line, and there MUST be only one interface per line.
export default class ClassName extends ParentClass implements
ArrayAccess,
Countable,
Serializable
{
// constants, properties, methods
}
5.3. Properties
- Visibility MUST be declared on all properties.
- A type declaration SHOULD be declared on all properties.
- There MUST NOT be more than one property declared per statement.
- Private properties SHOULD use the the private or protected visibility instead of prefixed with a single underscore. A single underscore MAY be used to denote an internal property that must still be exported, but should not be used elsewhere.
- A property declaration looks like the following:
export default class ClassName {
public foo = null;
}
5.4. Methods
- Method names MUST be declared in
camelCase()
. - Visibility MUST be declared on all methods.
- Private methods SHOULD use the the private or protected visibility instead of prefixed with a single underscore. A single underscore MAY be used to denote a internal method that must still be exported, but should not be used elsewhere.
- Method names MUST NOT be declared with a space after the method name. The opening brace MUST go on the same line as the method name, and the closing brace MUST go on the next line following the body. There MUST NOT be a space after the opening parenthesis, and there MUST NOT be a space before the closing parenthesis.
- A method declaration looks like the following. Note the placement of parentheses, commas, spaces, and braces:
export default class ClassName {
public static function fooBarBaz(arg1: string, arg2: number , arg3?: IOptions[] = []) {
// method body
}
}
5.5. Methods with a bound this
context
- When passing a method as callback or as an event handler, it's often necessary to bind the context.
- The context SHOULD NOT be bound in the constructor or at the call site.
- The context SHOULD be bound by declaring the method as a class property with an arrow function:
export default class ClassName {
// This method will automatically have it's context bound as the class instance.
public fooBarBaz = (arg1: string, arg2: number , arg3?: IOptions[] = []) => {
// method body
}
}
5.6. abstract
and static
- When present, the
abstract
declaration MUST precede the visibility declaration. - When present, the
static
declaration MUST come after the visibility declaration.
<?php
abstract class ClassName {
protected static $foo;
abstract protected function zim();
final public static function bar() {
// method body
}
}
6. Variables, objects & functions
6.1. Variable declarations
const
MUST be used where possible; otherwise, let
MUST be used. var
MUST NOT be used.- Multiple variables MUST NOT be declared at once.
// Good
const foo = "foo";
const bar = "bar";
// Bad
const foo = "foo",
bar = "bar";
let thing1, thing2, thing3;
- Variables MUST be named in either
lowerCamelCased
or UPPER_CASED
formatting.
6.2. Objects
- Objects keys MUST NOT use quotes unless necessary.
const object = {
lookMa: "noQuotes",
"quote-are-necessary-here",
}
- Object literal shorthand MUST be used where possible.
const foo = "foo";
const bar = "bar";
// Good
const good = {
foo,
bar,
other: "other",
};
// Bad
const bad = {
foo: foo,
bar: bar,
other: "other",
};
- The “spread” operator MUST be used instead of
Object.assign
.
const thing1 = {
foo: "foo",
};
const thing2 = {
bar: "bar",
};
// Good
const good = {
other: "other",
...thing1,
...thing2,
};
// Bad
const bad = Object.assign(
{},
thing1,
thing2
);
6.3. Declaring functions
- Functions MUST be declared as - An arrow function, - A named function, - A function declaration.
- Anonymous functions that are not an arrow function MUST NOT be used.
// Good
function foo(event: ClickEvent) {}
const foo = function foo(event: ClickEvent) {};
const foo = (event: ClickEvent) => {};
document.addEventListener("click", (event: ClickEvent) => {})
document.addEventListener("click", foo);
// Bad
const foo = function() {};
document.addEventListener("click", function(event: ClickEvent) {})
6.4. Calling Functions
When making a method or function call:
- There MUST NOT be a space between the method or function name and the opening parenthesis
- There MUST NOT be a space after the opening parenthesis
- There MUST NOT be a space before the closing parenthesis
In the argument list, there MUST NOT be a space before each comma, and there MUST be one space after each comma.
bar();
foo.bar(arg1);
Foo.baz(arg2, arg3);
Argument lists MAY be split across multiple lines, where each subsequent line is indented once. When doing so, the first item in the list MUST be on the next line, and there MUST be only one argument per line.
foo.bar(
longArgument,
longerArgument,
muchLongerArgument,
);
7. Control structures
The general style rules for control structures are listed below (many are automatically enforced by Prettier):
- There MUST be one space after the control structure keyword
- There MUST NOT be a space after the opening parenthesis
- There MUST NOT be a space before the closing parenthesis
- There MUST be one space between the closing parenthesis and the opening brace
- The structure body MUST be indented once
- The closing brace MUST be on the next line after the body
The body of each structure MUST be enclosed by braces. This standardizes how the structures look, and reduces the likelihood of introducing errors as new lines get added to the body.
7.1. if
, else if
, else
An if
structure example is shown below. Note the placement of parentheses, spaces, and braces, and that else
and elseif
are on the same line as the closing brace from the earlier body.
if (expr1) {
// if body
} else if (expr2) {
// else if body
} else {
// else body;
}
- The keyword
elseif
SHOULD be used instead of else if
so that all control keywords look like single words. - If statements MUST have opening and closing brackets and be split onto multiple lines. Single line if statements are prohibited.
7.2. switch
, case
A switch
structure example is shown below. Note the placement of parentheses, spaces, and braces.
switch (expr) {
case 0:
doThing('First case, with a break');
break;
case 1:
doThing('Second case, which falls through');
// no break
case 2:
case 3:
case 4:
doThing('Third case, return instead of break');
return;
default:
doThing('Default case');
break;
}
- The
case
statement MUST be indented once from switch
, and the break
keyword (or other terminating keyword) MUST be indented at the same level as the case
body. - There MUST be a comment such as
// no break
when fall-through is intentional in a non-empty case
body.
7.3. for of
, forEach
, and for in
for of
and foreach
are preferred over for in
.
const arrayVals = [1, 2, 3, 4];
const objectVals = {
key: "value",
};
arrayVals.forEach(val => {
// Do something
});
// Iterate over an object
for (const [key, value] of Object.entries(objectVals)) {
// do something
}
A for in
loop MUST contain a hasOwnProperty()
check.
for (const key in objectVals) {
if (objectVals.hasOwnProperty(key)) {
// Do something
}
}
8. Doc blocks
- Classes MUST contain a description comment.
- Class methods and properties MUST contain a visibility declaration.
- All files MUST contain an opening multi-line comment containing
@copyright 2009-2020 Vanilla Forums Inc.
where 2020 shall be replaced with the current year. Scripts MUST contain an @license
parameter with name. For example a file in the vanilla/vanilla
repo, which is licensed under GPLv2 MUST contain @license gpl-2.0-only
. - All functions, except for anonymous functions, and all class methods, MUST contain a multi-line JSDoc style comment. This comment:
- MUST contain a short description.
- MAY contain an extended description.
- MAY contain
@param
annotations. - MUST NOT contain type hints in its
@param
or @returns
annoations. Type hints should be declared directly as part of the function signature. - MUST NOT align its
@param
descriptions by using additional spaces. - MAY contain a single
@returns
annotation.