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. \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 # Files/scripts used to build the console tic-tac-toe game. \posix # Contains scripts to build on POSIX systems. \build.sh # Script used to build the application. \setup.sh # Script used to install all dependencies. \windows # Contains scripts to build on Windows. \build.bat # Script used to build the application. \setup.bat # Script used to install all dependencies. \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. \build # Contains build scripts to build the entire project. \posix # Contains scripts to build on POSIX systems. \build.sh # Builds all libraries and applications. \local_build.sh # A helper script containing common build functions. \setup.sh # Installs all third party dependencies. \windows # Contains scripts to build on Windows. \build.bat # Builds all libraries and applications. \setup.bat # Installs all third party dependencies. \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 # Files/script used to build the library. \posix # Contains scripts to build on POSIX systems. \build.sh # Script used to build the application. \setup.sh # Script used to install all dependencies. \windows # Contains scripts to build on Windows. \build.bat # Script used to build the application. \setup.bat # Script used to install all dependencies. \source # Contains the library source files (.cpp) 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
Layout
Example
Below is a full example of how a typical React component is structured in TypeScript. A breakdown of the structure is provided afterwards.
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 }
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 }
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
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);
Syntax
That is they are declared as inline unless they are function templates in which case the inline specified is omitted.
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 })();
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: