The purpose of this style guide is to keep jsdoom's source code readable, approachable, consistent, and maintainable. When contributing to jsdoom, please follow these guidelines. Pull requests containing code that does not follow the guidelines may be rejected until the code has been made conformant.
These are not strict rules! Exceptions are allowed where it improves readability. However, exceptions should be just that: Exceptional. Exceptions should be made sparingly, if they are made at all.
Please create a GitHub issue if you would like to request a change or an addition to this style guide.
You can link to a particular guideline in this style guide by using a url such as the below example, where the numbers at the end are the same as those at the beginning of the header where you wish to link. You can also copy the target URL of items in the table of contents.
https://github.com/pineapplemachine/jsdoom/blob/master/style.md#user-content-1.4.1.
Throughout this guide, specific examples of conforming code will be provided like so:
// An example of conforming code
console.log("jsdoom is pretty neat");
- 1. Whitespace
- 2. Newlines
- 2.1. Lines should not exceed eighty characters
- 2.4. Line breaks
- 2.4.1. Line breaks should not occur anywhere not mentioned in 2.4.
- 2.4.2. Line breaks should always occur after a semicolon
- 2.4.3. Line breaks may occur after a comma
- 2.4.4. Line breaks may occur after an open brace, bracket, or parethese
- 2.4.5. Line breaks may occur after an infix operator
- 2.4.6. Line breaks may occur after the last expression in a list
- 2.4.7. Line breaks may occur after either symbol of a ternary expression
- 2.2. Blank lines
- 2.2.1. Source files should not begin with a blank line
- 2.2.2. Functions and methods should be padded by blank lines
- 2.2.3. Function blocks should not contain blank lines
- 2.2.4. Logical groups of imports should be padded by blank lines
- 2.2.5. Logical groups of class members should be padded by blank lines
- 2.5. There should be a newline at the end of each source file
- 3. Punctuation
- 4. Comments
- 4.1. Begin all comments with a single space
- 4.2. Prefer multiple single-line comments to multi-line block comments
- 4.3. Prefer comments on their own line to trailing comments
- 4.4. Trailing comments should have one space between "//" and the end of the line
- 4.5. Documenting comments
- 4.5.1. Document all classes and interfaces
- 4.5.2. Document all functions and methods
- 4.5.3. Document the impure behavior of functions
- 4.5.4. Document all constants and enumerations
- 4.5.6. Document variables or attributes that are not completely self-explanatory
- 4.5.6. Document statements or expressions that are not completely self-explanatory
- 4.6. Todos
- 5. Names
- 5.1. Use descriptive names
- 5.2. Do not use needless or unconventional abbreviations
- 5.3. Names should contain only "A"-"Z", "a"-"z", and "0"-"9"
- 5.4. Classes and constants should have PascalCase names
- 5.5. Functions and variables should have camelCase names
- 5.6. TypeScript source files should have camelCase names
- 5.7. Single-character names
- 5.7.1. Single-character names should not be used except as mentioned in 5.7.
- 5.7.2. Parameters with very clear purpose may be named "a", "b", "c", and so on
- 5.7.3. The interpolant parameter should be named "t"
- 5.7.4. Extremely generic type parameters may be named "T"
- 5.7.5. Coordinates should be named "x", "y", "z", and "w"
- 5.7.6. Vector components should be named "ijk" or "xyzw"
- 6. Syntax conventions
- 6.1. Do not use "eval" or the function constructor
- 6.2. Do not use "var"
- 6.3. Prefer "const" to "let" when declaring variables
- 6.4. Do not use undeclared variables
- 6.5. Use seperate declarations
- 6.6. Prefer "if" and "else if" over "switch" and "case"
- 6.7. Do not nest ternary expressions
- 6.8. Prefer strict equality over regular equality
- 6.9. Prefer "as" when writing type assertions
- 6.10. Prefer Type[] to Array
- 6.11. Do not refer to the "arguments" object
- 6.12. Prefer rest parameters over referring to the "arguments" object
- 6.13. Avoid the spread operator in array and object literals
- 6.14. Do not mix full object properties with shorthand properties
- 6.15. Imports
- 6.15.1. Imports belong at the beginning of a source file
- 6.15.2. Prefer selective imports over default imports
- 6.15.3. Prefer absolute over relative imports
- 6.15.4. Group and order imports logically, then alphabetically by filename
- 6.15.5. Do not include unused imports
- 6.15.6. Avoid importing modules for side-effects only
- 6.16. Ordering of class members
- 7. Program logic conventions
- 7.1. Prefer local state over global state
- 7.2. Prefer immutable state over mutable state
- 7.3. Prefer pure functions over impure functions
- 7.4. Functions should not modify their inputs
- 7.5. Class getters should treat the instance as logically const
- 7.6. Prefer template strings over concatenation
- 7.7. Prefer joining over repeated concatenation
The following guidelines mainly pertain to the use of whitespace in code.
Code is indented using four space characters. Do not indent with tab characters.
// [Comment explaining the purpose of my function]
function myFunction(): void {
doStuff();
if(condition){
doOtherStuff();
}
}
Indentation must be in such a way that each line is indented at either the same level as the last line, one more level, or one less. There should never be a jump of two or more indentation levels between lines.
Longer expressions made up of chained function calls should be broken across
multiple lines by using several shorter statements with intermediate
assignments, or by putting the code inside a pair of paretheses ()
etc.
on a new, idented line.
They should not be broken across lines by starting each line with the
right-hand part of a member access, e.g. having a line begin with .map(...)
.
myArray.map((value) => {
return transformMyValue(value);
}).filter((value) => {
return filterMyValue(value);
});
There should never be a partial change in indentation. All changes in indentation from one line to the next should always be in multiples of four space characters.
The following guidelines mainly pertain to how whitespace should be
used around braces {}
, square brackets []
, angle brackets <>
, and
parentheses ()
.
Where an open brace {
, parenthese (
, etc. is followed by a corresponding
closing brace }
, parenthese )
, etc. with no code in between, there should
not be any whitespace, comments, or other characters in between those open and
closing characters.
const myEmptyObject: Object = {};
const myEmptyArray: number[] = [];
myFunctionInvokedWithNoArguments();
// [Comment explaining the purpose of my unconventional loop with no body]
while(myIndex++ < myMaximumIndex){}
Where a statement is spread across multiple lines, the lines between a
corresponding pair of braces {}
, paretheses ()
, square brackets []
,
or angle brackets <>
should be indented at one additional level.
const myMultiLineObjectLiteral: Object = {
myFirstAttribute: 1,
mySecondAttribute: 2,
};
const myMultiLineExpression: boolean = (
myFirstCondition &&
mySecondCondition
);
const myMultiLineArrayLiteral: string[] = [
"Hello world",
"How are you?",
];
Open braces {
should always be followed by a newline and close braces }
should always be preceded by a newline.
The code in between corresponding braces should be indented at one level
further than the lines containing those braces.
Normally, an open brace {
should not be immediately followed by any
character other than a corresponding closing brace }
or a newline \n
.
const myObject: Object = {
myAttribute: doStuff({
myOption: 1,
});
};
Open braces {
should not be preceded by a newline.
Open braces {
should be on the same line as the statement or
expression that they are a part of.
Except for where an expression is distributed across several lines due to its
length, expressions or blocks inside paretheses ()
, square brackets []
,
or angle brackets <>
should not have spaces in between the opening character
and the first character of the enclosed expression, nor in between the closing
character and the final character of the enclosed expression.
// No space in between `[` and `1`.
// No space in between `5` and `]`.
const myArray: number[] = [1, 2, 3, 4, 5];
// Newlines are okay for spreading a long statement across multiple lines.
const myArrayTooLongToFitOnOneLine = [
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
];
In objects and in typed declarations, there should be a single space after each
colon :
and no space before it.
const myObject: Object {
myAttribute: "hello world",
};
In comma-separated lists, there should be whitespace after each comma ,
and no space before it.
If the next item in a list after a given comma appears on the same line, then there should be a single space in beween the comma and the following non-whitespace character. If the next item in the list appears on the following line, then there should be a newline immediately after the comma and the next list item should be indented at the appropriate level.
const myArray: number[] = [1, 2, 3, 4];
const myMultiLineArray: string[] = [
"Hello",
"World",
];
The following guidelines mainly pertain to the use of whitespace around
infix operators, e.g. +
or *
.
Infix operators should appear on the same line as the last character of their left operand. They should be separated from the last non-whitespace character of that left operand by a single space character.
An infix operator should have either a single space separating it from the first non-whitespace character of its right operand, or it should be immediately followed by a newline with the right operand placed at the correct indentation level on the next line.
const mySum: number = 100 + 200;
const myMultiLineExpression: boolean = (
myFirstCondition ||
mySecondCondition
);
The following guidelines mainly pertain to the placement of new lines and blank lines in code.
In general, no one line of code should exceed 80 characters. Please consider eighty characters to be a soft limit and one hundred characters to be a hard length limit.
The following guidelines mainly pertain to where line breaks should appear in code.
Line breaks on lines containing code should not occur under circumstances other than the ones explicitly mentioned below.
Comments may exceptionally appear in between a character that would normally
precede a line break and the terminating newline.
However, this style of comments is not encouraged.
In general, comments should appear on their own lines and not at the end
of a line of code. (See 4.3.
.)
Semicolons terminating a statement should be immediately followed by a newline. Two statements should not appear on the same line.
const myFirstDeclaration: number = 1;
const mySecondDeclaration: number = 2;
Long lists of arguments, parameters, attributes, or other comma-separated lists may be broken up by placing newlines after the commas.
const myMultiLineArray: number[] = [
0x0100,
0x0200,
0x0400,
];
The code in between braces, brackets, etc. may appear on separate lines and be indented at one additional level.
It is appropriate for a line break to appear after an infix operator and before its right operand.
const myMultiLineExpression: boolean = (
myFirstCondition ||
mySecondCondition
);
The last character of a multi-line list should normally be a trailing comma.
However, in the case that the list appears on one line yet not on the same
line as the closing bracket ]
or other character, it is appropriate for
a line break to appear after the last item in the list.
const myValue: number = myMultiLineFunctionInvocation(
100, 200, 300, 400, 500, 600
);
A line break may immediately follow the first ?
or second :
symbol of
a ternary expression, or both, when breaking the expression across several
lines will improve readability.
const myTernaryExpressionResult: string = (myCondition ?
"My first string literal" :
"My second string literal"
);
The following guidelines mainly specifically to the placement of blank lines. A blank line is a line which contains no characters or which contains only whitespace characters.
The first line in a source file should not be a blank line.
Function or method declarations should be padded on each side by a single
blank line, except for where the immediately previous or following
line is the first line of the class declaration containing a method, or the
line containing a class declaration's closing brace }
.
This does not apply to helper functions that are declared inside another
function. In general, function blocks should not contain blank lines, not
even to pad helper function declarations. (See 2.2.3.
.)
// [Comment explaining the purpose of my function]
function myFirstFunction(): void {
doStuff();
}
// [Comment explaining the purpose of my function]
function mySecondFunction(): void {
doStuff();
}
// [Comment explaining the purpose of my function]
function myThirdFunction(): void {
doStuff();
}
class MyClass {
// [Comment explaining the purpose of my method]
myFirstMethod(): void {
doStuff();
}
// [Comment explaining the purpose of my method]
mySecondMethod(): void {
doStuff();
}
// [Comment explaining the purpose of my method]
myThirdMethod(): void {
doStuff();
}
}
The implementation of a function or method should not contain blank lines. If a function is made up of several different conceptual units, then they should be separated by explanatory comments instead of separated by blank lines. If a function is too long or complex to be readable without those empty lines, then parts of the implementation should be moved into other helper functions.
Logical groups of imports should be separated from each other by a single
blank line.
See 6.15.
for more information about how imports should be grouped.
Logical groups of class members should be separated from each other by a single
blank line.
See 6.16.
for more information about how class members should be grouped.
TypeScript source files should terminate with a newline character \n
.
The following guidelines mainly pertain to the use of punctuation characters in code.
Statements should always be terminated by a semicolon ;
.
Do not trust JavaScript's automatic semicolon insertion to get it right.
Where it is syntactically valid, the last item in a comma-separated list that
spans more than one line should be followed by a comma ,
.
const myMultiLineArray: number[] = [
0x0100,
0x0200,
0x0400,
];
Block statements such as the body of a loop or an if
statement should
always be enclosed within braces, even if it is syntactically valid to
omit those braces.
Single-line conditionals or loops without braces around their bodies should
not be used.
if(myExitCondition){
return;
}
while(myLoopCondition){
counter++;
}
When a single expression contains several different infix operators, each group
of identical operators should generally be enclosed within parentheses ()
.
This helps to avoid any confusion or ambiguity regarding the intended order of
operations.
const myValue: number = 10 * (20 + myOtherValue);
const myBoolean: boolean = (
(firstCondition || secondCondition) &&
(thirdCondition || fourthCondition)
);
The following guidelines mainly pertain to the use of punctuation characters as they apply to arrow functions.
The parameter list of an arrow function should always be enclosed
within paretheses ()
, even when there is only a single parameter.
myArray.map((value) => {
return value + value;
});
The bodies of arrow functions should always be enclosed within braces {}
,
even when the function body contains only a return
statement.
myArray.filter((value) => {
return value > 0;
});
The following guidelines mainly pertain to the use of punctuation characters as they apply to string literals.
String literals should be double-quoted ""
.
String literals should not ever be single-quoted ''
, even when the literal
itself contains double quotes.
Template strings (enclosed within backticks) should not be used for string literals that do not actually contain any interpolation.
const myString: string = "You say \"goodbye\" and I say \"hello\".";
String literals that are too long to fit on a single line should be spread across multiple lines by concatenating many shorter string literals.
const myLongStringLiteral = (
"This is a long string literal. In fact, it is so long, that it could " +
"not possibly fit on a single line and still be readable. " +
`Remember that the recommended maximum line length is ${MaxLineLength} ` +
"characters!"
);
The following guidelines mainly pertain to what is expected of code comments.
There should be a single space character in between a comment's opening
slashes //
and the first character of the comment's text.
// Note the space at the beginning of this comment!
Long comments should be spread across multiple single line comments
(i.e. // comment
) rather than given in a single multi-line block comment
(i.e. /* comment */
).
// This is a longer comment providing a lot of information about a function.
// It describes in detail the function's purpose, its accepted input, and its
// expected output. It's far too much information to fit on just one line.
function myFunction(value: number): number {
return doStuffWith(value);
}
Comments should usually not be on the same line as code. Comments should be on their own line, preceding the code that they apply to.
// [Comment explaining the purpose of my variable]
let myVariable: number = 0;
Note that 4.3.
discourages the use of trailing comments.
When trailing comments are used, there should always be exactly one space
in between the last character of code and the first character /
of the
trailing comment.
doStuff(); // My trailing comment
The following guidelines mainly pertain to when documenting comments are encouraged or required.
Every class and interface should have a comment explaining its purpose, even if it is only repeating information that should be self-evident.
// This class stores the color channel information taken from a PLAYPAL
// lump. Note that the color data is 24-bit. Color channel values should
// always be in the range [0, 256] inclusive.
class MyColorClass {
// The color's 8-bit red color channel.
red: number;
// The color's 8-bit green color channel.
green: number;
// The color's 8-bit blue color channel.
blue: number;
}
Every function or method should have a comment explaining its purpose, even if it is only repeating information that should be self-evident.
// Log a friendly greeting to the console.
function sayHello(): void {
console.log("Hello, world!");
}
Functions and methods with impure behavior should have that impure behavior documented in comments to the greatest extent that is practical.
Note that impure behavior includes modifying a function's inputs or reading or writing global state.
// [Comment explaining the purpose of my function]
// Calling this function will cause `myPreviouslyDeclaredGlobal`
// to be modified.
function myFunctionWithSideEffects(): void {
myPreviouslyDeclaredGlobal = doStuff();
}
Constants - defined as values set once and never reassigned, not necessarily
any variable declared using const
- should always be accompanied by a comment
explaining their purpose and their value.
// The Doom engine palette lump is always named "PLAYPAL".
const PlaypalLumpName: string = "PLAYPAL";
Enumerations should be preceded by a documenting comment, and every member should have a comment explaining its purpose.
// Enumeration of Doom linedef flags which pertain to texturing.
enum LinedefTextureFlags {
// Unpegged upper texture
UpperUnpegged = 0x0008,
// Unpegged lower texture
LowerUnpegged = 0x0010,
}
It is not necessary to write a comment explaining every variable or attribute, but those whose function is not immediately obvious from looking at the declaration should be accompanied by documentation comments.
It is not necessary, and in fact discouraged, to write a comment explaining every statement and expression. However, those statements and expressions which are more complicated or with less obvious purpose should be accompanied by documentation comments.
// Do stuff with the length of the vector described by (x, y).
doStuffWith(Math.sqrt((x * x) + (y * y)));
The following guidelines mainly pertain to how "TODO" comments should be used.
Comments recording future or unfinished tasks should consistently begin
with the characters // TODO:
.
When a todo comment is too long to fit on a single line, only the first line
explaining the task should be preceded by TODO
.
function myScaffoldingFunction(): void {
// TODO: Implement this function
}
It is not acceptable to write a todo comment without including some written
explanation of the task that is not finished.
Do not write a comment that says // TODO
without being followed by more
text explaining why the comment is present.
Where practical, todo comments should refer to an issue in the issue tracker, or to a relevant pull request. In general, if such a relevant issue or other link does not exist, then it should be created and referenced in the todo comment before the comment is included in the master branch of the code repository
// TODO: Create a help page and assign the URL.
// See https://github.com/pineapplemachine/jsdoom/issues/123456
const myHelpUrl: string = "";
The following guidelines mainly pertain to the naming of variables, classes, etc. and source file names.
All classes, functions, variables etc. should be assigned descriptive names that make their purpose as clear as possible. Avoid using vague or generic names that do not communicate purpose.
Identifiers should generally not contain abbreviations unless those abbreviations are essentially universal and/or the text that they are abbreviating is impractically long to actually include in code.
"HTML" abbreviating "HypertextMarkupLanguage" or "WAD" abbreviating "WheresAllTheData" is good and encouraged. However, "Idx" abbreviating "Index" or "Err" abbreviating "Error" is not acceptable. Please use common sense in judging whether an abbreviation is really necessary, and whether it might make the code more difficult to read.
Identifiers should be made up only of the characters A
through Z
,
a
through z
, and 0
through 9
.
Identifiers should not have an underscore _
in their name, even if they
identify private members.
Do not use characters such as emoji or math symbols in variable names.
const myAsciiVariableName: number = 0;
Class and interface names names, constructors, enumerations, and the names of constants should be written in PascalCase.
Note that constant in this case does not mean everything declared using const
,
but rather it refers to any variable that is initialized once and never changed
during program execution.
Constants are not a matter of syntax in TypeScript, but a matter of intent.
Names of functions, methods, variables, attributes, function parameters,
and anything else that is not mentioned by 5.4.
should be written
using camelCase.
TypeScript source files and directories containing TypeScript source files should be assigned pascalCase names.
- file.ts
- fileList.ts
- fileType.ts
The following guidelines mainly pertain to the use of single-character names. Single-character names are mostly discouraged, but are still the most appropriate naming choice for some cases.
Except as otherwise mentioned below in 5.7.
, single-character names should
not normally be used.
Here is a rule of thumb: Single-character identifiers should only be used when it follows a long-standing mathematical or programming convention, such that choosing any other name may make the purpose of the variable, etc. less clear.
Sequential single-letter identifiers such as a
, b
, c
, and so on are
acceptable where they are used to name the parameters of a function with very
clear inputs and purpose, where those inputs are mainly distinguished by
the order in which they appear.
// `a` and `b` are the preferred parameter names for comparator functions.
myArray.sort((a: number, b: number) => {
return a - b;
});
Where a parameter or other named value is used as the
interpolant parameter of an interpolation function,
that value should be named t
.
// Linearly interpolate between the numbers `a` and `b`.
// The value `t` should normally be in the inclusive range [0.0, 1.0].
function lerp(a: number, b: number, t: number): Vector {
return (a * (1 - t)) + (b * t);
}
A generic function or class accepting a single type parameter may have
that parameter named T
if the purpose of the parameter is generic enough
to not lend itself to a more descriptive name.
The single-character names x
, y
, z
, and w
are acceptable and encouraged
when they refer to spatial coordinates along the corresponding axes.
The components of vectors or quaternions should normally be named
either i
, j
, k
or x
, y
, z
, w
.
// [Comment explaining the purpose of my three-dimensional vector class]
class MyVector {
x: number;
y: number;
z: number;
}
The following guidelines mainly pertain to what TypeScript syntax options should be favored or avoided.
Do not use the eval
built-in function.
Do not use the Function
constructor to create a new function.
This functionality is not efficient and it opens the door to security issues.
Do not use var
when declaring variables. Use let
or const
instead.
const myVariable: number = 0;
Use const
instead of let
whenever it is syntactically valid, i.e. when the
declared reference is never changed.
Do not use variables without declaring them. Do not reference variables that have not yet been declared at the time that the code will be executed.
Do not declare multiple variables together on the same line. Use separate declarations, with each declaration on its own line.
const myFirstNumber: number = 0;
const mySecondNumber: number = 0;
Prefer using a series of if
and else if
statements instead of using a
switch
statement.
if(myVariable === 0){
doStuff();
}else if(myVariable === 1){
doOtherStuff();
}else if(myVariable === 2){
doYetMoreStuff();
}else{
doDefaultStuff();
}
Ternary expressions a ? b : c
should not be nested.
The strict equality ===
and strict inequalty !==
operators should be
used instead of the regular equality ==
and inequality !=
operators.
const myComparison: boolean = (myFirstValue === mySecondValue);
When writing a type assertion, prefer syntax like value as Type
over
syntax like <Type> value
. (Why?)
const myValue: number = myOtherValue as number;
When describing an array type, prefer syntax like Type[]
to syntax like
Array<Type>
.
const myStringArray: string[] = ["Hello", "World"];
Functions should not refer to the arguments
object.
Functions which accept a variable number of arguments should do so using a
rest parameter ...
and not by accessing the arguments
object.
// [Comment explaining the purpose of my function]
function myVariadicFunction(...strings: string[]): void {
doStuffWithList(strings);
}
Prefer using functions like Array.concat
or Object.assign
over using
the spread operator to construct arrays or objects.
// Use `Array.concat` instead of the spread operator
const myConcatenatedArray = (
myFirstArray.concat(mySecondArray)
);
// Assign each key individually or, if the keys are numerous or not
// necessarily known ahead of time, use `Object.assign` instead of
// using the spread operator.
const myComposedObject = {};
Object.assign(myComposedObject, myFirstObject, mySecondObject);
An object should either use only shorthand properties or only full properties. A single object literal should not mix both types of properties.
const myObjectWithFullProperties: Object = {
firstValue: firstValue,
secondValue: secondValue,
};
const myObjectWithShortProperties: Object = {
firstValue,
secondValue,
};
The following guidelines mainly pertain to how imports should be written.
Import statements should be at the very top of a TypeScript source file. They should not appear anywhere else.
Prefer selective imports (imports with braces {}
) over default imports
(imports without braces).
import {WADFile} from "@src/wad/file";
However, modules should still have a default export where it makes sense to choose a default.
// Actual export from "@src/wad/file"
export default WADFile;
Absolute import paths (those starting with an "at" sign @
) should be
preferred over relative paths (those starting with a dot .
).
Note that the meaning of these absolute paths are defined by macros in the
TypeScript config file tsconfig.json
and the webpack config
file webpack.config.js
.
import {WADFile} from "@src/wad/file";
Imports should first be ordered by logical group, and then alphabetically by filename. Groups of similar imports should be ordered from most general to most specialized. Here is a guideline for how to separate and order these groups:
- Native dependencies, such as Node.js imports.
- External dependencies, i.e. those listed in
package.json
. - Jsdoom library dependencies, e.g.
@src/wad/
or@src/lumps/
. - Jsdoom UI or engine dependencies, e.g.
@web/
.
import * as fs from "fs";
import * as path from "path";
import * as UPNG from "upng-js";
import {WADFile} from "@src/wad/file";
import {WADFileList} from "@src/wad/fileList";
import {LumpTypeView} from "@web/lumpTypeView";
Avoid importing unused symbols or modules.
Avoid writing side-effect-only import statements, i.e. statements
such as import "module";
.
The following guidelines mainly pertain to how class member declarations should be ordered.
Static attributes and constants should come before all other declarations in a class.
class MyClass {
// [Comment explaining the purpose of my constant]
static readonly MyStaticConstant: number = 0x8000;
}
Instance attributes should be declared after static attributes but before the constructor and any member functions or methods.
class MyClass {
// [Comment explaining the purpose of my attribute]
myFirstAttribute: string;
// [Comment explaining the purpose of my attribute]
mySecondAttribute: string;
}
The class constructor should come after all static and instance attribute declarations but before all method and member function declarations.
class MyClass {
constructor() {
this.myFirstAttribute = "hello";
this.mySecondAttribute = "world";
}
}
Static member functions should appear after attribute declarations and after the class constructor, but before instance method declarations.
class MyClass {
// [Comment explaining the purpose of my function]
static myStaticFunction(): MyClass {
return new MyClass();
}
}
Instance methods should appear after all other declarations in a class.
class MyClass {
// [Comment explaining the purpose of my method]
getTotalLength(): number {
return this.myFirstAttribute.length + this.mySecondAttribute.length;
}
}
The following guidelines mainly pertain to the use of consistent logic and design conventions in order to keep code modular and maintainable.
Code which relies on local state is normally easier to understand and maintain than code which relies on global state. It is not possible to completely avoid global state, particularly in code that deals with the DOM. However, global state should be avoided when possible. Whenever possible, state should be stored locally in scoped variables or in class instances.
Where it will not substantially impact performance in a negative way, treat variables and objects as though they were immutable.
Pure functions are those which accept inputs and produce their output without making any changes to the inputs and without producing any side-effects. In essence, a pure function is one which can be called at any time, any number of times, and the same inputs will always produce the same output.
// [Comment explaining the purpose of my pure function]
function myPureFunction(value: number): number {
return value + value;
}
Avoid writing functions that modify their inputs. Ensure that where such functions do exist, their modification of the input is clearly documented.
Getter methods (i.e. methods preceded by the get
keyword) should treat
the instance this
as logically const.
Logical const means that the object may technically be modified,
but any modification that does take place should not affect or be visible to
code which uses only the object's intended and documented API.
class MyClass {
// Used to cache the result of the myExpensiveProperty getter.
private lazilyComputedValue: number = NaN;
// No modification of the class instance whatsoever.
// `this` is literally const.
get myProperty(): number {
return 0x0100;
}
// Modification is trivial to the outside observer.
// `this` is logically const.
get myExpensiveProperty(): number {
if(Number.isNaN(this.lazilyComputedValue)){
this.lazilyComputedValue = doExpensiveComputation();
}
return this.lazilyComputedValue;
}
}
Prefer using template strings over string concatenation or joining.
const myString: string = `${helloString} ${worldString}!`;
Strings that are incrementally assembled, e.g. in a loop, should have their
substrings pushed to an array and combined at the end of the loop with a
single join
statement.
Avoid assembling strings via repeated concatenation.
// Create a comma-separated list representing the vertexes in an array.
function vertexListToString(vertexes: Vertex[]): string {
const parts: string[] = [];
for(const vertex of vertexes){
parts.push(`(${vertex.x}, ${vertex.y})`);
}
return parts.join(", ");
}