2021-01-02 23:30:59 +08:00
|
|
|
|
<!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>
|
|
|
|
|
|
2022-11-10 03:35:00 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<section>
|
|
|
|
|
|
|
|
|
|
<p>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
|
2022-11-10 03:35:00 +08:00
|
|
|
|
environment has different constraints on TypeScript than you might find outside
|
2023-10-12 06:35:20 +08:00
|
|
|
|
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>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2022-11-10 03:35:00 +08:00
|
|
|
|
<p>There is no automatic deployment process for this version as it's pushed
|
2023-10-12 06:35:20 +08:00
|
|
|
|
on-demand by volunteers.</p>
|
|
|
|
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
<h2 id="introduction" class="numbered">Introduction</h2>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<h3 id="terminology-notes" class="numbered">Terminology notes</h3>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2022-11-10 03:35:00 +08:00
|
|
|
|
<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>,
|
2023-10-12 06:35:20 +08:00
|
|
|
|
and <em>may</em>. The terms <em>prefer</em> and <em>avoid</em> correspond to <em>should</em> and <em>should
|
|
|
|
|
not</em>, respectively. Imperative and declarative statements are prescriptive and
|
|
|
|
|
correspond to <em>must</em>.</p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<h3 id="guide-notes" class="numbered">Guide notes</h3>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p>All examples given are <strong>non-normative</strong> and serve only to illustrate the
|
|
|
|
|
normative language of the style guide. That is, while the examples are in Google
|
|
|
|
|
Style, they may not illustrate the <em>only</em> stylish way to represent the code.
|
|
|
|
|
Optional formatting choices made in examples must not be enforced as rules.</p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<h2 id="source-file-basics" class="numbered">Source file basics</h2>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
|
2022-11-10 03:35:00 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p><a id="file-encoding"></a></p>
|
2022-11-10 03:35:00 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<h3 id="file-encoding-utf-8" class="numbered">File encoding: UTF-8</h3>
|
2022-11-10 03:35:00 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p>Source files are encoded in <strong>UTF-8</strong>.</p>
|
2022-11-10 03:35:00 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p><a id="special-characters"></a></p>
|
2022-11-10 03:35:00 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<h4 id="whitespace-characters" class="numbered">Whitespace characters</h4>
|
2022-11-10 03:35:00 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p>Aside from the line terminator sequence, the ASCII horizontal space character
|
|
|
|
|
(0x20) is the only whitespace character that appears anywhere in a source file.
|
|
|
|
|
This implies that all other whitespace characters in string literals are
|
|
|
|
|
escaped.</p>
|
2022-11-10 03:35:00 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<h4 id="special-escape-sequences" class="numbered">Special escape sequences</h4>
|
2022-11-10 03:35:00 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p>For any character that has a special escape sequence (<code>\'</code>, <code>\"</code>, <code>\\</code>, <code>\b</code>,
|
|
|
|
|
<code>\f</code>, <code>\n</code>, <code>\r</code>, <code>\t</code>, <code>\v</code>), that sequence is used rather than the
|
|
|
|
|
corresponding numeric escape (e.g <code>\x0a</code>, <code>\u000a</code>, or <code>\u{a}</code>). Legacy octal
|
|
|
|
|
escapes are never used.</p>
|
2022-11-10 03:35:00 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<h4 id="non-ascii-characters" class="numbered">Non-ASCII characters</h4>
|
2022-11-10 03:35:00 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p>For the remaining 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>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<pre><code class="language-ts good">// Perfectly clear, even without a comment.
|
|
|
|
|
const units = 'μs';
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
// Use escapes for non-printable characters.
|
|
|
|
|
const output = '\ufeff' + content; // byte order mark
|
2021-01-02 23:30:59 +08:00
|
|
|
|
</code></pre>
|
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<pre><code class="language-ts bad">// Hard to read and prone to mistakes, even with the comment.
|
|
|
|
|
const units = '\u03bcs'; // Greek letter mu, 's'
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
// The reader has no idea what this is.
|
|
|
|
|
const output = '\ufeff' + content;
|
2021-01-02 23:30:59 +08:00
|
|
|
|
</code></pre>
|
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
|
2022-11-10 03:35:00 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
|
2022-11-10 03:35:00 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p><a id="modules"></a>
|
|
|
|
|
<a id="source-organization"></a></p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<h2 id="source-file-structure" class="numbered">Source file structure</h2>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p>Files consist of the following, <strong>in order</strong>:</p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<ol>
|
|
|
|
|
<li>Copyright information, if present</li>
|
|
|
|
|
<li>JSDoc with <code>@fileoverview</code>, if present</li>
|
|
|
|
|
<li>Imports, if present</li>
|
|
|
|
|
<li>The file’s implementation</li>
|
|
|
|
|
</ol>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p><strong>Exactly one blank line</strong> separates each section that is present.</p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<h3 id="file-copyright" class="numbered">Copyright information</h3>
|
2022-11-10 03:35:00 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p>If license or copyright information is necessary in a file, add it in a JSDoc at
|
|
|
|
|
the top of the file. </p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p><a id="file-fileoverview"></a>
|
|
|
|
|
<a id="jsdoc-top-file-level-comments"></a></p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<h3 id="fileoverview" class="numbered"><code>@fileoverview</code> JSDoc</h3>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p>A file may have a top-level <code>@fileoverview</code> JSDoc. If present, it may provide a
|
|
|
|
|
description of the file's content, its uses, or information about its
|
|
|
|
|
dependencies. Wrapped lines are not indented.</p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p>Example:</p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<pre><code class="language-ts good">/**
|
|
|
|
|
* @fileoverview Description of file. Lorem ipsum dolor sit amet, consectetur
|
|
|
|
|
* adipiscing elit, sed do eiusmod tempor incididunt.
|
|
|
|
|
*/
|
|
|
|
|
</code></pre>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<h3 id="imports" class="numbered">Imports</h3>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p>There are four variants of import statements in ES6 and TypeScript:</p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<section>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<table>
|
|
|
|
|
<thead>
|
|
|
|
|
<tr>
|
|
|
|
|
<th>Import type</th>
|
|
|
|
|
<th>Example</th>
|
|
|
|
|
<th>Use for</th>
|
|
|
|
|
</tr>
|
|
|
|
|
</thead>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<tbody>
|
|
|
|
|
<tr>
|
|
|
|
|
<td>module[<sup>module_import]</sup>
|
|
|
|
|
</td>
|
|
|
|
|
<td><code>import * as foo from
|
|
|
|
|
'...';</code></td>
|
|
|
|
|
<td>TypeScript imports
|
|
|
|
|
</td>
|
|
|
|
|
</tr>
|
|
|
|
|
<tr>
|
|
|
|
|
<td>named[<sup>destructuring_import]</sup>
|
|
|
|
|
</td>
|
|
|
|
|
<td><code>import {SomeThing}
|
|
|
|
|
from '...';</code></td>
|
|
|
|
|
<td>TypeScript imports
|
|
|
|
|
</td>
|
|
|
|
|
</tr>
|
|
|
|
|
<tr>
|
|
|
|
|
<td>default
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
</td>
|
|
|
|
|
<td><code>import SomeThing
|
|
|
|
|
from '...';</code>
|
|
|
|
|
</td>
|
|
|
|
|
<td>Only for other
|
|
|
|
|
external code that
|
|
|
|
|
requires them</td>
|
|
|
|
|
</tr>
|
|
|
|
|
<tr>
|
|
|
|
|
<td>side-effect
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2022-11-10 03:35:00 +08:00
|
|
|
|
|
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
</td>
|
|
|
|
|
<td><code>import '...';</code>
|
2022-11-10 03:35:00 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
</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';
|
2022-11-10 03:35:00 +08:00
|
|
|
|
</code></pre>
|
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
</section>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<h4 id="import-paths" class="numbered">Import paths</h4>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<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>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<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>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p>Consider limiting the number of parent steps (<code>../../../</code>) as those can make
|
|
|
|
|
module and path structures hard to understand.</p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<pre><code class="language-ts good">import {Symbol1} from 'path/from/root';
|
|
|
|
|
import {Symbol2} from '../parent/file';
|
|
|
|
|
import {Symbol3} from './sibling';
|
|
|
|
|
</code></pre>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p><a id="module-versus-destructuring-import"></a></p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<h4 id="namespace-versus-named-imports" class="numbered">Namespace versus named imports</h4>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p>Both namespace and named imports can be used.</p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p>Prefer named imports for symbols used frequently in a file or for symbols that
|
|
|
|
|
have clear names, for example Jasmine's <code>describe</code> and <code>it</code>. Named imports can
|
|
|
|
|
be aliased to clearer names as needed with <code>as</code>.</p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p>Prefer namespace imports when using many different symbols from large APIs. A
|
|
|
|
|
namespace import, despite using the <code>*</code> character, is not comparable to a
|
|
|
|
|
<q>wildcard</q> import as seen in other languages. Instead, namespace imports give a
|
|
|
|
|
name to all the exports of a module, and each exported symbol from the module
|
|
|
|
|
becomes a property on the module name. Namespace imports can aid readability for
|
|
|
|
|
exported symbols that have common names like <code>Model</code> or <code>Controller</code> without the
|
|
|
|
|
need to declare aliases.</p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<pre><code class="language-ts bad">// Bad: overlong import statement of needlessly namespaced names.
|
|
|
|
|
import {Item as TableviewItem, Header as TableviewHeader, Row as TableviewRow,
|
|
|
|
|
Model as TableviewModel, Renderer as TableviewRenderer} from './tableview';
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
let item: TableviewItem|undefined;
|
|
|
|
|
</code></pre>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<pre><code class="language-ts good">// Better: use the module for namespacing.
|
|
|
|
|
import * as tableview from './tableview';
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
let item: tableview.Item|undefined;
|
|
|
|
|
</code></pre>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<pre><code class="language-ts bad">import * as testing from './testing';
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
// Bad: The module name does not improve readability.
|
|
|
|
|
testing.describe('foo', () => {
|
|
|
|
|
testing.it('bar', () => {
|
|
|
|
|
testing.expect(null).toBeNull();
|
|
|
|
|
testing.expect(undefined).toBeUndefined();
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
</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(null).toBeNull();
|
|
|
|
|
expect(undefined).toBeUndefined();
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
</code></pre>
|
|
|
|
|
|
|
|
|
|
<h5 id="jspb-import-by-path" class="numbered">Special case: Apps JSPB protos</h5>
|
|
|
|
|
|
|
|
|
|
<p>Apps JSPB protos must use named imports, even when it leads to long import
|
|
|
|
|
lines.</p>
|
|
|
|
|
|
|
|
|
|
<p>This rule exists to aid in build performance and dead code elimination since
|
|
|
|
|
often <code>.proto</code> files contain many <code>message</code>s that are not all needed together.
|
|
|
|
|
By leveraging destructured imports the build system can create finer grained
|
|
|
|
|
dependencies on Apps JSPB messages while preserving the ergonomics of path based
|
|
|
|
|
imports.</p>
|
|
|
|
|
|
|
|
|
|
<pre><code class="language-ts good">// Good: import the exact set of symbols you need from the proto file.
|
|
|
|
|
import {Foo, Bar} from './foo.proto';
|
|
|
|
|
|
|
|
|
|
function copyFooBar(foo: Foo, bar: Bar) {...}
|
|
|
|
|
</code></pre>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<h4 id="renaming-imports" class="numbered">Renaming imports</h4>
|
|
|
|
|
|
|
|
|
|
<p>Code <em>should</em> fix name collisions by using a namespace 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>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<h3 id="exports" class="numbered">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">
|
|
|
|
|
|
|
|
|
|
<p>Why?</p>
|
|
|
|
|
|
|
|
|
|
<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" class="numbered">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" class="numbered">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>
|
|
|
|
|
|
|
|
|
|
<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>
|
|
|
|
|
|
|
|
|
|
<p><a id="static-containers"></a></p>
|
|
|
|
|
|
|
|
|
|
<h4 id="container-classes" class="numbered">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="import-export-type" class="numbered">Import and export type</h3>
|
|
|
|
|
|
|
|
|
|
<h4 id="import-type" class="numbered">Import type</h4>
|
|
|
|
|
|
|
|
|
|
<p>You may use <code>import type {...}</code> when you use the imported symbol only as a type.
|
|
|
|
|
Use regular imports for values:</p>
|
|
|
|
|
|
|
|
|
|
<pre><code class="language-ts good">import type {Foo} from './foo';
|
|
|
|
|
import {Bar} from './foo';
|
|
|
|
|
|
|
|
|
|
import {type Foo, Bar} from './foo';
|
|
|
|
|
</code></pre>
|
|
|
|
|
|
|
|
|
|
<section class="zippy">
|
|
|
|
|
|
|
|
|
|
<p>Why?</p>
|
|
|
|
|
|
|
|
|
|
<p>The TypeScript compiler automatically handles the distinction and does not
|
|
|
|
|
insert runtime loads for type references. So why annotate type imports?</p>
|
|
|
|
|
|
|
|
|
|
<p>The TypeScript compiler can run in 2 modes:</p>
|
|
|
|
|
|
|
|
|
|
<ul>
|
|
|
|
|
<li>In development mode, we typically want quick iteration loops. The compiler
|
|
|
|
|
transpiles to JavaScript without full type information. This is much faster,
|
|
|
|
|
but requires <code>import type</code> in certain cases.</li>
|
|
|
|
|
<li>In production mode, we want correctness. The compiler type checks everything
|
|
|
|
|
and ensures <code>import type</code> is used correctly.</li>
|
|
|
|
|
</ul>
|
|
|
|
|
|
|
|
|
|
<p>Note: If you need to force a runtime load for side effects, use <code>import '...';</code>.
|
|
|
|
|
See </p>
|
|
|
|
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
<h4 id="export-type" class="numbered">Export type</h4>
|
|
|
|
|
|
|
|
|
|
<p>Use <code>export type</code> when re-exporting a type, e.g.:</p>
|
|
|
|
|
|
|
|
|
|
<pre><code class="language-ts good">export type {AnInterface} from './foo';
|
|
|
|
|
</code></pre>
|
|
|
|
|
|
|
|
|
|
<section class="zippy">
|
|
|
|
|
|
|
|
|
|
<p>Why?</p>
|
|
|
|
|
|
|
|
|
|
<p><code>export type</code> is useful to allow type re-exports in file-by-file transpilation.
|
|
|
|
|
See
|
|
|
|
|
<a href="https://www.typescriptlang.org/tsconfig#exports-of-non-value-identifiers"><code>isolatedModules</code> docs</a>.</p>
|
|
|
|
|
|
|
|
|
|
<p><code>export type</code> might also 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>
|
|
|
|
|
|
|
|
|
|
<p><a id="namespaces-vs-modules"></a></p>
|
|
|
|
|
|
|
|
|
|
<h4 id="use-modules-not-namespaces" class="numbered">Use modules not namespaces</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>
|
|
|
|
|
|
|
|
|
|
<p><a id="language-rules"></a></p>
|
|
|
|
|
|
|
|
|
|
<h2 id="language-features" class="numbered">Language features</h2>
|
|
|
|
|
|
|
|
|
|
<p>This section delineates which features may or may not be used, and any
|
|
|
|
|
additional constraints on their use.</p>
|
|
|
|
|
|
|
|
|
|
<p>Language features which are not discussed in this style guide <em>may</em> be used with
|
|
|
|
|
no recommendations of their usage.</p>
|
|
|
|
|
|
|
|
|
|
<p><a id="features-local-variable-declarations"></a></p>
|
|
|
|
|
|
|
|
|
|
<h3 id="local-variable-declarations" class="numbered">Local variable declarations</h3>
|
|
|
|
|
|
|
|
|
|
<p><a id="variables"></a>
|
|
|
|
|
<a id="features-use-const-and-let"></a></p>
|
|
|
|
|
|
|
|
|
|
<h4 id="use-const-and-let" class="numbered">Use const and let</h4>
|
|
|
|
|
|
|
|
|
|
<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>
|
|
|
|
|
|
|
|
|
|
<p><a id="features-one-variable-per-declaration"></a></p>
|
|
|
|
|
|
|
|
|
|
<h4 id="one-variable-per-declaration" class="numbered">One variable per declaration</h4>
|
|
|
|
|
|
|
|
|
|
<p>Every local variable declaration declares only one variable: declarations such
|
|
|
|
|
as <code class="badcode">let a = 1, b = 2;</code> are not used.</p>
|
|
|
|
|
|
|
|
|
|
<p><a id="features-array-literals"></a></p>
|
|
|
|
|
|
|
|
|
|
<h3 id="array-literals" class="numbered">Array literals</h3>
|
|
|
|
|
|
|
|
|
|
<p><a id="features-arrays-ctor"></a></p>
|
|
|
|
|
|
|
|
|
|
<h4 id="array-constructor" class="numbered">Do not use the <code>Array</code> constructor</h4>
|
|
|
|
|
|
|
|
|
|
<p><em>Do 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>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<p><a id="features-arrays-non-numeric-properties"></a></p>
|
|
|
|
|
|
|
|
|
|
<h4 id="do-not-define-properties-on-arrays" class="numbered">Do not define properties on arrays</h4>
|
|
|
|
|
|
|
|
|
|
<p>Do not define or use non-numeric properties on an array (other than <code>length</code>).
|
|
|
|
|
Use a <code>Map</code> (or <code>Object</code>) instead.</p>
|
|
|
|
|
|
|
|
|
|
<p><a id="features-arrays-spread-operator"></a></p>
|
|
|
|
|
|
|
|
|
|
<h4 id="array-spread-syntax" class="numbered">Using spread syntax</h4>
|
|
|
|
|
|
|
|
|
|
<p>Using spread syntax <code>[...foo];</code> is a convenient shorthand for shallow-copying or
|
|
|
|
|
concatenating iterables.</p>
|
|
|
|
|
|
|
|
|
|
<pre><code class="language-ts good">const foo = [
|
|
|
|
|
1,
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
const foo2 = [
|
|
|
|
|
...foo,
|
|
|
|
|
6,
|
|
|
|
|
7,
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
const foo3 = [
|
|
|
|
|
5,
|
|
|
|
|
...foo,
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
foo2[1] === 6;
|
|
|
|
|
foo3[1] === 1;
|
|
|
|
|
</code></pre>
|
|
|
|
|
|
|
|
|
|
<p>When using spread syntax, the value being spread <em>must</em> match what is being
|
|
|
|
|
created. 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 = [7];
|
|
|
|
|
const bar = [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 ? [7] : [];
|
|
|
|
|
const bar = [5, ...foo];
|
|
|
|
|
const fooStrings = ['a', 'b', 'c'];
|
|
|
|
|
const ids = [...fooStrings, 'd', 'e'];
|
|
|
|
|
</code></pre>
|
|
|
|
|
|
|
|
|
|
<p><a id="features-arrays-destructuring"></a></p>
|
|
|
|
|
|
|
|
|
|
<h4 id="array-destructuring" class="numbered">Array destructuring</h4>
|
|
|
|
|
|
|
|
|
|
<p>Array literals may be used on the left-hand side of an assignment to perform
|
|
|
|
|
destructuring (such as when unpacking multiple values from a single array or
|
|
|
|
|
iterable). A final <q>rest</q> element may be included (with no space between the
|
|
|
|
|
<code>...</code> and the variable name). Elements should be omitted if they are unused.</p>
|
|
|
|
|
|
|
|
|
|
<pre><code class="language-ts good">const [a, b, c, ...rest] = generateResults();
|
|
|
|
|
let [, b,, d] = someArray;
|
|
|
|
|
</code></pre>
|
|
|
|
|
|
|
|
|
|
<p>Destructuring may also be used for function parameters. Always specify <code>[]</code> as
|
|
|
|
|
the default value if a destructured array parameter is optional, and provide
|
|
|
|
|
default values on the left hand side:</p>
|
|
|
|
|
|
|
|
|
|
<pre><code class="language-ts good">function destructured([a = 4, b = 2] = []) { … }
|
|
|
|
|
</code></pre>
|
|
|
|
|
|
|
|
|
|
<p>Disallowed:</p>
|
|
|
|
|
|
|
|
|
|
<pre><code class="language-ts bad">function badDestructuring([a, b] = [4, 2]) { … }
|
|
|
|
|
</code></pre>
|
|
|
|
|
|
|
|
|
|
<p>Tip: For (un)packing multiple values into a function’s parameter or return,
|
|
|
|
|
prefer object destructuring to array destructuring when possible, as it allows
|
|
|
|
|
naming the individual elements and specifying a different type for each.</p>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<p><a id="features-object-literals"></a></p>
|
|
|
|
|
|
|
|
|
|
<h3 id="object-literals" class="numbered">Object literals</h3>
|
|
|
|
|
|
|
|
|
|
<p><a id="features-objects-ctor"></a></p>
|
|
|
|
|
|
|
|
|
|
<h4 id="object-constructor" class="numbered">Do not use the <code>Object</code> constructor</h4>
|
|
|
|
|
|
|
|
|
|
<p>The <code>Object</code> constructor is disallowed. Use an object literal (<code>{}</code> or <code>{a: 0,
|
|
|
|
|
b: 1, c: 2}</code>) instead.</p>
|
|
|
|
|
|
|
|
|
|
<h4 id="iterating-objects" class="numbered">Iterating objects</h4>
|
|
|
|
|
|
|
|
|
|
<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>
|
|
|
|
|
|
|
|
|
|
<p><a id="using-the-spread-operator"></a></p>
|
|
|
|
|
|
|
|
|
|
<h4 id="object-spread-syntax" class="numbered">Using spread syntax</h4>
|
|
|
|
|
|
|
|
|
|
<p>Using spread syntax <code>{...bar}</code> is a convenient shorthand for creating a shallow
|
|
|
|
|
copy of an object. When using spread syntax in object initialization, 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 spread syntax, the value being spread <em>must</em> match what is being
|
|
|
|
|
created. That is, when creating an object, only objects may be spread; arrays
|
|
|
|
|
and primitives (including <code>null</code> and <code>undefined</code>) <em>must not</em> be spread. Avoid
|
|
|
|
|
spreading objects that have prototypes other than the Object prototype (e.g.
|
|
|
|
|
class definitions, class instances, functions) as the behavior is unintuitive
|
|
|
|
|
(only enumerable non-prototype properties are shallow-copied).</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};
|
|
|
|
|
</code></pre>
|
|
|
|
|
|
|
|
|
|
<p><a id="features-objects-computed-property-names"></a></p>
|
|
|
|
|
|
|
|
|
|
<h4 id="computed-property-names" class="numbered">Computed property names</h4>
|
|
|
|
|
|
|
|
|
|
<p>Computed property names (e.g. <code>{['key' + foo()]: 42}</code>) are allowed, and are
|
|
|
|
|
considered dict-style (quoted) keys (i.e., must not be mixed with non-quoted
|
|
|
|
|
keys) unless the computed property is a
|
|
|
|
|
<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol">symbol</a>
|
|
|
|
|
(e.g. <code>[Symbol.iterator]</code>).</p>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<p><a id="features-objects-destructuring"></a></p>
|
|
|
|
|
|
|
|
|
|
<h4 id="object-destructuring" class="numbered">Object destructuring</h4>
|
|
|
|
|
|
|
|
|
|
<p>Object destructuring patterns may be used on the left-hand side of an assignment
|
|
|
|
|
to perform destructuring and unpack multiple values from a single object.</p>
|
|
|
|
|
|
|
|
|
|
<p>Destructured objects may also be used as function parameters, but should be kept
|
|
|
|
|
as simple as possible: a single level of unquoted shorthand properties. Deeper
|
|
|
|
|
levels of nesting and computed properties may not be used in parameter
|
|
|
|
|
destructuring. Specify any default values in the left-hand-side of the
|
|
|
|
|
destructured parameter (<code>{str = 'some default'} = {}</code>, rather than
|
|
|
|
|
<code class="badcode">{str} = {str: 'some default'}</code>), and if a
|
|
|
|
|
destructured object is itself optional, it must default to <code>{}</code>.</p>
|
|
|
|
|
|
|
|
|
|
<p>Example:</p>
|
|
|
|
|
|
|
|
|
|
<pre><code class="language-ts good">interface Options {
|
|
|
|
|
/** The number of times to do something. */
|
|
|
|
|
num?: number;
|
|
|
|
|
|
|
|
|
|
/** A string to do stuff to. */
|
|
|
|
|
str?: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function destructured({num, str = 'default'}: Options = {}) {}
|
|
|
|
|
</code></pre>
|
|
|
|
|
|
|
|
|
|
<p>Disallowed:</p>
|
|
|
|
|
|
|
|
|
|
<pre><code class="language-ts bad">function nestedTooDeeply({x: {num, str}}: {x: Options}) {}
|
|
|
|
|
function nontrivialDefault({num, str}: Options = {num: 42, str: 'default'}) {}
|
|
|
|
|
</code></pre>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<p><a id="formatting-class-literals"></a>
|
|
|
|
|
<a id="features-classes"></a></p>
|
|
|
|
|
|
|
|
|
|
<h3 id="classes" class="numbered">Classes</h3>
|
|
|
|
|
|
|
|
|
|
<h4 id="class-declarations" class="numbered">Class declarations</h4>
|
|
|
|
|
|
|
|
|
|
<p>Class declarations <em>must not</em> be terminated with semicolons:</p>
|
|
|
|
|
|
|
|
|
|
<pre><code class="language-ts good">class Foo {
|
|
|
|
|
}
|
|
|
|
|
</code></pre>
|
|
|
|
|
|
|
|
|
|
<pre><code class="language-ts bad">class Foo {
|
|
|
|
|
}; // Unnecessary semicolon
|
|
|
|
|
</code></pre>
|
|
|
|
|
|
|
|
|
|
<p>In contrast, statements that contain class expressions <em>must</em> be terminated with
|
|
|
|
|
a semicolon:</p>
|
|
|
|
|
|
|
|
|
|
<pre><code class="language-ts good">export const Baz = class extends Bar {
|
|
|
|
|
method(): number {
|
|
|
|
|
return this.x;
|
|
|
|
|
}
|
|
|
|
|
}; // Semicolon here as this is a statement, not a declaration
|
|
|
|
|
</code></pre>
|
|
|
|
|
|
|
|
|
|
<pre><code class="language-ts bad">exports const Baz = class extends Bar {
|
|
|
|
|
method(): number {
|
|
|
|
|
return this.x;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</code></pre>
|
|
|
|
|
|
|
|
|
|
<p>It is neither encouraged nor discouraged to have blank lines separating class
|
|
|
|
|
declaration braces from other class content:</p>
|
|
|
|
|
|
|
|
|
|
<pre><code class="language-ts good">// No spaces around braces - fine.
|
|
|
|
|
class Baz {
|
|
|
|
|
method(): number {
|
|
|
|
|
return this.x;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// A single space around both braces - also fine.
|
|
|
|
|
class Foo {
|
|
|
|
|
|
|
|
|
|
method(): number {
|
|
|
|
|
return this.x;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
</code></pre>
|
|
|
|
|
|
|
|
|
|
<h4 id="class-method-declarations" class="numbered">Class method declarations</h4>
|
|
|
|
|
|
|
|
|
|
<p>Class method declarations <em>must not</em> use a semicolon to separate individual
|
|
|
|
|
method declarations:</p>
|
|
|
|
|
|
|
|
|
|
<pre><code class="language-ts good">class Foo {
|
|
|
|
|
doThing() {
|
|
|
|
|
console.log("A");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</code></pre>
|
|
|
|
|
|
|
|
|
|
<pre><code class="language-ts bad">class Foo {
|
|
|
|
|
doThing() {
|
|
|
|
|
console.log("A");
|
|
|
|
|
}; // <-- unnecessary
|
|
|
|
|
}
|
|
|
|
|
</code></pre>
|
|
|
|
|
|
|
|
|
|
<p>Method declarations should be separated from surrounding code by a single blank
|
|
|
|
|
line:</p>
|
|
|
|
|
|
|
|
|
|
<pre><code class="language-ts good">class Foo {
|
|
|
|
|
doThing() {
|
|
|
|
|
console.log("A");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
getOtherThing(): number {
|
|
|
|
|
return 4;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</code></pre>
|
|
|
|
|
|
|
|
|
|
<pre><code class="language-ts bad">class Foo {
|
|
|
|
|
doThing() {
|
|
|
|
|
console.log("A");
|
|
|
|
|
}
|
|
|
|
|
getOtherThing(): number {
|
|
|
|
|
return 4;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</code></pre>
|
|
|
|
|
|
|
|
|
|
<p><a id="features-classes-overriding-tostring"></a></p>
|
|
|
|
|
|
|
|
|
|
<h5 id="overriding-tostring" class="numbered">Overriding toString</h5>
|
|
|
|
|
|
|
|
|
|
<p>The <code>toString</code> method may be overridden, but must always succeed and never have
|
|
|
|
|
visible side effects.</p>
|
|
|
|
|
|
|
|
|
|
<p>Tip: Beware, in particular, of calling other methods from toString, since
|
|
|
|
|
exceptional conditions could lead to infinite loops.</p>
|
|
|
|
|
|
|
|
|
|
<p><a id="features-classes-static-methods"></a></p>
|
|
|
|
|
|
|
|
|
|
<h4 id="static-methods" class="numbered">Static methods</h4>
|
|
|
|
|
|
|
|
|
|
<h5 id="avoid-private-static-methods" class="numbered">Avoid private static methods</h5>
|
|
|
|
|
|
|
|
|
|
<p>Where it does not interfere with readability, prefer module-local functions over
|
|
|
|
|
private static methods.</p>
|
|
|
|
|
|
|
|
|
|
<h5 id="avoid-static-method-dynamic-dispatch" class="numbered">Do not rely on dynamic dispatch</h5>
|
|
|
|
|
|
|
|
|
|
<p>Code <em>should not</em> rely on dynamic dispatch of static
|
|
|
|
|
methods. Static methods <em>should</em> only be called on the base class
|
|
|
|
|
itself (which defines it directly). Static methods <em>should not</em> be called on
|
|
|
|
|
variables containing a dynamic instance that may be either the constructor or a
|
|
|
|
|
subclass constructor (and <em>must</em> be defined with <code>@nocollapse</code> if this is done),
|
|
|
|
|
and <em>must not</em> be called directly on a subclass that doesn’t define the method
|
|
|
|
|
itself.</p>
|
|
|
|
|
|
|
|
|
|
<p>Disallowed:</p>
|
|
|
|
|
|
|
|
|
|
<pre><code class="language-ts bad">// Context for the examples below (this class is okay by itself)
|
|
|
|
|
class Base {
|
|
|
|
|
/** @nocollapse */ static foo() {}
|
|
|
|
|
}
|
|
|
|
|
class Sub extends Base {}
|
|
|
|
|
|
|
|
|
|
// Discouraged: don't call static methods dynamically
|
|
|
|
|
function callFoo(cls: typeof Base) {
|
|
|
|
|
cls.foo();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Disallowed: don't call static methods on subclasses that don't define it themselves
|
|
|
|
|
Sub.foo();
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
// Disallowed: don't access this in static methods.
|
|
|
|
|
class MyClass {
|
|
|
|
|
static foo() {
|
|
|
|
|
return this.staticField;
|
|
|
|
|
}
|
2021-01-02 23:30:59 +08:00
|
|
|
|
}
|
2023-10-12 06:35:20 +08:00
|
|
|
|
MyClass.staticField = 1;
|
|
|
|
|
</code></pre>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<h5 id="static-this" class="numbered">Avoid static <code>this</code> references</h5>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p>Code <em>must not</em> use <code>this</code> in a static context.</p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p>JavaScript allows accessing static fields through <code>this</code>. Different from other
|
|
|
|
|
languages, static fields are also inherited.</p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<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
|
2021-01-02 23:30:59 +08:00
|
|
|
|
}
|
|
|
|
|
</code></pre>
|
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<section class="zippy">
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p>Why?</p>
|
|
|
|
|
|
|
|
|
|
<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>
|
|
|
|
|
|
|
|
|
|
<p><a id="disallowed-features-omitting-parents-with-new"></a>
|
|
|
|
|
<a id="features-classes-constructors"></a></p>
|
|
|
|
|
|
|
|
|
|
<h4 id="constructors" class="numbered">Constructors</h4>
|
|
|
|
|
|
|
|
|
|
<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>Omitting parentheses can lead to subtle mistakes. These two lines are not
|
|
|
|
|
equivalent:</p>
|
|
|
|
|
|
|
|
|
|
<pre><code class="language-js good">new Foo().Bar();
|
|
|
|
|
new Foo.Bar();
|
|
|
|
|
</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>
|
|
|
|
|
|
|
|
|
|
<p>The constructor should be separated from surrounding code both above and below
|
|
|
|
|
by a single blank line:</p>
|
|
|
|
|
|
|
|
|
|
<pre><code class="language-ts good">class Foo {
|
|
|
|
|
myField = 10;
|
|
|
|
|
|
|
|
|
|
constructor(private readonly ctorParam) {}
|
|
|
|
|
|
|
|
|
|
doThing() {
|
|
|
|
|
console.log(ctorParam.getThing() + myField);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</code></pre>
|
|
|
|
|
|
|
|
|
|
<pre><code class="language-ts bad">class Foo {
|
|
|
|
|
myField = 10;
|
|
|
|
|
constructor(private readonly ctorParam) {}
|
|
|
|
|
doThing() {
|
|
|
|
|
console.log(ctorParam.getThing() + myField);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</code></pre>
|
|
|
|
|
|
|
|
|
|
<h4 id="class-members" class="numbered">Class members</h4>
|
|
|
|
|
|
|
|
|
|
<h5 id="private-fields" class="numbered">No #private fields</h5>
|
|
|
|
|
|
|
|
|
|
<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">
|
|
|
|
|
|
|
|
|
|
<p>Why?</p>
|
|
|
|
|
|
|
|
|
|
<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>
|
|
|
|
|
|
|
|
|
|
<h5 id="use-readonly" class="numbered">Use readonly</h5>
|
|
|
|
|
|
|
|
|
|
<p>Mark properties that are never reassigned outside of the constructor with the
|
|
|
|
|
<code>readonly</code> modifier (these need not be deeply immutable).</p>
|
|
|
|
|
|
|
|
|
|
<h5 id="parameter-properties" class="numbered">Parameter properties</h5>
|
|
|
|
|
|
|
|
|
|
<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>
|
|
|
|
|
|
|
|
|
|
<h5 id="field-initializers" class="numbered">Field initializers</h5>
|
|
|
|
|
|
|
|
|
|
<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>
|
|
|
|
|
|
|
|
|
|
<p>Tip: Properties should never be added to or removed from an instance after the
|
|
|
|
|
constructor is finished, since it significantly hinders VMs’ ability to optimize
|
|
|
|
|
classes' <q>shape</q>. Optional fields that may be filled in later should be
|
|
|
|
|
explicitly initialized to <code>undefined</code> to prevent later shape changes.</p>
|
|
|
|
|
|
|
|
|
|
<h5 id="properties-used-outside-of-class-lexical-scope" class="numbered">Properties used outside of class lexical scope</h5>
|
|
|
|
|
|
|
|
|
|
<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.</p>
|
|
|
|
|
|
|
|
|
|
<section class="zippy">
|
|
|
|
|
|
|
|
|
|
<p>Why?</p>
|
|
|
|
|
|
|
|
|
|
<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>
|
|
|
|
|
|
|
|
|
|
<p><a id="features-classes-getters-and-setters"></a></p>
|
|
|
|
|
|
|
|
|
|
<h5 id="classes-getters-and-setters" class="numbered">Getters and setters</h5>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<p>Getters and setters, also known as accessors, 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: getters <em>must not</em> change observable state).
|
|
|
|
|
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>
|
|
|
|
|
|
|
|
|
|
<pre><code class="language-ts bad">class Foo {
|
|
|
|
|
nextId = 0;
|
|
|
|
|
get next() {
|
|
|
|
|
return this.nextId++; // Bad: getter changes observable state
|
2021-01-02 23:30:59 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</code></pre>
|
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<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>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<pre><code class="language-ts good">class Foo {
|
|
|
|
|
private wrappedBar = '';
|
|
|
|
|
get bar() {
|
|
|
|
|
return this.wrappedBar || 'bar';
|
|
|
|
|
}
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
set bar(wrapped: string) {
|
|
|
|
|
this.wrappedBar = wrapped.trim();
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-01-02 23:30:59 +08:00
|
|
|
|
</code></pre>
|
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<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;
|
2021-01-02 23:30:59 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</code></pre>
|
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p>Getters and setters <em>must not</em> be defined using <code>Object.defineProperty</code>, since
|
|
|
|
|
this interferes with property renaming.</p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p><a id="features-classes-computed-properties"></a></p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<h5 id="class-computed-properties" class="numbered">Computed properties</h5>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p>Computed properties may only be used in classes when the property is a symbol.
|
|
|
|
|
Dict-style properties (that is, quoted or computed non-symbol keys) are not
|
|
|
|
|
allowed (see
|
|
|
|
|
<a href="#features-objects-mixing-keys">rationale for not mixing key types</a>. A
|
|
|
|
|
<code>[Symbol.iterator]</code> method should be defined for any classes that are logically
|
|
|
|
|
iterable. Beyond this, <code>Symbol</code> should be used sparingly.</p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p>Tip: be careful of using any other built-in symbols (e.g.
|
|
|
|
|
<code>Symbol.isConcatSpreadable</code>) as they are not polyfilled by the compiler and will
|
|
|
|
|
therefore not work in older browsers.</p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
|
2022-11-10 03:35:00 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<h4 id="visibility" class="numbered">Visibility</h4>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
|
|
|
|
<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>
|
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p>See also <a href="#export-visibility">export visibility</a>.</p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<h4 id="disallowed-class-patterns" class="numbered">Disallowed class patterns</h4>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p><a id="features-classes-prototypes"></a></p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<h5 id="class-prototypes" class="numbered">Do not manipulate <code>prototype</code>s directly</h5>
|
|
|
|
|
|
|
|
|
|
<p>The <code>class</code> keyword allows clearer and more readable class definitions than
|
|
|
|
|
defining <code>prototype</code> properties. Ordinary implementation code has no business
|
|
|
|
|
manipulating these objects. Mixins and modifying the prototypes of builtin
|
|
|
|
|
objects are explicitly forbidden.</p>
|
|
|
|
|
|
|
|
|
|
<p><strong>Exception</strong>: Framework code (such as Polymer, or Angular) may need to use <code>prototype</code>s, and should not resort
|
|
|
|
|
to even-worse workarounds to avoid doing so.</p>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<p><a id="features-functions"></a></p>
|
|
|
|
|
|
|
|
|
|
<h3 id="functions" class="numbered">Functions</h3>
|
|
|
|
|
|
|
|
|
|
<h4 id="terminology" class="numbered">Terminology</h4>
|
|
|
|
|
|
|
|
|
|
<p>There are many different types of functions, with subtle distinctions between
|
|
|
|
|
them. This guide uses the following terminology, which aligns with
|
|
|
|
|
<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions">MDN</a>:</p>
|
|
|
|
|
|
|
|
|
|
<ul>
|
|
|
|
|
<li><q>function declaration</q>: a declaration (i.e. not an expression) using the
|
|
|
|
|
<code>function</code> keyword</li>
|
|
|
|
|
<li><q>function expression</q>: an expression, typically used in an assignment or
|
|
|
|
|
passed as a parameter, using the <code>function</code> keyword</li>
|
|
|
|
|
<li><q>arrow function</q>: an expression using the <code>=></code> syntax</li>
|
|
|
|
|
<li><q>block body</q>: right hand side of an arrow function with braces</li>
|
|
|
|
|
<li><q>concise body</q>: right hand side of an arrow function without braces</li>
|
|
|
|
|
</ul>
|
|
|
|
|
|
|
|
|
|
<p>Methods and classes/constructors are not covered in this section.</p>
|
|
|
|
|
|
|
|
|
|
<h4 id="function-declarations" class="numbered">Prefer function declarations for named functions</h4>
|
|
|
|
|
|
|
|
|
|
<p>Prefer function declarations over arrow functions or function expressions when
|
|
|
|
|
defining named functions.</p>
|
|
|
|
|
|
|
|
|
|
<pre><code class="language-.ts good">function foo() {
|
|
|
|
|
return 42;
|
|
|
|
|
}
|
2021-01-02 23:30:59 +08:00
|
|
|
|
</code></pre>
|
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<pre><code class="language-ts bad">const foo = () => 42;
|
2021-01-02 23:30:59 +08:00
|
|
|
|
</code></pre>
|
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p>Arrow functions <em>may</em> be used, for example, when an explicit type annotation is
|
|
|
|
|
required.</p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<pre><code class="language-ts good">interface SearchFunction {
|
|
|
|
|
(source: string, subString: string): boolean;
|
2021-01-02 23:30:59 +08:00
|
|
|
|
}
|
2023-10-12 06:35:20 +08:00
|
|
|
|
|
|
|
|
|
const fooSearch: SearchFunction = (source, subString) => { ... };
|
2021-01-02 23:30:59 +08:00
|
|
|
|
</code></pre>
|
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<p><a id="features-functions-nested-functions"></a></p>
|
|
|
|
|
|
|
|
|
|
<h4 id="nested-functions" class="numbered">Nested functions</h4>
|
|
|
|
|
|
|
|
|
|
<p>Functions nested within other methods or functions <em>may</em> use function
|
|
|
|
|
declarations or arrow functions, as appropriate. In method bodies in particular,
|
|
|
|
|
arrow functions are preferred because they have access to the outer <code>this</code>.</p>
|
|
|
|
|
|
|
|
|
|
<p><a id="use-arrow-functions-in-expressions"></a></p>
|
|
|
|
|
|
|
|
|
|
<h4 id="function-expressions" class="numbered">Do not use function expressions</h4>
|
|
|
|
|
|
|
|
|
|
<p>Do not use function expressions. Use arrow functions instead.</p>
|
|
|
|
|
|
|
|
|
|
<pre><code class="language-ts good">bar(() => { this.doSomething(); })
|
2021-01-02 23:30:59 +08:00
|
|
|
|
</code></pre>
|
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<pre><code class="language-ts bad">bar(function() { ... })
|
|
|
|
|
</code></pre>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p><strong>Exception:</strong> Function expressions <em>may</em> be used <em>only if</em> code has to
|
|
|
|
|
dynamically rebind <code>this</code> (but this is <a href="#rebinding-this">discouraged</a>), or for
|
|
|
|
|
generator functions (which do not have an arrow syntax).</p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p><a id="features-functions-arrow-functions"></a>
|
|
|
|
|
<a id="expression-bodies-vs-block-bodies"></a></p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<h4 id="arrow-function-bodies" class="numbered">Arrow function bodies</h4>
|
|
|
|
|
|
|
|
|
|
<p>Use arrow functions with concise bodies (i.e. expressions) or block bodies as
|
|
|
|
|
appropriate.</p>
|
|
|
|
|
|
|
|
|
|
<pre><code class="language-ts good">// Top level functions use function declarations.
|
|
|
|
|
function someFunction() {
|
|
|
|
|
// Block bodies are fine:
|
|
|
|
|
const receipts = books.map((b: Book) => {
|
|
|
|
|
const receipt = payMoney(b.price);
|
|
|
|
|
recordTransaction(receipt);
|
|
|
|
|
return receipt;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Concise 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 must not access `this`.
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Nested arrow functions may be assigned to a const.
|
|
|
|
|
const computeTax = (amount: number) => amount * 0.12;
|
2021-01-02 23:30:59 +08:00
|
|
|
|
}
|
|
|
|
|
</code></pre>
|
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p>Only use a concise body if the return value of the function is actually used.
|
|
|
|
|
The block body makes sure the return type is <code>void</code> then and prevents potential
|
|
|
|
|
side effects.</p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<pre><code class="language-ts bad">// BAD: use a block body if the return value of the function is not used.
|
|
|
|
|
myPromise.then(v => console.log(v));
|
|
|
|
|
// BAD: this typechecks, but the return value still leaks.
|
|
|
|
|
let f: () => void;
|
|
|
|
|
f = () => 1;
|
|
|
|
|
</code></pre>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<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);
|
|
|
|
|
});
|
|
|
|
|
// GOOD: explicit `void` ensures no leaked return value
|
|
|
|
|
myPromise.then(v => void console.log(v));
|
|
|
|
|
</code></pre>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p>Tip: The <code>void</code> operator can be used to ensure an arrow function with an
|
|
|
|
|
expression body returns <code>undefined</code> when the result is unused.</p>
|
|
|
|
|
|
|
|
|
|
<h4 id="rebinding-this" class="numbered">Rebinding <code>this</code></h4>
|
|
|
|
|
|
|
|
|
|
<p>Function expressions and function declarations <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';
|
2021-01-02 23:30:59 +08:00
|
|
|
|
}
|
2023-10-12 06:35:20 +08:00
|
|
|
|
// Bad: the `this` pointer reference is implicitly set to document.body.
|
|
|
|
|
document.body.onclick = clickHandler;
|
2021-01-02 23:30:59 +08:00
|
|
|
|
</code></pre>
|
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<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>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p>Prefer arrow functions over other approaches to binding <code>this</code>, such as
|
|
|
|
|
<code>f.bind(this)</code>, <code>goog.bind(f, this)</code>, or <code>const self = this</code>.</p>
|
|
|
|
|
|
|
|
|
|
<h4 id="functions-as-callbacks" class="numbered">Prefer passing arrow functions as callbacks</h4>
|
|
|
|
|
|
|
|
|
|
<p>Callbacks can be invoked with unexpected arguments that can pass a type check
|
|
|
|
|
but still result in logical errors.</p>
|
|
|
|
|
|
|
|
|
|
<p>Avoid passing a named callback to a higher-order function, unless you are sure
|
|
|
|
|
of the stability of both functions' call signatures. Beware, in particular, of
|
|
|
|
|
less-commonly-used optional parameters.</p>
|
|
|
|
|
|
|
|
|
|
<pre><code class="language-ts bad">// BAD: Arguments are not explicitly passed, leading to unintended behavior
|
|
|
|
|
// when the optional `radix` argument gets the array indices 0, 1, and 2.
|
|
|
|
|
const numbers = ['11', '5', '10'].map(parseInt);
|
|
|
|
|
// > [11, NaN, 2];
|
2021-01-02 23:30:59 +08:00
|
|
|
|
</code></pre>
|
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p>Instead, prefer passing an arrow-function that explicitly forwards parameters to
|
|
|
|
|
the named callback.</p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<pre><code class="language-ts good">// GOOD: Arguments are explicitly passed to the callback
|
|
|
|
|
const numbers = ['11', '5', '3'].map((n) => parseInt(n));
|
|
|
|
|
// > [11, 5, 3]
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
// GOOD: Function is locally defined and is designed to be used as a callback
|
|
|
|
|
function dayFilter(element: string|null|undefined) {
|
|
|
|
|
return element != null && element.endsWith('day');
|
|
|
|
|
}
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
const days = ['tuesday', undefined, 'juice', 'wednesday'].filter(dayFilter);
|
|
|
|
|
</code></pre>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<h4 id="arrow-functions-as-properties" class="numbered">Arrow functions as properties</h4>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<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>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<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>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<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>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<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;
|
2021-01-02 23:30:59 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</code></pre>
|
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<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;
|
|
|
|
|
}
|
2021-01-02 23:30:59 +08:00
|
|
|
|
}
|
|
|
|
|
</code></pre>
|
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<h4 id="event-handlers" class="numbered">Event handlers</h4>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<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>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<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>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<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?');
|
2021-01-02 23:30:59 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</code></pre>
|
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p><a id="features-functions-default-parameters"></a></p>
|
|
|
|
|
|
|
|
|
|
<h4 id="parameter-initializers" class="numbered">Parameter initializers</h4>
|
|
|
|
|
|
|
|
|
|
<p>Optional function parameters <em>may</em> be given a default initializer to use when
|
|
|
|
|
the argument is omitted. Initializers <em>must not</em> have any observable side
|
|
|
|
|
effects. Initializers <em>should</em> be kept as simple as possible.</p>
|
|
|
|
|
|
|
|
|
|
<pre><code class="language-ts good">function process(name: string, extraContext: string[] = []) {}
|
|
|
|
|
function activate(index = 0) {}
|
|
|
|
|
</code></pre>
|
|
|
|
|
|
|
|
|
|
<pre><code class="language-ts bad">// BAD: side effect of incrementing the counter
|
|
|
|
|
let globalCounter = 0;
|
|
|
|
|
function newId(index = globalCounter++) {}
|
|
|
|
|
|
|
|
|
|
// BAD: exposes shared mutable state, which can introduce unintended coupling
|
|
|
|
|
// between function calls
|
|
|
|
|
class Foo {
|
|
|
|
|
private readonly defaultPaths: string[];
|
|
|
|
|
frobnicate(paths = defaultPaths) {}
|
2021-01-02 23:30:59 +08:00
|
|
|
|
}
|
|
|
|
|
</code></pre>
|
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p>Use default parameters sparingly. Prefer
|
|
|
|
|
<a href="#features-objects-destructuring">destructuring</a> to create readable APIs when
|
|
|
|
|
there are more than a small handful of optional parameters that do not have a
|
|
|
|
|
natural order.</p>
|
|
|
|
|
|
|
|
|
|
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p><a id="features-functions-rest-parameters"></a>
|
|
|
|
|
<a id="features-functions-spread-operator"></a></p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<h4 id="rest-and-spread" class="numbered">Prefer rest and spread when appropriate</h4>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p>Use a <em>rest</em> parameter instead of accessing <code>arguments</code>. Never name a local
|
|
|
|
|
variable or parameter <code>arguments</code>, which confusingly shadows the built-in name.</p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<pre><code class="language-ts good">function variadic(array: string[], ...numbers: number[]) {}
|
|
|
|
|
</code></pre>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p>Use function spread syntax instead of <code>Function.prototype.apply</code>.</p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p><a id="features-functions-generators"></a></p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<h4 id="formatting-functions" class="numbered">Formatting functions</h4>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p>Blank lines at the start or end of the function body are not allowed.</p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p>A single blank line <em>may</em> be used within function bodies sparingly to create
|
|
|
|
|
<em>logical groupings</em> of statements.</p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p>Generators should attach the <code>*</code> to the <code>function</code> and <code>yield</code> keywords, as in
|
|
|
|
|
<code>function* foo()</code> and <code>yield* iter</code>, rather than <del><code>function *foo()</code></del> or
|
|
|
|
|
<del><code>yield *iter</code></del>.</p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p>Parentheses around the left-hand side of a single-argument arrow function are
|
|
|
|
|
recommended but not required.</p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p>Do not put a space after the <code>...</code> in rest or spread syntax.</p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<pre><code class="language-ts good">function myFunction(...elements: number[]) {}
|
|
|
|
|
myFunction(...array, ...iterable, ...generator());
|
2021-01-02 23:30:59 +08:00
|
|
|
|
</code></pre>
|
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<h3 id="features-this" class="numbered">this</h3>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p>Only use <code>this</code> in class constructors and methods, functions that have an
|
|
|
|
|
explicit <code>this</code> type declared (e.g. <code>function func(this: ThisType, ...)</code>), or in
|
|
|
|
|
arrow functions defined in a scope where <code>this</code> may be used.</p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p>Never use <code>this</code> to refer to the global object, the context of an <code>eval</code>, the
|
|
|
|
|
target of an event, or unnecessarily <code>call()</code>ed or <code>apply()</code>ed functions.</p>
|
2022-11-10 03:35:00 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<pre><code class="language-ts bad">this.alert('Hello');
|
|
|
|
|
</code></pre>
|
2022-11-10 03:35:00 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<h3 id="interfaces" class="numbered">Interfaces</h3>
|
2022-11-10 03:35:00 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
|
2022-11-10 03:35:00 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
|
2022-11-10 03:35:00 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<h3 id="primitive-literals" class="numbered">Primitive literals</h3>
|
2022-11-10 03:35:00 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p><a id="features-string-literals"></a></p>
|
2022-11-10 03:35:00 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<h4 id="string-literals" class="numbered">String literals</h4>
|
2022-11-10 03:35:00 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p><a id="features-strings-use-single-quotes"></a></p>
|
2022-11-10 03:35:00 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<h5 id="use-single-quotes" class="numbered">Use single quotes</h5>
|
2022-11-10 03:35:00 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p>Ordinary string literals are delimited with single quotes (<code>'</code>), rather than
|
|
|
|
|
double quotes (<code>"</code>).</p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p>Tip: if a string contains a single quote character, consider using a template
|
|
|
|
|
string to avoid having to escape the quote.</p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p><a id="syntax-no-line-continuations"></a>
|
|
|
|
|
<a id="features-strings-no-line-continuations"></a></p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<h5 id="no-line-continuations" class="numbered">No line continuations</h5>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<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>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p>Disallowed:</p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<pre><code class="language-ts bad">const LONG_STRING = 'This is a very very very very very very very long string. \
|
|
|
|
|
It inadvertently contains long stretches of spaces due to how the \
|
|
|
|
|
continued lines are indented.';
|
|
|
|
|
</code></pre>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p>Instead, write</p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<pre><code class="language-ts good">const LONG_STRING = 'This is a very very very very very very long string. ' +
|
|
|
|
|
'It does not contain long stretches of spaces because it uses ' +
|
|
|
|
|
'concatenated strings.';
|
|
|
|
|
const SINGLE_STRING =
|
|
|
|
|
'http://it.is.also/acceptable_to_use_a_single_long_string_when_breaking_would_hinder_search_discoverability';
|
2021-01-02 23:30:59 +08:00
|
|
|
|
</code></pre>
|
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p><a id="features-strings-template-strings"></a></p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<h5 id="template-literals" class="numbered">Template literals</h5>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p>Use template literals (delimited with <code>`</code>) over complex string
|
|
|
|
|
concatenation, particularly if multiple string literals are involved. Template
|
|
|
|
|
literals may span multiple lines.</p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p>If a template literal spans multiple lines, it does not need to follow the
|
|
|
|
|
indentation of the enclosing block, though it may if the added whitespace does
|
|
|
|
|
not matter.</p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p>Example:</p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<pre><code class="language-ts good">function arithmetic(a: number, b: number) {
|
|
|
|
|
return `Here is a table of arithmetic operations:
|
|
|
|
|
${a} + ${b} = ${a + b}
|
|
|
|
|
${a} - ${b} = ${a - b}
|
|
|
|
|
${a} * ${b} = ${a * b}
|
|
|
|
|
${a} / ${b} = ${a / b}`;
|
|
|
|
|
}
|
2021-01-02 23:30:59 +08:00
|
|
|
|
</code></pre>
|
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p><a id="features-number-literals"></a></p>
|
|
|
|
|
|
|
|
|
|
<h4 id="number-literals" class="numbered">Number literals</h4>
|
|
|
|
|
|
|
|
|
|
<p>Numbers may be specified in decimal, hex, octal, or binary. Use exactly <code>0x</code>,
|
|
|
|
|
<code>0o</code>, and <code>0b</code> prefixes, with lowercase letters, for hex, octal, and binary,
|
|
|
|
|
respectively. Never include a leading zero unless it is immediately followed by
|
|
|
|
|
<code>x</code>, <code>o</code>, or <code>b</code>.</p>
|
|
|
|
|
|
|
|
|
|
<h4 id="type-coercion" class="numbered">Type coercion</h4>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2022-11-10 03:35:00 +08:00
|
|
|
|
<p>TypeScript code <em>may</em> use the <code>String()</code> and <code>Boolean()</code> (note: no <code>new</code>!)
|
2021-01-02 23:30:59 +08:00
|
|
|
|
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>
|
|
|
|
|
|
2022-11-10 03:35:00 +08:00
|
|
|
|
<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>
|
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<section class="zippy">
|
|
|
|
|
|
|
|
|
|
<p>Why?</p>
|
2022-11-10 03:35:00 +08:00
|
|
|
|
|
|
|
|
|
<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
|
2023-10-12 06:35:20 +08:00
|
|
|
|
or not.</p>
|
|
|
|
|
|
|
|
|
|
</section>
|
2022-11-10 03:35:00 +08:00
|
|
|
|
|
2021-01-02 23:30:59 +08:00
|
|
|
|
<p>Using string concatenation to cast to string is discouraged, as we check that
|
|
|
|
|
operands to the plus operator are of matching types.</p>
|
|
|
|
|
|
2022-11-10 03:35:00 +08:00
|
|
|
|
<p>Code <em>must</em> use <code>Number()</code> to parse numeric values, and <em>must</em> check its return
|
2021-01-02 23:30:59 +08:00
|
|
|
|
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>
|
2022-11-10 03:35:00 +08:00
|
|
|
|
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>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
|
|
|
|
<pre><code class="language-ts good">const aNumber = Number('123');
|
2022-11-10 03:35:00 +08:00
|
|
|
|
if (!isFinite(aNumber)) throw new Error(...);
|
2021-01-02 23:30:59 +08:00
|
|
|
|
</code></pre>
|
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
|
|
|
|
|
|
2022-11-10 03:35:00 +08:00
|
|
|
|
<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>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
|
|
|
|
<pre><code class="language-ts bad">const x = +y;
|
|
|
|
|
</code></pre>
|
|
|
|
|
|
2022-11-10 03:35:00 +08:00
|
|
|
|
<p>Code also <em>must not</em> use <code>parseInt</code> or <code>parseFloat</code> to parse numbers, except for
|
2021-01-02 23:30:59 +08:00
|
|
|
|
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>
|
|
|
|
|
|
2022-11-10 03:35:00 +08:00
|
|
|
|
<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>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
|
|
|
|
<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>
|
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p><a id="type-coercion-implicit"></a></p>
|
|
|
|
|
|
|
|
|
|
<h5 id="implicit-coercion" class="numbered">Implicit coercion</h5>
|
2022-11-10 03:35:00 +08:00
|
|
|
|
|
2021-01-02 23:30:59 +08:00
|
|
|
|
<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>
|
|
|
|
|
|
2022-11-10 03:35:00 +08:00
|
|
|
|
<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>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
|
|
|
|
<pre><code class="language-ts good">// Explicitly comparing > 0 is OK:
|
|
|
|
|
if (arr.length > 0) {...}
|
2023-10-12 06:35:20 +08:00
|
|
|
|
// so is relying on boolean coercion:
|
|
|
|
|
if (arr.length) {...}
|
2021-01-02 23:30:59 +08:00
|
|
|
|
</code></pre>
|
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p><a id="features-control-structures"></a></p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<h3 id="control-structures" class="numbered">Control structures</h3>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p><a id="formatting-braces"></a>
|
|
|
|
|
<a id="formatting-braces-all"></a></p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<h4 id="control-flow-statements-blocks" class="numbered">Control flow statements and blocks</h4>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p>Control flow statements (<code>if</code>, <code>else</code>, <code>for</code>, <code>do</code>, <code>while</code>, etc) always use
|
|
|
|
|
braced blocks for the containing code, even if the body contains only a single
|
|
|
|
|
statement. The first statement of a non-empty block must begin on its own line.</p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
|
|
|
|
<pre><code class="language-ts good">for (let i = 0; i < x; i++) {
|
|
|
|
|
doSomethingWith(i);
|
|
|
|
|
}
|
2022-11-10 03:35:00 +08:00
|
|
|
|
|
2021-01-02 23:30:59 +08:00
|
|
|
|
if (x) {
|
2022-11-10 03:35:00 +08:00
|
|
|
|
doSomethingWithALongMethodNameThatForcesANewLine(x);
|
2021-01-02 23:30:59 +08:00
|
|
|
|
}
|
|
|
|
|
</code></pre>
|
|
|
|
|
|
|
|
|
|
<pre><code class="language-ts bad">if (x)
|
2022-11-10 03:35:00 +08:00
|
|
|
|
doSomethingWithALongMethodNameThatForcesANewLine(x);
|
|
|
|
|
|
|
|
|
|
for (let i = 0; i < x; i++) doSomethingWith(i);
|
2021-01-02 23:30:59 +08:00
|
|
|
|
</code></pre>
|
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p><strong>Exception:</strong> <code>if</code> statements fitting on one line <em>may</em> elide the block.</p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
|
|
|
|
<pre><code class="language-ts good">if (x) x.doFoo();
|
|
|
|
|
</code></pre>
|
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<h5 id="assignment-in-control-statements" class="numbered">Assignment in control statements</h5>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2022-11-10 03:35:00 +08:00
|
|
|
|
<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>
|
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p><a id="features-for-loops"></a></p>
|
2022-11-10 03:35:00 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<h5 id="iterating-containers" class="numbered">Iterating containers</h5>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p>Prefer <code>for (... of someArr)</code> to iterate over arrays. <code>Array.prototype.forEach</code> and vanilla <code>for</code>
|
|
|
|
|
loops are also allowed:</p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<pre><code class="language-ts good">for (const x of someArr) {
|
|
|
|
|
// x is a value of someArr.
|
|
|
|
|
}
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
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.
|
2021-01-02 23:30:59 +08:00
|
|
|
|
}
|
|
|
|
|
</code></pre>
|
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p><code>for</code>-<code>in</code> loops may only be used on dict-style objects (see
|
|
|
|
|
<a href="#optimization-compatibility-for-property-access">below</a> for more info). Do not
|
|
|
|
|
use <code>for (... in ...)</code> to iterate over arrays as it will counterintuitively give
|
|
|
|
|
the array's indices (as strings!), not values:</p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<pre><code class="language-ts bad">for (const x in someArray) {
|
|
|
|
|
// x is the index!
|
2021-01-02 23:30:59 +08:00
|
|
|
|
}
|
|
|
|
|
</code></pre>
|
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p><code>Object.prototype.hasOwnProperty</code> should be used in <code>for</code>-<code>in</code> loops to exclude
|
|
|
|
|
unwanted prototype properties. Prefer <code>for</code>-<code>of</code> with <code>Object.keys</code>,
|
|
|
|
|
<code>Object.values</code>, or <code>Object.entries</code> over <code>for</code>-<code>in</code> when possible.</p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<pre><code class="language-ts good">for (const key in obj) {
|
|
|
|
|
if (!obj.hasOwnProperty(key)) continue;
|
|
|
|
|
doWork(key, obj[key]);
|
2021-01-02 23:30:59 +08:00
|
|
|
|
}
|
2023-10-12 06:35:20 +08:00
|
|
|
|
for (const key of Object.keys(obj)) {
|
|
|
|
|
doWork(key, obj[key]);
|
|
|
|
|
}
|
|
|
|
|
for (const value of Object.values(obj)) {
|
|
|
|
|
doWorkValOnly(value);
|
|
|
|
|
}
|
|
|
|
|
for (const [key, value] of Object.entries(obj)) {
|
|
|
|
|
doWork(key, value);
|
2021-01-02 23:30:59 +08:00
|
|
|
|
}
|
|
|
|
|
</code></pre>
|
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p><a id="formatting-grouping-parentheses"></a></p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<h4 id="grouping-parentheses" class="numbered">Grouping parentheses</h4>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p>Optional grouping parentheses are omitted only when the author and reviewer
|
|
|
|
|
agree that there is no reasonable chance that the code will be misinterpreted
|
|
|
|
|
without them, nor would they have made the code easier to read. It is <em>not</em>
|
|
|
|
|
reasonable to assume that every reader has the entire operator precedence table
|
|
|
|
|
memorized.</p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p>Do not use unnecessary parentheses around the entire expression following
|
|
|
|
|
<code>delete</code>, <code>typeof</code>, <code>void</code>, <code>return</code>, <code>throw</code>, <code>case</code>, <code>in</code>, <code>of</code>, or <code>yield</code>.</p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p><a id="exceptions"></a>
|
|
|
|
|
<a id="features-exceptions"></a></p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<h4 id="exception-handling" class="numbered">Exception handling</h4>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p>Exceptions are an important part of the language and should be used whenever
|
|
|
|
|
exceptional cases occur.</p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p>Custom exceptions provide a great way to convey additional error information
|
|
|
|
|
from functions. They should be defined and used wherever the native <code>Error</code> type
|
|
|
|
|
is insufficient.</p>
|
2022-11-10 03:35:00 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p>Prefer throwing exceptions over ad-hoc error-handling approaches (such as
|
|
|
|
|
passing an error container reference type, or returning an object with an error
|
|
|
|
|
property).</p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<h5 id="instantiate-errors-using-new" class="numbered">Instantiate errors using <code>new</code></h5>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<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>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<pre><code class="language-ts good">throw new Error('Foo is not a valid bar.');
|
2021-01-02 23:30:59 +08:00
|
|
|
|
</code></pre>
|
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<pre><code class="language-ts bad">throw Error('Foo is not a valid bar.');
|
|
|
|
|
</code></pre>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<h5 id="only-throw-errors" class="numbered">Only throw errors</h5>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p>JavaScript (and thus TypeScript) allow throwing or rejecting a Promise with
|
|
|
|
|
arbitrary values. However if the thrown or rejected value is not an <code>Error</code>, it
|
|
|
|
|
does not populate stack trace information, making debugging hard. This treatment
|
|
|
|
|
extends to <code>Promise</code> rejection values as <code>Promise.reject(obj)</code> is equivalent to
|
|
|
|
|
<code>throw obj;</code> in async functions.</p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<pre><code class="language-ts bad">// bad: does not get a stack trace.
|
|
|
|
|
throw 'oh noes!';
|
|
|
|
|
// For promises
|
|
|
|
|
new Promise((resolve, reject) => void reject('oh noes!'));
|
|
|
|
|
Promise.reject();
|
|
|
|
|
Promise.reject('oh noes!');
|
2021-01-02 23:30:59 +08:00
|
|
|
|
</code></pre>
|
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p>Instead, only throw (subclasses of) <code>Error</code>:</p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<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!');
|
|
|
|
|
// For promises
|
|
|
|
|
new Promise((resolve) => resolve()); // No reject is OK.
|
|
|
|
|
new Promise((resolve, reject) => void reject(new Error('oh noes!')));
|
|
|
|
|
Promise.reject(new Error('oh noes!'));
|
|
|
|
|
</code></pre>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<h5 id="catching-and-rethrowing" class="numbered">Catching and rethrowing</h5>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p>When catching errors, code <em>should</em> assume that all thrown errors are instances
|
|
|
|
|
of <code>Error</code>.</p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<section>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<pre><code class="language-ts good">function assertIsError(e: unknown): asserts e is Error {
|
|
|
|
|
if (!(e instanceof Error)) throw new Error("e is not an Error");
|
|
|
|
|
}
|
2022-11-10 03:35:00 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
try {
|
|
|
|
|
doSomething();
|
|
|
|
|
} catch (e: unknown) {
|
|
|
|
|
// All thrown errors must be Error subtypes. Do not handle
|
|
|
|
|
// other possible values unless you know they are thrown.
|
|
|
|
|
assertIsError(e);
|
|
|
|
|
displayError(e.message);
|
|
|
|
|
// or rethrow:
|
|
|
|
|
throw e;
|
2021-01-02 23:30:59 +08:00
|
|
|
|
}
|
|
|
|
|
</code></pre>
|
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
</section>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<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>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<pre><code class="language-ts good">try {
|
|
|
|
|
badApiThrowingStrings();
|
|
|
|
|
} catch (e: unknown) {
|
|
|
|
|
// Note: bad API throws strings instead of errors.
|
|
|
|
|
if (typeof e === 'string') { ... }
|
|
|
|
|
}
|
2021-01-02 23:30:59 +08:00
|
|
|
|
</code></pre>
|
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<section class="zippy">
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p>Why?</p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<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>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
</section>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p><a id="features-empty-catch-blocks"></a></p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<h5 id="empty-catch-blocks" class="numbered">Empty catch blocks</h5>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p>It is very rarely correct to do nothing in response to a caught exception. When
|
|
|
|
|
it truly is appropriate to take no action whatsoever in a catch block, the
|
|
|
|
|
reason this is justified is explained in a comment.</p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<pre><code class="language-ts good"> try {
|
|
|
|
|
return handleNumericResponse(response);
|
|
|
|
|
} catch (e: unknown) {
|
|
|
|
|
// Response is not numeric. Continue to handle as text.
|
2021-01-02 23:30:59 +08:00
|
|
|
|
}
|
2023-10-12 06:35:20 +08:00
|
|
|
|
return handleTextResponse(response);
|
2021-01-02 23:30:59 +08:00
|
|
|
|
</code></pre>
|
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p>Disallowed:</p>
|
|
|
|
|
|
|
|
|
|
<pre><code class="language-ts bad"> try {
|
|
|
|
|
shouldFail();
|
|
|
|
|
fail('expected an error');
|
|
|
|
|
} catch (expected: unknown) {
|
2021-01-02 23:30:59 +08:00
|
|
|
|
}
|
|
|
|
|
</code></pre>
|
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p>Tip: Unlike in some other languages, patterns like the above simply don’t work
|
|
|
|
|
since this will catch the error thrown by <code>fail</code>. Use <code>assertThrows()</code> instead.</p>
|
|
|
|
|
|
|
|
|
|
<p><a id="features-switch-statements"></a>
|
|
|
|
|
<a id="features-switch-default-case"></a></p>
|
|
|
|
|
|
|
|
|
|
<h4 id="switch-statements" class="numbered">Switch statements</h4>
|
|
|
|
|
|
|
|
|
|
<p>All <code>switch</code> statements <em>must</em> contain a <code>default</code> statement group, even if it
|
|
|
|
|
contains no code. The <code>default</code> statement group must be last.</p>
|
|
|
|
|
|
|
|
|
|
<pre><code class="language-ts good">switch (x) {
|
|
|
|
|
case Y:
|
|
|
|
|
doSomethingElse();
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
// nothing to do.
|
2021-01-02 23:30:59 +08:00
|
|
|
|
}
|
|
|
|
|
</code></pre>
|
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p>Within a switch block, each statement group either terminates abruptly with a
|
|
|
|
|
<code>break</code>, a <code>return</code> statement, or by throwing an exception. Non-empty statement
|
|
|
|
|
groups (<code>case ...</code>) <em>must not</em> fall through (enforced by the compiler):</p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<pre><code class="language-ts bad">switch (x) {
|
|
|
|
|
case X:
|
|
|
|
|
doSomething();
|
|
|
|
|
// fall through - not allowed!
|
|
|
|
|
case Y:
|
|
|
|
|
// ...
|
2021-01-02 23:30:59 +08:00
|
|
|
|
}
|
|
|
|
|
</code></pre>
|
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p>Empty statement groups are allowed to fall through:</p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<pre><code class="language-ts good">switch (x) {
|
|
|
|
|
case X:
|
|
|
|
|
case Y:
|
|
|
|
|
doSomething();
|
|
|
|
|
break;
|
|
|
|
|
default: // nothing to do.
|
2021-01-02 23:30:59 +08:00
|
|
|
|
}
|
|
|
|
|
</code></pre>
|
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p><a id="features-equality-checks"></a>
|
|
|
|
|
<a id="features-equality-checks-exceptions"></a></p>
|
|
|
|
|
|
|
|
|
|
<h4 id="equality-checks" class="numbered">Equality checks</h4>
|
|
|
|
|
|
|
|
|
|
<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>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<pre><code class="language-ts good">if (foo === 'bar' || baz !== bam) {
|
|
|
|
|
// All good here.
|
|
|
|
|
}
|
|
|
|
|
</code></pre>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<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>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<pre><code class="language-ts good">if (foo == null) {
|
|
|
|
|
// Will trigger when foo is null or undefined.
|
|
|
|
|
}
|
|
|
|
|
</code></pre>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<h4 id="type-and-non-nullability-assertions" class="numbered">Type and non-nullability assertions</h4>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
|
|
|
|
<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
|
2022-11-10 03:35:00 +08:00
|
|
|
|
comments <em>may</em> not be necessary. For example, generated proto code is always
|
2021-01-02 23:30:59 +08:00
|
|
|
|
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>
|
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<h5 id="type-assertions-syntax" class="numbered">Type assertion syntax</h5>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2022-11-10 03:35:00 +08:00
|
|
|
|
<p>Type assertions <em>must</em> use the <code>as</code> syntax (as opposed to the angle brackets
|
2021-01-02 23:30:59 +08:00
|
|
|
|
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>
|
|
|
|
|
|
2022-11-10 03:35:00 +08:00
|
|
|
|
<pre><code class="language-ts good">// z must be Foo because ...
|
|
|
|
|
const x = (z as Foo).length;
|
2021-01-02 23:30:59 +08:00
|
|
|
|
</code></pre>
|
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<h5 id="double-assertions" class="numbered">Double assertions</h5>
|
|
|
|
|
|
|
|
|
|
<p>From the
|
|
|
|
|
<a href="https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#type-assertions">TypeScript handbook</a>,
|
|
|
|
|
TypeScript only allows type assertions which convert to a <em>more specific</em> or
|
|
|
|
|
<em>less specific</em> version of a type. Adding a type assertion (<code>x as Foo</code>) which
|
|
|
|
|
does not meet this criteria will give the error: <q>Conversion of type 'X' to type
|
|
|
|
|
'Y' may be a mistake because neither type sufficiently overlaps with the other.</q></p>
|
|
|
|
|
|
|
|
|
|
<p>If you are sure an assertion is safe, you can perform a <em>double assertion</em>. This
|
|
|
|
|
involves casting through <code>unknown</code> since it is less specific than all types.</p>
|
|
|
|
|
|
|
|
|
|
<pre><code class="language-ts good">// x is a Foo here, because...
|
|
|
|
|
(x as unknown as Foo).fooMethod();
|
|
|
|
|
</code></pre>
|
|
|
|
|
|
|
|
|
|
<p>Use <code>unknown</code> (instead of <code>any</code> or <code>{}</code>) as the intermediate type.</p>
|
|
|
|
|
|
|
|
|
|
<h5 id="type-assertions-and-object-literals" class="numbered">Type assertions and object literals</h5>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
|
|
|
|
<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>
|
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<h4 id="keep-try-blocks-focused" class="numbered">Keep try blocks focused</h4>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p>Limit the amount of code inside a try block, if this can be done without hurting
|
|
|
|
|
readability.</p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<pre><code class="language-ts bad">try {
|
|
|
|
|
const result = methodThatMayThrow();
|
|
|
|
|
use(result);
|
|
|
|
|
} catch (error: unknown) {
|
|
|
|
|
// ...
|
2022-11-10 03:35:00 +08:00
|
|
|
|
}
|
|
|
|
|
</code></pre>
|
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<pre><code class="language-ts good">let result;
|
|
|
|
|
try {
|
|
|
|
|
result = methodThatMayThrow();
|
|
|
|
|
} catch (error: unknown) {
|
|
|
|
|
// ...
|
2021-01-02 23:30:59 +08:00
|
|
|
|
}
|
2023-10-12 06:35:20 +08:00
|
|
|
|
use(result);
|
2021-01-02 23:30:59 +08:00
|
|
|
|
</code></pre>
|
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<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>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<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>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<h3 id="decorators" class="numbered">Decorators</h3>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
|
|
|
|
<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>
|
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<section>
|
|
|
|
|
|
|
|
|
|
<p>Why?</p>
|
|
|
|
|
|
|
|
|
|
<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>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2022-11-10 03:35:00 +08:00
|
|
|
|
<p>When using decorators, the decorator <em>must</em> immediately precede the symbol it
|
2021-01-02 23:30:59 +08:00
|
|
|
|
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>
|
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<h3 id="disallowed-features" class="numbered">Disallowed features</h3>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p><a id="disallowed-features-wrapper-objects"></a></p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<h4 id="primitive-types-wrapper-classes" class="numbered">Wrapper objects for primitive types</h4>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<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>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<pre><code class="language-ts bad">const s = new String('hello');
|
|
|
|
|
const b = new Boolean(false);
|
|
|
|
|
const n = new Number(5);
|
|
|
|
|
</code></pre>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p>The wrappers may be called as functions for coercing (which is preferred over
|
|
|
|
|
using <code>+</code> or concatenating the empty string) or creating symbols. See
|
|
|
|
|
<a href="#type-coercion">type coercion</a> for more information.</p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p><a id="formatting-semicolons-are-required"></a>
|
|
|
|
|
<a id="disallowed-features-automatic-semicolon-insertion"></a></p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<h4 id="automatic-semicolon-insertion" class="numbered">Automatic Semicolon Insertion</h4>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p>Do not rely on Automatic Semicolon Insertion (ASI). Explicitly end 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>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<h4 id="enums" class="numbered">Const enums</h4>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p>Code <em>must not</em> use <code>const enum</code>; use plain <code>enum</code> instead.</p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<section class="zippy">
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p>Why?</p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<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>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
</section>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<h4 id="debugger-statements" class="numbered">Debugger statements</h4>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p>Debugger statements <em>must not</em> be included in production code.</p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<pre><code class="language-ts bad">function debugMe() {
|
|
|
|
|
debugger;
|
|
|
|
|
}
|
2021-01-02 23:30:59 +08:00
|
|
|
|
</code></pre>
|
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p><a id="disallowed-features-with"></a></p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<h4 id="diallowed-features-with" class="numbered"><code>with</code></h4>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p>Do not use the <code>with</code> keyword. It makes your code harder to understand and
|
|
|
|
|
<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/with">has been banned in strict mode since ES5</a>.</p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p><a id="disallowed-features-dynamic-code-evaluation"></a></p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<h4 id="dynamic-code-evaluation" class="numbered">Dynamic code evaluation</h4>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p>Do not use <code>eval</code> or the <code>Function(...string)</code> constructor (except for code
|
|
|
|
|
loaders). These features are potentially dangerous and simply do not work in
|
|
|
|
|
environments using strict
|
|
|
|
|
<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP">Content Security Policies</a>.</p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p><a id="disallowed-features-non-standard-features"></a></p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<h4 id="non-standard-features" class="numbered">Non-standard features</h4>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p>Do not use non-standard ECMAScript or Web Platform features.</p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p>This includes:</p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<ul>
|
|
|
|
|
<li>Old features that have been marked deprecated or removed entirely from
|
|
|
|
|
ECMAScript / the Web Platform (see
|
|
|
|
|
<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Deprecated_and_obsolete_features">MDN</a>)</li>
|
|
|
|
|
<li>New ECMAScript features that are not yet standardized
|
|
|
|
|
<ul>
|
|
|
|
|
<li>Avoid using features that are in current TC39 working draft or currently
|
|
|
|
|
in the <a href="https://tc39.es/process-document/">proposal process</a></li>
|
|
|
|
|
<li>Use only ECMAScript features defined in the current ECMA-262
|
|
|
|
|
specification</li>
|
|
|
|
|
</ul></li>
|
|
|
|
|
<li>Proposed but not-yet-complete web standards:
|
|
|
|
|
<ul>
|
|
|
|
|
<li>WHATWG proposals that have not completed the
|
|
|
|
|
<a href="https://whatwg.org/faq#adding-new-features">proposal process</a>.</li>
|
|
|
|
|
</ul></li>
|
|
|
|
|
<li>Non-standard language “extensions” (such as those provided by some external
|
|
|
|
|
transpilers)</li>
|
|
|
|
|
</ul>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p>Projects targeting specific JavaScript runtimes, such as latest-Chrome-only,
|
|
|
|
|
Chrome extensions, Node.JS, Electron, can obviously use those APIs. Use caution
|
|
|
|
|
when considering an API surface that is proprietary and only implemented in some
|
|
|
|
|
browsers; consider whether there is a common library that can abstract this API
|
|
|
|
|
surface away for you.</p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p><a id="disallowed-features-modifying-builtin-objects"></a></p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<h4 id="modifying-builtin-objects" class="numbered">Modifying builtin objects</h4>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p>Never modify builtin types, either by adding methods to their constructors or to
|
|
|
|
|
their prototypes. Avoid depending on libraries that do
|
|
|
|
|
this.</p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p>Do not add symbols to the global object unless absolutely necessary (e.g.
|
|
|
|
|
required by a third-party API).</p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p><a id="syntax"></a>
|
|
|
|
|
<a id="naming-rules-common-to-all-identifiers"></a></p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<h2 id="naming" class="numbered">Naming</h2>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<h3 id="identifiers" class="numbered">Identifiers</h3>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p>Identifiers <em>must</em> use only ASCII letters, digits, underscores (for constants
|
|
|
|
|
and structured test method names), and (rarely) the '$' sign.</p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<h4 id="naming-style" class="numbered">Naming style</h4>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<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>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p><a id="identifiers-abbreviations"></a></p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<h4 id="descriptive-names" class="numbered">Descriptive names</h4>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<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>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<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>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<pre><code class="language-ts good">// Good identifiers:
|
|
|
|
|
errorCount // No abbreviation.
|
|
|
|
|
dnsConnectionIndex // Most people know what "DNS" stands for.
|
|
|
|
|
referrerUrl // Ditto for "URL".
|
|
|
|
|
customerId // "Id" is both ubiquitous and unlikely to be misunderstood.
|
2021-01-02 23:30:59 +08:00
|
|
|
|
</code></pre>
|
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<pre><code class="language-ts bad">// Disallowed identifiers:
|
|
|
|
|
n // Meaningless.
|
|
|
|
|
nErr // Ambiguous abbreviation.
|
|
|
|
|
nCompConns // Ambiguous abbreviation.
|
|
|
|
|
wgcConnections // Only your group knows what this stands for.
|
|
|
|
|
pcReader // Lots of things can be abbreviated "pc".
|
|
|
|
|
cstmrId // Deletes internal letters.
|
|
|
|
|
kSecondsPerDay // Do not use Hungarian notation.
|
|
|
|
|
customerID // Incorrect camelcase of "ID".
|
2021-01-02 23:30:59 +08:00
|
|
|
|
</code></pre>
|
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p><a id="naming-camel-case-defined"></a></p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<h4 id="camel-case" class="numbered">Camel case</h4>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<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>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<h4 id="identifiers-dollar-sign" class="numbered">Dollar sign</h4>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p>Identifiers <em>should not</em> generally use <code>$</code>, except when required by naming
|
|
|
|
|
conventions for third party frameworks. <a href="#naming-style">See above</a> for more on
|
|
|
|
|
using <code>$</code> with <code>Observable</code> values.</p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p><a id="naming-class-names"></a>
|
|
|
|
|
<a id="naming-method-names"></a>
|
|
|
|
|
<a id="naming-enum-names"></a>
|
|
|
|
|
<a id="naming-constant-names"></a>
|
|
|
|
|
<a id="naming-local-variable-names"></a>
|
|
|
|
|
<a id="naming-non-constant-field-names"></a>
|
|
|
|
|
<a id="naming-parameter-names"></a></p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<h3 id="naming-rules-by-identifier-type" class="numbered">Rules by identifier type</h3>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p>Most identifier names should follow the casing in the table below, based on the
|
|
|
|
|
identifier's type.</p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
|
|
|
|
<table>
|
|
|
|
|
<thead>
|
|
|
|
|
<tr>
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<th>Style</th>
|
|
|
|
|
<th>Category</th>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
</tr>
|
|
|
|
|
</thead>
|
|
|
|
|
|
|
|
|
|
<tbody>
|
|
|
|
|
<tr>
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<td><code>UpperCamelCase</code>
|
|
|
|
|
|
2021-01-02 23:30:59 +08:00
|
|
|
|
</td>
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<td>class / interface / type / enum / decorator / type
|
|
|
|
|
parameters / component functions in TSX / JSXElement type
|
|
|
|
|
parameter</td>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
</tr>
|
|
|
|
|
<tr>
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<td><code>lowerCamelCase</code>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
</td>
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<td>variable / parameter / function / method / property /
|
|
|
|
|
module alias</td>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
</tr>
|
|
|
|
|
<tr>
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<td><code>CONSTANT_CASE</code>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
</td>
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<td>global constant values, including enum values. See
|
|
|
|
|
<a href="#identifiers-constants">Constants</a> below.</td>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
</tr>
|
|
|
|
|
<tr>
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<td><code>#ident</code></td>
|
|
|
|
|
<td>private identifiers are never used.</td>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
</tr>
|
|
|
|
|
</tbody>
|
|
|
|
|
</table>
|
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p><a id="naming-template-parameter-names"></a></p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<h4 id="identifiers-type-parameters" class="numbered">Type parameters</h4>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<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>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<h4 id="identifiers-test-names" class="numbered">Test names</h4>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p>Test method names inxUnit-style test frameworks <em>may</em> be structured with <code>_</code> separators, e.g.
|
|
|
|
|
<code>testX_whenY_doesZ()</code>.</p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<h4 id="identifiers-underscore-prefix-suffix" class="numbered"><code>_</code> prefix/suffix</h4>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p>Identifiers must not use <code>_</code> as a prefix or suffix.</p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<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>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<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>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<pre><code class="language-ts">const [a, , b] = [1, 5, 10]; // a <- 1, b <- 10
|
2021-01-02 23:30:59 +08:00
|
|
|
|
</code></pre>
|
2023-10-12 06:35:20 +08:00
|
|
|
|
</blockquote>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<h4 id="identifiers-imports" class="numbered">Imports</h4>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<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>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<pre><code class="language-ts good">import * as fooBar from './foo_bar';
|
|
|
|
|
</code></pre>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<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>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<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>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<h4 id="identifiers-constants" class="numbered">Constants</h4>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<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>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<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.
|
2021-01-02 23:30:59 +08:00
|
|
|
|
</code></pre>
|
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<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;
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
bar() {
|
|
|
|
|
return 2 * Foo.MY_SPECIAL_NUMBER;
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-01-02 23:30:59 +08:00
|
|
|
|
</code></pre>
|
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<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>
|
2022-11-10 03:35:00 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p>If a value is an arrow function that implements an interface, then it <em>may</em> be
|
|
|
|
|
declared <code>lowerCamelCase</code>.</p>
|
2022-11-10 03:35:00 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p><a id="naming-local-aliases"></a></p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<h4 id="aliases" class="numbered">Aliases</h4>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<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>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<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>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<pre><code class="language-ts good">const {BrewStateEnum} = SomeType;
|
|
|
|
|
const CAPACITY = 5;
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
class Teapot {
|
|
|
|
|
readonly BrewStateEnum = BrewStateEnum;
|
|
|
|
|
readonly CAPACITY = CAPACITY;
|
|
|
|
|
}
|
|
|
|
|
</code></pre>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<h2 id="type-system" class="numbered">Type system</h2>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<h3 id="type-inference" class="numbered">Type inference</h3>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2022-11-10 03:35:00 +08:00
|
|
|
|
<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>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
|
|
|
|
<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>
|
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p>Explicitly specifying types may be required to prevent generic type parameters
|
|
|
|
|
from being inferred as <code>unknown</code>. For example, initializing generic types with
|
|
|
|
|
no values (e.g. empty arrays, objects, <code>Map</code>s, or <code>Set</code>s).</p>
|
|
|
|
|
|
2021-01-02 23:30:59 +08:00
|
|
|
|
<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
|
2022-11-10 03:35:00 +08:00
|
|
|
|
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>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<h4 id="return-types" class="numbered">Return types</h4>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
|
|
|
|
<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>
|
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p><a id="null-vs-undefined"></a></p>
|
|
|
|
|
|
|
|
|
|
<h3 id="undefined-and-null" class="numbered">Undefined and null</h3>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p>TypeScript supports <code>undefined</code> and <code>null</code> types. Nullable types can be
|
2021-01-02 23:30:59 +08:00
|
|
|
|
constructed as a union type (<code>string|null</code>); similarly with <code>undefined</code>. There
|
2023-10-12 06:35:20 +08:00
|
|
|
|
is no special syntax for unions of <code>undefined</code> and <code>null</code>.</p>
|
|
|
|
|
|
|
|
|
|
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
|
|
|
|
<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>
|
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<h4 id="nullableundefined-type-aliases" class="numbered">Nullable/undefined type aliases</h4>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
|
|
|
|
<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>
|
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p><a id="optionals-vs-undefined-type"></a></p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<h4 id="prefer-optional-over-undefined" class="numbered">Prefer optional over <code>|undefined</code></h4>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
|
|
|
|
<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>
|
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p><a id="structural-types-vs-nominal-types"></a></p>
|
|
|
|
|
|
|
|
|
|
<h3 id="use-structural-types" class="numbered">Use structural types</h3>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
|
|
|
|
<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>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>
|
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p>Use interfaces to define structural types, not classes</p>
|
|
|
|
|
|
|
|
|
|
<pre><code class="language-ts good">interface Foo {
|
|
|
|
|
a: number;
|
|
|
|
|
b: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const foo: Foo = {
|
|
|
|
|
a: 123,
|
|
|
|
|
b: 'abc',
|
|
|
|
|
}
|
|
|
|
|
</code></pre>
|
|
|
|
|
|
|
|
|
|
<pre><code class="language-ts bad">class Foo {
|
|
|
|
|
readonly a: number;
|
|
|
|
|
readonly b: number;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const foo: Foo = {
|
|
|
|
|
a: 123,
|
|
|
|
|
b: 'abc',
|
|
|
|
|
}
|
|
|
|
|
</code></pre>
|
|
|
|
|
|
|
|
|
|
<section class="zippy">
|
|
|
|
|
|
|
|
|
|
<p>Why?</p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
|
|
|
|
<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>
|
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
</section>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p><a id="interfaces-vs-type-aliases"></a></p>
|
|
|
|
|
|
|
|
|
|
<h3 id="prefer-interfaces" class="numbered">Prefer interfaces over type literal aliases</h3>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
|
|
|
|
<p>TypeScript supports
|
2022-11-10 03:35:00 +08:00
|
|
|
|
<a href="https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#type-aliases">type aliases</a>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
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>
|
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<section class="zippy">
|
|
|
|
|
|
|
|
|
|
<p>Why?</p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
|
|
|
|
<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
|
2022-11-10 03:35:00 +08:00
|
|
|
|
are also
|
2021-01-02 23:30:59 +08:00
|
|
|
|
<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
|
2023-10-12 06:35:20 +08:00
|
|
|
|
to type aliases when there are so many issues around display/perf.</q></p>
|
|
|
|
|
|
|
|
|
|
</section>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<h3 id="arrayt-type" class="numbered"><code>Array<T></code> Type</h3>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
|
|
|
|
<p>For simple types (containing just alphanumeric characters and dot), use the
|
2023-10-12 06:35:20 +08:00
|
|
|
|
syntax sugar for arrays, <code>T[]</code> or <code>readonly T[]</code>, rather than the longer form
|
|
|
|
|
<code>Array<T></code> or <code>ReadonlyArray<T></code>.</p>
|
|
|
|
|
|
|
|
|
|
<p>For multi-dimensional non-<code>readonly</code> arrays of simple types, use the syntax
|
|
|
|
|
sugar form (<code>T[][]</code>, <code>T[][][]</code>, and so on) rather than the longer form.</p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
|
|
|
|
<p>For anything more complex, use the longer form <code>Array<T></code>.</p>
|
|
|
|
|
|
2022-11-10 03:35:00 +08:00
|
|
|
|
<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>
|
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<pre><code class="language-ts good">let a: string[];
|
|
|
|
|
let b: readonly string[];
|
|
|
|
|
let c: ns.MyObj[];
|
|
|
|
|
let d: string[][];
|
|
|
|
|
let e: Array<{n: number, s: string}>;
|
|
|
|
|
let f: Array<string|number>;
|
|
|
|
|
let g: ReadonlyArray<string|number>;
|
|
|
|
|
let h: InjectionToken<string[]>; // Use syntax sugar for nested types.
|
|
|
|
|
let i: ReadonlyArray<string[]>;
|
|
|
|
|
let j: Array<readonly string[]>;
|
2021-01-02 23:30:59 +08:00
|
|
|
|
</code></pre>
|
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<pre><code class="language-ts bad">let a: Array<string>; // The syntax sugar is shorter.
|
|
|
|
|
let b: ReadonlyArray<string>;
|
|
|
|
|
let c: Array<ns.MyObj>;
|
|
|
|
|
let d: Array<string[]>;
|
|
|
|
|
let e: {n: number, s: string}[]; // The braces make it harder to read.
|
|
|
|
|
let f: (string|number)[]; // Likewise with parens.
|
|
|
|
|
let g: readonly (string | number)[];
|
|
|
|
|
let h: InjectionToken<Array<string>>;
|
|
|
|
|
let i: readonly string[][];
|
|
|
|
|
let j: (readonly string[])[];
|
2021-01-02 23:30:59 +08:00
|
|
|
|
</code></pre>
|
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<h3 id="indexable-key-string-type" class="numbered">Indexable types / index signatures (<code>{[key: string]: T}</code>)</h3>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
|
|
|
|
<p>In JavaScript, it's common to use an object as an associative array (aka <q>map</q>,
|
2022-11-10 03:35:00 +08:00
|
|
|
|
<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>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
|
|
|
|
<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>
|
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<h3 id="mapped-conditional-types" class="numbered">Mapped and conditional types</h3>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
|
|
|
|
<p>TypeScript's
|
2022-11-10 03:35:00 +08:00
|
|
|
|
<a href="https://www.typescriptlang.org/docs/handbook/2/mapped-types.html">mapped types</a>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
and
|
2022-11-10 03:35:00 +08:00
|
|
|
|
<a href="https://www.typescriptlang.org/docs/handbook/2/conditional-types.html">conditional types</a>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
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>
|
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p>For example, TypeScript's builtin <code>Pick<T, Keys></code> type allows creating a new
|
2021-01-02 23:30:59 +08:00
|
|
|
|
type by subsetting another type <code>T</code>, but simple interface extension can often be
|
2023-10-12 06:35:20 +08:00
|
|
|
|
easier to understand.</p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
|
|
|
|
<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
|
2023-10-12 06:35:20 +08:00
|
|
|
|
understand.</p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<h3 id="any" class="numbered"><code>any</code> Type</h3>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
|
|
|
|
<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>
|
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<section>
|
|
|
|
|
|
|
|
|
|
<p><strong>Consider <em>not</em> to use <code>any</code>.</strong> In circumstances where you want to use <code>any</code>,
|
|
|
|
|
consider one of:</p>
|
|
|
|
|
|
|
|
|
|
</section>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
|
|
|
|
<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>
|
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<h4 id="any-specific" class="numbered">Providing a more specific type</h4>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2022-11-10 03:35:00 +08:00
|
|
|
|
<p>Use interfaces , an
|
|
|
|
|
inline object type, or a type alias:</p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
|
|
|
|
<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>
|
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<h4 id="any-unknown" class="numbered">Using <code>unknown</code> over <code>any</code></h4>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
|
|
|
|
<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>
|
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<section>
|
|
|
|
|
|
|
|
|
|
<p>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></p>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<h4 id="any-suppress" class="numbered">Suppressing <code>any</code> lint warnings</h4>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
|
|
|
|
<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>
|
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<h3 id="empty-interface-type" class="numbered"><code>{}</code> Type</h3>
|
|
|
|
|
|
|
|
|
|
<p>The <code>{}</code> type, also known as an <em>empty interface</em> type, represents a interface
|
|
|
|
|
with no properties. An empty interface type has no specified properties and
|
|
|
|
|
therefore any non-nullish value is assignable to it.</p>
|
|
|
|
|
|
|
|
|
|
<pre><code class="language-ts bad">let player: {};
|
|
|
|
|
|
|
|
|
|
player = {
|
|
|
|
|
health: 50,
|
|
|
|
|
}; // Allowed.
|
|
|
|
|
|
|
|
|
|
console.log(player.health) // Property 'health' does not exist on type '{}'.
|
|
|
|
|
</code></pre>
|
|
|
|
|
|
|
|
|
|
<pre><code class="language-ts bad">function takeAnything(obj:{}) {
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
takeAnything({});
|
|
|
|
|
takeAnything({ a: 1, b: 2 });
|
|
|
|
|
</code></pre>
|
|
|
|
|
|
|
|
|
|
<p>Google3 code <strong>should not</strong> use <code>{}</code> for most use cases. <code>{}</code> represents any
|
|
|
|
|
non-nullish primitive or object type, which is rarely appropriate. Prefer one of
|
|
|
|
|
the following more-descriptive types:</p>
|
|
|
|
|
|
|
|
|
|
<ul>
|
|
|
|
|
<li><code>unknown</code> can hold any value, including <code>null</code> or <code>undefined</code>, and is
|
|
|
|
|
generally more appropriate for opaque values.</li>
|
|
|
|
|
<li><code>Record<string, T></code> is better for dictionary-like objects, and provides
|
|
|
|
|
better type safety by being explicit about the type <code>T</code> of contained values
|
|
|
|
|
(which may itself be <code>unknown</code>).</li>
|
|
|
|
|
<li><code>object</code> excludes primitives as well, leaving only non-nullish functions and
|
|
|
|
|
objects, but without any other assumptions about what properties may be
|
|
|
|
|
available.</li>
|
|
|
|
|
</ul>
|
|
|
|
|
|
|
|
|
|
<h3 id="tuple-types" class="numbered">Tuple types</h3>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
|
|
|
|
<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>
|
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<h3 id="wrapper-types" class="numbered">Wrapper types</h3>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
2022-11-10 03:35:00 +08:00
|
|
|
|
<p>There are a few types related to JavaScript primitives that <em>should not</em> ever be
|
2021-01-02 23:30:59 +08:00
|
|
|
|
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>
|
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<h3 id="return-type-only-generics" class="numbered">Return type only generics</h3>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
|
|
|
|
<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>
|
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<p><a id="appendices-style-related-tools"></a></p>
|
|
|
|
|
|
|
|
|
|
<h2 id="toolchain-requirements" class="numbered">Toolchain requirements</h2>
|
|
|
|
|
|
|
|
|
|
<p>Google style requires using a number of tools in specific ways, outlined here.</p>
|
|
|
|
|
|
|
|
|
|
<p><a id="clang-format"></a>
|
|
|
|
|
<a id="google-format"></a>
|
|
|
|
|
<a id="use-the-google3-formatter"></a>
|
|
|
|
|
<a id="source-code-formatting"></a>
|
|
|
|
|
<a id="appendices-clang-format"></a>
|
|
|
|
|
<a id="formatting"></a>
|
|
|
|
|
<a id="formatting-array-literals"></a>
|
|
|
|
|
<a id="formatting-block-indentation"></a>
|
|
|
|
|
<a id="formatting-column-limit"></a>
|
|
|
|
|
<a id="formatting-empty-blocks"></a>
|
|
|
|
|
<a id="formatting-function-expressions"></a>
|
|
|
|
|
<a id="formatting-nonempty-blocks"></a>
|
|
|
|
|
<a id="formatting-object-literals"></a>
|
|
|
|
|
<a id="formatting-one-statement-perline"></a>
|
|
|
|
|
<a id="formatting-statements"></a>
|
|
|
|
|
<a id="formatting-switch-statements"></a>
|
|
|
|
|
<a id="formatting-vertical-whitespace"></a>
|
|
|
|
|
<a id="formatting-whitespace"></a></p>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<h3 id="typescript-compiler" class="numbered">TypeScript compiler</h3>
|
|
|
|
|
|
|
|
|
|
<p>All TypeScript files must pass type checking using the standard
|
|
|
|
|
tool chain.</p>
|
|
|
|
|
|
|
|
|
|
<h4 id="ts-ignore" class="numbered">@ts-ignore</h4>
|
|
|
|
|
|
|
|
|
|
<p>Do not use <code>@ts-ignore</code> nor the variants <code>@ts-expect-error</code> or <code>@ts-nocheck</code>.</p>
|
|
|
|
|
|
|
|
|
|
<section class="zippy">
|
|
|
|
|
|
|
|
|
|
<p>Why?</p>
|
|
|
|
|
|
|
|
|
|
<p>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>
|
|
|
|
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<p>You may use <code>@ts-expect-error</code> in unit tests, though you generally <em>should not</em>.
|
|
|
|
|
<code>@ts-expect-error</code> suppresses all errors. It's easy to accidentally over-match
|
|
|
|
|
and suppress more serious errors. Consider one of:</p>
|
|
|
|
|
|
|
|
|
|
<ul>
|
|
|
|
|
<li>When testing APIs that need to deal with unchecked values at runtime, add
|
|
|
|
|
casts to the expected type or to <code>any</code> and add an explanatory comment. This
|
|
|
|
|
limits error suppression to a single expression.</li>
|
|
|
|
|
<li>Suppress the lint warning and document why, similar to
|
|
|
|
|
<a href="#any-suppress">suppressing <code>any</code> lint warnings</a>.</li>
|
|
|
|
|
</ul>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<h3 id="conformance" class="numbered">Conformance</h3>
|
|
|
|
|
|
|
|
|
|
<p>Google TypeScript includes several <em>conformance frameworks</em>,
|
|
|
|
|
|
|
|
|
|
<a href="https://tsetse.info">tsetse</a> and
|
|
|
|
|
<a href="https://github.com/google/tsec">tsec</a>.</p>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<p>These rules are commonly used to enforce critical restrictions (such as defining
|
|
|
|
|
globals, which could break the codebase) and security patterns (such as using
|
|
|
|
|
<code>eval</code> or assigning to <code>innerHTML</code>), or more loosely to improve code quality.</p>
|
|
|
|
|
|
|
|
|
|
<p>Google-style TypeScript must abide by any applicable global or framework-local
|
|
|
|
|
conformance rules.</p>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<p><a id="jsdoc"></a>
|
|
|
|
|
<a id="formatting-comments"></a></p>
|
|
|
|
|
|
|
|
|
|
<h2 id="comments-documentation" class="numbered">Comments and documentation</h2>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<h4 id="jsdoc-vs-comments" class="numbered">JSDoc versus 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>
|
|
|
|
|
|
|
|
|
|
<p><a id="formatting-block-comment-style"></a></p>
|
|
|
|
|
|
|
|
|
|
<h4 id="multi-line-comments" class="numbered">Multi-line comments</h4>
|
|
|
|
|
|
|
|
|
|
<p>Multi-line comments are indented at the same level as the surrounding code. They
|
|
|
|
|
<em>must</em> use multiple single-line comments (<code>//</code>-style), not block comment style
|
|
|
|
|
(<code>/* */</code>).</p>
|
|
|
|
|
|
|
|
|
|
<pre><code class="language-ts good">// This is
|
|
|
|
|
// fine
|
|
|
|
|
</code></pre>
|
|
|
|
|
|
|
|
|
|
<pre><code class="language-ts bad">/*
|
|
|
|
|
* This should
|
|
|
|
|
* use multiple
|
|
|
|
|
* single-line comments
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/* This should use // */
|
|
|
|
|
</code></pre>
|
|
|
|
|
|
|
|
|
|
<p>Comments are not enclosed in boxes drawn with asterisks or other characters.</p>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<h3 id="jsdoc-general-form" class="numbered">JSDoc general form</h3>
|
|
|
|
|
|
|
|
|
|
<p>The basic formatting of JSDoc comments is as seen in this example:</p>
|
|
|
|
|
|
|
|
|
|
<pre><code class="language-ts good">/**
|
|
|
|
|
* Multiple lines of JSDoc text are written here,
|
|
|
|
|
* wrapped normally.
|
|
|
|
|
* @param arg A number to do something to.
|
|
|
|
|
*/
|
|
|
|
|
function doSomething(arg: number) { … }
|
|
|
|
|
</code></pre>
|
|
|
|
|
|
|
|
|
|
<p>or in this single-line example:</p>
|
|
|
|
|
|
|
|
|
|
<pre><code class="language-ts good">/** This short jsdoc describes the function. */
|
|
|
|
|
function doSomething(arg: number) { … }
|
|
|
|
|
</code></pre>
|
|
|
|
|
|
|
|
|
|
<p>If a single-line comment overflows into multiple lines, it <em>must</em> use the
|
|
|
|
|
multi-line style with <code>/**</code> and <code>*/</code> on their own lines.</p>
|
|
|
|
|
|
|
|
|
|
<p>Many tools extract metadata from JSDoc comments to perform code validation and
|
|
|
|
|
optimization. As such, these comments <em>must</em> be well-formed.</p>
|
|
|
|
|
|
|
|
|
|
<h3 id="jsdoc-markdown" class="numbered">Markdown</h3>
|
|
|
|
|
|
|
|
|
|
<p>JSDoc is written in Markdown, though it <em>may</em> include HTML when necessary.</p>
|
|
|
|
|
|
|
|
|
|
<p>This means that tooling parsing JSDoc will ignore plain text formatting, so if
|
|
|
|
|
you did this:</p>
|
|
|
|
|
|
|
|
|
|
<pre><code class="language-ts bad">/**
|
|
|
|
|
* Computes weight based on three factors:
|
|
|
|
|
* items sent
|
|
|
|
|
* items received
|
|
|
|
|
* last timestamp
|
|
|
|
|
*/
|
|
|
|
|
</code></pre>
|
|
|
|
|
|
|
|
|
|
<p>it will be rendered like this:</p>
|
|
|
|
|
|
|
|
|
|
<pre><code>Computes weight based on three factors: items sent items received last timestamp
|
|
|
|
|
</code></pre>
|
|
|
|
|
|
|
|
|
|
<p>Instead, write a Markdown list:</p>
|
|
|
|
|
|
|
|
|
|
<pre><code class="language-ts good">/**
|
|
|
|
|
* Computes weight based on three factors:
|
|
|
|
|
*
|
|
|
|
|
* - items sent
|
|
|
|
|
* - items received
|
|
|
|
|
* - last timestamp
|
|
|
|
|
*/
|
|
|
|
|
</code></pre>
|
|
|
|
|
|
|
|
|
|
<h3 id="jsdoc-tags" class="numbered">JSDoc tags</h3>
|
|
|
|
|
|
|
|
|
|
<p>Google style allows a subset of JSDoc tags. Most tags must occupy their own line, with the tag at the beginning
|
|
|
|
|
of the line.</p>
|
|
|
|
|
|
|
|
|
|
<pre><code class="language-ts good">/**
|
|
|
|
|
* The "param" tag must occupy its own line and may not be combined.
|
|
|
|
|
* @param left A description of the left param.
|
|
|
|
|
* @param right A description of the right param.
|
|
|
|
|
*/
|
|
|
|
|
function add(left: number, right: number) { ... }
|
|
|
|
|
</code></pre>
|
|
|
|
|
|
|
|
|
|
<pre><code class="language-ts bad">/**
|
|
|
|
|
* The "param" tag must occupy its own line and may not be combined.
|
|
|
|
|
* @param left @param right
|
|
|
|
|
*/
|
|
|
|
|
function add(left: number, right: number) { ... }
|
|
|
|
|
</code></pre>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<h3 id="jsdoc-line-wrapping" class="numbered">Line wrapping</h3>
|
|
|
|
|
|
|
|
|
|
<p>Line-wrapped block tags are indented four spaces. Wrapped description text <em>may</em>
|
|
|
|
|
be lined up with the description on previous lines, but this horizontal
|
|
|
|
|
alignment is discouraged.</p>
|
|
|
|
|
|
|
|
|
|
<pre><code class="language-ts good">/**
|
|
|
|
|
* Illustrates line wrapping for long param/return descriptions.
|
|
|
|
|
* @param foo This is a param with a particularly long description that just
|
|
|
|
|
* doesn't fit on one line.
|
|
|
|
|
* @return This returns something that has a lengthy description too long to fit
|
|
|
|
|
* in one line.
|
|
|
|
|
*/
|
|
|
|
|
exports.method = function(foo) {
|
|
|
|
|
return 5;
|
|
|
|
|
};
|
|
|
|
|
</code></pre>
|
|
|
|
|
|
|
|
|
|
<p>Do not indent when wrapping a <code>@desc</code> or <code>@fileoverview</code> description.</p>
|
|
|
|
|
|
|
|
|
|
<h3 id="document-all-top-level-exports-of-modules" class="numbered">Document all top-level exports of modules</h3>
|
|
|
|
|
|
|
|
|
|
<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><strong>Exception:</strong> Symbols that are only exported to be consumed by tooling, such as
|
|
|
|
|
@NgModule classes, do not require comments.</p>
|
|
|
|
|
|
|
|
|
|
<h3 id="jsdoc-class-comments" class="numbered">Class comments</h3>
|
|
|
|
|
|
|
|
|
|
<p>JSDoc comments for classes should provide the reader with enough information to
|
|
|
|
|
know how and when to use the class, as well as any additional considerations
|
|
|
|
|
necessary to correctly use the class. Textual descriptions may be omitted on the
|
|
|
|
|
constructor.</p>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<h3 id="method-and-function-comments" class="numbered">Method and function comments</h3>
|
|
|
|
|
|
|
|
|
|
<p>Method, parameter, and return descriptions may be omitted if they are obvious
|
|
|
|
|
from the rest of the method’s JSDoc or from the method name and type signature.</p>
|
|
|
|
|
|
|
|
|
|
<p>Method descriptions begin with a verb phrase that describes what the method
|
|
|
|
|
does. This phrase is not an imperative sentence, but instead is written in the
|
|
|
|
|
third person, as if there is an implied <q>This method ...</q> before it.</p>
|
|
|
|
|
|
|
|
|
|
<h3 id="parameter-property-comments" class="numbered">Parameter property comments</h3>
|
|
|
|
|
|
|
|
|
|
<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>
|
|
|
|
|
|
|
|
|
|
<p><a id="omit-comments-that-are-redundant-with-typescript"></a>
|
|
|
|
|
<a id="do-not-use-override"></a></p>
|
|
|
|
|
|
|
|
|
|
<h3 id="jsdoc-type-annotations" class="numbered">JSDoc type annotations</h3>
|
|
|
|
|
|
|
|
|
|
<p>JSDoc type annotations are redundant in TypeScript source code. 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>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<h3 id="redundant-comments" class="numbered">Make comments that actually add information</h3>
|
|
|
|
|
|
|
|
|
|
<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>
|
|
|
|
|
|
|
|
|
|
<p><a id="formatting-param-name-comments"></a></p>
|
|
|
|
|
|
|
|
|
|
<h4 id="comments-when-calling-a-function" class="numbered">Comments when calling a function</h4>
|
|
|
|
|
|
|
|
|
|
<p>“Parameter name” comments should be used whenever the method name and parameter
|
|
|
|
|
value do not sufficiently convey the meaning of the parameter.</p>
|
|
|
|
|
|
|
|
|
|
<p>Before adding these comments, consider refactoring the method to instead accept
|
|
|
|
|
an interface and destructure it to greatly improve call-site
|
|
|
|
|
readability.</p>
|
|
|
|
|
|
|
|
|
|
<p><q>Parameter name</q> comments go before the parameter value, and include the
|
|
|
|
|
parameter name and a <code>=</code> suffix:</p>
|
|
|
|
|
|
|
|
|
|
<pre><code class="language-ts good">someFunction(obviousParam, /* shouldRender= */ true, /* name= */ 'hello');
|
|
|
|
|
</code></pre>
|
|
|
|
|
|
|
|
|
|
<p>Existing code may use a legacy parameter name comment style, which places these
|
|
|
|
|
comments ~after~ the parameter value and omits the <code>=</code>. Continuing to use this
|
|
|
|
|
style within the file for consistency is acceptable.</p>
|
|
|
|
|
|
|
|
|
|
<pre><code class="language-ts">someFunction(obviousParam, true /* shouldRender */, 'hello' /* name */);
|
|
|
|
|
</code></pre>
|
|
|
|
|
|
|
|
|
|
<h3 id="place-documentation-prior-to-decorators" class="numbered">Place documentation prior to decorators</h3>
|
|
|
|
|
|
|
|
|
|
<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="policies" class="numbered">Policies</h2>
|
|
|
|
|
|
|
|
|
|
<p><a id="policies-be-consistent"></a>
|
|
|
|
|
<a id="policies-newly-added-code-use-google-style"></a></p>
|
|
|
|
|
|
|
|
|
|
<h3 id="consistency" class="numbered">Consistency</h3>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
|
|
|
|
<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>
|
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<p>Brand new files <em>must</em> use Google Style, regardless of the style choices of
|
|
|
|
|
other files in the same package. When adding new code to a file that is not in
|
|
|
|
|
Google Style, reformatting the existing code first is recommended, subject to
|
|
|
|
|
the advice <a href="#reformatting-existing-code">below</a>. If this reformatting is not
|
|
|
|
|
done, then new code <em>should</em> be as consistent as possible with existing code in
|
|
|
|
|
the same file, but <em>must not</em> violate the style guide.</p>
|
|
|
|
|
|
|
|
|
|
<p><a id="policies-code-not-in-google-style"></a>
|
|
|
|
|
<a id="policies-reformatting-existing-code"></a></p>
|
|
|
|
|
|
|
|
|
|
<h4 id="reformatting-existing-code" class="numbered">Reformatting existing code</h4>
|
|
|
|
|
|
|
|
|
|
<p>You will occasionally encounter files in the codebase that are not in proper
|
|
|
|
|
Google Style. These may have come from an acquisition, or may have been written
|
|
|
|
|
before Google Style took a position on some issue, or may be in non-Google Style
|
|
|
|
|
for any other reason.</p>
|
|
|
|
|
|
|
|
|
|
<p>When updating the style of existing code, follow these guidelines.</p>
|
|
|
|
|
|
|
|
|
|
<ol>
|
|
|
|
|
<li>It is not required to change all existing code to meet current style
|
|
|
|
|
guidelines. Reformatting existing code is a trade-off between code churn and
|
|
|
|
|
consistency. Style rules evolve over time and these kinds of tweaks to
|
|
|
|
|
maintain compliance would create unnecessary churn. However, if significant
|
|
|
|
|
changes are being made to a file it is expected that the file will be in
|
|
|
|
|
Google Style.</li>
|
|
|
|
|
<li>Be careful not to allow opportunistic style fixes to muddle the focus of a
|
|
|
|
|
CL. If you find yourself making a lot of style changes that aren’t critical
|
|
|
|
|
to the central focus of a CL, promote those changes to a separate CL.</li>
|
|
|
|
|
</ol>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<p><a id="policies-deprecation"></a></p>
|
|
|
|
|
|
|
|
|
|
<h3 id="deprecation" class="numbered">Deprecation</h3>
|
|
|
|
|
|
|
|
|
|
<p>Mark deprecated methods, classes or interfaces with an <code>@deprecated</code> JSDoc
|
|
|
|
|
annotation. A deprecation comment must include simple, clear directions for
|
|
|
|
|
people to fix their call sites.</p>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<p><a id="policies-generated-code-mostly-exempt"></a></p>
|
|
|
|
|
|
|
|
|
|
<h3 id="generated-code" class="numbered">Generated code: mostly exempt</h3>
|
|
|
|
|
|
|
|
|
|
<p>Source code generated by the build process is not required to be in Google
|
|
|
|
|
Style. However, any generated identifiers that will be referenced from
|
|
|
|
|
hand-written source code must follow the naming requirements. As a special
|
|
|
|
|
exception, such identifiers are allowed to contain underscores, which may help
|
|
|
|
|
to avoid conflicts with hand-written identifiers.</p>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<p><a id="goals"></a></p>
|
|
|
|
|
|
|
|
|
|
<h4 id="style-guide-goals" class="numbered">Style guide goals</h4>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
|
|
|
|
|
<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>
|
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
</li>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
<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>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>
|
2023-10-12 06:35:20 +08:00
|
|
|
|
<li>We require a single set of compiler 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>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
<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
|
2022-11-10 03:35:00 +08:00
|
|
|
|
that changes that we make to the language, don't break users.</li>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
</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>
|
|
|
|
|
|
2023-10-12 06:35:20 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<p><a id="appendices-legacy-exceptions"></a>
|
|
|
|
|
<a id="appendices-legacy-exceptions-declareLegacyNamespace"></a>
|
|
|
|
|
<a id="appendices-legacy-exceptions-forward-declare"></a>
|
|
|
|
|
<a id="appendices-legacy-exceptions-function"></a>
|
|
|
|
|
<a id="appendices-legacy-exceptions-goog-provide"></a>
|
|
|
|
|
<a id="appendices-legacy-exceptions-goog-provide-summary"></a>
|
|
|
|
|
<a id="appendices-legacy-exceptions-goog-scope"></a>
|
|
|
|
|
<a id="appendices-legacy-exceptions-module-get"></a>
|
|
|
|
|
<a id="appendices-legacy-exceptions-overview"></a>
|
|
|
|
|
<a id="appendices-legacy-exceptions-var"></a>
|
|
|
|
|
<a id="appendices-legacy-exceptions-var-const"></a>
|
|
|
|
|
<a id="appendices-legacy-exceptions-var-declare"></a>
|
|
|
|
|
<a id="appendices-legacy-exceptions-var-scope"></a>
|
|
|
|
|
<a id="features-classes-old-style"></a></p>
|
|
|
|
|
|
|
|
|
|
<div class="footnotes">
|
|
|
|
|
<hr>
|
|
|
|
|
<ol>
|
|
|
|
|
|
|
|
|
|
<li id="fn1">
|
|
|
|
|
<p>Namespace imports are often called 'module imports' <a href="#fnref1" rev="footnote">↩</a></p>
|
|
|
|
|
</li>
|
|
|
|
|
|
|
|
|
|
<li id="fn2">
|
|
|
|
|
<p>named imports are sometimes called 'destructuring
|
|
|
|
|
imports' because they use similar syntax to
|
|
|
|
|
destructuring assignments. <a href="#fnref2" rev="footnote">↩</a></p>
|
|
|
|
|
</li>
|
|
|
|
|
|
|
|
|
|
</ol>
|
|
|
|
|
</div>
|
2021-01-02 23:30:59 +08:00
|
|
|
|
</div>
|
|
|
|
|
</body>
|
2023-10-12 06:35:20 +08:00
|
|
|
|
</html>
|