mirror of
https://github.com/google/styleguide.git
synced 2024-03-22 13:11:43 +08:00
2708 lines
94 KiB
HTML
2708 lines
94 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<title>Google TypeScript Style Guide</title>
|
|
<link rel="stylesheet" href="javaguide.css">
|
|
<script src="include/styleguide.js"></script>
|
|
<link rel="shortcut icon" href="https://www.google.com/favicon.ico">
|
|
<script src="include/jsguide.js"></script>
|
|
</head>
|
|
<body onload="initStyleGuide();">
|
|
<div id="content">
|
|
<h1>Google TypeScript Style Guide</h1>
|
|
<h1>TypeScript Style Guide</h1>
|
|
|
|
<p>go/tsstyle</p>
|
|
|
|
<section markdown="1">
|
|
This guide is based on the internal Google TypeScript style guide, but it has
|
|
been slightly adjusted to remove Google-internal sections. Google's internal
|
|
environment has different constraints on TypeScript than you might find outside
|
|
of Google. The advice here is specifically useful for people authoring code
|
|
they intend to import into Google, but otherwise may not apply in your external
|
|
environment.
|
|
|
|
<p>There is no automatic deployment process for this version as it's pushed
|
|
on-demand by volunteers.
|
|
</p></section>
|
|
|
|
<p>This Style Guide uses <a href="https://tools.ietf.org/html/rfc2119">RFC 2119</a>
|
|
terminology when using the phrases <em>must</em>, <em>must not</em>, <em>should</em>, <em>should not</em>,
|
|
and <em>may</em>. All examples given are non-normative and serve only to illustrate the
|
|
normative language of the style guide.</p>
|
|
|
|
<h2 id="syntax">Syntax</h2>
|
|
|
|
<h3 id="identifiers">Identifiers</h3>
|
|
|
|
<p>Identifiers <em>must</em> use only ASCII letters, digits, underscores (for constants
|
|
and structured test method names), and the '\(' sign. Thus each valid identifier
|
|
name is matched by the regular expression `[\)\w]+`.</p>
|
|
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>Style</th>
|
|
<th>Category</th>
|
|
</tr>
|
|
</thead>
|
|
|
|
<tbody>
|
|
<tr>
|
|
<td><code>UpperCamelCase</code>
|
|
</td>
|
|
<td>class / interface / type / enum / decorator / type
|
|
parameters</td>
|
|
</tr>
|
|
<tr>
|
|
<td><code>lowerCamelCase</code>
|
|
</td>
|
|
<td>variable / parameter / function / method / property /
|
|
module alias</td>
|
|
</tr>
|
|
<tr>
|
|
<td><code>CONSTANT_CASE</code>
|
|
</td>
|
|
<td>global constant values, including enum values. See
|
|
<a href="#identifiers-constants">Constants</a> below.</td>
|
|
</tr>
|
|
<tr>
|
|
<td><code>#ident</code></td>
|
|
<td>private identifiers are never used.</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
|
|
<h4 id="identifiers-abbreviations">Abbreviations</h4>
|
|
|
|
<p>
|
|
Treat abbreviations like acronyms in names as whole words, i.e. use
|
|
<code>loadHttpUrl</code>, not <del><code>loadHTTPURL</code></del>, unless required by a platform name (e.g.
|
|
<code>XMLHttpRequest</code>).</p>
|
|
|
|
<h4 id="identifiers-dollar-sign">Dollar sign</h4>
|
|
|
|
<p>Identifiers <em>should not</em> generally use <code>$</code>, except when aligning with naming
|
|
conventions for third party frameworks. <a href="#naming-style">See below</a> for more on
|
|
using <code>$</code> with <code>Observable</code> values.</p>
|
|
|
|
<h4 id="identifiers-type-parameters">Type parameters</h4>
|
|
|
|
<p>Type parameters, like in <code>Array<T></code>, <em>may</em> use a single upper case character
|
|
(<code>T</code>) or <code>UpperCamelCase</code>.</p>
|
|
|
|
<h4 id="identifiers-test-names">Test names</h4>
|
|
|
|
<p>Test method names in Closure <code>testSuite</code>s and similar xUnit-style test
|
|
frameworks <em>may</em> be structured with <code>_</code> separators, e.g. <code>testX_whenY_doesZ()</code>.</p>
|
|
|
|
<h4 id="identifiers-underscore-prefix-suffix"><code>_</code> prefix/suffix</h4>
|
|
|
|
<p>Identifiers must not use <code>_</code> as a prefix or suffix.</p>
|
|
|
|
<p>This also means that <code>_</code> <em>must not</em> be used as an identifier by itself (e.g. to
|
|
indicate a parameter is unused).</p>
|
|
|
|
<blockquote>
|
|
<p>Tip: If you only need some of the elements from an array (or TypeScript
|
|
tuple), you can insert extra commas in a destructuring statement to ignore
|
|
in-between elements:</p>
|
|
|
|
<pre><code class="language-ts">const [a, , b] = [1, 5, 10]; // a <- 1, b <- 10
|
|
</code></pre>
|
|
</blockquote>
|
|
|
|
<h4 id="identifiers-imports">Imports</h4>
|
|
|
|
<p>Module namespace imports are <code>lowerCamelCase</code> while files are <code>snake_case</code>,
|
|
which means that imports correctly will not match in casing style, such as</p>
|
|
|
|
<pre><code class="language-ts good">import * as fooBar from './foo_bar';
|
|
</code></pre>
|
|
|
|
<p>Some libraries might commonly use a namespace import prefix that violates this
|
|
naming scheme, but overbearingly common open source use makes the violating
|
|
style more readable. The only libraries that currently fall under this exception
|
|
are:</p>
|
|
|
|
<ul>
|
|
<li><a href="https://jquery.com/">jquery</a>, using the <code>$</code> prefix</li>
|
|
<li><a href="https://threejs.org/">threejs</a>, using the <code>THREE</code> prefix</li>
|
|
</ul>
|
|
|
|
<h4 id="identifiers-constants">Constants</h4>
|
|
|
|
<p><strong>Immutable</strong>: <code>CONSTANT_CASE</code> indicates that a value is <em>intended</em> to not be
|
|
changed, and <em>may</em> be used for values that can technically be modified (i.e.
|
|
values that are not deeply frozen) to indicate to users that they must not be
|
|
modified.</p>
|
|
|
|
<pre><code class="language-ts good">const UNIT_SUFFIXES = {
|
|
'milliseconds': 'ms',
|
|
'seconds': 's',
|
|
};
|
|
// Even though per the rules of JavaScript UNIT_SUFFIXES is
|
|
// mutable, the uppercase shows users to not modify it.
|
|
</code></pre>
|
|
|
|
<p>A constant can also be a <code>static readonly</code> property of a class.</p>
|
|
|
|
<pre><code class="language-ts good">class Foo {
|
|
private static readonly MY_SPECIAL_NUMBER = 5;
|
|
|
|
bar() {
|
|
return 2 * Foo.MY_SPECIAL_NUMBER;
|
|
}
|
|
}
|
|
</code></pre>
|
|
|
|
<p><strong>Global</strong>: Only symbols declared on the module level, static fields of module
|
|
level classes, and values of module level enums, <em>may</em> use <code>CONST_CASE</code>. If a
|
|
value can be instantiated more than once over the lifetime of the program (e.g.
|
|
a local variable declared within a function, or a static field on a class nested
|
|
in a function) then it <em>must</em> use <code>lowerCamelCase</code>.</p>
|
|
|
|
<p>If a value is an arrow function that implements an interface, then it <em>may</em> be
|
|
declared <code>lowerCamelCase</code>.</p>
|
|
|
|
<h4 id="aliases">Aliases</h4>
|
|
|
|
<p>When creating a local-scope alias of an existing symbol, use the format of the
|
|
existing identifier. The local alias <em>must</em> match the existing naming and format
|
|
of the source. For variables use <code>const</code> for your local aliases, and for class
|
|
fields use the <code>readonly</code> attribute.</p>
|
|
|
|
<blockquote>
|
|
<p>Note: If you're creating an alias just to expose it to a template in your
|
|
framework of choice, remember to also apply the proper
|
|
<a href="#properties-used-outside-of-class-lexical-scope">access modifiers</a>.</p>
|
|
</blockquote>
|
|
|
|
<pre><code class="language-ts good">const {Foo} = SomeType;
|
|
const CAPACITY = 5;
|
|
|
|
class Teapot {
|
|
readonly BrewStateEnum = BrewStateEnum;
|
|
readonly CAPACITY = CAPACITY;
|
|
}
|
|
</code></pre>
|
|
|
|
<h4 id="naming-style">Naming style</h4>
|
|
|
|
<p>TypeScript expresses information in types, so names <em>should not</em> be decorated
|
|
with information that is included in the type. (See also
|
|
<a href="https://testing.googleblog.com/2017/10/code-health-identifiernamingpostforworl.html">Testing Blog</a>
|
|
for more about what
|
|
not to include.)</p>
|
|
|
|
<p>Some concrete examples of this rule:</p>
|
|
|
|
<ul>
|
|
<li>Do not use trailing or leading underscores for private properties or
|
|
methods.</li>
|
|
<li>Do not use the <code>opt_</code> prefix for optional parameters.
|
|
<ul>
|
|
<li>For accessors, see <a href="#getters-and-setters-accessors">accessor rules</a>
|
|
below.</li>
|
|
</ul></li>
|
|
<li>Do not mark interfaces specially (<del><code>IMyInterface</code></del> or
|
|
<del><code>MyFooInterface</code></del>) unless it's idiomatic in its
|
|
environment. When
|
|
introducing an interface for a class, give it a name that expresses why the
|
|
interface exists in the first place (e.g. <code>class TodoItem</code> and <code>interface
|
|
TodoItemStorage</code> if the interface expresses the format used for
|
|
storage/serialization in JSON).</li>
|
|
<li>Suffixing <code>Observable</code>s with <code>$</code> is a common external convention and can
|
|
help resolve confusion regarding observable values vs concrete values.
|
|
Judgement on whether this is a useful convention is left up to individual
|
|
teams, but <em>should</em> be consistent within projects.</li>
|
|
</ul>
|
|
|
|
<h4 id="descriptive-names">Descriptive names</h4>
|
|
|
|
<p>Names <em>must</em> be descriptive and clear to a new reader. Do not use abbreviations
|
|
that are ambiguous or unfamiliar to readers outside your project, and do not
|
|
abbreviate by deleting letters within a word.</p>
|
|
|
|
<ul>
|
|
<li><strong>Exception</strong>: Variables that are in scope for 10 lines or fewer, including
|
|
arguments that are <em>not</em> part of an exported API, <em>may</em> use short (e.g.
|
|
single letter) variable names.</li>
|
|
</ul>
|
|
|
|
<h3 id="file-encoding-utf-8">File encoding: UTF-8</h3>
|
|
|
|
<p>For non-ASCII characters, use the actual Unicode character (e.g. <code>∞</code>). For
|
|
non-printable characters, the equivalent hex or Unicode escapes (e.g. <code>\u221e</code>)
|
|
can be used along with an explanatory comment.</p>
|
|
|
|
<pre><code class="language-ts good">// Perfectly clear, even without a comment.
|
|
const units = 'μs';
|
|
|
|
// Use escapes for non-printable characters.
|
|
const output = '\ufeff' + content; // byte order mark
|
|
</code></pre>
|
|
|
|
<pre><code class="language-ts bad">// Hard to read and prone to mistakes, even with the comment.
|
|
const units = '\u03bcs'; // Greek letter mu, 's'
|
|
|
|
// The reader has no idea what this is.
|
|
const output = '\ufeff' + content;
|
|
</code></pre>
|
|
|
|
<h3 id="syntax-no-line-continuations">No line continuations</h3>
|
|
|
|
<p>Do not use <em>line continuations</em> (that is, ending a line inside a string literal
|
|
with a backslash) in either ordinary or template string literals. Even though
|
|
ES5 allows this, it can lead to tricky errors if any trailing whitespace comes
|
|
after the slash, and is less obvious to readers.</p>
|
|
|
|
<p>Disallowed:</p>
|
|
|
|
<pre><code class="language-ts bad">const LONG_STRING = 'This is a very long string that far exceeds the 80 \
|
|
column limit. It unfortunately contains long stretches of spaces due \
|
|
to how the continued lines are indented.';
|
|
</code></pre>
|
|
|
|
<p>Instead, write</p>
|
|
|
|
<pre><code class="language-ts good">const LONG_STRING = 'This is a very long string that far exceeds the 80 ' +
|
|
'column limit. It does not contain long stretches of spaces since ' +
|
|
'the concatenated strings are cleaner.';
|
|
</code></pre>
|
|
|
|
<h3 id="comments-documentation">Comments & Documentation</h3>
|
|
|
|
<h4 id="jsdoc-vs-comments">JSDoc vs comments</h4>
|
|
|
|
<p>There are two types of comments, JSDoc (<code>/** ... */</code>) and non-JSDoc ordinary
|
|
comments (<code>// ...</code> or <code>/* ... */</code>).</p>
|
|
|
|
<ul>
|
|
<li>Use <code>/** JSDoc */</code> comments for documentation, i.e. comments a user of the
|
|
code should read.</li>
|
|
<li>Use <code>// line comments</code> for implementation comments, i.e. comments that only
|
|
concern the implementation of the code itself.</li>
|
|
</ul>
|
|
|
|
<p>JSDoc comments are understood by tools (such as editors and documentation
|
|
generators), while ordinary comments are only for other humans.</p>
|
|
|
|
<h4 id="jsdoc-rules-follow-the-javascript-style">JSDoc rules follow the JavaScript style</h4>
|
|
|
|
<p>In general, follow the
|
|
|
|
<a href="https://google.github.io/styleguide/jsguide.html#jsdoc">JavaScript style guide's rules for JSDoc</a>,
|
|
sections 7.1 - 7.5. The remainder of this section describes exceptions to those
|
|
rules.</p>
|
|
|
|
<h4 id="document-all-top-level-exports-of-modules">Document all top-level exports of modules</h4>
|
|
|
|
<p>Use <code>/** JSDoc */</code> comments to communicate information to the users of your
|
|
code. Avoid merely restating the property or parameter name. You <em>should</em> also
|
|
document all properties and methods (exported/public or not) whose purpose is
|
|
not immediately obvious from their name, as judged by your reviewer.</p>
|
|
|
|
<p>Exception: Symbols that are only exported to be consumed by tooling, such as
|
|
@NgModule classes, do not require comments.</p>
|
|
|
|
<h4 id="omit-comments-that-are-redundant-with-typescript">Omit comments that are redundant with TypeScript</h4>
|
|
|
|
<p><a id="do-not-use-override"></a></p>
|
|
|
|
<p>For example, do not declare types in <code>@param</code> or <code>@return</code> blocks, do not write
|
|
<code>@implements</code>, <code>@enum</code>, <code>@private</code>, <code>@override</code> etc. on code that uses the
|
|
<code>implements</code>, <code>enum</code>, <code>private</code>, <code>override</code> etc. keywords.</p>
|
|
|
|
<h4 id="redundant-comments">Make comments that actually add information</h4>
|
|
|
|
<p>For non-exported symbols, sometimes the name and type of the function or
|
|
parameter is enough. Code will <em>usually</em> benefit from more documentation than
|
|
just variable names though!</p>
|
|
|
|
<ul>
|
|
<li><p>Avoid comments that just restate the parameter name and type, e.g.</p>
|
|
|
|
<pre><code class="language-ts bad">/** @param fooBarService The Bar service for the Foo application. */
|
|
</code></pre></li>
|
|
<li><p>Because of this rule, <code>@param</code> and <code>@return</code> lines are only required when
|
|
they add information, and <em>may</em> otherwise be omitted.</p>
|
|
|
|
<pre><code class="language-ts good">/**
|
|
* POSTs the request to start coffee brewing.
|
|
* @param amountLitres The amount to brew. Must fit the pot size!
|
|
*/
|
|
brew(amountLitres: number, logger: Logger) {
|
|
// ...
|
|
}
|
|
</code></pre></li>
|
|
</ul>
|
|
|
|
<h4 id="parameter-property-comments">Parameter property comments</h4>
|
|
|
|
<p>A
|
|
<a href="https://www.typescriptlang.org/docs/handbook/2/classes.html#parameter-properties">parameter property</a>
|
|
is a constructor parameter that is prefixed by one of the modifiers <code>private</code>,
|
|
<code>protected</code>, <code>public</code>, or <code>readonly</code>. A parameter property declares both a
|
|
parameter and an instance property, and implicitly assigns into it. For example,
|
|
<code>constructor(private readonly foo: Foo)</code>, declares that the constructor takes a
|
|
parameter <code>foo</code>, but also declares a private readonly property <code>foo</code>, and
|
|
assigns the parameter into that property before executing the remainder of the
|
|
constructor.</p>
|
|
|
|
<p>To document these fields, use JSDoc's <code>@param</code> annotation. Editors display the
|
|
description on constructor calls and property accesses.</p>
|
|
|
|
<pre><code class="language-ts good">/** This class demonstrates how parameter properties are documented. */
|
|
class ParamProps {
|
|
/**
|
|
* @param percolator The percolator used for brewing.
|
|
* @param beans The beans to brew.
|
|
*/
|
|
constructor(
|
|
private readonly percolator: Percolator,
|
|
private readonly beans: CoffeeBean[]) {}
|
|
}
|
|
</code></pre>
|
|
|
|
<pre><code class="language-ts good">/** This class demonstrates how ordinary fields are documented. */
|
|
class OrdinaryClass {
|
|
/** The bean that will be used in the next call to brew(). */
|
|
nextBean: CoffeeBean;
|
|
|
|
constructor(initialBean: CoffeeBean) {
|
|
this.nextBean = initialBean;
|
|
}
|
|
}
|
|
</code></pre>
|
|
|
|
<h4 id="comments-when-calling-a-function">Comments when calling a function</h4>
|
|
|
|
<p>If needed, document parameters at call sites inline using block comments. Also
|
|
consider named parameters using object literals and destructuring. The exact
|
|
formatting and placement of the comment is not prescribed.</p>
|
|
|
|
<pre><code class="language-ts good">// Inline block comments for parameters that'd be hard to understand:
|
|
new Percolator().brew(/* amountLitres= */ 5);
|
|
// Also consider using named arguments and destructuring parameters (in brew's declaration):
|
|
new Percolator().brew({amountLitres: 5});
|
|
</code></pre>
|
|
|
|
<pre><code class="language-ts good">/** An ancient {@link CoffeeBrewer} */
|
|
export class Percolator implements CoffeeBrewer {
|
|
/**
|
|
* Brews coffee.
|
|
* @param amountLitres The amount to brew. Must fit the pot size!
|
|
*/
|
|
brew(amountLitres: number) {
|
|
// This implementation creates terrible coffee, but whatever.
|
|
// TODO(b/12345): Improve percolator brewing.
|
|
}
|
|
}
|
|
</code></pre>
|
|
|
|
<h4 id="place-documentation-prior-to-decorators">Place documentation prior to decorators</h4>
|
|
|
|
<p>When a class, method, or property have both decorators like <code>@Component</code> and
|
|
JsDoc, please make sure to write the JsDoc before the decorator.</p>
|
|
|
|
<ul>
|
|
<li><p>Do not write JsDoc between the Decorator and the decorated statement.</p>
|
|
|
|
<pre><code class="language-ts bad">@Component({
|
|
selector: 'foo',
|
|
template: 'bar',
|
|
})
|
|
/** Component that prints "bar". */
|
|
export class FooComponent {}
|
|
</code></pre></li>
|
|
<li><p>Write the JsDoc block before the Decorator.</p>
|
|
|
|
<pre><code class="language-ts good">/** Component that prints "bar". */
|
|
@Component({
|
|
selector: 'foo',
|
|
template: 'bar',
|
|
})
|
|
export class FooComponent {}
|
|
</code></pre></li>
|
|
</ul>
|
|
|
|
<h2 id="language-rules">Language Rules</h2>
|
|
|
|
<p>TypeScript language features which are not discussed in this style guide <em>may</em>
|
|
be used with no recommendations of their usage.</p>
|
|
|
|
<h3 id="visibility">Visibility</h3>
|
|
|
|
<p>Restricting visibility of properties, methods, and entire types helps with
|
|
keeping code decoupled.</p>
|
|
|
|
<ul>
|
|
<li>Limit symbol visibility as much as possible.</li>
|
|
<li>Consider converting private methods to non-exported functions within the
|
|
same file but outside of any class, and moving private properties into a
|
|
separate, non-exported class.</li>
|
|
<li>TypeScript symbols are public by default. Never use the <code>public</code> modifier
|
|
except when declaring non-readonly public parameter properties (in
|
|
constructors).</li>
|
|
</ul>
|
|
|
|
<pre><code class="language-ts bad">class Foo {
|
|
public bar = new Bar(); // BAD: public modifier not needed
|
|
|
|
constructor(public readonly baz: Baz) {} // BAD: readonly implies it's a property which defaults to public
|
|
}
|
|
</code></pre>
|
|
|
|
<pre><code class="language-ts good">class Foo {
|
|
bar = new Bar(); // GOOD: public modifier not needed
|
|
|
|
constructor(public baz: Baz) {} // public modifier allowed
|
|
}
|
|
</code></pre>
|
|
|
|
<p>See also <a href="#export-visibility">export visibility</a> below.</p>
|
|
|
|
<h3 id="constructors">Constructors</h3>
|
|
|
|
<p>Constructor calls <em>must</em> use parentheses, even when no arguments are passed:</p>
|
|
|
|
<pre><code class="language-ts bad">const x = new Foo;
|
|
</code></pre>
|
|
|
|
<pre><code class="language-ts good">const x = new Foo();
|
|
</code></pre>
|
|
|
|
<p>It is unnecessary to provide an empty constructor or one that simply delegates
|
|
into its parent class because ES2015 provides a default class constructor if one
|
|
is not specified. However constructors with parameter properties, visibility
|
|
modifiers or parameter decorators <em>should not</em> be omitted even if the body of
|
|
the constructor is empty.</p>
|
|
|
|
<pre><code class="language-ts bad">class UnnecessaryConstructor {
|
|
constructor() {}
|
|
}
|
|
</code></pre>
|
|
|
|
<pre><code class="language-ts bad">class UnnecessaryConstructorOverride extends Base {
|
|
constructor(value: number) {
|
|
super(value);
|
|
}
|
|
}
|
|
</code></pre>
|
|
|
|
<pre><code class="language-ts good">class DefaultConstructor {
|
|
}
|
|
|
|
class ParameterProperties {
|
|
constructor(private myService) {}
|
|
}
|
|
|
|
class ParameterDecorators {
|
|
constructor(@SideEffectDecorator myService) {}
|
|
}
|
|
|
|
class NoInstantiation {
|
|
private constructor() {}
|
|
}
|
|
</code></pre>
|
|
|
|
<h3 id="class-members">Class Members</h3>
|
|
|
|
<h4 id="private-fields">No <code>#private</code> fields</h4>
|
|
|
|
<p>Do not use private fields (also known as private identifiers):</p>
|
|
|
|
<pre><code class="language-ts bad">class Clazz {
|
|
#ident = 1;
|
|
}
|
|
</code></pre>
|
|
|
|
<p>Instead, use TypeScript's visibility annotations:</p>
|
|
|
|
<pre><code class="language-ts good">class Clazz {
|
|
private ident = 1;
|
|
}
|
|
</code></pre>
|
|
|
|
<section class="zippy" markdown="1">
|
|
Why?
|
|
|
|
<p> Private identifiers cause substantial emit size and
|
|
performance regressions when down-leveled by TypeScript, and are unsupported
|
|
before ES2015. They can only be downleveled to ES2015, not lower. At the same
|
|
time, they do not offer substantial benefits when static type checking is used
|
|
to enforce visibility.</p>
|
|
|
|
</section>
|
|
|
|
<h4 id="use-readonly">Use <code>readonly</code></h4>
|
|
|
|
<p>Mark properties that are never reassigned outside of the constructor with the
|
|
<code>readonly</code> modifier (these need not be deeply immutable).</p>
|
|
|
|
<h4 id="parameter-properties">Parameter properties</h4>
|
|
|
|
<p>Rather than plumbing an obvious initializer through to a class member, use a
|
|
TypeScript
|
|
<a href="https://www.typescriptlang.org/docs/handbook/2/classes.html#parameter-properties">parameter property</a>.</p>
|
|
|
|
<pre><code class="language-ts bad">class Foo {
|
|
private readonly barService: BarService;
|
|
|
|
constructor(barService: BarService) {
|
|
this.barService = barService;
|
|
}
|
|
}
|
|
</code></pre>
|
|
|
|
<pre><code class="language-ts good">class Foo {
|
|
constructor(private readonly barService: BarService) {}
|
|
}
|
|
</code></pre>
|
|
|
|
<p>If the parameter property needs documentation,
|
|
<a href="#parameter-property-comments">use an <code>@param</code> JSDoc tag</a>.</p>
|
|
|
|
<h4 id="field-initializers">Field initializers</h4>
|
|
|
|
<p>If a class member is not a parameter, initialize it where it's declared, which
|
|
sometimes lets you drop the constructor entirely.</p>
|
|
|
|
<pre><code class="language-ts bad">class Foo {
|
|
private readonly userList: string[];
|
|
constructor() {
|
|
this.userList = [];
|
|
}
|
|
}
|
|
</code></pre>
|
|
|
|
<pre><code class="language-ts good">class Foo {
|
|
private readonly userList: string[] = [];
|
|
}
|
|
</code></pre>
|
|
|
|
<h4 id="properties-used-outside-of-class-lexical-scope">Properties used outside of class lexical scope</h4>
|
|
|
|
<p>Properties used from outside the lexical scope of their containing class, such
|
|
as an Angular component's properties used from a template, <em>must not</em> use
|
|
<code>private</code> visibility, as they are used outside of the lexical scope of their
|
|
containing class.</p>
|
|
|
|
<p>Use either <code>protected</code> or <code>public</code> as appropriate to the property in question.
|
|
Angular and AngularJS template properties should use <code>protected</code>, but Polymer
|
|
should use <code>public</code>.</p>
|
|
|
|
<p>TypeScript code <em>must not</em> use <code>obj['foo']</code> to bypass the visibility of a
|
|
property. See
|
|
<a href="http://go/typescript-testing#export-private-visibility">testing and private visibility</a>
|
|
if you want to access protected fields from a test.</p>
|
|
|
|
<section class="zippy" markdown="1">
|
|
Why?
|
|
|
|
<p>When a property is <code>private</code>, you are declaring to both automated systems and
|
|
humans that the property accesses are scoped to the methods of the declaring
|
|
class, and they will rely on that. For example, a check for unused code will
|
|
flag a private property that appears to be unused, even if some other file
|
|
manages to bypass the visibility restriction.</p>
|
|
|
|
<p>Though it might appear that <code>obj['foo']</code> can bypass visibility in the TypeScript
|
|
compiler, this pattern can be broken by rearranging the build rules,
|
|
and also violates <a href="#optimization-compatibility">optimization compatibility</a>.
|
|
</p></section>
|
|
|
|
<h4>Getters and Setters (Accessors)</h4>
|
|
|
|
<p>Getters and setters for class members <em>may</em> be used. The getter method <em>must</em> be
|
|
a <a href="https://en.wikipedia.org/wiki/Pure_function">pure function</a> (i.e., result is
|
|
consistent and has no side effects). They are also useful as a means of
|
|
restricting the visibility of internal or verbose implementation details (shown
|
|
below).</p>
|
|
|
|
<pre><code class="language-ts good">class Foo {
|
|
constructor(private readonly someService: SomeService) {}
|
|
|
|
get someMember(): string {
|
|
return this.someService.someVariable;
|
|
}
|
|
|
|
set someMember(newValue: string) {
|
|
this.someService.someVariable = newValue;
|
|
}
|
|
}
|
|
</code></pre>
|
|
|
|
<p>If an accessor is used to hide a class property, the hidden property <em>may</em> be
|
|
prefixed or suffixed with any whole word, like <code>internal</code> or <code>wrapped</code>. When
|
|
using these private properties, access the value through the accessor whenever
|
|
possible. At least one accessor for a property <em>must</em> be non-trivial: do not
|
|
define <q>pass-through</q> accessors only for the purpose of hiding a property.
|
|
Instead, make the property public (or consider making it <code>readonly</code> rather than
|
|
just defining a getter with no setter).</p>
|
|
|
|
<pre><code class="language-ts good">class Foo {
|
|
private wrappedBar = '';
|
|
get bar() {
|
|
return this.wrappedBar || 'bar';
|
|
}
|
|
|
|
set bar(wrapped: string) {
|
|
this.wrappedBar = wrapped.trim();
|
|
}
|
|
}
|
|
</code></pre>
|
|
|
|
<pre><code class="language-ts bad">class Bar {
|
|
private barInternal = '';
|
|
// Neither of these accessors have logic, so just make bar public.
|
|
get bar() {
|
|
return this.barInternal;
|
|
}
|
|
|
|
set bar(value: string) {
|
|
this.barInternal = value;
|
|
}
|
|
}
|
|
</code></pre>
|
|
|
|
<h4 id="static-this">Static this references</h4>
|
|
|
|
<p>Code <em>must not</em> use <code>this</code> in a static context.</p>
|
|
|
|
<p>JavaScript allows accessing static fields through <code>this</code>. Different from other
|
|
languages, static fields are also inherited.</p>
|
|
|
|
<pre><code class="bad">class ShoeStore {
|
|
static storage: Storage = ...;
|
|
|
|
static isAvailable(s: Shoe) {
|
|
// Bad: do not use `this` in a static method.
|
|
return this.storage.has(s.id);
|
|
}
|
|
}
|
|
|
|
class EmptyShoeStore extends ShoeStore {
|
|
static storage: Storage = EMPTY_STORE; // overrides storage from ShoeStore
|
|
}
|
|
</code></pre>
|
|
|
|
<section class="zippy">
|
|
Why?
|
|
|
|
<p>This code is generally surprising: authors might not expect that static fields
|
|
can be accessed through the this pointer, and might be surprised to find that
|
|
they can be overridden - this feature is not commonly used.</p>
|
|
|
|
<p>This code also encourages an anti-pattern of having substantial static state,
|
|
which causes problems with testability.</p>
|
|
|
|
</section>
|
|
|
|
<h3 id="primitive-types-wrapper-classes">Primitive Types & Wrapper Classes</h3>
|
|
|
|
<p>TypeScript code <em>must not</em> instantiate the wrapper classes for the primitive
|
|
types <code>String</code>, <code>Boolean</code>, and <code>Number</code>. Wrapper classes have surprising
|
|
behavior, such as <code>new Boolean(false)</code> evaluating to <code>true</code>.</p>
|
|
|
|
<pre><code class="language-ts bad">const s = new String('hello');
|
|
const b = new Boolean(false);
|
|
const n = new Number(5);
|
|
</code></pre>
|
|
|
|
<pre><code class="language-ts good">const s = 'hello';
|
|
const b = false;
|
|
const n = 5;
|
|
</code></pre>
|
|
|
|
<h3 id="array-constructor">Array constructor</h3>
|
|
|
|
<p>TypeScript code <em>must not</em> use the <code>Array()</code> constructor, with or without <code>new</code>.
|
|
It has confusing and contradictory usage:</p>
|
|
|
|
|
|
|
|
<pre><code class="language-ts bad">const a = new Array(2); // [undefined, undefined]
|
|
const b = new Array(2, 3); // [2, 3];
|
|
</code></pre>
|
|
|
|
|
|
|
|
<p>Instead, always use bracket notation to initialize arrays, or <code>from</code> to
|
|
initialize an <code>Array</code> with a certain size:</p>
|
|
|
|
<pre><code class="language-ts good">const a = [2];
|
|
const b = [2, 3];
|
|
|
|
// Equivalent to Array(2):
|
|
const c = [];
|
|
c.length = 2;
|
|
|
|
// [0, 0, 0, 0, 0]
|
|
Array.from<number>({length: 5}).fill(0);
|
|
</code></pre>
|
|
|
|
<h3 id="type-coercion">Type coercion</h3>
|
|
|
|
<p>TypeScript code <em>may</em> use the <code>String()</code> and <code>Boolean()</code> (note: no <code>new</code>!)
|
|
functions, string template literals, or <code>!!</code> to coerce types.</p>
|
|
|
|
<pre><code class="language-ts good">const bool = Boolean(false);
|
|
const str = String(aNumber);
|
|
const bool2 = !!str;
|
|
const str2 = `result: ${bool2}`;
|
|
</code></pre>
|
|
|
|
<p>Values of enum types (including unions of enum types and other types) <em>must not</em>
|
|
be converted to booleans with <code>Boolean()</code> or <code>!!</code>, and must instead be compared
|
|
explicitly with comparison operators.</p>
|
|
|
|
<pre><code class="language-ts bad">enum SupportLevel {
|
|
NONE,
|
|
BASIC,
|
|
ADVANCED,
|
|
}
|
|
|
|
const level: SupportLevel = ...;
|
|
let enabled = Boolean(level);
|
|
|
|
const maybeLevel: SupportLevel|undefined = ...;
|
|
enabled = !!maybeLevel;
|
|
</code></pre>
|
|
|
|
<pre><code class="language-ts good">enum SupportLevel {
|
|
NONE,
|
|
BASIC,
|
|
ADVANCED,
|
|
}
|
|
|
|
const level: SupportLevel = ...;
|
|
let enabled = level !== SupportLevel.NONE;
|
|
|
|
const maybeLevel: SupportLevel|undefined = ...;
|
|
enabled = level !== undefined && level !== SupportLevel.NONE;
|
|
</code></pre>
|
|
|
|
<section class="zippy" markdown="1">
|
|
Why?
|
|
|
|
<p>For most purposes, it doesn't matter what number or string value an enum name is
|
|
mapped to at runtime, because values of enum types are referred to by name in
|
|
source code. Consequently, engineers are accustomed to not thinking about this,
|
|
and so situations where it <em>does</em> matter are undesirable because they will be
|
|
surprising. Such is the case with conversion of enums to booleans; in
|
|
particular, by default, the first declared enum value is falsy (because it is 0)
|
|
while the others are truthy, which is likely to be unexpected. Readers of code
|
|
that uses an enum value may not even know whether it's the first declared value
|
|
or not.
|
|
</p></section>
|
|
|
|
<p>Using string concatenation to cast to string is discouraged, as we check that
|
|
operands to the plus operator are of matching types.</p>
|
|
|
|
<p>Code <em>must</em> use <code>Number()</code> to parse numeric values, and <em>must</em> check its return
|
|
for <code>NaN</code> values explicitly, unless failing to parse is impossible from context.</p>
|
|
|
|
<p>Note: <code>Number('')</code>, <code>Number(' ')</code>, and <code>Number('\t')</code> would return <code>0</code> instead
|
|
of <code>NaN</code>. <code>Number('Infinity')</code> and <code>Number('-Infinity')</code> would return <code>Infinity</code>
|
|
and <code>-Infinity</code> respectively. Additionally, exponential notation such as
|
|
<code>Number('1e+309')</code> and <code>Number('-1e+309')</code> can overflow into <code>Infinity</code>. These
|
|
cases may require special handling.</p>
|
|
|
|
<pre><code class="language-ts good">const aNumber = Number('123');
|
|
if (!isFinite(aNumber)) throw new Error(...);
|
|
</code></pre>
|
|
|
|
<p>Code <em>must not</em> use unary plus (<code>+</code>) to coerce strings to numbers. Parsing
|
|
numbers can fail, has surprising corner cases, and can be a code smell (parsing
|
|
at the wrong layer). A unary plus is too easy to miss in code reviews given
|
|
this.</p>
|
|
|
|
<pre><code class="language-ts bad">const x = +y;
|
|
</code></pre>
|
|
|
|
<p>Code also <em>must not</em> use <code>parseInt</code> or <code>parseFloat</code> to parse numbers, except for
|
|
non-base-10 strings (see below). Both of those functions ignore trailing
|
|
characters in the string, which can shadow error conditions (e.g. parsing <code>12
|
|
dwarves</code> as <code>12</code>).</p>
|
|
|
|
<pre><code class="language-ts bad">const n = parseInt(someString, 10); // Error prone,
|
|
const f = parseFloat(someString); // regardless of passing a radix.
|
|
</code></pre>
|
|
|
|
<p>Code that requires parsing with a radix <em>must</em> check that its input contains
|
|
only appropriate digits for that radix before calling into <code>parseInt</code>;</p>
|
|
|
|
<pre><code class="language-ts good">if (!/^[a-fA-F0-9]+$/.test(someString)) throw new Error(...);
|
|
// Needed to parse hexadecimal.
|
|
// tslint:disable-next-line:ban
|
|
const n = parseInt(someString, 16); // Only allowed for radix != 10
|
|
</code></pre>
|
|
|
|
<p>Use <code>Number()</code> followed by <code>Math.floor</code> or <code>Math.trunc</code> (where available) to
|
|
parse integer numbers:</p>
|
|
|
|
<pre><code class="language-ts good">let f = Number(someString);
|
|
if (isNaN(f)) handleError();
|
|
f = Math.floor(f);
|
|
</code></pre>
|
|
|
|
<h4 id="type-coercion-implicit">Implicit coercion</h4>
|
|
|
|
<p>Do not use explicit boolean coercions in conditional clauses that have implicit
|
|
boolean coercion. Those are the conditions in an <code>if</code>, <code>for</code> and <code>while</code>
|
|
statements.</p>
|
|
|
|
<pre><code class="language-ts bad">const foo: MyInterface|null = ...;
|
|
if (!!foo) {...}
|
|
while (!!foo) {...}
|
|
</code></pre>
|
|
|
|
<pre><code class="language-ts good">const foo: MyInterface|null = ...;
|
|
if (foo) {...}
|
|
while (foo) {...}
|
|
</code></pre>
|
|
|
|
<p><a href="#type-coercion">As with explicit conversions</a>, values of enum types (including
|
|
unions of enum types and other types) <em>must not</em> be implicitly coerced to
|
|
booleans, and must instead be compared explicitly with comparison operators.</p>
|
|
|
|
<pre><code class="language-ts bad">enum SupportLevel {
|
|
NONE,
|
|
BASIC,
|
|
ADVANCED,
|
|
}
|
|
|
|
const level: SupportLevel = ...;
|
|
if (level) {...}
|
|
|
|
const maybeLevel: SupportLevel|undefined = ...;
|
|
if (level) {...}
|
|
</code></pre>
|
|
|
|
<pre><code class="language-ts good">enum SupportLevel {
|
|
NONE,
|
|
BASIC,
|
|
ADVANCED,
|
|
}
|
|
|
|
const level: SupportLevel = ...;
|
|
if (level !== SupportLevel.NONE) {...}
|
|
|
|
const maybeLevel: SupportLevel|undefined = ...;
|
|
if (level !== undefined && level !== SupportLevel.NONE) {...}
|
|
</code></pre>
|
|
|
|
<p>Other types of values may be either implicitly coerced to booleans or compared
|
|
explicitly with comparison operators:</p>
|
|
|
|
<pre><code class="language-ts good">// Explicitly comparing > 0 is OK:
|
|
if (arr.length > 0) {...}
|
|
// so is relying on boolean coercion:
|
|
if (arr.length) {...}
|
|
</code></pre>
|
|
|
|
<h3 id="variables">Variables</h3>
|
|
|
|
<p>Always use <code>const</code> or <code>let</code> to declare variables. Use <code>const</code> by default, unless
|
|
a variable needs to be reassigned. Never use <code>var</code>.</p>
|
|
|
|
<pre><code class="language-ts good">const foo = otherValue; // Use if "foo" never changes.
|
|
let bar = someValue; // Use if "bar" is ever assigned into later on.
|
|
</code></pre>
|
|
|
|
<p><code>const</code> and <code>let</code> are block scoped, like variables in most other languages.
|
|
<code>var</code> in JavaScript is function scoped, which can cause difficult to understand
|
|
bugs. Don't use it.</p>
|
|
|
|
<pre><code class="language-ts bad">var foo = someValue; // Don't use - var scoping is complex and causes bugs.
|
|
</code></pre>
|
|
|
|
<p>Variables <em>must not</em> be used before their declaration.</p>
|
|
|
|
<h3 id="exceptions">Exceptions</h3>
|
|
|
|
<h4>Instantiate Errors using new</h4>
|
|
|
|
<p>Always use <code>new Error()</code> when instantiating exceptions, instead of just calling
|
|
<code>Error()</code>. Both forms create a new <code>Error</code> instance, but using <code>new</code> is more
|
|
consistent with how other objects are instantiated.</p>
|
|
|
|
<pre><code class="language-ts good">throw new Error('Foo is not a valid bar.');
|
|
</code></pre>
|
|
|
|
<pre><code class="language-ts bad">throw Error('Foo is not a valid bar.');
|
|
</code></pre>
|
|
|
|
<h4>Only throw Errors</h4>
|
|
|
|
<p>JavaScript (and thus TypeScript) allow throwing arbitrary values. However if the
|
|
thrown value is not an <code>Error</code>, it does not get a stack trace filled in, making
|
|
debugging hard.</p>
|
|
|
|
<pre><code class="language-ts bad">// bad: does not get a stack trace.
|
|
throw 'oh noes!';
|
|
</code></pre>
|
|
|
|
<p>Instead, only throw (subclasses of) <code>Error</code>:</p>
|
|
|
|
<pre><code class="language-ts good">// Throw only Errors
|
|
throw new Error('oh noes!');
|
|
// ... or subtypes of Error.
|
|
class MyError extends Error {}
|
|
throw new MyError('my oh noes!');
|
|
</code></pre>
|
|
|
|
<h4>Catching & rethrowing</h4>
|
|
|
|
<p>When catching errors, code <em>should</em> assume that all thrown errors are instances
|
|
of <code>Error</code>.</p>
|
|
|
|
<section markdown="1">
|
|
|
|
<pre><code class="language-ts good">try {
|
|
doSomething();
|
|
} catch (e: unknown) {
|
|
// All thrown errors must be Error subtypes. Do not handle
|
|
// other possible values unless you know they are thrown.
|
|
assert(e, isInstanceOf(Error));
|
|
displayError(e.message);
|
|
// or rethrow:
|
|
throw e;
|
|
}
|
|
</code></pre>
|
|
|
|
</section>
|
|
|
|
<p>Exception handlers <em>must not</em> defensively handle non-<code>Error</code> types unless the
|
|
called API is conclusively known to throw non-<code>Error</code>s in violation of the above
|
|
rule. In that case, a comment should be included to specifically identify where
|
|
the non-<code>Error</code>s originate.</p>
|
|
|
|
<pre><code class="language-ts good">try {
|
|
badApiThrowingStrings();
|
|
} catch (e: unknown) {
|
|
// Note: bad API throws strings instead of errors.
|
|
if (typeof e === 'string') { ... }
|
|
}
|
|
</code></pre>
|
|
|
|
<section class="zippy" markdown="1">
|
|
Why?
|
|
|
|
<p>Avoid <a href="https://en.wikipedia.org/wiki/Defensive_programming#Offensive_programming">overly defensive programming</a>. Repeating the same
|
|
defenses against a problem that will not exist in most code leads to
|
|
boiler-plate code that is not useful.
|
|
</p></section>
|
|
|
|
<h3 id="iterating-objects">Iterating objects</h3>
|
|
|
|
<p>Iterating objects with <code>for (... in ...)</code> is error prone. It will include
|
|
enumerable properties from the prototype chain.</p>
|
|
|
|
<p>Do not use unfiltered <code>for (... in ...)</code> statements:</p>
|
|
|
|
<pre><code class="language-ts bad">for (const x in someObj) {
|
|
// x could come from some parent prototype!
|
|
}
|
|
</code></pre>
|
|
|
|
<p>Either filter values explicitly with an <code>if</code> statement, or use <code>for (... of
|
|
Object.keys(...))</code>.</p>
|
|
|
|
<pre><code class="language-ts good">for (const x in someObj) {
|
|
if (!someObj.hasOwnProperty(x)) continue;
|
|
// now x was definitely defined on someObj
|
|
}
|
|
for (const x of Object.keys(someObj)) { // note: for _of_!
|
|
// now x was definitely defined on someObj
|
|
}
|
|
for (const [key, value] of Object.entries(someObj)) { // note: for _of_!
|
|
// now key was definitely defined on someObj
|
|
}
|
|
</code></pre>
|
|
|
|
<h3 id="iterating-containers">Iterating containers</h3>
|
|
|
|
<p>Do not use <code>for (... in ...)</code> to iterate over arrays. It will counterintuitively
|
|
give the array's indices (as strings!), not values:</p>
|
|
|
|
<pre><code class="language-ts bad">for (const x in someArray) {
|
|
// x is the index!
|
|
}
|
|
</code></pre>
|
|
|
|
<p>Prefer <code>for (... of someArr)</code> to iterate over arrays,
|
|
go/tsjs-practices/iteration. <code>Array.prototype.forEach</code> and vanilla <code>for</code> loops
|
|
are also allowed:</p>
|
|
|
|
<pre><code class="language-ts good">for (const x of someArr) {
|
|
// x is a value of someArr.
|
|
}
|
|
|
|
for (let i = 0; i < someArr.length; i++) {
|
|
// Explicitly count if the index is needed, otherwise use the for/of form.
|
|
const x = someArr[i];
|
|
// ...
|
|
}
|
|
for (const [i, x] of someArr.entries()) {
|
|
// Alternative version of the above.
|
|
}
|
|
</code></pre>
|
|
|
|
<h3 id="using-the-spread-operator">Using the spread operator</h3>
|
|
|
|
<p>Using the spread operator <code>[...foo]; {...bar}</code> is a convenient shorthand for
|
|
copying arrays and objects. When using the spread operator on objects, later
|
|
values replace earlier values at the same key.</p>
|
|
|
|
<pre><code class="language-ts good">const foo = {
|
|
num: 1,
|
|
};
|
|
|
|
const foo2 = {
|
|
...foo,
|
|
num: 5,
|
|
};
|
|
|
|
const foo3 = {
|
|
num: 5,
|
|
...foo,
|
|
}
|
|
|
|
foo2.num === 5;
|
|
foo3.num === 1;
|
|
|
|
</code></pre>
|
|
|
|
<p>When using the spread operator, the value being spread <em>must</em> match what is
|
|
being created. That is, when creating an object, only objects may be used with
|
|
the spread operator; when creating an array, only spread iterables. Primitives,
|
|
including <code>null</code> and <code>undefined</code>, <em>must not</em> be spread.</p>
|
|
|
|
<pre><code class="language-ts bad">const foo = {num: 7};
|
|
const bar = {num: 5, ...(shouldUseFoo && foo)}; // might be undefined
|
|
|
|
// Creates {0: 'a', 1: 'b', 2: 'c'} but has no length
|
|
const fooStrings = ['a', 'b', 'c'];
|
|
const ids = {...fooStrings};
|
|
</code></pre>
|
|
|
|
<pre><code class="language-ts good">const foo = shouldUseFoo ? {num: 7} : {};
|
|
const bar = {num: 5, ...foo};
|
|
const fooStrings = ['a', 'b', 'c'];
|
|
const ids = [...fooStrings, 'd', 'e'];
|
|
</code></pre>
|
|
|
|
<h3 id="control-flow-statements-blocks">Control flow statements & blocks</h3>
|
|
|
|
<p>Control flow statements always use blocks for the containing code.</p>
|
|
|
|
<pre><code class="language-ts good">for (let i = 0; i < x; i++) {
|
|
doSomethingWith(i);
|
|
}
|
|
|
|
if (x) {
|
|
doSomethingWithALongMethodNameThatForcesANewLine(x);
|
|
}
|
|
</code></pre>
|
|
|
|
<pre><code class="language-ts bad">if (x)
|
|
doSomethingWithALongMethodNameThatForcesANewLine(x);
|
|
|
|
for (let i = 0; i < x; i++) doSomethingWith(i);
|
|
</code></pre>
|
|
|
|
<p>The exception is that <code>if</code> statements fitting on one line <em>may</em> elide the block.</p>
|
|
|
|
<pre><code class="language-ts good">if (x) x.doFoo();
|
|
</code></pre>
|
|
|
|
<h3>Assignment in control statements</h3>
|
|
|
|
<p>Prefer to avoid assignment of variables inside control statements. Assignment
|
|
can be easily mistaken for equality checks inside control statements.</p>
|
|
|
|
<pre><code class="language-ts bad">if (x = someFunction()) {
|
|
// Assignment easily mistaken with equality check
|
|
// ...
|
|
}
|
|
</code></pre>
|
|
|
|
<pre><code class="language-ts good">x = someFunction();
|
|
if (x) {
|
|
// ...
|
|
}
|
|
</code></pre>
|
|
|
|
<p>In cases where assignment inside the control statement is preferred, enclose the
|
|
assignment in additional parenthesis to indicate it is intentional.</p>
|
|
|
|
<pre><code class="language-ts">while ((x = someFunction())) {
|
|
// Double parenthesis shows assignment is intentional
|
|
// ...
|
|
}
|
|
|
|
</code></pre>
|
|
|
|
<h3>Switch Statements</h3>
|
|
|
|
<p>All <code>switch</code> statements <em>must</em> contain a <code>default</code> statement group, even if it
|
|
contains no code.</p>
|
|
|
|
<pre><code class="language-ts good">switch (x) {
|
|
case Y:
|
|
doSomethingElse();
|
|
break;
|
|
default:
|
|
// nothing to do.
|
|
}
|
|
</code></pre>
|
|
|
|
<p>Non-empty statement groups (<code>case ...</code>) <em>must not</em> fall through (enforced by the
|
|
compiler):</p>
|
|
|
|
<pre><code class="language-ts bad">switch (x) {
|
|
case X:
|
|
doSomething();
|
|
// fall through - not allowed!
|
|
case Y:
|
|
// ...
|
|
}
|
|
</code></pre>
|
|
|
|
<p>Empty statement groups are allowed to fall through:</p>
|
|
|
|
<pre><code class="language-ts good">switch (x) {
|
|
case X:
|
|
case Y:
|
|
doSomething();
|
|
break;
|
|
default: // nothing to do.
|
|
}
|
|
</code></pre>
|
|
|
|
<h3 id="equality-checks">Equality Checks</h3>
|
|
|
|
<p>Always use triple equals (<code>===</code>) and not equals (<code>!==</code>). The double equality
|
|
operators cause error prone type coercions that are hard to understand and
|
|
slower to implement for JavaScript Virtual Machines. See also the
|
|
<a href="https://dorey.github.io/JavaScript-Equality-Table/">JavaScript equality table</a>.</p>
|
|
|
|
<pre><code class="language-ts bad">if (foo == 'bar' || baz != bam) {
|
|
// Hard to understand behaviour due to type coercion.
|
|
}
|
|
</code></pre>
|
|
|
|
<pre><code class="language-ts good">if (foo === 'bar' || baz !== bam) {
|
|
// All good here.
|
|
}
|
|
</code></pre>
|
|
|
|
<p><strong>Exception</strong>: Comparisons to the literal <code>null</code> value <em>may</em> use the <code>==</code> and
|
|
<code>!=</code> operators to cover both <code>null</code> and <code>undefined</code> values.</p>
|
|
|
|
<pre><code class="language-ts good">if (foo == null) {
|
|
// Will trigger when foo is null or undefined.
|
|
}
|
|
</code></pre>
|
|
|
|
<h3>Keep try blocks focused</h3>
|
|
|
|
<p>Limit the amount of code inside a try block, if this can be done without hurting
|
|
readability.</p>
|
|
|
|
<pre><code class="language-ts bad">try {
|
|
const result = methodThatMayThrow();
|
|
use(result);
|
|
} catch (error: unknown) {
|
|
// ...
|
|
}
|
|
</code></pre>
|
|
|
|
<pre><code class="language-ts good">let result;
|
|
try {
|
|
result = methodThatMayThrow();
|
|
} catch (error: unknown) {
|
|
// ...
|
|
}
|
|
use(result);
|
|
</code></pre>
|
|
|
|
<p>Moving the non-throwable lines out of the try/catch block helps the reader learn
|
|
which method throws exceptions. Some inline calls that do not throw exceptions
|
|
could stay inside because they might not be worth the extra complication of a
|
|
temporary variable.</p>
|
|
|
|
<p><strong>Exception</strong>: There may be performance issues if try blocks are inside a loop.
|
|
Widening try blocks to cover a whole loop is ok.</p>
|
|
|
|
<h3 id="function-declarations">Function Declarations</h3>
|
|
|
|
<p>Prefer <code>function foo() { ... }</code> to declare top-level named functions.</p>
|
|
|
|
<p>Top-level arrow functions <em>may</em> be used, for example to provide an explicit type
|
|
annotation.</p>
|
|
|
|
<pre><code class="language-ts good">interface SearchFunction {
|
|
(source: string, subString: string): boolean;
|
|
}
|
|
|
|
const fooSearch: SearchFunction = (source, subString) => { ... };
|
|
</code></pre>
|
|
|
|
<blockquote>
|
|
<p>Note the difference between function declarations (<code>function foo() {}</code>)
|
|
discussed here, and function expressions (<del><code>doSomethingWith(function()
|
|
{});</code></del>) discussed <a href="#function-expressions">below</a>.</p>
|
|
</blockquote>
|
|
|
|
<h3 id="function-expressions">Function Expressions</h3>
|
|
|
|
<h4 id="use-arrow-functions-in-expressions">Use arrow functions in expressions</h4>
|
|
|
|
<p>Always use arrow functions instead of pre-ES6 function expressions defined with
|
|
the <code>function</code> keyword.</p>
|
|
|
|
<pre><code class="language-ts good">bar(() => { this.doSomething(); })
|
|
</code></pre>
|
|
|
|
<pre><code class="language-ts bad">bar(function() { ... })
|
|
</code></pre>
|
|
|
|
<p>Function expressions (defined with the <code>function</code> keyword) <em>may</em> only be used if
|
|
code has to dynamically rebind the <code>this</code> pointer, but code <em>should not</em> rebind
|
|
the <code>this</code> pointer in general. Code in regular functions (as opposed to arrow
|
|
functions and methods) <em>should not</em> access <code>this</code>.</p>
|
|
|
|
<h4 id="expression-bodies-vs-block-bodies">Expression bodies vs block bodies</h4>
|
|
|
|
<p>Use arrow functions with expressions or blocks as their body as appropriate.</p>
|
|
|
|
<pre><code class="language-ts good">// Top level functions use function declarations.
|
|
function someFunction() {
|
|
// Block arrow function bodies, i.e. bodies with => { }, are fine:
|
|
const receipts = books.map((b: Book) => {
|
|
const receipt = payMoney(b.price);
|
|
recordTransaction(receipt);
|
|
return receipt;
|
|
});
|
|
|
|
// Expression bodies are fine, too, if the return value is used:
|
|
const longThings = myValues.filter(v => v.length > 1000).map(v => String(v));
|
|
|
|
function payMoney(amount: number) {
|
|
// function declarations are fine, but don't access `this` in them.
|
|
}
|
|
|
|
// Nested arrow functions may be assigned to a const.
|
|
const computeTax = (amount: number) => amount * 0.12;
|
|
}
|
|
</code></pre>
|
|
|
|
<p>Only use an expression body if the return value of the function is actually
|
|
used.</p>
|
|
|
|
<pre><code class="language-ts bad">// BAD: use a block ({ ... }) if the return value of the function is not used.
|
|
myPromise.then(v => console.log(v));
|
|
</code></pre>
|
|
|
|
<pre><code class="language-ts good">// GOOD: return value is unused, use a block body.
|
|
myPromise.then(v => {
|
|
console.log(v);
|
|
});
|
|
// GOOD: code may use blocks for readability.
|
|
const transformed = [1, 2, 3].map(v => {
|
|
const intermediate = someComplicatedExpr(v);
|
|
const more = acrossManyLines(intermediate);
|
|
return worthWrapping(more);
|
|
});
|
|
</code></pre>
|
|
|
|
<h4 id="rebinding-this">Rebinding <code>this</code></h4>
|
|
|
|
<p>Function expressions <em>must not</em> use <code>this</code> unless they specifically exist to
|
|
rebind the <code>this</code> pointer. Rebinding <code>this</code> can in most cases be avoided by
|
|
using arrow functions or explicit parameters.</p>
|
|
|
|
<pre><code class="language-ts bad">function clickHandler() {
|
|
// Bad: what's `this` in this context?
|
|
this.textContent = 'Hello';
|
|
}
|
|
// Bad: the `this` pointer reference is implicitly set to document.body.
|
|
document.body.onclick = clickHandler;
|
|
</code></pre>
|
|
|
|
<pre><code class="language-ts good">// Good: explicitly reference the object from an arrow function.
|
|
document.body.onclick = () => { document.body.textContent = 'hello'; };
|
|
// Alternatively: take an explicit parameter
|
|
const setTextFn = (e: HTMLElement) => { e.textContent = 'hello'; };
|
|
document.body.onclick = setTextFn.bind(null, document.body);
|
|
</code></pre>
|
|
|
|
<h4 id="arrow-functions-as-properties">Arrow functions as properties</h4>
|
|
|
|
<p>Classes usually <em>should not</em> contain properties initialized to arrow functions.
|
|
Arrow function properties require the calling function to understand that the
|
|
callee's <code>this</code> is already bound, which increases confusion about what <code>this</code>
|
|
is, and call sites and references using such handlers look broken (i.e. require
|
|
non-local knowledge to determine that they are correct). Code <em>should</em> always
|
|
use arrow functions to call instance methods (<code>const handler = (x) => {
|
|
this.listener(x); };</code>), and <em>should not</em> obtain or pass references to instance
|
|
methods (<del><code>const handler = this.listener; handler(x);</code></del>).</p>
|
|
|
|
<blockquote>
|
|
<p>Note: in some specific situations, e.g. when binding functions in a template,
|
|
arrow functions as properties are useful and create much more readable code.
|
|
Use judgement with this rule. Also, see the
|
|
<a href="#event-handlers"><code>Event Handlers</code></a> section below.</p>
|
|
</blockquote>
|
|
|
|
<pre><code class="language-ts bad">class DelayHandler {
|
|
constructor() {
|
|
// Problem: `this` is not preserved in the callback. `this` in the callback
|
|
// will not be an instance of DelayHandler.
|
|
setTimeout(this.patienceTracker, 5000);
|
|
}
|
|
private patienceTracker() {
|
|
this.waitedPatiently = true;
|
|
}
|
|
}
|
|
</code></pre>
|
|
|
|
<pre><code class="language-ts bad">// Arrow functions usually should not be properties.
|
|
class DelayHandler {
|
|
constructor() {
|
|
// Bad: this code looks like it forgot to bind `this`.
|
|
setTimeout(this.patienceTracker, 5000);
|
|
}
|
|
private patienceTracker = () => {
|
|
this.waitedPatiently = true;
|
|
}
|
|
}
|
|
</code></pre>
|
|
|
|
<pre><code class="language-ts good">// Explicitly manage `this` at call time.
|
|
class DelayHandler {
|
|
constructor() {
|
|
// Use anonymous functions if possible.
|
|
setTimeout(() => {
|
|
this.patienceTracker();
|
|
}, 5000);
|
|
}
|
|
private patienceTracker() {
|
|
this.waitedPatiently = true;
|
|
}
|
|
}
|
|
</code></pre>
|
|
|
|
<h4 id="event-handlers">Event Handlers</h4>
|
|
|
|
<p>Event handlers <em>may</em> use arrow functions when there is no need to uninstall the
|
|
handler (for example, if the event is emitted by the class itself). If the
|
|
handler requires uninstallation, arrow function properties are the right
|
|
approach, because they automatically capture <code>this</code> and provide a stable
|
|
reference to uninstall.</p>
|
|
|
|
<pre><code class="language-ts good">// Event handlers may be anonymous functions or arrow function properties.
|
|
class Component {
|
|
onAttached() {
|
|
// The event is emitted by this class, no need to uninstall.
|
|
this.addEventListener('click', () => {
|
|
this.listener();
|
|
});
|
|
// this.listener is a stable reference, we can uninstall it later.
|
|
window.addEventListener('onbeforeunload', this.listener);
|
|
}
|
|
onDetached() {
|
|
// The event is emitted by window. If we don't uninstall, this.listener will
|
|
// keep a reference to `this` because it's bound, causing a memory leak.
|
|
window.removeEventListener('onbeforeunload', this.listener);
|
|
}
|
|
// An arrow function stored in a property is bound to `this` automatically.
|
|
private listener = () => {
|
|
confirm('Do you want to exit the page?');
|
|
}
|
|
}
|
|
</code></pre>
|
|
|
|
<p>Do not use <code>bind</code> in the expression that installs an event handler, because it
|
|
creates a temporary reference that can't be uninstalled.</p>
|
|
|
|
<pre><code class="language-ts bad">// Binding listeners creates a temporary reference that prevents uninstalling.
|
|
class Component {
|
|
onAttached() {
|
|
// This creates a temporary reference that we won't be able to uninstall
|
|
window.addEventListener('onbeforeunload', this.listener.bind(this));
|
|
}
|
|
onDetached() {
|
|
// This bind creates a different reference, so this line does nothing.
|
|
window.removeEventListener('onbeforeunload', this.listener.bind(this));
|
|
}
|
|
private listener() {
|
|
confirm('Do you want to exit the page?');
|
|
}
|
|
}
|
|
</code></pre>
|
|
|
|
<h3 id="automatic-semicolon-insertion">Automatic Semicolon Insertion</h3>
|
|
|
|
<p>Do not rely on Automatic Semicolon Insertion (ASI). Explicitly terminate all
|
|
statements using a semicolon. This prevents bugs due to incorrect semicolon
|
|
insertions and ensures compatibility with tools with limited ASI support (e.g.
|
|
clang-format).</p>
|
|
|
|
<h3 id="ts-ignore">@ts-ignore</h3>
|
|
|
|
<p>Do not use <code>@ts-ignore</code> nor variants <code>@ts-expect-error</code> or <code>@ts-nocheck</code>. They
|
|
superficially seem to be an easy way to <q>fix</q> a compiler error, but in practice,
|
|
a specific compiler error is often caused by a larger problem that can be fixed
|
|
more directly.</p>
|
|
|
|
<p>For example, if you are using <code>@ts-ignore</code> to suppress a type error, then it's
|
|
hard to predict what types the surrounding code will end up seeing. For many
|
|
type errors, the advice in <a href="#any">how to best use <code>any</code></a> is useful.</p>
|
|
|
|
<h3 id="type-and-non-nullability-assertions">Type and Non-nullability Assertions</h3>
|
|
|
|
<p>Type assertions (<code>x as SomeType</code>) and non-nullability assertions (<code>y!</code>) are
|
|
unsafe. Both only silence the TypeScript compiler, but do not insert any runtime
|
|
checks to match these assertions, so they can cause your program to crash at
|
|
runtime.</p>
|
|
|
|
<p>Because of this, you <em>should not</em> use type and non-nullability assertions
|
|
without an obvious or explicit reason for doing so.</p>
|
|
|
|
<p>Instead of the following:</p>
|
|
|
|
<pre><code class="language-ts bad">(x as Foo).foo();
|
|
|
|
y!.bar();
|
|
</code></pre>
|
|
|
|
<p>When you want to assert a type or non-nullability the best answer is to
|
|
explicitly write a runtime check that performs that check.</p>
|
|
|
|
<pre><code class="language-ts good">// assuming Foo is a class.
|
|
if (x instanceof Foo) {
|
|
x.foo();
|
|
}
|
|
|
|
if (y) {
|
|
y.bar();
|
|
}
|
|
</code></pre>
|
|
|
|
<p>Sometimes due to some local property of your code you can be sure that the
|
|
assertion form is safe. In those situations, you <em>should</em> add clarification to
|
|
explain why you are ok with the unsafe behavior:</p>
|
|
|
|
<pre><code class="language-ts good">// x is a Foo, because ...
|
|
(x as Foo).foo();
|
|
|
|
// y cannot be null, because ...
|
|
y!.bar();
|
|
</code></pre>
|
|
|
|
<p>If the reasoning behind a type or non-nullability assertion is obvious, the
|
|
comments <em>may</em> not be necessary. For example, generated proto code is always
|
|
nullable, but perhaps it is well-known in the context of the code that certain
|
|
fields are always provided by the backend. Use your judgement.</p>
|
|
|
|
<h4 id="type-assertions-syntax">Type Assertions Syntax</h4>
|
|
|
|
<p>Type assertions <em>must</em> use the <code>as</code> syntax (as opposed to the angle brackets
|
|
syntax). This enforces parentheses around the assertion when accessing a member.</p>
|
|
|
|
<pre><code class="language-ts bad">const x = (<Foo>z).length;
|
|
const y = <Foo>z.length;
|
|
</code></pre>
|
|
|
|
<pre><code class="language-ts good">// z must be Foo because ...
|
|
const x = (z as Foo).length;
|
|
</code></pre>
|
|
|
|
<h4 id="type-assertions-and-object-literals">Type Assertions and Object Literals</h4>
|
|
|
|
<p>Use type annotations (<code>: Foo</code>) instead of type assertions (<code>as Foo</code>) to specify
|
|
the type of an object literal. This allows detecting refactoring bugs when the
|
|
fields of an interface change over time.</p>
|
|
|
|
<pre><code class="language-ts bad">interface Foo {
|
|
bar: number;
|
|
baz?: string; // was "bam", but later renamed to "baz".
|
|
}
|
|
|
|
const foo = {
|
|
bar: 123,
|
|
bam: 'abc', // no error!
|
|
} as Foo;
|
|
|
|
function func() {
|
|
return {
|
|
bar: 123,
|
|
bam: 'abc', // no error!
|
|
} as Foo;
|
|
}
|
|
</code></pre>
|
|
|
|
<pre><code class="language-ts good">interface Foo {
|
|
bar: number;
|
|
baz?: string;
|
|
}
|
|
|
|
const foo: Foo = {
|
|
bar: 123,
|
|
bam: 'abc', // complains about "bam" not being defined on Foo.
|
|
};
|
|
|
|
function func(): Foo {
|
|
return {
|
|
bar: 123,
|
|
bam: 'abc', // complains about "bam" not being defined on Foo.
|
|
};
|
|
}
|
|
</code></pre>
|
|
|
|
<h3 id="member-property-declarations">Member property declarations</h3>
|
|
|
|
<p>Interface and class declarations <em>must use</em> a semicolon to separate individual
|
|
member declarations:</p>
|
|
|
|
<pre><code class="language-ts good">interface Foo {
|
|
memberA: string;
|
|
memberB: number;
|
|
}
|
|
</code></pre>
|
|
|
|
<p>Interfaces specifically <em>must not</em> use a comma to separate fields, for symmetry
|
|
with class declarations:</p>
|
|
|
|
<pre><code class="language-ts bad">interface Foo {
|
|
memberA: string,
|
|
memberB: number,
|
|
}
|
|
</code></pre>
|
|
|
|
<p>Inline object type declarations <em>must</em> use a comma as a separator:</p>
|
|
|
|
<pre><code class="language-ts good">type SomeTypeAlias = {
|
|
memberA: string,
|
|
memberB: number,
|
|
};
|
|
|
|
let someProperty: {memberC: string, memberD: number};
|
|
</code></pre>
|
|
|
|
<h4 id="optimization-compatibility-for-property-access">Optimization compatibility for property access</h4>
|
|
|
|
<p>Code <em>must not</em> mix quoted property access with dotted property access:</p>
|
|
|
|
<pre><code class="language-ts bad">// Bad: code must use either non-quoted or quoted access for any property
|
|
// consistently across the entire application:
|
|
console.log(x['someField']);
|
|
console.log(x.someField);
|
|
</code></pre>
|
|
|
|
<p>Properties that are external to the application, e.g. properties on JSON objects
|
|
or external APIs, <em>must</em> be accessed using <code>.dotted</code> notation, and <em>must</em> be
|
|
declared as so-called ambient properties, using the <code>declare</code> modifier.
|
|
</p>
|
|
|
|
<pre><code class="language-ts good">// Good: using "declare" to declare types that are external to the application,
|
|
// so that their properties are not renamed.
|
|
declare interface ServerInfoJson {
|
|
appVersion: string;
|
|
user: UserJson; // Note: UserJson must also use `declare`!
|
|
}
|
|
// serverResponse must be ServerInfoJson as per the application's contract.
|
|
const data = JSON.parse(serverResponse) as ServerInfoJson;
|
|
console.log(data.appVersion); // Type safe & renaming safe!
|
|
</code></pre>
|
|
|
|
<h4 id="optimization-compatibility-for-module-object-imports">Optimization compatibility for module object imports</h4>
|
|
|
|
<p>When importing a module object, directly access properties on the module object
|
|
rather than passing it around. This ensures that modules can be analyzed and
|
|
optimized. Treating
|
|
<a href="#module-versus-destructuring-imports">module imports</a> as namespaces is fine.</p>
|
|
|
|
<pre><code class="language-ts bad">import * as utils from 'utils';
|
|
class A {
|
|
readonly utils = utils; // <--- BAD: passing around the module object
|
|
}
|
|
</code></pre>
|
|
|
|
<pre><code class="language-ts good">import * as utils from 'utils';
|
|
class A {
|
|
readonly utils = {method1: utils.method1, method2: utils.method2};
|
|
}
|
|
</code></pre>
|
|
|
|
<p>or, more tersely:</p>
|
|
|
|
<pre><code class="language-ts good">import {method1, method2} from 'utils';
|
|
class A {
|
|
readonly utils = {method1, method2};
|
|
}
|
|
</code></pre>
|
|
|
|
<h4 id="exception">Exception</h4>
|
|
|
|
<p>This optimization compatibility rule applies to all web apps. It does not apply
|
|
to code that only runs server side (e.g. in NodeJS for a test runner). It is
|
|
still strongly encouraged to always declare all types and avoid mixing quoted
|
|
and unquoted property access, for code hygiene.</p>
|
|
|
|
<h3 id="enums">Const Enums</h3>
|
|
|
|
<p>Code <em>must not</em> use <code>const enum</code>; use plain <code>enum</code> instead.</p>
|
|
|
|
<section class="zippy" markdown="1">
|
|
Why?
|
|
|
|
<p>TypeScript enums already cannot be mutated; <code>const enum</code> is a separate language
|
|
feature related to optimization that makes the enum invisible to
|
|
JavaScript users of the module.
|
|
</p></section>
|
|
|
|
<h3 id="debugger-statements">Debugger statements</h3>
|
|
|
|
<p>Debugger statements <em>must not</em> be included in production code.</p>
|
|
|
|
<pre><code class="language-ts bad">function debugMe() {
|
|
debugger;
|
|
}
|
|
</code></pre>
|
|
|
|
<h3 id="decorators">Decorators</h3>
|
|
|
|
<p>Decorators are syntax with an <code>@</code> prefix, like <code>@MyDecorator</code>.</p>
|
|
|
|
<p>Do not define new decorators. Only use the decorators defined by
|
|
frameworks:</p>
|
|
|
|
<ul>
|
|
<li>Angular (e.g. <code>@Component</code>, <code>@NgModule</code>, etc.)</li>
|
|
<li>Polymer (e.g. <code>@property</code>)</li>
|
|
</ul>
|
|
|
|
<section markdown="1">
|
|
Why?
|
|
|
|
<p>We generally want to avoid decorators, because they were an experimental
|
|
feature that have since diverged from the TC39 proposal and have known bugs that
|
|
won't be fixed.
|
|
</p></section>
|
|
|
|
<p>When using decorators, the decorator <em>must</em> immediately precede the symbol it
|
|
decorates, with no empty lines between:</p>
|
|
|
|
<pre><code class="language-ts">/** JSDoc comments go before decorators */
|
|
@Component({...}) // Note: no empty line after the decorator.
|
|
class MyComp {
|
|
@Input() myField: string; // Decorators on fields may be on the same line...
|
|
|
|
@Input()
|
|
myOtherField: string; // ... or wrap.
|
|
}
|
|
</code></pre>
|
|
|
|
<h2 id="source-organization">Source Organization</h2>
|
|
|
|
<h3 id="modules">Modules</h3>
|
|
|
|
<h4 id="import-paths">Import Paths</h4>
|
|
|
|
<p>TypeScript code <em>must</em> use paths to import other TypeScript code. Paths <em>may</em> be
|
|
relative, i.e. starting with <code>.</code> or <code>..</code>,
|
|
or rooted at the base directory, e.g.
|
|
<code>root/path/to/file</code>.</p>
|
|
|
|
<p>Code <em>should</em> use relative imports (<code>./foo</code>) rather than absolute imports
|
|
<code>path/to/foo</code> when referring to files within the same (logical) project as this
|
|
allows to move the project around without introducing changes in these imports.</p>
|
|
|
|
<p>Consider limiting the number of parent steps (<code>../../../</code>) as those can make
|
|
module and path structures hard to understand.</p>
|
|
|
|
<pre><code class="language-ts good">import {Symbol1} from 'path/from/root';
|
|
import {Symbol2} from '../parent/file';
|
|
import {Symbol3} from './sibling';
|
|
</code></pre>
|
|
|
|
<h4 id="namespaces-vs-modules">Namespaces vs Modules</h4>
|
|
|
|
<p>TypeScript supports two methods to organize code: <em>namespaces</em> and <em>modules</em>,
|
|
but namespaces are disallowed. That
|
|
is, your code <em>must</em> refer to code in other files using imports and exports of
|
|
the form <code>import {foo} from 'bar';</code></p>
|
|
|
|
<p>Your code <em>must not</em> use the <code>namespace Foo { ... }</code> construct. <code>namespace</code>s
|
|
<em>may</em> only be used when required to interface with external, third party code.
|
|
To semantically namespace your code, use separate files.</p>
|
|
|
|
<p>Code <em>must not</em> use <code>require</code> (as in <code>import x = require('...');</code>) for imports.
|
|
Use ES6 module syntax.</p>
|
|
|
|
<pre><code class="language-ts bad">// Bad: do not use namespaces:
|
|
namespace Rocket {
|
|
function launch() { ... }
|
|
}
|
|
|
|
// Bad: do not use <reference>
|
|
/// <reference path="..."/>
|
|
|
|
// Bad: do not use require()
|
|
import x = require('mydep');
|
|
</code></pre>
|
|
|
|
<blockquote>
|
|
<p>NB: TypeScript <code>namespace</code>s used to be called internal modules and used to use
|
|
the <code>module</code> keyword in the form <code>module Foo { ... }</code>. Don't use that either.
|
|
Always use ES6 imports.</p>
|
|
</blockquote>
|
|
|
|
<h3 id="exports">Exports</h3>
|
|
|
|
<p>Use named exports in all code:</p>
|
|
|
|
<pre><code class="language-ts good">// Use named exports:
|
|
export class Foo { ... }
|
|
</code></pre>
|
|
|
|
<p>Do not use default exports. This ensures that all imports follow a uniform
|
|
pattern.</p>
|
|
|
|
<pre><code class="language-ts bad">// Do not use default exports:
|
|
export default class Foo { ... } // BAD!
|
|
</code></pre>
|
|
|
|
<section class="zippy" markdown="1">
|
|
Why?
|
|
|
|
<p>Default exports provide no canonical name, which makes central maintenance
|
|
difficult with relatively little benefit to code owners, including potentially
|
|
decreased readability:</p>
|
|
|
|
<pre><code class="language-ts bad">import Foo from './bar'; // Legal.
|
|
import Bar from './bar'; // Also legal.
|
|
</code></pre>
|
|
|
|
<p>Named exports have the benefit of erroring when import statements try to import
|
|
something that hasn't been declared. In <code>foo.ts</code>:</p>
|
|
|
|
<pre><code class="language-ts bad">const foo = 'blah';
|
|
export default foo;
|
|
</code></pre>
|
|
|
|
<p>And in <code>bar.ts</code>:</p>
|
|
|
|
<pre><code class="language-ts bad">import {fizz} from './foo';
|
|
</code></pre>
|
|
|
|
<p>Results in <code>error TS2614: Module '"./foo"' has no exported member 'fizz'.</code> While
|
|
<code>bar.ts</code>:</p>
|
|
|
|
<pre><code class="language-ts bad">import fizz from './foo';
|
|
</code></pre>
|
|
|
|
<p>Results in <code>fizz === foo</code>, which is probably unexpected and difficult to debug.</p>
|
|
|
|
<p>Additionally, default exports encourage people to put everything into one big
|
|
object to namespace it all together:</p>
|
|
|
|
<pre><code class="language-ts bad">export default class Foo {
|
|
static SOME_CONSTANT = ...
|
|
static someHelpfulFunction() { ... }
|
|
...
|
|
}
|
|
</code></pre>
|
|
|
|
<p>With the above pattern, we have file scope, which can be used as a namespace. We
|
|
also have a perhaps needless second scope (the class <code>Foo</code>) that can be
|
|
ambiguously used as both a type and a value in other files.</p>
|
|
|
|
<p>Instead, prefer use of file scope for namespacing, as well as named exports:</p>
|
|
|
|
<pre><code class="language-ts good">export const SOME_CONSTANT = ...
|
|
export function someHelpfulFunction()
|
|
export class Foo {
|
|
// only class stuff here
|
|
}
|
|
</code></pre>
|
|
|
|
</section>
|
|
|
|
<h4 id="export-visibility">Export visibility</h4>
|
|
|
|
<p>TypeScript does not support restricting the visibility for exported symbols.
|
|
Only export symbols that are used outside of the module. Generally minimize the
|
|
exported API surface of modules.</p>
|
|
|
|
<h4 id="mutable-exports">Mutable Exports</h4>
|
|
|
|
<p>Regardless of technical support, mutable exports can create hard to understand
|
|
and debug code, in particular with re-exports across multiple modules. One way
|
|
to paraphrase this style point is that <code>export let</code> is not allowed.</p>
|
|
|
|
<section markdown="1">
|
|
|
|
<pre><code class="language-ts bad">export let foo = 3;
|
|
// In pure ES6, foo is mutable and importers will observe the value change after a second.
|
|
// In TS, if foo is re-exported by a second file, importers will not see the value change.
|
|
window.setTimeout(() => {
|
|
foo = 4;
|
|
}, 1000 /* ms */);
|
|
</code></pre>
|
|
|
|
</section>
|
|
|
|
<p>If one needs to support externally accessible and mutable bindings, they
|
|
<em>should</em> instead use explicit getter functions.</p>
|
|
|
|
<pre><code class="language-ts good">let foo = 3;
|
|
window.setTimeout(() => {
|
|
foo = 4;
|
|
}, 1000 /* ms */);
|
|
// Use an explicit getter to access the mutable export.
|
|
export function getFoo() { return foo; };
|
|
</code></pre>
|
|
|
|
<p>For the common pattern of conditionally exporting either of two values, first do
|
|
the conditional check, then the export. Make sure that all exports are final
|
|
after the module's body has executed.</p>
|
|
|
|
<pre><code class="language-ts good">function pickApi() {
|
|
if (useOtherApi()) return OtherApi;
|
|
return RegularApi;
|
|
}
|
|
export const SomeApi = pickApi();
|
|
</code></pre>
|
|
|
|
<h4 id="container-classes">Container Classes</h4>
|
|
|
|
<p>Do not create container classes with static methods or properties for the sake
|
|
of namespacing.</p>
|
|
|
|
<pre><code class="language-ts bad">export class Container {
|
|
static FOO = 1;
|
|
static bar() { return 1; }
|
|
}
|
|
</code></pre>
|
|
|
|
<p>Instead, export individual constants and functions:</p>
|
|
|
|
<pre><code class="language-ts good">export const FOO = 1;
|
|
export function bar() { return 1; }
|
|
</code></pre>
|
|
|
|
<h3 id="imports">Imports</h3>
|
|
|
|
<p>There are four variants of import statements in ES6 and TypeScript:</p>
|
|
|
|
<section>
|
|
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>Import type</th>
|
|
<th>Example</th>
|
|
<th>Use for</th>
|
|
</tr>
|
|
</thead>
|
|
|
|
<tbody>
|
|
<tr>
|
|
<td>module
|
|
</td>
|
|
<td><code>import * as foo from
|
|
'...';</code></td>
|
|
<td>TypeScript imports
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td>destructuring
|
|
</td>
|
|
<td><code>import {SomeThing} from
|
|
'...';</code></td>
|
|
<td>TypeScript imports
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td>default
|
|
</td>
|
|
<td><code>import SomeThing from
|
|
'...';</code></td>
|
|
<td>Only for other external code that
|
|
requires them</td>
|
|
</tr>
|
|
<tr>
|
|
<td>side-effect
|
|
|
|
</td>
|
|
<td><code>import '...';</code>
|
|
|
|
</td>
|
|
<td>Only to import libraries for
|
|
their side-effects on load (such
|
|
as custom elements)</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
|
|
<pre><code class="language-ts good">// Good: choose between two options as appropriate (see below).
|
|
import * as ng from '@angular/core';
|
|
import {Foo} from './foo';
|
|
|
|
// Only when needed: default imports.
|
|
import Button from 'Button';
|
|
|
|
// Sometimes needed to import libraries for their side effects:
|
|
import 'jasmine';
|
|
import '@polymer/paper-button';
|
|
</code></pre>
|
|
|
|
</section>
|
|
|
|
<h4 id="module-versus-destructuring-imports">Module versus destructuring imports</h4>
|
|
|
|
<p>Both module and destructuring imports have advantages depending on the
|
|
situation.</p>
|
|
|
|
<p>Despite the <code>*</code>, a module import is not comparable to a <q>wildcard</q> import as
|
|
seen in other languages. Instead, module imports give a name to the entire
|
|
module and each symbol reference mentions the module, which can make code more
|
|
readable and gives autocompletion on all symbols in a module. They also require
|
|
less import churn (all symbols are available), fewer name collisions, and allow
|
|
terser names in the module that's imported. Module imports are particularly
|
|
useful when using many different symbols from large APIs.</p>
|
|
|
|
<p>Destructuring imports give local names for each imported symbol. They allow
|
|
terser code when using the imported symbol, which is particularly useful for
|
|
very commonly used symbols, such as Jasmine's <code>describe</code> and <code>it</code>.</p>
|
|
|
|
<pre><code class="language-ts bad">// Bad: overlong import statement of needlessly namespaced names.
|
|
import {TableViewItem, TableViewHeader, TableViewRow, TableViewModel,
|
|
TableViewRenderer} from './tableview';
|
|
let item: TableViewItem = ...;
|
|
</code></pre>
|
|
|
|
<pre><code class="language-ts good">// Better: use the module for namespacing.
|
|
import * as tableview from './tableview';
|
|
let item: tableview.Item = ...;
|
|
</code></pre>
|
|
|
|
<pre><code class="language-ts">import * as testing from './testing';
|
|
|
|
// All tests will use the same three functions repeatedly.
|
|
// When importing only a few symbols that are used very frequently, also
|
|
// consider importing the symbols directly (see below).
|
|
testing.describe('foo', () => {
|
|
testing.it('bar', () => {
|
|
testing.expect(...);
|
|
testing.expect(...);
|
|
});
|
|
});
|
|
</code></pre>
|
|
|
|
<pre><code class="language-ts good">// Better: give local names for these common functions.
|
|
import {describe, it, expect} from './testing';
|
|
|
|
describe('foo', () => {
|
|
it('bar', () => {
|
|
expect(...);
|
|
expect(...);
|
|
});
|
|
});
|
|
...
|
|
</code></pre>
|
|
|
|
<h4 id="renaming-imports">Renaming imports</h4>
|
|
|
|
<p>Code <em>should</em> fix name collisions by using a module import or renaming the
|
|
exports themselves. Code <em>may</em> rename imports (<code>import {SomeThing as
|
|
SomeOtherThing}</code>) if needed.</p>
|
|
|
|
<p>Three examples where renaming can be helpful:</p>
|
|
|
|
<ol>
|
|
<li>If it's necessary to avoid collisions with other imported symbols.</li>
|
|
<li>If the imported symbol name is generated.</li>
|
|
<li>If importing symbols whose names are unclear by themselves, renaming can
|
|
improve code clarity. For example, when using RxJS the <code>from</code> function might
|
|
be more readable when renamed to <code>observableFrom</code>.</li>
|
|
</ol>
|
|
|
|
<h4 id="import-export-type">Import & export type</h4>
|
|
|
|
<p>Do not use <code>import type {...}</code> or <code>export type {...}</code>.</p>
|
|
|
|
<pre><code class="language-ts bad">import type {Foo};
|
|
export type {Bar};
|
|
export type {Bar} from './bar';
|
|
</code></pre>
|
|
|
|
<p>Instead, just use regular imports and exports:</p>
|
|
|
|
<pre><code class="language-ts good">import {Foo} from './foo';
|
|
export {Bar} from './bar';
|
|
</code></pre>
|
|
|
|
<p><strong>Note:</strong> this does not apply to <code>export</code> as applied to a type definition, i.e.
|
|
<code>export type Foo = ...;</code>.</p>
|
|
|
|
<pre><code class="language-ts good">export type Foo = string;
|
|
</code></pre>
|
|
|
|
<p>TypeScript tooling automatically distinguishes symbols used as types vs symbols
|
|
used as values and only generates runtime loads for the latter.</p>
|
|
|
|
<section class="zippy" markdown="1">
|
|
Why?
|
|
|
|
<p>TypeScript tooling automatically handles the distinction and does not insert
|
|
runtime loads for type references. This gives a better developer UX: toggling
|
|
back and forth between <code>import type</code> and <code>import</code> is bothersome. At the same
|
|
time, <code>import type</code> gives no guarantees: your code might still have a hard
|
|
dependency on some import through a different transitive path.</p>
|
|
|
|
<p>If you need to force a runtime load for side effects, use <code>import '...';</code>. See
|
|
</p>
|
|
|
|
<p><code>export type</code> might seem useful to avoid ever exporting a value symbol for an
|
|
API. However it does not give guarantees either: downstream code might still
|
|
import an API through a different path. A better way to split & guarantee type
|
|
vs value usages of an API is to actually split the symbols into e.g.
|
|
<code>UserService</code> and <code>AjaxUserService</code>. This is less error prone and also better
|
|
communicates intent.</p>
|
|
|
|
</section>
|
|
|
|
<h3 id="organize-by-feature">Organize By Feature</h3>
|
|
|
|
<p>Organize packages by feature, not by type. For example, an online shop <em>should</em>
|
|
have packages named <code>products</code>, <code>checkout</code>, <code>backend</code>, not <del><code>views</code>, <code>models</code>,
|
|
<code>controllers</code></del>.</p>
|
|
|
|
<h2 id="type-system">Type System</h2>
|
|
|
|
<h3 id="type-inference">Type Inference</h3>
|
|
|
|
<p>Code <em>may</em> rely on type inference as implemented by the TypeScript compiler for
|
|
all type expressions (variables, fields, return types, etc).
|
|
</p>
|
|
|
|
<pre><code class="language-ts good">const x = 15; // Type inferred.
|
|
</code></pre>
|
|
|
|
<p>Leave out type annotations for trivially inferred types: variables or parameters
|
|
initialized to a <code>string</code>, <code>number</code>, <code>boolean</code>, <code>RegExp</code> literal or <code>new</code>
|
|
expression.</p>
|
|
|
|
<pre><code class="language-ts bad">const x: boolean = true; // Bad: 'boolean' here does not aid readability
|
|
</code></pre>
|
|
|
|
<pre><code class="language-ts bad">// Bad: 'Set' is trivially inferred from the initialization
|
|
const x: Set<string> = new Set();
|
|
</code></pre>
|
|
|
|
<pre><code class="language-ts good">const x = new Set<string>();
|
|
</code></pre>
|
|
|
|
<p>For more complex expressions, type annotations can help with readability of the
|
|
program:</p>
|
|
|
|
<pre><code class="language-ts bad">// Hard to reason about the type of 'value' without an annotation.
|
|
const value = await rpc.getSomeValue().transform();
|
|
</code></pre>
|
|
|
|
<pre><code class="language-ts good">// Can tell the type of 'value' at a glance.
|
|
const value: string[] = await rpc.getSomeValue().transform();
|
|
</code></pre>
|
|
|
|
<p>Whether an annotation is required is decided by the code reviewer.</p>
|
|
|
|
<h4 id="return-types">Return types</h4>
|
|
|
|
<p>Whether to include return type annotations for functions and methods is up to
|
|
the code author. Reviewers <em>may</em> ask for annotations to clarify complex return
|
|
types that are hard to understand. Projects <em>may</em> have a local policy to always
|
|
require return types, but this is not a general TypeScript style requirement.</p>
|
|
|
|
<p>There are two benefits to explicitly typing out the implicit return values of
|
|
functions and methods:</p>
|
|
|
|
<ul>
|
|
<li>More precise documentation to benefit readers of the code.</li>
|
|
<li>Surface potential type errors faster in the future if there are code changes
|
|
that change the return type of the function.</li>
|
|
</ul>
|
|
|
|
<h3 id="null-vs-undefined">Null vs Undefined</h3>
|
|
|
|
<p>TypeScript supports <code>null</code> and <code>undefined</code> types. Nullable types can be
|
|
constructed as a union type (<code>string|null</code>); similarly with <code>undefined</code>. There
|
|
is no special syntax for unions of <code>null</code> and <code>undefined</code>.</p>
|
|
|
|
<p>TypeScript code can use either <code>undefined</code> or <code>null</code> to denote absence of a
|
|
value, there is no general guidance to prefer one over the other. Many
|
|
JavaScript APIs use <code>undefined</code> (e.g. <code>Map.get</code>), while many DOM and Google APIs
|
|
use <code>null</code> (e.g. <code>Element.getAttribute</code>), so the appropriate absent value
|
|
depends on the context.</p>
|
|
|
|
<h4 id="nullableundefined-type-aliases">Nullable/undefined type aliases</h4>
|
|
|
|
<p>Type aliases <em>must not</em> include <code>|null</code> or <code>|undefined</code> in a union type.
|
|
Nullable aliases typically indicate that null values are being passed around
|
|
through too many layers of an application, and this clouds the source of the
|
|
original issue that resulted in <code>null</code>. They also make it unclear when specific
|
|
values on a class or interface might be absent.</p>
|
|
|
|
<p>Instead, code <em>must</em> only add <code>|null</code> or <code>|undefined</code> when the alias is actually
|
|
used. Code <em>should</em> deal with null values close to where they arise, using the
|
|
above techniques.</p>
|
|
|
|
<pre><code class="language-ts bad">// Bad
|
|
type CoffeeResponse = Latte|Americano|undefined;
|
|
|
|
class CoffeeService {
|
|
getLatte(): CoffeeResponse { ... };
|
|
}
|
|
</code></pre>
|
|
|
|
<pre><code class="language-ts good">// Better
|
|
type CoffeeResponse = Latte|Americano;
|
|
|
|
class CoffeeService {
|
|
getLatte(): CoffeeResponse|undefined { ... };
|
|
}
|
|
</code></pre>
|
|
|
|
<pre><code class="language-ts good">// Best
|
|
type CoffeeResponse = Latte|Americano;
|
|
|
|
class CoffeeService {
|
|
getLatte(): CoffeeResponse {
|
|
return assert(fetchResponse(), 'Coffee maker is broken, file a ticket');
|
|
};
|
|
}
|
|
</code></pre>
|
|
|
|
<h4 id="optionals-vs-undefined-type">Optionals vs <code>|undefined</code> type</h4>
|
|
|
|
<p>In addition, TypeScript supports a special construct for optional parameters and
|
|
fields, using <code>?</code>:</p>
|
|
|
|
<pre><code class="language-ts good">interface CoffeeOrder {
|
|
sugarCubes: number;
|
|
milk?: Whole|LowFat|HalfHalf;
|
|
}
|
|
|
|
function pourCoffee(volume?: Milliliter) { ... }
|
|
</code></pre>
|
|
|
|
<p>Optional parameters implicitly include <code>|undefined</code> in their type. However, they
|
|
are different in that they can be left out when constructing a value or calling
|
|
a method. For example, <code>{sugarCubes: 1}</code> is a valid <code>CoffeeOrder</code> because <code>milk</code>
|
|
is optional.</p>
|
|
|
|
<p>Use optional fields (on interfaces or classes) and parameters rather than a
|
|
<code>|undefined</code> type.</p>
|
|
|
|
<p>For classes preferably avoid this pattern altogether and initialize as many
|
|
fields as possible.</p>
|
|
|
|
<pre><code class="language-ts good">class MyClass {
|
|
field = '';
|
|
}
|
|
</code></pre>
|
|
|
|
<h3 id="structural-types-vs-nominal-types">Structural Types vs Nominal Types</h3>
|
|
|
|
<p>TypeScript's type system is structural, not nominal. That is, a value matches a
|
|
type if it has at least all the properties the type requires and the properties'
|
|
types match, recursively.</p>
|
|
|
|
<p>Use structural typing where appropriate in your code. Outside of test code, use
|
|
interfaces to define structural types, not classes. In test code it can be
|
|
useful to have mock implementations structurally match the code under test
|
|
without introducing an extra interface.</p>
|
|
|
|
<p>When providing a structural-based implementation, explicitly include the type at
|
|
the declaration of the symbol (this allows more precise type checking and error
|
|
reporting).</p>
|
|
|
|
<pre><code class="language-ts good">const foo: Foo = {
|
|
a: 123,
|
|
b: 'abc',
|
|
}
|
|
</code></pre>
|
|
|
|
<pre><code class="language-ts bad">const badFoo = {
|
|
a: 123,
|
|
b: 'abc',
|
|
}
|
|
</code></pre>
|
|
|
|
<section class="zippy" markdown="1">
|
|
Why?
|
|
|
|
<p>The <q>badFoo</q> object above relies on type inference. Additional fields could be
|
|
added to <q>badFoo</q> and the type is inferred based on the object itself.</p>
|
|
|
|
<p>When passing a <q>badFoo</q> to a function that takes a <q>Foo</q>, the error will be at
|
|
the function call site, rather than at the object declaration site. This is also
|
|
useful when changing the surface of an interface across broad codebases.</p>
|
|
|
|
<pre><code class="language-ts">interface Animal {
|
|
sound: string;
|
|
name: string;
|
|
}
|
|
|
|
function makeSound(animal: Animal) {}
|
|
|
|
/**
|
|
* 'cat' has an inferred type of '{sound: string}'
|
|
*/
|
|
const cat = {
|
|
sound: 'meow',
|
|
};
|
|
|
|
/**
|
|
* 'cat' does not meet the type contract required for the function, so the
|
|
* TypeScript compiler errors here, which may be very far from where 'cat' is
|
|
* defined.
|
|
*/
|
|
makeSound(cat);
|
|
|
|
/**
|
|
* Horse has a structural type and the type error shows here rather than the
|
|
* function call. 'horse' does not meet the type contract of 'Animal'.
|
|
*/
|
|
const horse: Animal = {
|
|
sound: 'niegh',
|
|
};
|
|
|
|
const dog: Animal = {
|
|
sound: 'bark',
|
|
name: 'MrPickles',
|
|
};
|
|
|
|
makeSound(dog);
|
|
makeSound(horse);
|
|
</code></pre>
|
|
|
|
</section>
|
|
|
|
<h3 id="interfaces-vs-type-aliases">Interfaces vs Type Aliases</h3>
|
|
|
|
<p>TypeScript supports
|
|
<a href="https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#type-aliases">type aliases</a>
|
|
for naming a type expression. This can be used to name primitives, unions,
|
|
tuples, and any other types.</p>
|
|
|
|
<p>However, when declaring types for objects, use interfaces instead of a type
|
|
alias for the object literal expression.</p>
|
|
|
|
<pre><code class="language-ts good">interface User {
|
|
firstName: string;
|
|
lastName: string;
|
|
}
|
|
</code></pre>
|
|
|
|
<pre><code class="language-ts bad">type User = {
|
|
firstName: string,
|
|
lastName: string,
|
|
}
|
|
</code></pre>
|
|
|
|
<section class="zippy" markdown="1">
|
|
Why?
|
|
|
|
<p>These forms are nearly equivalent, so under the principle of just choosing one
|
|
out of two forms to prevent variation, we should choose one. Additionally, there
|
|
are also
|
|
<a href="https://ncjamieson.com/prefer-interfaces/">interesting technical reasons to prefer interface</a>.
|
|
That page quotes the TypeScript team lead: <q>Honestly, my take is that it should
|
|
really just be interfaces for anything that they can model. There is no benefit
|
|
to type aliases when there are so many issues around display/perf.</q>
|
|
</p></section>
|
|
|
|
<h3 id="arrayt-type"><code>Array<T></code> Type</h3>
|
|
|
|
<p>For simple types (containing just alphanumeric characters and dot), use the
|
|
syntax sugar for arrays, <code>T[]</code>, rather than the longer form <code>Array<T></code>.</p>
|
|
|
|
<p>For anything more complex, use the longer form <code>Array<T></code>.</p>
|
|
|
|
<p>These rules apply at each level of nesting, i.e. a simple <code>T[]</code> nested in a more
|
|
complex type would still be spelled as <code>T[]</code>, using the syntax sugar.</p>
|
|
|
|
<p>This also applies for <code>readonly T[]</code> vs <code>ReadonlyArray<T></code>.</p>
|
|
|
|
<pre><code class="language-ts good">const a: string[];
|
|
const b: readonly string[];
|
|
const c: ns.MyObj[];
|
|
const d: Array<string|number>;
|
|
const e: ReadonlyArray<string|number>;
|
|
const f: InjectionToken<string[]>; // Use syntax sugar for nested types.
|
|
</code></pre>
|
|
|
|
<pre><code class="language-ts bad">const a: Array<string>; // the syntax sugar is shorter
|
|
const b: ReadonlyArray<string>;
|
|
const c: {n: number, s: string}[]; // the braces/parens make it harder to read
|
|
const d: (string|number)[];
|
|
const e: readonly (string|number)[];
|
|
</code></pre>
|
|
|
|
<h3 id="indexable-key-string-type">Indexable Types / index signatures (<code>{[key: string]: T}</code>)</h3>
|
|
|
|
<p>In JavaScript, it's common to use an object as an associative array (aka <q>map</q>,
|
|
<q>hash</q>, or <q>dict</q>). Such objects can be typed using an
|
|
<a href="https://www.typescriptlang.org/docs/handbook/2/objects.html#index-signatures">index signature</a>
|
|
(<code>[k: string]: T</code>) in TypeScript:</p>
|
|
|
|
<pre><code class="language-ts">const fileSizes: {[fileName: string]: number} = {};
|
|
fileSizes['readme.txt'] = 541;
|
|
</code></pre>
|
|
|
|
<p>In TypeScript, provide a meaningful label for the key. (The label only exists
|
|
for documentation; it's unused otherwise.)</p>
|
|
|
|
<pre><code class="language-ts bad">const users: {[key: string]: number} = ...;
|
|
</code></pre>
|
|
|
|
<pre><code class="language-ts good">const users: {[userName: string]: number} = ...;
|
|
</code></pre>
|
|
|
|
<blockquote>
|
|
<p>Rather than using one of these, consider using the ES6 <code>Map</code> and <code>Set</code> types
|
|
instead. JavaScript objects have
|
|
<a href="http://2ality.com/2012/01/objects-as-maps.html">surprising undesirable behaviors</a>
|
|
and the ES6 types more explicitly convey your intent. Also, <code>Map</code>s can be
|
|
keyed by—and <code>Set</code>s can contain—types other than <code>string</code>.</p>
|
|
</blockquote>
|
|
|
|
<p>TypeScript's builtin <code>Record<Keys, ValueType></code> type allows constructing types
|
|
with a defined set of keys. This is distinct from associative arrays in that the
|
|
keys are statically known. See advice on that
|
|
<a href="#mapped-conditional-types">below</a>.</p>
|
|
|
|
<h3 id="mapped-conditional-types">Mapped & Conditional Types</h3>
|
|
|
|
<p>TypeScript's
|
|
<a href="https://www.typescriptlang.org/docs/handbook/2/mapped-types.html">mapped types</a>
|
|
and
|
|
<a href="https://www.typescriptlang.org/docs/handbook/2/conditional-types.html">conditional types</a>
|
|
allow specifying new types based on other types. TypeScript's standard library
|
|
includes several type operators based on these (<code>Record</code>, <code>Partial</code>, <code>Readonly</code>
|
|
etc).</p>
|
|
|
|
<p>These type system features allow succinctly specifying types and constructing
|
|
powerful yet type safe abstractions. They come with a number of drawbacks
|
|
though:</p>
|
|
|
|
<ul>
|
|
<li>Compared to explicitly specifying properties and type relations (e.g. using
|
|
interfaces and extension, see below for an example), type operators require
|
|
the reader to mentally evaluate the type expression. This can make programs
|
|
substantially harder to read, in particular combined with type inference and
|
|
expressions crossing file boundaries.</li>
|
|
<li>Mapped & conditional types' evaluation model, in particular when combined
|
|
with type inference, is underspecified, not always well understood, and
|
|
often subject to change in TypeScript compiler versions. Code can
|
|
<q>accidentally</q> compile or seem to give the right results. This increases
|
|
future support cost of code using type operators.</li>
|
|
<li>Mapped & conditional types are most powerful when deriving types from
|
|
complex and/or inferred types. On the flip side, this is also when they are
|
|
most prone to create hard to understand and maintain programs.</li>
|
|
<li>Some language tooling does not work well with these type system features.
|
|
E.g. your IDE's find references (and thus rename property refactoring) will
|
|
not find properties in a <code>Pick<T, Keys></code> type, and Code Search won't
|
|
hyperlink them.</li>
|
|
<li>
|
|
</ul>
|
|
|
|
<p>The style recommendation is:</p>
|
|
|
|
<ul>
|
|
<li>Always use the simplest type construct that can possibly express your code.</li>
|
|
<li>A little bit of repetition or verbosity is often much cheaper than the long
|
|
term cost of complex type expressions.</li>
|
|
<li>Mapped & conditional types may be used, subject to these considerations.</li>
|
|
</ul>
|
|
|
|
<section class="zippy" markdown="1">
|
|
For example, TypeScript's builtin <code>Pick<T, Keys></code> type allows creating a new
|
|
type by subsetting another type <code>T</code>, but simple interface extension can often be
|
|
easier to understand.
|
|
|
|
<pre><code class="language-ts">interface User {
|
|
shoeSize: number;
|
|
favoriteIcecream: string;
|
|
favoriteChocolate: string;
|
|
}
|
|
|
|
// FoodPreferences has favoriteIcecream and favoriteChocolate, but not shoeSize.
|
|
type FoodPreferences = Pick<User, 'favoriteIcecream'|'favoriteChocolate'>;
|
|
</code></pre>
|
|
|
|
<p>This is equivalent to spelling out the properties on <code>FoodPreferences</code>:</p>
|
|
|
|
<pre><code class="language-ts">interface FoodPreferences {
|
|
favoriteIcecream: string;
|
|
favoriteChocolate: string;
|
|
}
|
|
</code></pre>
|
|
|
|
<p>To reduce duplication, <code>User</code> could extend <code>FoodPreferences</code>, or (possibly
|
|
better) nest a field for food preferences:</p>
|
|
|
|
<pre><code class="language-ts good">interface FoodPreferences { /* as above */ }
|
|
interface User extends FoodPreferences {
|
|
shoeSize: number;
|
|
// also includes the preferences.
|
|
}
|
|
</code></pre>
|
|
|
|
<p>Using interfaces here makes the grouping of properties explicit, improves IDE
|
|
support, allows better optimization, and arguably makes the code easier to
|
|
understand.
|
|
</p></section>
|
|
|
|
<h3 id="any"><code>any</code> Type</h3>
|
|
|
|
<p>TypeScript's <code>any</code> type is a super and subtype of all other types, and allows
|
|
dereferencing all properties. As such, <code>any</code> is dangerous - it can mask severe
|
|
programming errors, and its use undermines the value of having static types in
|
|
the first place.</p>
|
|
|
|
<section markdown="1">
|
|
<strong>Consider <em>not</em> to use <code>any</code>.</strong> In circumstances where you
|
|
want to use <code>any</code>, consider one of:
|
|
</section>
|
|
|
|
<ul>
|
|
<li><a href="#any-specific">Provide a more specific type</a></li>
|
|
<li><a href="#any-unknown">Use <code>unknown</code></a></li>
|
|
<li><a href="#any-suppress">Suppress the lint warning and document why</a></li>
|
|
</ul>
|
|
|
|
<h4 id="any-specific">Providing a more specific type</h4>
|
|
|
|
<p>Use interfaces , an
|
|
inline object type, or a type alias:</p>
|
|
|
|
<pre><code class="language-ts good">// Use declared interfaces to represent server-side JSON.
|
|
declare interface MyUserJson {
|
|
name: string;
|
|
email: string;
|
|
}
|
|
|
|
// Use type aliases for types that are repetitive to write.
|
|
type MyType = number|string;
|
|
|
|
// Or use inline object types for complex returns.
|
|
function getTwoThings(): {something: number, other: string} {
|
|
// ...
|
|
return {something, other};
|
|
}
|
|
|
|
// Use a generic type, where otherwise a library would say `any` to represent
|
|
// they don't care what type the user is operating on (but note "Return type
|
|
// only generics" below).
|
|
function nicestElement<T>(items: T[]): T {
|
|
// Find the nicest element in items.
|
|
// Code can also put constraints on T, e.g. <T extends HTMLElement>.
|
|
}
|
|
</code></pre>
|
|
|
|
<h4 id="any-unknown">Using <code>unknown</code> over <code>any</code></h4>
|
|
|
|
<p>The <code>any</code> type allows assignment into any other type and dereferencing any
|
|
property off it. Often this behaviour is not necessary or desirable, and code
|
|
just needs to express that a type is unknown. Use the built-in type <code>unknown</code> in
|
|
that situation — it expresses the concept and is much safer as it does not allow
|
|
dereferencing arbitrary properties.</p>
|
|
|
|
<pre><code class="language-ts good">// Can assign any value (including null or undefined) into this but cannot
|
|
// use it without narrowing the type or casting.
|
|
const val: unknown = value;
|
|
</code></pre>
|
|
|
|
<pre><code class="language-ts bad">const danger: any = value /* result of an arbitrary expression */;
|
|
danger.whoops(); // This access is completely unchecked!
|
|
</code></pre>
|
|
|
|
<section markdown="1">
|
|
To safely use <code>unknown</code> values, narrow the type using a
|
|
<a href="https://www.typescriptlang.org/docs/handbook/advanced-types.html#type-guards-and-differentiating-types">type guard</a>
|
|
</section>
|
|
|
|
<h4 id="any-suppress">Suppressing <code>any</code> lint warnings</h4>
|
|
|
|
<p>Sometimes using <code>any</code> is legitimate, for example in tests to construct a mock
|
|
object. In such cases, add a comment that suppresses the lint warning, and
|
|
document why it is legitimate.</p>
|
|
|
|
<pre><code class="language-ts good">// This test only needs a partial implementation of BookService, and if
|
|
// we overlooked something the test will fail in an obvious way.
|
|
// This is an intentionally unsafe partial mock
|
|
// tslint:disable-next-line:no-any
|
|
const mockBookService = ({get() { return mockBook; }} as any) as BookService;
|
|
// Shopping cart is not used in this test
|
|
// tslint:disable-next-line:no-any
|
|
const component = new MyComponent(mockBookService, /* unused ShoppingCart */ null as any);
|
|
</code></pre>
|
|
|
|
<h3 id="tuple-types">Tuple Types</h3>
|
|
|
|
<p>If you are tempted to create a Pair type, instead use a tuple type:</p>
|
|
|
|
<pre><code class="language-ts bad">interface Pair {
|
|
first: string;
|
|
second: string;
|
|
}
|
|
function splitInHalf(input: string): Pair {
|
|
...
|
|
return {first: x, second: y};
|
|
}
|
|
</code></pre>
|
|
|
|
<pre><code class="language-ts good">function splitInHalf(input: string): [string, string] {
|
|
...
|
|
return [x, y];
|
|
}
|
|
|
|
// Use it like:
|
|
const [leftHalf, rightHalf] = splitInHalf('my string');
|
|
</code></pre>
|
|
|
|
<p>However, often it's clearer to provide meaningful names for the properties.</p>
|
|
|
|
<p>If declaring an <code>interface</code> is too heavyweight, you can use an inline object
|
|
literal type:</p>
|
|
|
|
<pre><code class="language-ts good">function splitHostPort(address: string): {host: string, port: number} {
|
|
...
|
|
}
|
|
|
|
// Use it like:
|
|
const address = splitHostPort(userAddress);
|
|
use(address.port);
|
|
|
|
// You can also get tuple-like behavior using destructuring:
|
|
const {host, port} = splitHostPort(userAddress);
|
|
</code></pre>
|
|
|
|
<h3 id="wrapper-types">Wrapper types</h3>
|
|
|
|
<p>There are a few types related to JavaScript primitives that <em>should not</em> ever be
|
|
used:</p>
|
|
|
|
<ul>
|
|
<li><code>String</code>, <code>Boolean</code>, and <code>Number</code> have slightly different meaning from the
|
|
corresponding primitive types <code>string</code>, <code>boolean</code>, and <code>number</code>. Always use
|
|
the lowercase version.</li>
|
|
<li><code>Object</code> has similarities to both <code>{}</code> and <code>object</code>, but is slightly looser.
|
|
Use <code>{}</code> for a type that include everything except <code>null</code> and <code>undefined</code>,
|
|
or lowercase <code>object</code> to further exclude the other primitive types (the
|
|
three mentioned above, plus <code>symbol</code> and <code>bigint</code>).</li>
|
|
</ul>
|
|
|
|
<p>Further, never invoke the wrapper types as constructors (with <code>new</code>).</p>
|
|
|
|
<h3 id="return-type-only-generics">Return type only generics</h3>
|
|
|
|
<p>Avoid creating APIs that have return type only generics. When working with
|
|
existing APIs that have return type only generics always explicitly specify the
|
|
generics.</p>
|
|
|
|
<h2 id="consistency">Consistency</h2>
|
|
|
|
<p>For any style question that isn't settled definitively by this specification, do
|
|
what the other code in the same file is already doing (<q>be consistent</q>). If that
|
|
doesn't resolve the question, consider emulating the other files in the same
|
|
directory.</p>
|
|
|
|
<h3 id="goals">Goals</h3>
|
|
|
|
<p>In general, engineers usually know best about what's needed in their code, so if
|
|
there are multiple options and the choice is situation dependent, we should let
|
|
decisions be made locally. So the default answer should be <q>leave it out</q>.</p>
|
|
|
|
<p>The following points are the exceptions, which are the reasons we have some
|
|
global rules. Evaluate your style guide proposal against the following:</p>
|
|
|
|
<ol>
|
|
<li><p><strong>Code should avoid patterns that are known to cause problems, especially
|
|
for users new to the language.</strong></p>
|
|
|
|
<p>Examples:</p>
|
|
|
|
<ul>
|
|
<li>The <code>any</code> type is easy to misuse (is that variable <em>really</em> both a
|
|
number and callable as a function?), so we have recommendations for how
|
|
to use it.</li>
|
|
<li>TypeScript <code>namespace</code> causes trouble for Closure optimization.</li>
|
|
<li>Periods within filenames make them ugly/confusing to import from
|
|
JavaScript.</li>
|
|
<li>Static functions in classes optimize confusingly, while often file-level
|
|
functions accomplish the same goal.</li>
|
|
<li>Users unaware of the <code>private</code> keyword will attempt to obfuscate their
|
|
function names with underscores.</li>
|
|
</ul></li>
|
|
<li><p><strong>Code across
|
|
projects should be consistent across
|
|
irrelevant variations.</strong></p>
|
|
|
|
<p>When there are two options that are equivalent in a superficial way, we
|
|
should consider choosing one just so we don't divergently evolve for no
|
|
reason and avoid pointless debates in code reviews.</p>
|
|
|
|
<p>We should usually match JavaScript style as well, because people often write
|
|
both languages together.</p>
|
|
|
|
<p>Examples:</p>
|
|
|
|
<ul>
|
|
<li>The capitalization style of names.</li>
|
|
<li><code>x as T</code> syntax vs the equivalent <code><T>x</code> syntax (disallowed).</li>
|
|
<li><code>Array<[number, number]></code> vs <code>[number, number][]</code>.</li>
|
|
</ul></li>
|
|
<li><p><strong>Code should be maintainable in the long term.</strong></p>
|
|
|
|
<p>Code usually lives longer than the original author works on it, and the
|
|
TypeScript team must keep all of Google working into the future.</p>
|
|
|
|
<p>Examples:</p>
|
|
|
|
<ul>
|
|
<li>We use software to automate changes to code, so code is autoformatted so
|
|
it's easy for software to meet whitespace rules.</li>
|
|
<li>We require a single set of Closure compilation flags, so a given TS
|
|
library can be written assuming a specific set of flags, and users can
|
|
always safely use a shared library.</li>
|
|
<li>Code must import the libraries it uses (<q>strict deps</q>) so that a
|
|
refactor in a dependency doesn't change the dependencies of its users.</li>
|
|
<li>We ask users to write tests. Without tests we cannot have confidence
|
|
that changes that we make to the language, don't break users.</li>
|
|
</ul></li>
|
|
<li><p><strong>Code reviewers should be focused on improving the quality of the code, not
|
|
enforcing arbitrary rules.</strong></p>
|
|
|
|
<p>If it's possible to implement your rule as an
|
|
|
|
automated check that is often a good sign.
|
|
This also supports principle 3.</p>
|
|
|
|
<p>If it really just doesn't matter that much -- if it's an obscure corner of
|
|
the language or if it avoids a bug that is unlikely to occur -- it's
|
|
probably worth leaving out.</p></li>
|
|
</ol>
|
|
|
|
</div>
|
|
</body>
|
|
</html> |