TypeScript Style Guide
This article specifies the most general guidelines used for all Spire projects written using TypeScript as the primary programming language. Each individual project may extend or override the guidelines specified herein. The overall aim of this guideline is to provide consistency across the numerous projects developed by Spire in order to promote and benefit from modern web development practices as well as tailor them to our specific requirements.
In general, the guidelines are very specific and opinionated. This is done intentionally to ensure that every detail has been given proper consideration and that every line of code is written with care and diligence. It is taken as a prerequisite that writing robust, clean and maintainable code requires paying close attention to even the smallest of details. As such, when there are two or more ways to write or express any given statement or expression, be it using spaces or tabs, number of characters per line, purple or violet, the guideline will often require that one particular approach be used to the exclusion of others.
Finally, this document is constantly evolving and as such older projects may not be up-to-date with the guidelines found here. In those circumstances one must use their best judgment about how to integrate these guidelines into older codebases. As a general principle, consistency should favor the local over the global, that is it is more important to be consistent with a function definition than within a file, and more important to be consistent within a file than within a directory, project, etc...
Contents
Development Environment
The primary operating systems supported by Spire projects are Ubuntu 18.02 LTS, Windows 10, and macOS. On all systems, nodejs is used as the build system and webpack is used as the bundler.
For source control, git is used and projects are to be hosted on Spire Trading's Github page.
Each developer is welcome to use an editor of their choice, however Visual Studio Code is recommended.
File Names
Use snake case using all lower case letters for files and directories. The name of a file should correspond to the primary class that it exports.
The default extension for TypeScript files is ts
The extension used for TypeScript files containing JSX is tsx
All files end with a single new line character.
Directory Structure
A project is broken down into a library component, a test component, and one or more application components. These components are referred to as artifacts. The applications go into their own applications directory and the libraries into the library directory. Within each artifact is a build directory containing the build scripts needed to build that artifact. Scripts for POSIX systems are found in the posix sub-directory and Windows build files are within the windows sub-directory. The build scripts specified are setup to download and install any dependencies needed to produce the artifact and the build script which produces the artifact. Typically the setup script is run only once or when a new dependency is introduced to the project, and the build script is run anytime a new build is needed.
Finally there is a top-level build directory for the project as a whole which produces all artifacts by recursively calling each artifact's individual build scripts. Additionally the top-level build directory also includes a setup script which downloads all third party dependencies. Some projects may also opt to include an install script which sets up a suitable development environment, and other scripts for continuous builds, deployment and testing.
Assuming a project named sudoku the following directory structure should be used:
sudoku # Root level directory. build.sh # Script used to build all projects on POSIX. build.bat # Script used to build all projects on Windows. \applications # Contains all applications. \sudoku # Contains the source for the sudoku web application. package.json # The Node JS package description. tsconfig.json # The TypeScript compiler configuration. webpack.config.js # The Webpack configuration. build.sh # Script used to build application on POSIX. build.bat # Script used to build application Windows. \source # Contains the web application source files. \index.html # The HTML file that loads the JavaScript application. \index.tsx # The TypeScript/JSX file containing the application. \library # Contains the common library code. package.json # The Node JS package description. tsconfig.json # The TypeScript compiler configuration. webpack.config.js # The Webpack configuration. build.sh # Script used to build library on POSIX. build.bat # Script used to build library on Windows. \source # Contains the library source files. index.ts # Exports all definitions contained in this directory. \pages # Contains the source code for individual web pages. index.ts # Exports all definitions for every web page defined # in this directory. \landing_page # Contains the source code for the landing page. index.ts # Exports the landing page. landing_page.tsx # The TypeScript/JSX source code for the landing page. \page_2 # Contains the source for for another web page. ... # Structured in a similar manner as the landing page.
An example of the above directory structure containing a basic skeletal implementation of all files can be found here: https://github.com/spiretrading/sudoku
Component Names
Naming components can be difficult. Names are usually prefixed with the context or domain. Most components will fall under one of the following categories.
Field
The primary function of a field is to display data. The data can be edited. It can have a readonly property. It contains a onChange callback.
Examples: DurationField, NumberField
Selection Field
A specialized field. It requires a list of potential values to choose from.
Example: CountrySelectionField
Input
The primary function of a input is to get user input. Inputs usually do not have a initial value unlike fields. Inputs can have a readonly mode, but it is only to be used when the input should not accept input.
Example: SecurityInput
Button
Buttons are a specialized form of input. Only has a onClick callback.
Examples: Button, BurgerButton
Box
A component that displays data that cannot be directly edited.
Syntax
Indentation
Code is indented using 2 spaces per level, tabs are not permitted.
1 function factorial(n: number): number {
2 if(n == 0) {
3 return 1;
4 }
5 return n * factorial(n - 1);
6 }
Line Structure
Statements must end with a semi-colon ';' or a closing brace '}', even in cases where TypeScript allows them to be optional.
Lines are limited to 80 characters. Exceptions to this rule are long imports and long string literals where breaking up the literal into multiple lines is not possible. In order to break up long statements into multiple lines the following rules are used:
- Break the line at a comma, operator or opening bracket.
1 // Correct, line break at comma.
2 f(a, ..., b,
3 c, ..., d);
4
5 // Incorrect, line break after expression.
6 f(a, ..., b
7 , c, ..., d);
8
9 // Correct, line break after operator.
10 a + ... + b +
11 c + ... + d;
12
13 // Incorrect, line break after expression.
14 a + ... + b
15 + c + ... + d;
16
17 // Correct, line break after opening bracket.
18 f(
19 a, ..., b, c, ..., d);
20
21 // Incorrect, line break after function name.
22 f
23 (a, ..., b, c, ..., d);
- For statements that begin new blocks of code (such as if/while/class...), in order to avoid confusing the line continuation with the code block, the line continuation is indented two extra levels. For example consider the following snippet of code:
1 // Incorrect.
2 if(condition_a && condition_b && ... &&
3 condition_c) {
4 console.log('meow');
5 }
The line continuation on line 2 clashes with the code block on line 3. To avoid this clash the above code is indented as follows:
1 // Correct.
2 if(condition_a && condition_b && ... &&
3 condition_c) {
4 console.log('meow');
5 }
The extra level of indentation in the line continuation makes it clear where the condition ends and the code block begins.
Braces
Braces are placed using a variant of the OTBS style. The opening brace is placed on the same line as the declaring statement with one single space preceding it, and the closing brace is placed on a line of its own at the same level of indentation as the declaring statement. For if statements, the else/else if is placed on the same line as the closing brace. For do/while loops, the while is placed on the same line as the closing brace.
Examples:
1 // Correct.
2 if(<cond>) {
3 <body>
4 } else {
5 <default>
6 }
7
8 // Correct.
9 do {
10 <body>
11 } while(<cond>);
12
13 // Incorrect.
14 if(<cond>)
15 {
16 <body>
17 }
18 else
19 {
20 <default>
21 }
22
23 // Incorrect.
24 do
25 {
26 <body>
27 }
28 while(<cond>);
Spacing
The following are correct use cases for white spaces:
- Used for indentation.
- Used to surround binary operations.
1 // Correct.
2 const x = a + b;
3 const y = 5;
4
5 //! Incorrect
6 const x = a+b;
7 const y=5;
8 const z= 5;
- One space is placed after a comma.
1 // Correct.
2 f(1, 2);
3 function g(a: number, b: number): number;
4
5 //! Incorrect
6 f(1,2);
7 function g(a:number,b:number);
Naming
Camel case is used for naming whereas variables and functions use mixedCase and namespaces, modules, and types use CapWords. Names should be descriptive but should avoid being verbose.
Classes and variables should be given the name of a noun and functions the name of a verb. Functions that return a boolean or boolean variables should be named in the form of a question starting with isX or hasX.
Imports
Imports are listed in order of most global to local. Where dependencies have the same locality, then they are ordered alphabetically. For example, the first group of dependencies to be imported are third party packages which are listed in alphabetical order based on the package name. In the above snippet the third party dependencies are jquery, react and react-router-dom which are imported in alphabetical order. Then local dependencies are imported where directories further up the hierarchy are imported first. That is, the directory '../../..' which is three levels up is imported before the directory '..' which is only one level up.
When multiple names are imported from a package, they must be listed in alphabetical order.
1 import * as $ from 'jquery';
2 import * as React from 'react';
3 import * as Router from 'react-router-dom';
4 import {HBoxLayout, Padding, VBoxLayout} from '../../..';
5 import {Model} from '..';
Declarations
Any declaration (such as a function or class) that is intended to be imported by another file must be documented using JSDoc style. If the definition is local then that documentation should be omitted entirely.
Any documentation should be preceded by a blank line. In the code snippet, Properties is part of the public API so it is documented along with all of its fields, but the State is internal to the file so no aspect of it is documented.
1 /** Stores the React properties used to render a Page. */
2 export interface Properties {
3
4 /** The model used to display the page. */
5 model: Model;
6 }
7
8 export interface State {
9 redirect: string;
10 isLoading: boolean;
11 }
Variable Declarations
Variables are declared so that only one variable is declared per line, prefer declaring variables using const when possible, and deferring to let otherwise, variables should always be initialized at the point of their declaration.
For complex initialization of values, use an immediately invoked lambda expression as follows:
1 const value = (() => {
2 if(condition) {
3 return 123;
4 }
5 return 321;
6 })();
Example
Below is a full example combining numerous elements of the style guide to define a typical React component. It should be used as a general reference for how code is laid out, structured, and properly spaced.
1 import * as $ from 'jquery';
2 import * as React from 'react';
3 import * as Router from 'react-router-dom';
4 import {HBoxLayout, Padding, VBoxLayout} from '..';
5 import {Model} from '.';
6
7 /** Stores the React properties used to render a Page. */
8 export interface Properties {
9
10 /** The model used to display the page. */
11 model: Model;
12 }
13
14 export interface State {
15 redirect: string;
16 isLoading: boolean;
17 }
18
19 /** Displays an HTML page. */
20 export class Page extends React.Component<Properties, State> {
21 constructor(props: Properties) {
22 super(props);
23 this.state = {
24 redirect: '',
25 isLoading: true
26 };
27 }
28
29 public componentWillMount(): void {
30 this.props.model.load().then(
31 () => {
32 this.setState({
33 isLoading: false
34 });
35 });
36 }
37
38 public render(): JSX.Element {
39 if(this.state.redirect) {
40 return <Router.Redirect push to={this.state.redirect}/>;
41 } else if(this.state.isLoading) {
42 return <LoadingPage/>;
43 }
44 const style = {
45 backgroundColor: '#FFFFFF',
46 font: 'Roboto 14px'
47 };
48 return (
49 <div style={style}>
50 <h1>Hello world!</h1>
51 <img src='cat.jpg'/>
52 </div>);
53 }
54 }
Additional References
The bulk of this style guide focuses on syntax rather than good programming practices. The following list are reference materials for best practices on writing portable, efficient, and clean TypeScript: