diff --git a/cppguide.html b/cppguide.html index ffb061d..7c87799 100644 --- a/cppguide.html +++ b/cppguide.html @@ -21,8 +21,8 @@ this power brings with it complexity, which in turn can make code more bug-prone and harder to read and maintain.

The goal of this guide is to manage this complexity by -describing in detail the dos and don'ts of writing C++ code -. These rules exist to +describing in detail the dos and don'ts of writing C++ +code. These rules exist to keep the code base manageable while still allowing coders to use C++ language features productively.

@@ -164,9 +164,8 @@ input.

C++ Version

-

Currently, code should target C++17, i.e., should not use C++2x - features, with the exception of designated - initializers. The C++ version targeted by this guide will advance +

Currently, code should target C++20, i.e., should not use C++23 + features. The C++ version targeted by this guide will advance (aggressively) over time.

@@ -176,7 +175,7 @@ input.

Consider portability to other environments before using features -from C++14 and C++17 in your project.

+from C++17 and C++20 in your project.

Header Files

@@ -415,6 +414,18 @@ should be included as:

#include "base/logging.h"
 
+

Headers should only be included using an angle-bracketed path if the library +requires you to do so. In particular, the following headers require angle +brackets:

+ + +

In dir/foo.cc or dir/foo_test.cc, whose main purpose is to implement or test the stuff in @@ -426,9 +437,9 @@ as follows:

  • A blank line
  • -
  • C system headers (more precisely: headers in angle brackets with the - .h extension), e.g., <unistd.h>, - <stdlib.h>.
  • +
  • C system headers, and any other headers in angle brackets with the + .h extension, e.g., <unistd.h>, + <stdlib.h>, <Python.h>.
  • A blank line
  • @@ -664,9 +675,9 @@ namespace baz = ::foo::bar::baz;
    // Shorten access to some commonly used names (in a .h file).
     namespace librarian {
    -namespace impl {  // Internal, not part of the API.
    +namespace internal {  // Internal, not part of the API.
     namespace sidetable = ::pipeline_diagnostics::sidetable;
    -}  // namespace impl
    +}  // namespace internal
     
     inline void my_inline_function() {
       // namespace alias local to a function (or method).
    @@ -686,6 +697,11 @@ inline void my_inline_function() {
     using ::absl::container_internal::ImplementationDetail;
     
    + +
  • Single-line nested namespace declarations + + are preferred in new code, but are not required.

    +
  • @@ -932,7 +948,7 @@ the formal language of the C++ standard. It means that the initializing expression is a constant expression, and if the object is initialized by a constructor call, then the constructor must be specified as constexpr, too:

    -
    struct Foo { constexpr Foo(int) {} };
    +
    struct Foo { constexpr Foo(int) {} };
     
     int n = 5;  // Fine, 5 is a constant expression.
     Foo x(2);   // Fine, 2 is a constant expression and the chosen constructor is constexpr.
    @@ -940,12 +956,8 @@ Foo a[] = { Foo(1), Foo(2), Foo(3) };  // Fine

    Constant initialization is always allowed. Constant initialization of static storage duration variables should be marked with constexpr -or where possible the - - - -ABSL_CONST_INIT -attribute. Any non-local static storage +or constinit. +Any non-local static storage duration variable that is not so marked should be presumed to have dynamic initialization, and reviewed very carefully.

    @@ -1017,10 +1029,8 @@ does not make an observable difference. For example:

    thread_local variables that aren't declared inside a function must be initialized with a true compile-time constant, and this must be enforced by using the - - - -ABSL_CONST_INIT + + constinit attribute. Prefer thread_local over other ways of defining thread-local data.

    @@ -1093,13 +1103,11 @@ get a particularly hard to diagnose use-after-free.

    initialized with a true compile-time constant (i.e., they must have no dynamic initialization). To enforce this, thread_local variables at class or namespace scope must be annotated with - - - - ABSL_CONST_INIT + + constinit (or constexpr, but that should be rare):

    -
      ABSL_CONST_INIT thread_local Foo foo = ...;
    +  
       constinit thread_local Foo foo = ...;
       

    thread_local variables inside a function have no initialization @@ -1177,8 +1185,7 @@ for your code , terminating the program may be an appropriate error handling response. Otherwise, consider a factory function or Init() method as described in -TotW #42 -. +TotW #42. Avoid Init() methods on objects with no other states that affect which public methods may be called (semi-constructed objects of this form are particularly hard to work @@ -1445,8 +1452,6 @@ by making their constructors protected, by declaring their destructors protected or by giving them one or more pure virtual member functions. Prefer to avoid deriving from concrete classes.

    - -

    Structs vs. Classes

    Use a struct only for passive objects that @@ -1466,9 +1471,9 @@ break those invariants. Constructors, destructors, and helper methods may be present; however, these methods must not require or enforce any invariants.

    -

    If more functionality or invariants are required, a -class is more appropriate. If in doubt, make -it a class.

    +

    If more functionality or invariants are required, or struct has wide visibility and expected to +evolve, then a class is more appropriate. If in doubt, make it a class. +

    For consistency with STL, you can use struct instead of class for @@ -1678,17 +1683,23 @@ definitions. If possible, avoid defining operators as templates, because they must satisfy this rule for any possible template arguments. If you define an operator, also define any related operators that make sense, and make sure they -are defined consistently. For example, if you overload -<, overload all the comparison operators, -and make sure < and > never -return true for the same arguments.

    +are defined consistently.

    Prefer to define non-modifying binary operators as non-member functions. If a binary operator is defined as a class member, implicit conversions will apply to the right-hand argument, but not the left-hand one. It will -confuse your users if a < b compiles but -b < a doesn't.

    +confuse your users if a + b compiles but +b + a doesn't.

    + +

    For a type T whose values can be compared for +equality, define a non-member operator== and document when +two values of type T are considered equal. +If there is a single obvious notion of when a value t1 +of type T is less than another such value t2 then +you may also define operator<=>, which should be +consistent with operator==. +Prefer not to overload the other comparison and ordering operators.

    Don't go out of your way to avoid defining operator overloads. For example, prefer to define ==, @@ -1788,7 +1799,7 @@ improve readability, and often provide the same or better performance.

    Prefer to return by value or, failing that, return by reference. -Avoid returning a pointer unless it can be null.

    +Avoid returning a raw pointer unless it can be null.

    Parameters are either inputs to the function, outputs from the function, or both. Non-optional input parameters should usually be values @@ -1802,10 +1813,10 @@ optional outputs and optional input/output parameters.

    -Avoid defining functions that require a const reference parameter -to outlive the call, because const reference parameters bind -to temporaries. Instead, find a way to eliminate the lifetime requirement -(for example, by copying the parameter), or pass it by const +Avoid defining functions that require a reference parameter to outlive the call. +In some cases reference parameters can bind to temporaries, leading to lifetime +bugs. Instead, find a way to eliminate the lifetime requirement +(for example, by copying the parameter), or pass retained parameters by pointer and document the lifetime and non-null requirements.

    @@ -2249,10 +2260,10 @@ qualifier to methods), except as follows:

  • You may use them to define pairs of overloads, such as one taking Foo&& and the other taking const Foo&. Usually the preferred solution is just to pass by value, but an overloaded - pair of functions sometimes yields better performance and is sometimes - necessary in generic code that needs to support a wide variety of types. - As always: if you're writing more complicated code for the sake of - performance, make sure you have evidence that it actually helps.
  • + pair of functions sometimes yields better performance, for example if the + functions sometimes don't consume the input. As always: if you're writing + more complicated code for the sake of performance, make sure you have evidence + that it actually helps.

    Friends

    @@ -2636,9 +2647,9 @@ casts when explicit type conversion is necessary. including void*. Use this only if you know what you are doing and you understand the aliasing issues. Also, consider dereferencing the pointer (without a cast) and - using absl::bit_cast to cast the resulting value. + using std::bit_cast to cast the resulting value. -
  • Use absl::bit_cast to interpret the raw bits of a +
  • Use std::bit_cast to interpret the raw bits of a value using a different type of the same size (a type pun), such as interpreting the bits of a double as int64_t.
  • @@ -2874,10 +2885,13 @@ putting the "adjective" (const) before the const first, we do not require it. But be consistent with the code around you!

    -

    Use of constexpr

    +

    Use of constexpr, constinit, and consteval

    Use constexpr to define true -constants or to ensure constant initialization.

    +constants or to ensure constant initialization. +Use constinit to ensure constant +initialization for non-constant variables. +

    Some variables can be declared constexpr @@ -2885,7 +2899,8 @@ to indicate the variables are true constants, i.e., fixed at compilation/link time. Some functions and constructors can be declared constexpr which enables them to be used in defining a constexpr -variable.

    +variable. Functions can be declared consteval +to restrict their use to compile time.

    Use of constexpr enables definition of @@ -2906,9 +2921,11 @@ in these definitions.

    robust specification of the constant parts of an interface. Use constexpr to specify true constants and the functions that support their -definitions. Avoid complexifying function definitions to +definitions. consteval may be used for +code that must not be invoked at runtime. +Avoid complexifying function definitions to enable their use with constexpr. Do not use -constexpr to force inlining.

    +constexpr or consteval to force inlining.

    Integer Types

    @@ -2948,7 +2965,9 @@ like int16_t, uint32_t, int64_t, etc. You should always use those in preference to short, unsigned long long and the like, when you need a guarantee -on the size of an integer. Of the built-in integer types, only +on the size of an integer. Prefer to omit the std:: +prefix for these types, as the extra 5 characters do +not merit the added clutter. Of the built-in integer types, only int should be used. When appropriate, you are welcome to use standard type aliases like size_t and ptrdiff_t.

    @@ -3148,7 +3167,6 @@ possible:

  • Prefer not using ## to generate function/class/variable names.
  • -

    Exporting macros from headers (i.e., defining them in a header @@ -3230,8 +3248,8 @@ auto c = b; // c is an int auto d{42}; // d is an int, not a std::initializer_list<int>

    auto can be qualified with const, and can be - used as part of a pointer or reference type, but it can't be used as a - template argument. A rare variant of this syntax uses + used as part of a pointer or reference type, and (since C++17) as a + non-type template argument. A rare variant of this syntax uses decltype(auto) instead of auto, in which case the deduced type is the result of applying decltype @@ -3525,8 +3543,8 @@ ordering of fields than the Point example above.

    While designated initializers have long been part of the C standard and -supported by C++ compilers as an extension, only recently have they made it -into the C++ standard, being added as part of C++20.

    +supported by C++ compilers as an extension, they were not supported by +C++ prior to C++20.

    The rules in the C++ standard are stricter than in C and compiler extensions, requiring that the designated initializers appear in the same order as the @@ -3768,6 +3786,113 @@ error messages are part of your user interface, and your code should be tweaked as necessary so that the error messages are understandable and actionable from a user point of view.

    +

    Concepts and Constraints

    + +

    Use concepts sparingly. +In general, concepts and constraints should only be used in cases +where templates would have been used prior to C++20. +Avoid introducing new concepts in headers, +unless the headers are marked as internal to the library. +Do not define concepts that are not enforced by the compiler. +Prefer constraints over template metaprogramming, and +avoid the template<Concept T> syntax; +instead, use the requires(Concept<T>) +syntax.

    + +

    +

    The concept keyword is a new mechanism for defining +requirements (such as type traits or interface specifications) +for a template parameter. +The requires keyword provides mechanisms for placing +anonymous constraints on templates and verifying that constraints +are satisfied at compile time. +Concepts and constraints are often used together, but can be +also used independently.

    + +

    + + +

    + + +

    +

    Predefined concepts in the standard library should be +preferred to type traits, when equivalent ones exist. +(e.g., if std::is_integral_v would have been used +before C++20, then std::integral should be used in +C++20 code.) +Similarly, prefer modern constraint syntax +(via requires(Condition)). +Avoid legacy template metaprogramming constructs +(such as std::enable_if<Condition>) +as well as the template<Concept T> +syntax.

    + +

    Do not manually re-implement any existing concepts or traits. +For example, use +requires(std::default_initializable<T>) +instead of +requires(requires { T v; }) +or the like. + +

    New concept declarations should be rare, and only +defined internally within a library, such that they are not +exposed at API boundaries. +More generally, do not use concepts or constraints in cases where +you wouldn't use their legacy template equivalents in C++17. +

    + +

    Do not define concepts that duplicate the function body, +or impose requirements that would be insignificant or obvious +from reading the body of the code or the resulting error messages. +For example, avoid the following: +

    template <typename T>     // Bad - redundant with negligible benefit
    +concept Addable = std::copyable<T> && requires(T a, T b) { a + b; };
    +template <Addable T>
    +T Add(T x, T y, T z) { return x + y + z; }
    +
    +Instead, prefer to leave code as an ordinary template unless +you can demonstrate that concepts result in significant +improvement for that particular case, such as in the resulting +error messages for a deeply nested or non-obvious +requirement. + +

    Concepts should be statically verifiable by the compiler. +Do not use any concept whose primary benefits would come from a +semantic (or otherwise unenforced) constraint. +Requirements that are unenforced at compile time should instead +be imposed via other mechanisms such as comments, assertions, +or tests.

    +

    Boost

    Use only approved libraries from the Boost library @@ -3955,9 +4080,10 @@ guide, the following C++ features may not be used:

    There are several ways to create names that are aliases of other entities:

    -
    typedef Foo Bar;
    -using Bar = Foo;
    -using other_namespace::Foo;
    +
    using Bar = Foo;
    +typedef Foo Bar;  // But prefer `using` in C++ code.
    +using ::other_namespace::Foo;
    +using enum MyEnumType;  // Creates aliases for all enumerators in MyEnumType.
     

    In new code, using is preferable to typedef, @@ -4245,6 +4371,10 @@ using PropertiesMap = hash_map<UrlTableProperties *, std::string>; enum class UrlTableError { ...

    +

    Concept Names

    + +Concept names follow the same rules as type names. +

    Variable Names

    The names of variables (including function parameters) and data members are @@ -4308,9 +4438,10 @@ const int kAndroid8_0_0 = 24; // Android 8.0.0

    All such variables with static storage duration (i.e., statics and globals, see -Storage Duration for details) should be named this way. This -convention is optional for variables of other storage classes, e.g., automatic -variables; otherwise the usual variable naming rules apply. For example:

    +Storage Duration for details) should be named this way, including those in templates where +different instantiations of the template may have different values. This convention is optional for +variables of other storage classes, e.g., automatic variables; otherwise the usual variable naming +rules apply. For example:

    void ComputeFoo(absl::string_view suffix) {
       // Either of these is acceptable.
    @@ -4515,7 +4646,7 @@ author line, consider deleting the author line.
     New files should usually not contain copyright notice or
     author line.

    -

    Class Comments

    +

    Struct and Class Comments

    Every non-obvious class or struct declaration should have an accompanying comment that describes what it is for and how it should @@ -4532,6 +4663,8 @@ class GargantuanTableIterator { };

    +

    Class Comments

    +

    The class comment 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. Document the synchronization assumptions @@ -4925,7 +5058,8 @@ if included in the source as straight UTF-8.

    When possible, avoid the u8 prefix. It has significantly different semantics starting in C++20 than in C++17, producing arrays of char8_t -rather than char. +rather than char, and will change again in C++23. +

    You shouldn't use char16_t and char32_t character types, since they're for @@ -5091,7 +5225,8 @@ double d = 1248e6;

    float f = 1.0f;
    -float f2 = 1;   // Also OK
    +float f2 = 1.0;  // Also OK
    +float f3 = 1;    // Also OK
     long double ld = -0.5L;
     double d = 1248.0e6;
     
    diff --git a/go/best-practices.md b/go/best-practices.md index 46c6148..2e8457b 100644 --- a/go/best-practices.md +++ b/go/best-practices.md @@ -597,7 +597,11 @@ to have them in the same package. Code within a package can access unexported identifiers in the package. If you have a few related types whose *implementation* is tightly coupled, placing them in the same package lets you achieve this coupling without polluting the public -API with these details. +API with these details. A good test for this coupling is to imagine a +hypothetical user of two packages, where the packages cover closely related +topics: if the user must import both packages in order to use either in any +meaningful way, combining them together is usually the right thing to do. The +standard library generally demonstrates this kind of scoping and layering well. All of that being said, putting your entire project in a single package would likely make that package too large. When something is conceptually distinct, @@ -776,7 +780,7 @@ var ( ErrMarsupial = errors.New("marsupials are not supported") ) -func pet(animal Animal) error { +func process(animal Animal) error { switch { case seen[animal]: return ErrDuplicate @@ -849,6 +853,8 @@ to know if using status codes is the right choice. [`os.PathError`]: https://pkg.go.dev/os#PathError [`errors.Is`]: https://pkg.go.dev/errors#Is +[`errors.As`]: https://pkg.go.dev/errors#As +[`package cmp`]: https://pkg.go.dev/github.com/google/go-cmp/cmp [status]: https://pkg.go.dev/google.golang.org/grpc/status [canonical codes]: https://pkg.go.dev/google.golang.org/grpc/codes @@ -978,6 +984,10 @@ func (*FortuneTeller) SuggestFortune(context.Context, *pb.SuggestionRequest) (*p } ``` +See also: + +* [Error Documentation Conventions](#documentation-conventions-errors) + ### Placement of %w in errors @@ -1255,7 +1265,7 @@ information to the reader: // string. // // format is the format, and data is the interpolation data. -func Sprintf(format string, data ...interface{}) string +func Sprintf(format string, data ...any) string ``` However, this snippet demonstrates a code scenario similar to the previous where @@ -1272,7 +1282,7 @@ reader: // the format specification, the function will inline warnings about formatting // errors into the output string as described by the Format errors section // above. -func Sprintf(format string, data ...interface{}) string +func Sprintf(format string, data ...any) string ``` Consider your likely audience in choosing what to document and at what depth. @@ -1317,9 +1327,9 @@ func (Worker) Run(ctx context.Context) error ``` Where context behavior is different or non-obvious, it should be expressly -documented: +documented if any of the following are true. -* If the function returns an error other than `ctx.Err()` when the context is +* The function returns an error other than `ctx.Err()` when the context is cancelled: ```go @@ -1330,8 +1340,7 @@ documented: func (Worker) Run(ctx context.Context) error ``` -* If the function has other mechanisms that may interrupt it or affect - lifetime: +* The function has other mechanisms that may interrupt it or affect lifetime: ```go // Good: @@ -1347,7 +1356,7 @@ documented: func (Worker) Stop() ``` -* If the function has special expectations about context lifetime, lineage, or +* The function has special expectations about context lifetime, lineage, or attached values: ```go @@ -1394,9 +1403,9 @@ Similarly, the extra remark about concurrency can safely be removed here: func (*Buffer) Grow(n int) ``` -Documentation is strongly encouraged if: +Documentation is strongly encouraged if any of the following are true. -* it is unclear whether the operation is read-only or a mutating +* It is unclear whether the operation is read-only or mutating: ```go // Good: @@ -1411,7 +1420,7 @@ Documentation is strongly encouraged if: Why? A cache hit when looking up the key mutate a LRU cache internally. How this is implemented may not be obvious to all readers. -* synchronization is provided by API +* Synchronization is provided by the API: ```go // Good: @@ -1427,7 +1436,7 @@ Documentation is strongly encouraged if: **Note:** If the API is a type and the API provides synchronization in entirety, conventionally only the type definition documents the semantics. -* the API consumes user-implemented types of interfaces, and the interface's +* The API consumes user-implemented types of interfaces, and the interface's consumer has particular concurrency requirements: ```go @@ -1489,6 +1498,84 @@ If it is potentially unclear how to clean up the resources, explain how: func (c *Client) Get(url string) (resp *Response, err error) ``` +See also: + +* [GoTip #110: Don’t Mix Exit With Defer] + +[GoTip #110: Don’t Mix Exit With Defer]: https://google.github.io/styleguide/go/index.html#gotip + + + +#### Errors + +Document significant error sentinel values or error types that your functions +return to callers so that callers can anticipate what types of conditions they +can handle in their code. + +```go +// Good: +package os + +// Read reads up to len(b) bytes from the File and stores them in b. It returns +// the number of bytes read and any error encountered. +// +// At end of file, Read returns 0, io.EOF. +func (*File) Read(b []byte) (n int, err error) { +``` + +When a function returns a specific error type, correctly note whether the error +is a pointer receiver or not: + +```go +// Good: +package os + +type PathError struct { + Op string + Path string + Err error +} + +// Chdir changes the current working directory to the named directory. +// +// If there is an error, it will be of type *PathError. +func Chdir(dir string) error { +``` + +Documenting whether the values returned are pointer receivers enables callers to +correctly compare the errors using [`errors.Is`], [`errors.As`], and +[`package cmp`]. This is because a non-pointer value is not equivalent to a +pointer value. + +**Note:** In the `Chdir` example, the return type is written as `error` rather +than `*PathError` due to +[how nil interface values work](https://go.dev/doc/faq#nil_error). + +Document overall error conventions in the +[package's documentation](decisions#package-comments) when the behavior is +applicable to most errors found in the package: + +```go +// Good: +// Package os provides a platform-independent interface to operating system +// functionality. +// +// Often, more information is available within the error. For example, if a +// call that takes a file name fails, such as Open or Stat, the error will +// include the failing file name when printed and will be of type *PathError, +// which may be unpacked for more information. +package os +``` + +Thoughtful application of these approaches can add +[extra information to errors](#error-extra-info) without much effort and help +callers avoid adding redundant annotations. + +See also: + +* [Go Tip #106: Error Naming Conventions](https://google.github.io/styleguide/go/index.html#gotip) +* [Go Tip #89: When to Use Canonical Status Codes as Errors](https://google.github.io/styleguide/go/index.html#gotip) + ### Preview @@ -1944,7 +2031,7 @@ func foo(ctx context.Context) { } ``` -**Note**: [Contexts are never included in option structs](decisions#contexts). +**Note:** [Contexts are never included in option structs](decisions#contexts). This option is often preferred when some of the following apply: @@ -2411,7 +2498,7 @@ func ExerciseGame(t *testing.T, cfg *Config, p chess.Player) error { if cfg.Simulation == Modem { conn, err := modempool.Allocate() if err != nil { - t.Fatalf("no modem for the opponent could be provisioned: %v", err) + t.Fatalf("No modem for the opponent could be provisioned: %v", err) } t.Cleanup(func() { modempool.Return(conn) }) } @@ -2437,7 +2524,7 @@ func TestAcceptance(t *testing.T) { player := deepblue.New() err := chesstest.ExerciseGame(t, chesstest.SimpleGame, player) if err != nil { - t.Errorf("deepblue player failed acceptance test: %v", err) + t.Errorf("Deep Blue player failed acceptance test: %v", err) } } ``` @@ -2578,14 +2665,14 @@ func paint(color string) error { func badSetup(t *testing.T) { // This should call t.Helper, but doesn't. if err := paint("taupe"); err != nil { - t.Fatalf("could not paint the house under test: %v", err) // line 15 + t.Fatalf("Could not paint the house under test: %v", err) // line 15 } } func mustGoodSetup(t *testing.T) { t.Helper() if err := paint("lilac"); err != nil { - t.Fatalf("could not paint the house under test: %v", err) + t.Fatalf("Could not paint the house under test: %v", err) } } @@ -2605,10 +2692,10 @@ differs: ```text === RUN TestBad - paint_test.go:15: could not paint the house under test: no "taupe" paint today + paint_test.go:15: Could not paint the house under test: no "taupe" paint today --- FAIL: TestBad (0.00s) === RUN TestGood - paint_test.go:32: could not paint the house under test: no "lilac" paint today + paint_test.go:32: Could not paint the house under test: no "lilac" paint today --- FAIL: TestGood (0.00s) FAIL ``` @@ -2616,7 +2703,7 @@ FAIL The error with `paint_test.go:15` refers to the line of the setup function that failed in `badSetup`: -`t.Fatalf("could not paint the house under test: %v", err)` +`t.Fatalf("Could not paint the house under test: %v", err)` Whereas `paint_test.go:32` refers to the line of the test that failed in `TestGood`: @@ -2695,33 +2782,41 @@ and those should not depend on the system under test. Therefore, if a test helper [registers a fatal test failure](#test-helper-error-handling), it can and should do so from the test's goroutine. + + +### Use field names in struct literals + -### Use field labels for struct literals - -In table-driven tests, prefer to specify the key for each test case specified. -This is helpful when the test cases cover a large amount of vertical space (e.g. -more than 20-30 lines), when there are adjacent fields with the same type, and -also when you wish to omit fields which have the zero value. For example: +In table-driven tests, prefer to specify field names when initializing test case +struct literals. This is helpful when the test cases cover a large amount of +vertical space (e.g. more than 20-30 lines), when there are adjacent fields with +the same type, and also when you wish to omit fields which have the zero value. +For example: ```go // Good: -tests := []struct { - foo *pb.Foo - bar *pb.Bar - want string -}{ - { - foo: pb.Foo_builder{ - Name: "foo", - // ... - }.Build(), - bar: pb.Bar_builder{ - Name: "bar", - // ... - }.Build(), - want: "result", - }, +func TestStrJoin(t *testing.T) { + tests := []struct { + slice []string + separator string + skipEmpty bool + want string + }{ + { + slice: []string{"a", "b", ""}, + separator: ",", + want: "a,b,", + }, + { + slice: []string{"a", "b", ""}, + separator: ",", + skipEmpty: true, + want: "a,b", + }, + // ... + } + // ... } ``` @@ -2742,7 +2837,7 @@ func mustLoadDataset(t *testing.T) []byte { data, err := os.ReadFile("path/to/your/project/testdata/dataset") if err != nil { - t.Fatalf("could not load dataset: %v", err) + t.Fatalf("Could not load dataset: %v", err) } return data } @@ -2756,7 +2851,7 @@ func TestParseData(t *testing.T) { data := mustLoadDataset(t) parsed, err := ParseData(data) if err != nil { - t.Fatalf("unexpected error parsing data: %v", err) + t.Fatalf("Unexpected error parsing data: %v", err) } want := &DataTable{ /* ... */ } if got := parsed; !cmp.Equal(got, want) { @@ -2768,7 +2863,7 @@ func TestListContents(t *testing.T) { data := mustLoadDataset(t) contents, err := ListContents(data) if err != nil { - t.Fatalf("unexpected error listing contents: %v", err) + t.Fatalf("Unexpected error listing contents: %v", err) } want := []string{ /* ... */ } if got := contents; !cmp.Equal(got, want) { @@ -2916,7 +3011,7 @@ func mustLoadDataset(t *testing.T) []byte { dataset.err = err }) if err := dataset.err; err != nil { - t.Fatalf("could not load dataset: %v", err) + t.Fatalf("Could not load dataset: %v", err) } return dataset.data } @@ -2972,8 +3067,8 @@ guidance outlines when each method is preferred. ### Prefer "+" for simple cases -Prefer using "+" when concatenating few strings. This method is the -syntactically the simplest and requires no import. +Prefer using "+" when concatenating few strings. This method is syntactically +the simplest and requires no import. ```go // Good: @@ -3024,7 +3119,7 @@ for i, d := range digitsOfPi { str := b.String() ``` -**NOTE**: For more discussion, see +**Note:** For more discussion, see [GoTip #29: Building Strings Efficiently](https://google.github.io/styleguide/go/index.html#gotip). diff --git a/go/decisions.md b/go/decisions.md index 8c842e9..1968b56 100644 --- a/go/decisions.md +++ b/go/decisions.md @@ -897,9 +897,14 @@ import ( ) ``` -It is acceptable to split the project packages into multiple groups, for example -if you want a separate group for renamed, imported-only-for-side-effects or -another special group of imports. +It is acceptable to split the project packages into multiple groups if you want +a separate group, as long as the groups have some meaning. Common reasons to do +this: + +* renamed imports +* packages imported for their side-effects + +Example: ```go // Good: @@ -1273,14 +1278,19 @@ maintainable. #### Field names -Struct literals should usually specify **field names** for types defined outside -the current package. +Struct literals must specify **field names** for types defined outside the +current package. * Include field names for types from other packages. ```go // Good: - good := otherpkg.Type{A: 42} + // https://pkg.go.dev/encoding/csv#Reader + r := csv.Reader{ + Comma: ',', + Comment: '#', + FieldsPerRecord: 4, + } ``` The position of fields in a struct and the full set of fields (both of which @@ -1290,19 +1300,9 @@ the current package. ```go // Bad: - // https://pkg.go.dev/encoding/csv#Reader r := csv.Reader{',', '#', 4, false, false, false, false} ``` - Field names may be omitted within small, simple structs whose composition - and order are documented as being stable. - - ```go - // Good: - okay := image.Point{42, 54} - also := image.Point{X: 42, Y: 54} - ``` - * For package-local types, field names are optional. ```go @@ -1721,33 +1721,6 @@ func (r *SomeType) SomeLongFunctionName(foo1, foo2, foo3 string, See [best practices](best-practices#funcargs) for a few options for shortening the call sites of functions that would otherwise have many arguments. -```go -// Good: -good := foo.Call(long, CallOptions{ - Names: list, - Of: of, - The: parameters, - Func: all, - Args: on, - Now: separate, - Visible: lines, -}) -``` - -```go -// Bad: -bad := foo.Call( - long, - list, - of, - parameters, - all, - on, - separate, - lines, -) -``` - Lines can often be shortened by factoring out local variables. ```go @@ -1770,9 +1743,9 @@ bad := foo.Call(long, list, of, parameters, with, arbitrary, line, breaks) ``` -Do not add comments to specific function parameters. Instead, use an -[option struct](best-practices#option-structure) or add more detail to the -function documentation. +Avoid adding inline comments to specific function arguments where possible. +Instead, use an [option struct](best-practices#option-structure) or add more +detail to the function documentation. ```go // Good: @@ -1787,21 +1760,6 @@ bad := server.New( ) ``` -If call-sites are uncomfortably long, consider refactoring: - -```go -// Good: -// Sometimes variadic arguments can be factored out -replacements := []string{ - "from", "to", // related values can be formatted adjacent to one another - "source", "dest", - "original", "new", -} - -// Use the replacement struct as inputs to NewReplacer. -replacer := strings.NewReplacer(replacements...) -``` - If the API cannot be changed or if the local call is unusual (whether or not the call is too long), it is always permissible to add line breaks if it aids in understanding the call. @@ -2110,7 +2068,7 @@ exclusively at func MustParse(version string) *Version { v, err := Parse(version) if err != nil { - log.Fatalf("MustParse(%q) = _, %v", version, err) + panic(fmt.Sprintf("MustParse(%q) = _, %v", version, err)) } return v } @@ -2120,8 +2078,6 @@ func MustParse(version string) *Version { var DefaultVersion = MustParse("1.2.3") ``` -**Note:** `log.Fatalf` is not the standard library log. See [#logging]. - The same convention may be used in test helpers that only stop the current test (using `t.Fatal`). Such helpers are often convenient in creating test values, for example in struct fields of [table driven tests](#table-driven-tests), as @@ -2133,7 +2089,7 @@ func mustMarshalAny(t *testing.T, m proto.Message) *anypb.Any { t.Helper() any, err := anypb.New(m) if err != nil { - t.Fatalf("MustMarshalAny(t, m) = %v; want %v", err, nil) + t.Fatalf("mustMarshalAny(t, m) = %v; want %v", err, nil) } return any } @@ -2189,8 +2145,8 @@ func Version(o *servicepb.Object) (*version.Version, error) { When you spawn goroutines, make it clear when or whether they exit. Goroutines can leak by blocking on channel sends or receives. The garbage -collector will not terminate a goroutine even if the channels it is blocked on -are unreachable. +collector will not terminate a goroutine blocked on a channel even if no other +goroutine has a reference to the channel. Even when goroutines do not leak, leaving them in-flight when they are no longer needed can cause other subtle and hard-to-diagnose problems. Sending on a @@ -2764,11 +2720,11 @@ See also: ### Logging -Go programs in the Google codebase use a variant of the -[standard `log` package]. It has a similar but more powerful interface and -interoperates well with internal Google systems. An open source version of this -library is available as [package `glog`], and open source Google projects may -use that, but this guide refers to it as `log` throughout. +Go programs in the Google codebase use a variant of the standard [`log`] +package. It has a similar but more powerful interface and interoperates well +with internal Google systems. An open source version of this library is +available as [package `glog`], and open source Google projects may use that, but +this guide refers to it as `log` throughout. **Note:** For abnormal program exits, this library uses `log.Fatal` to abort with a stacktrace, and `log.Exit` to stop without one. There is no `log.Panic` @@ -2785,7 +2741,8 @@ See also: * When and how to use the log package to [stop the program](best-practices#checks-and-panics) -[standard `log` package]: https://pkg.go.dev/log +[`log`]: https://pkg.go.dev/log +[`log/slog`]: https://pkg.go.dev/log/slog [package `glog`]: https://pkg.go.dev/github.com/golang/glog [`log.Exit`]: https://pkg.go.dev/github.com/golang/glog#Exit [`log.Fatal`]: https://pkg.go.dev/github.com/golang/glog#Fatal @@ -2978,15 +2935,15 @@ right: // Bad: package assert -func IsNotNil(t *testing.T, name string, val interface{}) { +func IsNotNil(t *testing.T, name string, val any) { if val == nil { - t.Fatalf("data %s = nil, want not nil", name) + t.Fatalf("Data %s = nil, want not nil", name) } } func StringEq(t *testing.T, name, got, want string) { if got != want { - t.Fatalf("data %s = %q, want %q", name, got, want) + t.Fatalf("Data %s = %q, want %q", name, got, want) } } ``` @@ -3013,7 +2970,7 @@ want := BlogPost{ } if !cmp.Equal(got, want) { - t.Errorf("blog post = %v, want = %v", got, want) + t.Errorf("Blog post = %v, want = %v", got, want) } ``` @@ -3029,7 +2986,7 @@ func TestBlogPost_VeritableRant(t *testing.T) { post := BlogPost{Body: "I am Gunnery Sergeant Hartman, your senior drill instructor."} if got, want := postLength(post), 60; got != want { - t.Errorf("length of post = %v, want %v", got, want) + t.Errorf("Length of post = %v, want %v", got, want) } } ``` @@ -3361,7 +3318,8 @@ than relying on parsing the error message. Within unit tests, it is common to only care whether an error occurred or not. If so, then it is sufficient to only test whether the error was non-nil when you expected an error. If you would like to test that the error semantically matches -some other error, then consider using `cmp` with [`cmpopts.EquateErrors`]. +some other error, then consider using [`errors.Is`] or `cmp` with +[`cmpopts.EquateErrors`]. > **Note:** If a test uses [`cmpopts.EquateErrors`] but all of its `wantErr` > values are either `nil` or `cmpopts.AnyError`, then using `cmp` is @@ -3370,9 +3328,10 @@ some other error, then consider using `cmp` with [`cmpopts.EquateErrors`]. > > ```go > // Good: -> gotErr := f(test.input) != nil +> err := f(test.input) +> gotErr := err != nil > if gotErr != test.wantErr { -> t.Errorf("f(%q) returned err = %v, want error presence = %v", test.input, gotErr, test.wantErr) +> t.Errorf("f(%q) = %v, want error presence = %v", test.input, err, test.wantErr) > } > ``` @@ -3381,6 +3340,7 @@ See also [tott-350]: https://testing.googleblog.com/2015/01/testing-on-toilet-change-detector-tests.html [`cmpopts.EquateErrors`]: https://pkg.go.dev/github.com/google/go-cmp/cmp/cmpopts#EquateErrors +[`errors.Is`]: https://pkg.go.dev/errors#Is @@ -3471,6 +3431,9 @@ t.Run("check that there is no mention of scratched records or hovercrafts", ...) t.Run("AM/PM confusion", ...) ``` +See also +[Go Tip #117: Subtest Names](https://google.github.io/styleguide/go/index.html#gotip). + [Go test runner]: https://golang.org/cmd/go/#hdr-Testing_flags [identify the inputs]: #identify-the-input [special meaning for test filters]: https://blog.golang.org/subtests#:~:text=Perhaps%20a%20bit,match%20any%20tests @@ -3491,39 +3454,37 @@ similar testing logic. [tests of `fmt.Sprintf`]: https://cs.opensource.google/go/go/+/master:src/fmt/fmt_test.go [tests for `net.Dial`]: https://cs.opensource.google/go/go/+/master:src/net/dial_test.go;l=318;drc=5b606a9d2b7649532fe25794fa6b99bd24e7697c -Here is the minimal structure of a table-driven test, copied from the standard -`strings` library. If needed, you may use different names, move the test slice -into the test function, or add extra facilities such as subtests or setup and -cleanup functions. Always keep [useful test failures](#useful-test-failures) in -mind. +Here is the minimal structure of a table-driven test. If needed, you may use +different names or add extra facilities such as subtests or setup and cleanup +functions. Always keep [useful test failures](#useful-test-failures) in mind. ```go // Good: -var compareTests = []struct { - a, b string - i int -}{ - {"", "", 0}, - {"a", "", 1}, - {"", "a", -1}, - {"abc", "abc", 0}, - {"ab", "abc", -1}, - {"abc", "ab", 1}, - {"x", "ab", 1}, - {"ab", "x", -1}, - {"x", "a", 1}, - {"b", "x", -1}, - // test runtime·memeq's chunked implementation - {"abcdefgh", "abcdefgh", 0}, - {"abcdefghi", "abcdefghi", 0}, - {"abcdefghi", "abcdefghj", -1}, -} - func TestCompare(t *testing.T) { - for _, tt := range compareTests { - cmp := Compare(tt.a, tt.b) - if cmp != tt.i { - t.Errorf(`Compare(%q, %q) = %v`, tt.a, tt.b, cmp) + compareTests := []struct { + a, b string + want int + }{ + {"", "", 0}, + {"a", "", 1}, + {"", "a", -1}, + {"abc", "abc", 0}, + {"ab", "abc", -1}, + {"abc", "ab", 1}, + {"x", "ab", 1}, + {"ab", "x", -1}, + {"x", "a", 1}, + {"b", "x", -1}, + // test runtime·memeq's chunked implementation + {"abcdefgh", "abcdefgh", 0}, + {"abcdefghi", "abcdefghi", 0}, + {"abcdefghi", "abcdefghj", -1}, + } + + for _, test := range compareTests { + got := Compare(test.a, test.b) + if got != test.want { + t.Errorf("Compare(%q, %q) = %v, want %v", test.a, test.b, got, test.want) } } } @@ -3639,7 +3600,7 @@ func TestDecode(t *testing.T) { case prod: codex = setupCodex(t) default: - t.Fatalf("unknown codex type: %v", codex) + t.Fatalf("Unknown codex type: %v", codex) } output, err := Decode(test.input, codex) if got, want := output, test.output; got != want { @@ -3673,7 +3634,7 @@ tests := []struct { } for i, d := range tests { if strings.ToUpper(d.input) != d.want { - t.Errorf("failed on case #%d", i) + t.Errorf("Failed on case #%d", i) } } ``` diff --git a/go/guide.md b/go/guide.md index e051b09..961469f 100644 --- a/go/guide.md +++ b/go/guide.md @@ -419,8 +419,8 @@ initial capitalization. ### Line length -There is no fixed line length for Go source code. If a line feels too long, it -should be refactored instead of broken. If it is already as short as it is +There is no fixed line length for Go source code. If a line feels too long, +prefer refactoring instead of splitting it. If it is already as short as it is practical for it to be, the line should be allowed to remain long. Do not split a line: diff --git a/javaguide.css b/javaguide.css index 39a7bc6..19f2fa3 100644 --- a/javaguide.css +++ b/javaguide.css @@ -2,13 +2,16 @@ table { border-collapse: collapse; } -td, th { +td, +th { border: 1px solid #ccc; padding: 2px 12px; font-size: 10pt; } -code, samp, var { +code, +samp, +var { color: #060; } @@ -17,16 +20,9 @@ pre { display: block; color: #060; background-color: #e8fff6; - border-color: #f0fff0; - border-style: solid; - border-top-width: 1px; - border-bottom-width: 1px; - border-right-width: 1px; + border: 1px solid #f0fff0; border-left-width: 5px; - padding-left: 12px; - padding-right: 12px; - padding-top: 4px; - padding-bottom: 4px; + padding: 4px 12px; } pre.badcode { @@ -42,10 +38,8 @@ hr { } html { - margin-top:2em; - margin-left:10%; - margin-right:10%; - padding:0; + margin: 2em 10% 0; + padding: 0; } .bp-reset-element, @@ -104,32 +98,32 @@ tbody, tfoot, thead, tr { - margin:0; - padding:0; - border:0; - font-weight:inherit; - font-style:inherit; - font-size:100%; - font-family:inherit; - vertical-align:baseline; + margin: 0; + padding: 0; + border: 0; + font-weight: inherit; + font-style: inherit; + font-size: 100%; + font-family: inherit; + vertical-align: baseline; } body { - font-family:'Arial', sans-serif; - font-size:81.25%; - color:#222; - background-color:#fff; - line-height:1.67; + font-family: 'Arial', sans-serif; + font-size: 81.25%; + color: #222; + background-color: #fff; + line-height: 1.67; overflow: auto; } .change { text-align: right; - margin-bottom:1em; + margin-bottom: 1em; } em { - font-style: italic + font-style: italic; } h1, @@ -138,12 +132,12 @@ h3, h4, h5, h6 { - font-weight:bold; + font-weight: bold; } h1 { - margin-bottom:.50em; - text-align: center + margin-bottom: 0.5em; + text-align: center; } h2, @@ -151,22 +145,36 @@ h3, h4, h5, h6 { - margin-top:1.5em; - margin-bottom:.75em; + margin-top: 1.5em; + margin-bottom: 0.75em; } -h1 {font-size:200%;} -h2 {font-size:167%;} -h3 {font-size:133%;} -h4 {font-size:120%;} -h5 {font-size:110%;} +h1 { + font-size: 200%; +} + +h2 { + font-size: 167%; +} + +h3 { + font-size: 133%; +} + +h4 { + font-size: 120%; +} + +h5 { + font-size: 110%; +} p { - margin:0 0 1.5em; + margin: 0 0 1.5em; } a[href=''] { - cursor:default; + cursor: default; } h1 img, @@ -175,238 +183,238 @@ h3 img, h4 img, h5 img, h6 img { - margin:0; + margin: 0; } a img { - border:none; + border: none; } pre { - margin:1.5em 0; - white-space:pre; + margin: 1.5em 0; + white-space: pre; } pre, code, kbd, tt { - font:1em 'Droid Sans Mono', monospace; - line-height:1.5; + font: 1em 'Droid Sans Mono', monospace; + line-height: 1.5; } dl { - margin:0 0 1.5em 0; + margin: 0 0 1.5em; } dl dt { - font-weight:bold; + font-weight: bold; } dd { - margin-left:1.5em; + margin-left: 1.5em; } dd.toc3 { - margin-left:3em; + margin-left: 3em; } hr { - height:0; - border:0; - border-top:1px solid #ccc; - background-color:#ccc; + height: 0; + border: 0; + border-top: 1px solid #ccc; + background-color: #ccc; } table { - border:1px solid #bbb; - border-spacing:0; - border-collapse:collapse; - margin:0 0 1.5em; - vertical-align:middle; - width:100%; + border: 1px solid #bbb; + border-spacing: 0; + border-collapse: collapse; + margin: 0 0 1.5em; + vertical-align: middle; + width: 100%; } table.unlined, table.unlined th, table.unlined tr, table.unlined td { - border:0; + border: 0; } th, td, caption { - float:none !important; - text-align:left; - font-weight:normal; - vertical-align:middle; - padding:4px; + float: none !important; + text-align: left; + font-weight: normal; + vertical-align: middle; + padding: 4px; } caption { - padding:0; + padding: 0; } td { - border:1px solid #bbb; - vertical-align:top; + border: 1px solid #bbb; + vertical-align: top; } th { - border:0; - border-bottom:1px solid black; - font-weight:bold; - background:rgb(229, 236, 249); + border: 0; + border-bottom: 1px solid black; + font-weight: bold; + background: rgb(229, 236, 249); } table th code { - background-color:inherit; - color:inherit; + background-color: inherit; + color: inherit; } table tfoot th { - border:1px solid #bbb; + border: 1px solid #bbb; } tfoot { - font-style:italic; + font-style: italic; } caption { - background:#eee; + background: #eee; } table[border='0'] { - border:none; + border: none; } -table[border='0']>tbody>tr>td, -table[border='0']>tr>td { - border:none; +table[border='0'] > tbody > tr > td, +table[border='0'] > tr > td { + border: none; } tr.alt td, td.alt { - background-color:#efefef; + background-color: #efefef; } table.striped tr:nth-child(even) td, table tr.even td { - background:#efefef; + background: #efefef; } table.columns { - border:none; + border: none; } -table.columns>tbody>tr>td, -table.columns>tr>td { - border:none; - padding:0 3em 0 0; +table.columns > tbody > tr > td, +table.columns > tr > td { + border: none; + padding: 0 3em 0 0; } -table.columns>tbody>tr>td:last-child, -table.columns>tr>td:last-child { - border:none; - padding:0; +table.columns > tbody > tr > td:last-child, +table.columns > tr > td:last-child { + border: none; + padding: 0; } ul, ol { - margin:0 1.5em 1.5em 0; - padding-left:2em; + margin: 0 1.5em 1.5em 0; + padding-left: 2em; } li ul, li ol { - margin:0; + margin: 0; } ul { - list-style-type:disc; + list-style-type: disc; } ol { - list-style-type:decimal; + list-style-type: decimal; } ul { - list-style-type:disc; + list-style-type: disc; } ul ul { - list-style-type:circle; + list-style-type: circle; } ul ul ul { - list-style-type:square; + list-style-type: square; } ul.disc { - list-style-type:disc; + list-style-type: disc; } ul.circle { - list-style-type:circle; + list-style-type: circle; } ul.square { - list-style-type:square; + list-style-type: square; } ol { - list-style-type:decimal; + list-style-type: decimal; } ol ol { - list-style-type:lower-alpha; + list-style-type: lower-alpha; } ol ol ol { - list-style-type:lower-roman; + list-style-type: lower-roman; } ol ul { - list-style-type:circle; + list-style-type: circle; } ol.decimal { - list-style-type:decimal; + list-style-type: decimal; } ol.upper-alpha { - list-style-type:upper-alpha; + list-style-type: upper-alpha; } ol.lower-alpha { - list-style-type:lower-alpha; + list-style-type: lower-alpha; } ol.upper-roman { - list-style-type:upper-roman; + list-style-type: upper-roman; } ol.lower-roman { - list-style-type:lower-roman; + list-style-type: lower-roman; } ol.nolist, ul.nolist { - padding-left:0; - list-style-image:none; - list-style-type:none; - margin-left:0; + padding-left: 0; + list-style-image: none; + list-style-type: none; + margin-left: 0; } .center { - text-align:center; + text-align: center; } code, kbd, pre { - color:#009900; + color: #090; } kbd { @@ -414,107 +422,108 @@ kbd { } table.striped code { - background-color:inherit; + background-color: inherit; } pre { - padding:6px 10px; - background-color:#FAFAFA; - border:1px solid #bbb; - overflow:auto; + padding: 6px 10px; + background-color: #fafafa; + border: 1px solid #bbb; + overflow: auto; } pre.prettyprint { - padding:6px 10px !important; - border:1px solid #bbb !important; + padding: 6px 10px !important; + border: 1px solid #bbb !important; } -code.bad, code.badcode { +code.bad, +code.badcode { color: magenta; } -pre.bad, pre.badcode { - background-color:#ffe6d8; - border-top:1px inset #a03; - border-left:1px inset #a03; + +pre.bad, +pre.badcode { + background-color: #ffe6d8; + border-top: 1px inset #a03; + border-left: 1px inset #a03; } .tip { - background-color:#fffbd9; - padding:6px 8px 6px 10px; - border-left:6px solid #ffef70; + background-color: #fffbd9; + padding: 6px 8px 6px 10px; + border-left: 6px solid #ffef70; } .note { - background-color:#e5ecf9; - padding:6px 8px 6px 10px; - border-left:6px solid #36c; + background-color: #e5ecf9; + padding: 6px 8px 6px 10px; + border-left: 6px solid #36c; } @media print { - .str { - color:#060; + color: #060; } .kwd { - color:#006; - font-weight:bold; + color: #006; + font-weight: bold; } .com { - color:#600; - font-style:italic; + color: #600; + font-style: italic; } .typ { - color:#404; - font-weight:bold; + color: #404; + font-weight: bold; } .lit { - color:#044; + color: #044; } .pun, .opn, .clo { - color:#440; + color: #440; } .pln { - color:#000; + color: #000; } .tag { - color:#006; - font-weight:bold; + color: #006; + font-weight: bold; } .atn { - color:#404; + color: #404; } .atv { - color:#060; + color: #060; } h1 { - font-style:italic; + font-style: italic; } } ol.linenums { - margin-top:0; - margin-bottom:0; + margin-top: 0; + margin-bottom: 0; } code { - background-color:#FAFAFA; + background-color: #fafafa; padding: 0.25em 0.5em; - white-space: nowrap + white-space: nowrap; } - /* TOC CSS */ table.columns { @@ -552,15 +561,16 @@ li.toc_entry { * at href boundaries */ li.toc_entry::after { - content: " "; - } + content: ' '; +} li.toc_entry a { white-space: nowrap; } /* Horizontal TOC */ -.toc td, .toc th { +.toc td, +.toc th { border-width: 1px 5px; overflow: hidden; } @@ -568,7 +578,7 @@ li.toc_entry a { /* Vertical TOC */ .toc td.two_columns { - border-width: 0px; + border-width: 0; } /* Numbered sections */ diff --git a/pyguide.md b/pyguide.md index 4d38b3c..74d3fab 100644 --- a/pyguide.md +++ b/pyguide.md @@ -47,6 +47,7 @@ See README.md for details. + [3.8.2 Modules](#s3.8.2-comments-in-modules) + [3.8.2.1 Test modules](#s3.8.2.1-test-modules) + [3.8.3 Functions and Methods](#s3.8.3-functions-and-methods) + + [3.8.3.1 Overridden Methods](#s3.8.3.1-overridden-methods) + [3.8.4 Classes](#s3.8.4-comments-in-classes) + [3.8.5 Block and Inline Comments](#s3.8.5-block-and-inline-comments) + [3.8.6 Punctuation, Spelling, and Grammar](#s3.8.6-punctuation-spelling-and-grammar) @@ -213,8 +214,8 @@ that the arguments are actually unused. ### 2.2 Imports -Use `import` statements for packages and modules only, not for individual -classes or functions. +Use `import` statements for packages and modules only, not for individual types, +classes, or functions. @@ -404,11 +405,15 @@ Exceptions must follow certain conditions: - Make use of built-in exception classes when it makes sense. For example, raise a `ValueError` to indicate a programming mistake like a violated - precondition (such as if you were passed a negative number but required a - positive one). Do not use `assert` statements for validating argument values - of a public API. `assert` is used to ensure internal correctness, not to - enforce correct usage nor to indicate that some unexpected event occurred. - If an exception is desired in the latter cases, use a raise statement. For + precondition, such as may happen when validating function arguments. + +- Do not use `assert` statements in place of conditionals or validating + preconditions. They must not be critical to the application logic. A litmus + test would be that the `assert` could be removed without breaking the code. + `assert` conditionals are + [not guaranteed](https://docs.python.org/3/reference/simple_stmts.html#the-assert-statement) + to be evaluated. For [pytest](https://pytest.org) based tests, `assert` is + okay and expected to verify expectations. For example: @@ -435,6 +440,7 @@ Exceptions must follow certain conditions: if port is None: raise ConnectionError( f'Could not connect to service on port {minimum} or higher.') + # The code does not depend on the result of this assert. assert port >= minimum, ( f'Unexpected port {port} when minimum was {minimum}.') return port @@ -452,8 +458,10 @@ Exceptions must follow certain conditions: The new minimum port. """ assert minimum >= 1024, 'Minimum port must be at least 1024.' + # The following code depends on the previous assert. port = self._find_next_open_port(minimum) assert port is not None + # The type checking of the return statement relies on the assert. return port ``` @@ -642,20 +650,18 @@ Complicated comprehensions or generator expressions can be hard to read. #### 2.7.4 Decision -Okay to use for simple cases. Each portion must fit on one line: mapping -expression, `for` clause, filter expression. Multiple `for` clauses or filter -expressions are not permitted. Use loops instead when things get more -complicated. +Comprehensions are allowed, however multiple `for` clauses or filter expressions +are not permitted. Optimize for readability, not conciseness. ```python Yes: result = [mapping_expr for value in iterable if filter_expr] - result = [{'key': value} for value in iterable - if a_long_filter_expression(value)] - - result = [complicated_transform(x) - for x in iterable if predicate(x)] + result = [ + is_valid(metric={'key': value}) + for value in interesting_iterable + if a_longer_filter_expression(value) + ] descriptive_name = [ transform({'key': key, 'value': value}, color='black') @@ -665,36 +671,33 @@ Yes: result = [] for x in range(10): - for y in range(5): - if x * y > 10: - result.append((x, y)) + for y in range(5): + if x * y > 10: + result.append((x, y)) - return {x: complicated_transform(x) - for x in long_generator_function(parameter) - if x is not None} + return { + x: complicated_transform(x) + for x in long_generator_function(parameter) + if x is not None + } - squares_generator = (x**2 for x in range(10)) + return (x**2 for x in range(10)) unique_names = {user.name for user in users if user is not None} - - eat(jelly_bean for jelly_bean in jelly_beans - if jelly_bean.color == 'black') ``` ```python No: - result = [complicated_transform( - x, some_argument=x+1) - for x in iterable if predicate(x)] - result = [(x, y) for x in range(10) for y in range(5) if x * y > 10] - return ((x, y, z) - for x in range(5) - for y in range(5) - if x != y - for z in range(5) - if y != z) + return ( + (x, y, z) + for x in range(5) + for y in range(5) + if x != y + for z in range(5) + if y != z + ) ``` @@ -848,8 +851,8 @@ function may only contain an expression. #### 2.10.4 Decision -Okay to use them for one-liners. If the code inside the lambda function is -longer than 60-80 chars, it's probably better to define it as a regular +Lambdas are allowed. If the code inside the lambda function spans multiple lines +or is longer than 60-80 chars, it might be better to define it as a regular [nested function](#lexical-scoping). For common operations like multiplication, use the functions from the `operator` @@ -992,7 +995,7 @@ _FOO = flags.DEFINE_string(...) No: def foo(a, b=[]): ... -No: def foo(a, b=time.time()): # The time the module was loaded??? +No: def foo(a, b=time.time()): # Is `b` supposed to represent when this module was loaded? ... No: def foo(a, b=_FOO.value): # sys.argv has not yet been parsed... ... @@ -1071,7 +1074,7 @@ implement computations a subclass may ever want to override and extend. ### 2.14 True/False Evaluations -Use the "implicit" false if at all possible. +Use the "implicit" false if at all possible (with a few caveats). @@ -1798,6 +1801,150 @@ Trailing commas in sequences of items are recommended only when the closing container token `]`, `)`, or `}` does not appear on the same line as the final element, as well as for tuples with a single element. The presence of a trailing comma is also used as a hint to our Python code auto-formatter +[Black](https://github.com/psf/black) or [Pyink](https://github.com/google/pyink) +to direct it to auto-format the container of items to one item per line when the +`,` after the final element is present. + +```python +Yes: golomb3 = [0, 1, 3] + golomb4 = [ + 0, + 1, + 4, + 6, + ] +``` + +```python +No: golomb4 = [ + 0, + 1, + 4, + 6,] +``` + + + + + +### 3.5 Blank Lines + +Two blank lines between top-level definitions, be they function or class +definitions. One blank line between method definitions and between the docstring +of a `class` and the first method. No blank line following a `def` line. Use +single blank lines as you judge appropriate within functions or methods. + +Blank lines need not be anchored to the definition. For example, related +comments immediately preceding function, class, and method definitions can make +sense. Consider if your comment might be more useful as part of the docstring. + + + + + +### 3.6 Whitespace + +Follow standard typographic rules for the use of spaces around punctuation. + +No whitespace inside parentheses, brackets or braces. + +```python +Yes: spam(ham[1], {'eggs': 2}, []) +``` + +```python +No: spam( ham[ 1 ], { 'eggs': 2 }, [ ] ) +``` + +No whitespace before a comma, semicolon, or colon. Do use whitespace after a +comma, semicolon, or colon, except at the end of the line. + +```python +Yes: if x == 4: + print(x, y) + x, y = y, x +``` + +```python +No: if x == 4 : + print(x , y) + x , y = y , x +``` + +No whitespace before the open paren/bracket that starts an argument list, +indexing or slicing. + +```python +Yes: spam(1) +``` + +```python +No: spam (1) +``` + +```python +Yes: dict['key'] = list[index] +``` + +```python +No: dict ['key'] = list [index] +``` + +No trailing whitespace. + +Surround binary operators with a single space on either side for assignment +(`=`), comparisons (`==, <, >, !=, <>, <=, >=, in, not in, is, is not`), and +Booleans (`and, or, not`). Use your better judgment for the insertion of spaces +around arithmetic operators (`+`, `-`, `*`, `/`, `//`, `%`, `**`, `@`). + +```python +Yes: x == 1 +``` + +```python +No: x<1 +``` + +Never use spaces around `=` when passing keyword arguments or defining a default +parameter value, with one exception: +[when a type annotation is present](#typing-default-values), *do* use spaces +around the `=` for the default parameter value. + +```python +Yes: def complex(real, imag=0.0): return Magic(r=real, i=imag) +Yes: def complex(real, imag: float = 0.0): return Magic(r=real, i=imag) +``` + +```python +No: def complex(real, imag = 0.0): return Magic(r = real, i = imag) +No: def complex(real, imag: float=0.0): return Magic(r = real, i = imag) +``` + +Don't use spaces to vertically align tokens on consecutive lines, since it +becomes a maintenance burden (applies to `:`, `#`, `=`, etc.): + +```python +Yes: + foo = 1000 # comment + long_name = 2 # comment that should not be aligned + + dictionary = { + 'foo': 1, + 'long_name': 2, + } +``` + +```python +No: + foo = 1000 # comment + long_name = 2 # comment that should not be aligned + + dictionary = { + 'foo' : 1, + 'long_name': 2, + } +``` + @@ -1929,15 +2076,6 @@ should use the same style as the docstring for an attribute or a function argument (`"""The Bigtable path."""`, rather than `"""Returns the Bigtable path."""`). -A method that overrides a method from a base class may have a simple docstring -sending the reader to its overridden method's docstring, such as `"""See base -class."""`. The rationale is that there is no need to repeat in many places -documentation that is already present in the base method's docstring. However, -if the overriding method's behavior is substantially different from the -overridden method, or details need to be provided (e.g., documenting additional -side effects), a docstring with at least those differences is required on the -overriding method. - Certain aspects of a function should be documented in special sections, listed below. Each section begins with a heading line, which ends with a colon. All sections other than the heading should maintain a hanging indent of two or four @@ -1961,16 +2099,18 @@ aptly described using a one-line docstring. : Describe the semantics of the return value, including any type information that the type annotation does not provide. If the function only returns None, this section is not required. It may also be omitted if the docstring - starts with Returns or Yields (e.g. `"""Returns row from Bigtable as a tuple - of strings."""`) and the opening sentence is sufficient to describe the - return value. Do not imitate 'NumPy style' - ([example](http://numpy.org/doc/stable/reference/generated/numpy.linalg.qr.html)), - which frequently documents a tuple return value as if it were multiple + starts with "Return", "Returns", "Yield", or "Yields" (e.g. `"""Returns row + from Bigtable as a tuple of strings."""`) *and* the opening sentence is + sufficient to describe the return value. Do not imitate older 'NumPy style' + ([example](https://numpy.org/doc/1.24/reference/generated/numpy.linalg.qr.html)), + which frequently documented a tuple return value as if it were multiple return values with individual names (never mentioning the tuple). Instead, describe such a return value as: "Returns: A tuple (mat_a, mat_b), where mat_a is ..., and ...". The auxiliary names in the docstring need not necessarily correspond to any internal names used in the function body (as - those are not part of the API). + those are not part of the API). If the function uses `yield` (is a + generator), the `Yields:` section should document the object returned by + `next()`, instead of the generator object itself that the call evaluates to. [*Raises:*](#doc-function-raises) @@ -2057,6 +2197,47 @@ def fetch_smalltable_rows( """ ``` + + + +##### 3.8.3.1 Overridden Methods + +A method that overrides a method from a base class does not need a docstring if +it is explicitly decorated with +[`@override`](https://typing-extensions.readthedocs.io/en/latest/#override) +(from `typing_extensions` or `typing` modules), unless the overriding method's +behavior materially refines the base method's contract, or details need to be +provided (e.g., documenting additional side effects), in which case a docstring +with at least those differences is required on the overriding method. + +```python +from typing_extensions import override + +class Parent: + def do_something(self): + """Parent method, includes docstring.""" + +# Child class, method annotated with override. +class Child(Parent): + @override + def do_something(self): + pass +``` + +```python +# Child class, but without @override decorator, a docstring is required. +class Child(Parent): + def do_something(self): + pass + +# Docstring is trivial, @override is sufficient to indicate that docs can be +# found in the base class. +class Child(Parent): + @override + def do_something(self): + """See base class.""" +``` + @@ -2065,8 +2246,8 @@ def fetch_smalltable_rows( #### 3.8.4 Classes Classes should have a docstring below the class definition describing the class. -If your class has public attributes, they should be documented here in an -`Attributes` section and follow the same formatting as a +Public attributes, excluding [properties](#properties), should be documented +here in an `Attributes` section and follow the same formatting as a [function's `Args`](#doc-function-args) section. ```python @@ -2090,8 +2271,9 @@ class SampleClass: self.likes_spam = likes_spam self.eggs = 0 - def public_method(self): - """Performs operation blah.""" + @property + def butter_sticks(self) -> int: + """The number of butter sticks we have.""" ``` All class docstrings should start with a one-line summary that describes what @@ -2366,7 +2548,7 @@ messages shown to the user) should follow three guidelines: ```python Yes: if not 0 <= p <= 1: - raise ValueError(f'Not a probability: {p!r}') + raise ValueError(f'Not a probability: {p=}') try: os.rmdir(workdir) @@ -2378,7 +2560,7 @@ messages shown to the user) should follow three guidelines: ```python No: if p < 0 or p > 1: # PROBLEM: also false for float('nan')! - raise ValueError(f'Not a probability: {p!r}') + raise ValueError(f'Not a probability: {p=}') try: os.rmdir(workdir) @@ -2477,27 +2659,36 @@ documentation must explain clearly how resource lifetime is managed. Use `TODO` comments for code that is temporary, a short-term solution, or good-enough but not perfect. -A `TODO` comment begins with the word `TODO` in all caps, and a parenthesized -context identifier. Ideally a bug reference, sometimes a username. A bug -reference like `TODO(https://crbug.com/bug_id_number):` is -preferable, because bugs are tracked and have follow-up comments, whereas -individuals move around and may lose context over time. The `TODO` is followed by an explanation of -what there is to do. - +A `TODO` comment begins with the word `TODO` in all caps, a following colon, and +a link to a resource that contains the context, ideally a bug reference. A bug +reference is preferable because bugs are tracked and have follow-up comments. +Follow this piece of context with an explanatory string introduced with a hyphen +`-`. The purpose is to have a consistent `TODO` format that can be searched to find -out how to get more details. A `TODO` is not a commitment that the person -referenced will fix the problem. Thus when you create a `TODO` with a username, -it is almost always your *own* username that is given. +out how to get more details. + +```python +# TODO: crbug.com/192795 - Investigate cpufreq optimizations. +``` + +Old style, formerly recommended, but discouraged for use in new code: + ```python # TODO(crbug.com/192795): Investigate cpufreq optimizations. -# TODO(yourusername): File an issue and use a '*' for repetition. +# TODO(yourusername): Use a "\*" here for concatenation operator. +``` + +Avoid adding TODOs that refer to an individual or team as the context: + +```python +# TODO: @yourusername - File an issue and use a '*' for repetition. ``` If your `TODO` is of the form "At a future date do something" make sure that you either include a very specific date ("Fix by November 2009") or a very specific event ("Remove this code when all clients can handle XML responses.") that -future code maintainers will comprehend. +future code maintainers will comprehend. Issues are ideal for tracking this. @@ -2722,7 +2913,9 @@ Always use a `.py` filename extension. Never use dashes. class. - Prepending a single underscore (`_`) has some support for protecting module - variables and functions (linters will flag protected member access). + variables and functions (linters will flag protected member access). Note + that it is okay for unit tests to access protected constants from the + modules under test. - Prepending a double underscore (`__` aka "dunder") to an instance variable or method effectively makes the variable or method private to its class @@ -2930,13 +3123,20 @@ the function into smaller and more manageable pieces. * Familiarize yourself with [PEP-484](https://peps.python.org/pep-0484/). -* In methods, only annotate `self`, or `cls` if it is necessary for proper - type information. e.g., +* Annotating `self` or `cls` is generally not necessary. + [`Self`](https://docs.python.org/3/library/typing.html#typing.Self) can be + used if it is necessary for proper type information, e.g. ```python - @classmethod - def create(cls: Type[_T]) -> _T: - return cls() + from typing import Self + + class BaseClass: + @classmethod + def create(cls) -> Self: + ... + + def difference(self, other: Self) -> float: + ... ``` * Similarly, don't feel compelled to annotate the return value of `__init__` @@ -3327,15 +3527,16 @@ return type is the same as the argument type in the code above, use #### 3.19.12 Imports For Typing -For symbols from the `typing` and `collections.abc` modules used to support -static analysis and type checking, always import the symbol itself. This keeps -common annotations more concise and matches typing practices used around the -world. You are explicitly allowed to import multiple specific classes on one -line from the `typing` and `collections.abc` modules. Ex: +For symbols (including types, functions, and constants) from the `typing` or +`collections.abc` modules used to support static analysis and type checking, +always import the symbol itself. This keeps common annotations more concise and +matches typing practices used around the world. You are explicitly allowed to +import multiple specific symbols on one line from the `typing` and +`collections.abc` modules. For example: ```python from collections.abc import Mapping, Sequence -from typing import Any, Generic +from typing import Any, Generic, cast, TYPE_CHECKING ``` Given that this way of importing adds items to the local namespace, names in diff --git a/pylintrc b/pylintrc index 140a876..5c771e6 100644 --- a/pylintrc +++ b/pylintrc @@ -5,7 +5,7 @@ # Its canonical open-source location is: # https://google.github.io/styleguide/pylintrc -[MASTER] +[MAIN] # Files or directories to be skipped. They should be base names, not paths. ignore=third_party @@ -50,7 +50,8 @@ confidence= # --enable=similarities". If you want to run only the classes checker, but have # no Warning level messages displayed, use"--disable=all --enable=classes # --disable=W" -disable=abstract-method, +disable=R, + abstract-method, apply-builtin, arguments-differ, attribute-defined-outside-init, @@ -66,7 +67,6 @@ disable=abstract-method, coerce-method, delslice-method, div-method, - duplicate-code, eq-without-hash, execfile-builtin, file-builtin, @@ -80,7 +80,6 @@ disable=abstract-method, import-error, import-self, import-star-module-level, - inconsistent-return-statements, input-builtin, intern-builtin, invalid-str-codec, @@ -94,10 +93,6 @@ disable=abstract-method, next-method-called, next-method-defined, no-absolute-import, - no-else-break, - no-else-continue, - no-else-raise, - no-else-return, no-init, # added no-member, no-name-in-module, @@ -123,24 +118,12 @@ disable=abstract-method, standarderror-builtin, suppressed-message, sys-max-int, - too-few-public-methods, - too-many-ancestors, - too-many-arguments, - too-many-boolean-expressions, - too-many-branches, - too-many-instance-attributes, - too-many-locals, - too-many-nested-blocks, - too-many-public-methods, - too-many-return-statements, - too-many-statements, trailing-newlines, unichr-builtin, unicode-builtin, unnecessary-pass, unpacking-in-except, useless-else-on-loop, - useless-object-inheritance, useless-suppression, using-cmp-argument, wrong-import-order, @@ -225,7 +208,7 @@ no-docstring-rgx=(__.*__|main|test.*|.*test|.*Test)$ # Minimum line length for functions/classes that require docstrings, shorter # ones are exempt. -docstring-min-length=10 +docstring-min-length=12 [TYPECHECK] @@ -235,10 +218,6 @@ docstring-min-length=10 # produce valid context managers. contextmanager-decorators=contextlib.contextmanager,contextlib2.contextmanager -# Tells whether missing members accessed in mixin class should be ignored. A -# mixin class is detected if its name ends with "mixin" (case insensitive). -ignore-mixin-members=yes - # List of module names for which member attributes should not be checked # (useful for modules/projects where namespaces are manipulated during runtime # and thus existing member attributes cannot be deduced by static analysis. It @@ -261,7 +240,7 @@ generated-members= # Maximum number of characters on a single line. max-line-length=80 -# TODO(https://github.com/PyCQA/pylint/issues/3352): Direct pylint to exempt +# TODO(https://github.com/pylint-dev/pylint/issues/3352): Direct pylint to exempt # lines made too long by directives to pytype. # Regexp for a line that is allowed to be longer than the limit. @@ -418,12 +397,3 @@ valid-classmethod-first-arg=cls, # List of valid names for the first argument in a metaclass class method. valid-metaclass-classmethod-first-arg=mcs - - -[EXCEPTIONS] - -# Exceptions that will emit a warning when being caught. Defaults to -# "Exception" -overgeneral-exceptions=builtins.StandardError, - builtins.Exception, - builtins.BaseException diff --git a/styleguide.css b/styleguide.css index adba8f3..2e23d06 100644 --- a/styleguide.css +++ b/styleguide.css @@ -7,7 +7,13 @@ body { margin-left: 100px; } -h1, h2, h3, h4, h5, h6, .toc_title { +h1, +h2, +h3, +h4, +h5, +h6, +.toc_title { color: #06c; margin-top: 2em; margin-bottom: 1em; @@ -18,38 +24,47 @@ h1 { font-size: 18pt; } -h2, .toc_title { +h2, +.toc_title { font-weight: bold; font-size: 12pt; margin-left: -40px; } -h3, h4, h5, h6 { +h3, +h4, +h5, +h6 { font-size: 10pt; margin-left: -20px; } -.toc_category, .toc_stylepoint { +.toc_category, +.toc_stylepoint { font-size: 10pt; - padding-top: .3em; - padding-bottom: .3em; + padding-top: 0.3em; + padding-bottom: 0.3em; } table { border-collapse: collapse; } -td, th { +td, +th { border: 1px solid #ccc; padding: 2px 12px; font-size: 10pt; } -.toc td, .toc th { +.toc td, +.toc th { border-width: 1px 5px; } -code, samp, var { +code, +samp, +var { color: #060; } @@ -58,16 +73,9 @@ pre { display: block; color: #060; background-color: #f8fff8; - border-color: #f0fff0; - border-style: solid; - border-top-width: 1px; - border-bottom-width: 1px; - border-right-width: 1px; + border: 1px solid #f0fff0; border-left-width: 5px; - padding-left: 12px; - padding-right: 12px; - padding-top: 4px; - padding-bottom: 4px; + padding: 4px 12px; } pre.badcode { @@ -79,8 +87,7 @@ pre.badcode { .showhide_button { float: left; cursor: pointer; - border-width: 1px; - border-style: solid; + border: 1px solid; border-color: #ddd #aaa #aaa #ddd; padding: 0 3px 1px; margin: 0 4px 8px 0; @@ -93,9 +100,7 @@ pre.badcode { float: left; display: none; background-color: #f8f8ff; - border-color: #f0f0ff; - border-style: solid; - border-width: 1px; + border: 1px solid #f0f0ff; font-size: 75%; margin-top: 0; margin-left: -50px; @@ -118,7 +123,7 @@ hr { .stylepoint_section { display: block; margin-bottom: 1em; - color: #5588ff; + color: #58f; font-family: sans-serif; font-size: 90%; font-weight: bold; @@ -126,7 +131,7 @@ hr { } .stylepoint_subsection { - color: #667799; + color: #679; font-family: sans-serif; font-size: 90%; font-weight: bold; @@ -134,7 +139,7 @@ hr { } .stylepoint_subsubsection { - color: #667799; + color: #679; font-family: sans-serif; font-size: 80%; font-weight: bold; @@ -144,4 +149,3 @@ hr { .revision { text-align: right; } - diff --git a/tsguide.html b/tsguide.html index bda9817..7926ba2 100644 --- a/tsguide.html +++ b/tsguide.html @@ -11,231 +11,76 @@

    Google TypeScript Style Guide

    -

    TypeScript Style Guide

    -

    go/tsstyle

    -
    -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 +
    + +

    This guide is based on the internal Google TypeScript style guide, but it has +been slightly adjusted to remove Google-internal sections. Google's internal environment has different constraints on TypeScript than you might find outside -of Google. The advice here is specifically useful for people authoring code -they intend to import into Google, but otherwise may not apply in your external -environment. +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.

    There is no automatic deployment process for this version as it's pushed -on-demand by volunteers. -

    +on-demand by volunteers.

    + +
    + +

    Introduction

    + + + +

    Terminology notes

    This Style Guide uses RFC 2119 terminology when using the phrases must, must not, should, should not, -and may. All examples given are non-normative and serve only to illustrate the -normative language of the style guide.

    +and may. The terms prefer and avoid correspond to should and should +not, respectively. Imperative and declarative statements are prescriptive and +correspond to must.

    -

    Syntax

    +

    Guide notes

    -

    Identifiers

    +

    All examples given are non-normative 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 only stylish way to represent the code. +Optional formatting choices made in examples must not be enforced as rules.

    -

    Identifiers must use only ASCII letters, digits, underscores (for constants -and structured test method names), and the '\(' sign. Thus each valid identifier -name is matched by the regular expression `[\)\w]+`.

    + - - - - - - - +

    Source file basics

    - - - - - - - - - - - - - - - - - - -
    StyleCategory
    UpperCamelCase -class / interface / type / enum / decorator / type -parameters
    lowerCamelCase -variable / parameter / function / method / property / -module alias
    CONSTANT_CASE -global constant values, including enum values. See -Constants below.
    #identprivate identifiers are never used.
    + -

    Abbreviations

    + -

    -Treat abbreviations like acronyms in names as whole words, i.e. use -loadHttpUrl, not loadHTTPURL, unless required by a platform name (e.g. -XMLHttpRequest).

    +

    -

    Dollar sign

    +

    File encoding: UTF-8

    -

    Identifiers should not generally use $, except when aligning with naming -conventions for third party frameworks. See below for more on -using $ with Observable values.

    +

    Source files are encoded in UTF-8.

    -

    Type parameters

    +

    -

    Type parameters, like in Array<T>, may use a single upper case character -(T) or UpperCamelCase.

    +

    Whitespace characters

    -

    Test names

    +

    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.

    -

    Test method names in Closure testSuites and similar xUnit-style test -frameworks may be structured with _ separators, e.g. testX_whenY_doesZ().

    +

    Special escape sequences

    -

    _ prefix/suffix

    +

    For any character that has a special escape sequence (\', \", \\, \b, +\f, \n, \r, \t, \v), that sequence is used rather than the +corresponding numeric escape (e.g \x0a, \u000a, or \u{a}). Legacy octal +escapes are never used.

    -

    Identifiers must not use _ as a prefix or suffix.

    +

    Non-ASCII characters

    -

    This also means that _ must not be used as an identifier by itself (e.g. to -indicate a parameter is unused).

    - -
    -

    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:

    - -
    const [a, , b] = [1, 5, 10];  // a <- 1, b <- 10
    -
    -
    - -

    Imports

    - -

    Module namespace imports are lowerCamelCase while files are snake_case, -which means that imports correctly will not match in casing style, such as

    - -
    import * as fooBar from './foo_bar';
    -
    - -

    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:

    - - - -

    Constants

    - -

    Immutable: CONSTANT_CASE indicates that a value is intended to not be -changed, and may 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.

    - -
    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.
    -
    - -

    A constant can also be a static readonly property of a class.

    - -
    class Foo {
    -  private static readonly MY_SPECIAL_NUMBER = 5;
    -
    -  bar() {
    -    return 2 * Foo.MY_SPECIAL_NUMBER;
    -  }
    -}
    -
    - -

    Global: Only symbols declared on the module level, static fields of module -level classes, and values of module level enums, may use CONST_CASE. 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 must use lowerCamelCase.

    - -

    If a value is an arrow function that implements an interface, then it may be -declared lowerCamelCase.

    - -

    Aliases

    - -

    When creating a local-scope alias of an existing symbol, use the format of the -existing identifier. The local alias must match the existing naming and format -of the source. For variables use const for your local aliases, and for class -fields use the readonly attribute.

    - -
    -

    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 -access modifiers.

    -
    - -
    const {Foo} = SomeType;
    -const CAPACITY = 5;
    -
    -class Teapot {
    -  readonly BrewStateEnum = BrewStateEnum;
    -  readonly CAPACITY = CAPACITY;
    -}
    -
    - -

    Naming style

    - -

    TypeScript expresses information in types, so names should not be decorated -with information that is included in the type. (See also -Testing Blog - for more about what -not to include.)

    - -

    Some concrete examples of this rule:

    - - - -

    Descriptive names

    - -

    Names must 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.

    - - - -

    File encoding: UTF-8

    - -

    For non-ASCII characters, use the actual Unicode character (e.g. ). For -non-printable characters, the equivalent hex or Unicode escapes (e.g. \u221e) -can be used along with an explanatory comment.

    +

    For the remaining non-ASCII characters, use the actual Unicode character (e.g. +). For non-printable characters, the equivalent hex or Unicode escapes (e.g. +\u221e) can be used along with an explanatory comment.

    // Perfectly clear, even without a comment.
     const units = 'μs';
    @@ -251,220 +96,978 @@ const units = '\u03bcs'; // Greek letter mu, 's'
     const output = '\ufeff' + content;
     
    -

    No line continuations

    + -

    Do not use line continuations (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.

    + + + + + + + + + + +

    +

    + +

    Source file structure

    + +

    Files consist of the following, in order:

    + +
      +
    1. Copyright information, if present
    2. +
    3. JSDoc with @fileoverview, if present
    4. +
    5. Imports, if present
    6. +
    7. The file’s implementation
    8. +
    + +

    Exactly one blank line separates each section that is present.

    + + + + + + + +

    If license or copyright information is necessary in a file, add it in a JSDoc at +the top of the file.

    + +

    +

    + +

    @fileoverview JSDoc

    + +

    A file may have a top-level @fileoverview 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.

    + +

    Example:

    + +
    /**
    + * @fileoverview Description of file. Lorem ipsum dolor sit amet, consectetur
    + * adipiscing elit, sed do eiusmod tempor incididunt.
    + */
    +
    + + + +

    Imports

    + +

    There are four variants of import statements in ES6 and TypeScript:

    + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Import typeExampleUse for
    module[module_import] +import * as foo from +'...';TypeScript imports +
    named[destructuring_import] +import {SomeThing} +from '...';TypeScript imports +
    default + +import SomeThing +from '...'; +Only for other +external code that +requires them
    side-effect + + + +import '...'; + + + +Only to import +libraries for their +side-effects on load +(such as custom +elements)
    + +
    // 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';
    +
    + +
    + +

    Import paths

    + +

    TypeScript code must use paths to import other TypeScript code. Paths may be +relative, i.e. starting with . or .., + or rooted at the base directory, e.g. +root/path/to/file.

    + +

    Code should use relative imports (./foo) rather than absolute imports +path/to/foo when referring to files within the same (logical) project as this +allows to move the project around without introducing changes in these imports.

    + +

    Consider limiting the number of parent steps (../../../) as those can make +module and path structures hard to understand.

    + +
    import {Symbol1} from 'path/from/root';
    +import {Symbol2} from '../parent/file';
    +import {Symbol3} from './sibling';
    +
    + + + +

    + +

    Namespace versus named imports

    + +

    Both namespace and named imports can be used.

    + +

    Prefer named imports for symbols used frequently in a file or for symbols that +have clear names, for example Jasmine's describe and it. Named imports can +be aliased to clearer names as needed with as.

    + +

    Prefer namespace imports when using many different symbols from large APIs. A +namespace import, despite using the * character, is not comparable to a +wildcard 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 Model or Controller without the +need to declare aliases.

    + +
    // 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';
    +
    +let item: TableviewItem|undefined;
    +
    + +
    // Better: use the module for namespacing.
    +import * as tableview from './tableview';
    +
    +let item: tableview.Item|undefined;
    +
    + +
    import * as testing from './testing';
    +
    +// Bad: The module name does not improve readability.
    +testing.describe('foo', () => {
    +  testing.it('bar', () => {
    +    testing.expect(null).toBeNull();
    +    testing.expect(undefined).toBeUndefined();
    +  });
    +});
    +
    + +
    // Better: give local names for these common functions.
    +import {describe, it, expect} from './testing';
    +
    +describe('foo', () => {
    +  it('bar', () => {
    +    expect(null).toBeNull();
    +    expect(undefined).toBeUndefined();
    +  });
    +});
    +
    + +
    Special case: Apps JSPB protos
    + +

    Apps JSPB protos must use named imports, even when it leads to long import +lines.

    + +

    This rule exists to aid in build performance and dead code elimination since +often .proto files contain many messages 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.

    + +
    // 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) {...}
    +
    + + + +

    Renaming imports

    + +

    Code should fix name collisions by using a namespace import or renaming the +exports themselves. Code may rename imports (import {SomeThing as +SomeOtherThing}) if needed.

    + +

    Three examples where renaming can be helpful:

    + +
      +
    1. If it's necessary to avoid collisions with other imported symbols.
    2. +
    3. If the imported symbol name is generated.
    4. +
    5. If importing symbols whose names are unclear by themselves, renaming can +improve code clarity. For example, when using RxJS the from function might +be more readable when renamed to observableFrom.
    6. +
    + + + + + + + +

    Exports

    + +

    Use named exports in all code:

    + +
    // Use named exports:
    +export class Foo { ... }
    +
    + +

    Do not use default exports. This ensures that all imports follow a uniform +pattern.

    + +
    // Do not use default exports:
    +export default class Foo { ... } // BAD!
    +
    + +
    + +

    Why?

    + +

    Default exports provide no canonical name, which makes central maintenance +difficult with relatively little benefit to code owners, including potentially +decreased readability:

    + +
    import Foo from './bar';  // Legal.
    +import Bar from './bar';  // Also legal.
    +
    + +

    Named exports have the benefit of erroring when import statements try to import +something that hasn't been declared. In foo.ts:

    + +
    const foo = 'blah';
    +export default foo;
    +
    + +

    And in bar.ts:

    + +
    import {fizz} from './foo';
    +
    + +

    Results in error TS2614: Module '"./foo"' has no exported member 'fizz'. While +bar.ts:

    + +
    import fizz from './foo';
    +
    + +

    Results in fizz === foo, which is probably unexpected and difficult to debug.

    + +

    Additionally, default exports encourage people to put everything into one big +object to namespace it all together:

    + +
    export default class Foo {
    +  static SOME_CONSTANT = ...
    +  static someHelpfulFunction() { ... }
    +  ...
    +}
    +
    + +

    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 Foo) that can be +ambiguously used as both a type and a value in other files.

    + +

    Instead, prefer use of file scope for namespacing, as well as named exports:

    + +
    export const SOME_CONSTANT = ...
    +export function someHelpfulFunction()
    +export class Foo {
    +  // only class stuff here
    +}
    +
    + +
    + +

    Export visibility

    + +

    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.

    + + + +

    Mutable exports

    + + + +

    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 export let is not allowed.

    + +
    + +
    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 */);
    +
    + +
    + +

    If one needs to support externally accessible and mutable bindings, they +should instead use explicit getter functions.

    + +
    let foo = 3;
    +window.setTimeout(() => {
    +  foo = 4;
    +}, 1000 /* ms */);
    +// Use an explicit getter to access the mutable export.
    +export function getFoo() { return foo; };
    +
    + +

    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.

    + +
    function pickApi() {
    +  if (useOtherApi()) return OtherApi;
    +  return RegularApi;
    +}
    +export const SomeApi = pickApi();
    +
    + +

    + +

    Container classes

    + +

    Do not create container classes with static methods or properties for the sake +of namespacing.

    + +
    export class Container {
    +  static FOO = 1;
    +  static bar() { return 1; }
    +}
    +
    + +

    Instead, export individual constants and functions:

    + +
    export const FOO = 1;
    +export function bar() { return 1; }
    +
    + + + +

    Import and export type

    + +

    Import type

    + +

    You may use import type {...} when you use the imported symbol only as a type. +Use regular imports for values:

    + +
    import type {Foo} from './foo';
    +import {Bar} from './foo';
    +
    +import {type Foo, Bar} from './foo';
    +
    + +
    + +

    Why?

    + +

    The TypeScript compiler automatically handles the distinction and does not +insert runtime loads for type references. So why annotate type imports?

    + +

    The TypeScript compiler can run in 2 modes:

    + + + +

    Note: If you need to force a runtime load for side effects, use import '...';. +See

    + +
    + +

    Export type

    + +

    Use export type when re-exporting a type, e.g.:

    + +
    export type {AnInterface} from './foo';
    +
    + +
    + +

    Why?

    + +

    export type is useful to allow type re-exports in file-by-file transpilation. +See +isolatedModules docs.

    + +

    export type 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. +UserService and AjaxUserService. This is less error prone and also better +communicates intent.

    + +
    + +

    + +

    Use modules not namespaces

    + +

    TypeScript supports two methods to organize code: namespaces and modules, +but namespaces are disallowed. That +is, your code must refer to code in other files using imports and exports of +the form import {foo} from 'bar';

    + +

    Your code must not use the namespace Foo { ... } construct. namespaces +may only be used when required to interface with external, third party code. +To semantically namespace your code, use separate files.

    + + + +

    Code must not use require (as in import x = require('...');) for imports. +Use ES6 module syntax.

    + +
    // 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');
    +
    + + + +
    +

    NB: TypeScript namespaces used to be called internal modules and used to use +the module keyword in the form module Foo { ... }. Don't use that either. +Always use ES6 imports.

    +
    + +

    + +

    Language features

    + +

    This section delineates which features may or may not be used, and any +additional constraints on their use.

    + +

    Language features which are not discussed in this style guide may be used with +no recommendations of their usage.

    + +

    + +

    Local variable declarations

    + +

    +

    + +

    Use const and let

    + +

    Always use const or let to declare variables. Use const by default, unless +a variable needs to be reassigned. Never use var.

    + +
    const foo = otherValue;  // Use if "foo" never changes.
    +let bar = someValue;     // Use if "bar" is ever assigned into later on.
    +
    + +

    const and let are block scoped, like variables in most other languages. +var in JavaScript is function scoped, which can cause difficult to understand +bugs. Don't use it.

    + +
    var foo = someValue;     // Don't use - var scoping is complex and causes bugs.
    +
    + +

    Variables must not be used before their declaration.

    + +

    + +

    One variable per declaration

    + +

    Every local variable declaration declares only one variable: declarations such +as let a = 1, b = 2; are not used.

    + +

    + +

    Array literals

    + +

    + +

    Do not use the Array constructor

    + +

    Do not use the Array() constructor, with or without new. It has confusing +and contradictory usage:

    + + + +
    const a = new Array(2); // [undefined, undefined]
    +const b = new Array(2, 3); // [2, 3];
    +
    + + + +

    Instead, always use bracket notation to initialize arrays, or from to +initialize an Array with a certain size:

    + +
    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);
    +
    + + + +

    + +

    Do not define properties on arrays

    + +

    Do not define or use non-numeric properties on an array (other than length). +Use a Map (or Object) instead.

    + +

    + +

    Using spread syntax

    + +

    Using spread syntax [...foo]; is a convenient shorthand for shallow-copying or +concatenating iterables.

    + +
    const foo = [
    +  1,
    +];
    +
    +const foo2 = [
    +  ...foo,
    +  6,
    +  7,
    +];
    +
    +const foo3 = [
    +  5,
    +  ...foo,
    +];
    +
    +foo2[1] === 6;
    +foo3[1] === 1;
    +
    + +

    When using spread syntax, the value being spread must match what is being +created. When creating an array, only spread iterables. Primitives (including +null and undefined) must not be spread.

    + +
    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};
    +
    + +
    const foo = shouldUseFoo ? [7] : [];
    +const bar = [5, ...foo];
    +const fooStrings = ['a', 'b', 'c'];
    +const ids = [...fooStrings, 'd', 'e'];
    +
    + +

    + +

    Array destructuring

    + +

    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 rest element may be included (with no space between the +... and the variable name). Elements should be omitted if they are unused.

    + +
    const [a, b, c, ...rest] = generateResults();
    +let [, b,, d] = someArray;
    +
    + +

    Destructuring may also be used for function parameters. Always specify [] as +the default value if a destructured array parameter is optional, and provide +default values on the left hand side:

    + +
    function destructured([a = 4, b = 2] = []) { … }
    +

    Disallowed:

    -
    const LONG_STRING = 'This is a very long string that far exceeds the 80 \
    -    column limit. It unfortunately contains long stretches of spaces due \
    -    to how the continued lines are indented.';
    +
    function badDestructuring([a, b] = [4, 2]) { … }
     
    -

    Instead, write

    +

    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.

    -
    const LONG_STRING = 'This is a very long string that far exceeds the 80 ' +
    -    'column limit. It does not contain long stretches of spaces since ' +
    -    'the concatenated strings are cleaner.';
    -
    + -

    Comments & Documentation

    +

    -

    JSDoc vs comments

    +

    Object literals

    -

    There are two types of comments, JSDoc (/** ... */) and non-JSDoc ordinary -comments (// ... or /* ... */).

    +

    -
      -
    • Use /** JSDoc */ comments for documentation, i.e. comments a user of the -code should read.
    • -
    • Use // line comments for implementation comments, i.e. comments that only -concern the implementation of the code itself.
    • -
    +

    Do not use the Object constructor

    -

    JSDoc comments are understood by tools (such as editors and documentation -generators), while ordinary comments are only for other humans.

    +

    The Object constructor is disallowed. Use an object literal ({} or {a: 0, +b: 1, c: 2}) instead.

    -

    JSDoc rules follow the JavaScript style

    +

    Iterating objects

    -

    In general, follow the +

    Iterating objects with for (... in ...) is error prone. It will include +enumerable properties from the prototype chain.

    -JavaScript style guide's rules for JSDoc, -sections 7.1 - 7.5. The remainder of this section describes exceptions to those -rules.

    +

    Do not use unfiltered for (... in ...) statements:

    -

    Document all top-level exports of modules

    - -

    Use /** JSDoc */ comments to communicate information to the users of your -code. Avoid merely restating the property or parameter name. You should also -document all properties and methods (exported/public or not) whose purpose is -not immediately obvious from their name, as judged by your reviewer.

    - -

    Exception: Symbols that are only exported to be consumed by tooling, such as -@NgModule classes, do not require comments.

    - -

    Omit comments that are redundant with TypeScript

    - -

    - -

    For example, do not declare types in @param or @return blocks, do not write -@implements, @enum, @private, @override etc. on code that uses the -implements, enum, private, override etc. keywords.

    - -

    Make comments that actually add information

    - -

    For non-exported symbols, sometimes the name and type of the function or -parameter is enough. Code will usually benefit from more documentation than -just variable names though!

    - -
      -
    • Avoid comments that just restate the parameter name and type, e.g.

      - -
      /** @param fooBarService The Bar service for the Foo application. */
      -
    • -
    • Because of this rule, @param and @return lines are only required when -they add information, and may otherwise be omitted.

      - -
      /**
      - * POSTs the request to start coffee brewing.
      - * @param amountLitres The amount to brew. Must fit the pot size!
      - */
      -brew(amountLitres: number, logger: Logger) {
      -  // ...
      -}
      -
    • -
    - -

    Parameter property comments

    - -

    A -parameter property -is a constructor parameter that is prefixed by one of the modifiers private, -protected, public, or readonly. A parameter property declares both a -parameter and an instance property, and implicitly assigns into it. For example, -constructor(private readonly foo: Foo), declares that the constructor takes a -parameter foo, but also declares a private readonly property foo, and -assigns the parameter into that property before executing the remainder of the -constructor.

    - -

    To document these fields, use JSDoc's @param annotation. Editors display the -description on constructor calls and property accesses.

    - -
    /** 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[]) {}
    +
    for (const x in someObj) {
    +  // x could come from some parent prototype!
     }
     
    -
    /** This class demonstrates how ordinary fields are documented. */
    -class OrdinaryClass {
    -  /** The bean that will be used in the next call to brew(). */
    -  nextBean: CoffeeBean;
    +

    Either filter values explicitly with an if statement, or use for (... of +Object.keys(...)).

    - constructor(initialBean: CoffeeBean) { - this.nextBean = initialBean; - } +
    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
     }
     
    -

    Comments when calling a function

    +

    -

    If needed, document parameters at call sites inline using block comments. Also -consider named parameters using object literals and destructuring. The exact -formatting and placement of the comment is not prescribed.

    +

    Using spread syntax

    -
    // Inline block comments for parameters that'd be hard to understand:
    -new Percolator().brew(/* amountLitres= */ 5);
    -// Also consider using named arguments and destructuring parameters (in brew's declaration):
    -new Percolator().brew({amountLitres: 5});
    -
    +

    Using spread syntax {...bar} 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.

    -
    /** An ancient {@link CoffeeBrewer} */
    -export class Percolator implements CoffeeBrewer {
    -  /**
    -   * Brews coffee.
    -   * @param amountLitres The amount to brew. Must fit the pot size!
    -   */
    -  brew(amountLitres: number) {
    -    // This implementation creates terrible coffee, but whatever.
    -    // TODO(b/12345): Improve percolator brewing.
    -  }
    +
    const foo = {
    +  num: 1,
    +};
    +
    +const foo2 = {
    +  ...foo,
    +  num: 5,
    +};
    +
    +const foo3 = {
    +  num: 5,
    +  ...foo,
     }
    +
    +foo2.num === 5;
    +foo3.num === 1;
    +
     
    -

    Place documentation prior to decorators

    +

    When using spread syntax, the value being spread must match what is being +created. That is, when creating an object, only objects may be spread; arrays +and primitives (including null and undefined) must not 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).

    -

    When a class, method, or property have both decorators like @Component and -JsDoc, please make sure to write the JsDoc before the decorator.

    +
    const foo = {num: 7};
    +const bar = {num: 5, ...(shouldUseFoo && foo)}; // might be undefined
     
    -
      -
    • Do not write JsDoc between the Decorator and the decorated statement.

      +// Creates {0: 'a', 1: 'b', 2: 'c'} but has no length +const fooStrings = ['a', 'b', 'c']; +const ids = {...fooStrings}; +
    -
    @Component({
    -  selector: 'foo',
    -  template: 'bar',
    -})
    -/** Component that prints "bar". */
    -export class FooComponent {}
    -
    -
  • Write the JsDoc block before the Decorator.

    +
    const foo = shouldUseFoo ? {num: 7} : {};
    +const bar = {num: 5, ...foo};
    +
    -
    /** Component that prints "bar". */
    -@Component({
    -  selector: 'foo',
    -  template: 'bar',
    -})
    -export class FooComponent {}
    -
  • - +

    -

    Language Rules

    +

    Computed property names

    -

    TypeScript language features which are not discussed in this style guide may -be used with no recommendations of their usage.

    +

    Computed property names (e.g. {['key' + foo()]: 42}) 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 +symbol +(e.g. [Symbol.iterator]).

    -

    Visibility

    + -

    Restricting visibility of properties, methods, and entire types helps with -keeping code decoupled.

    +

    -
      -
    • Limit symbol visibility as much as possible.
    • -
    • 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.
    • -
    • TypeScript symbols are public by default. Never use the public modifier -except when declaring non-readonly public parameter properties (in -constructors).
    • -
    +

    Object destructuring

    -
    class Foo {
    -  public bar = new Bar();  // BAD: public modifier not needed
    +

    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.

    - constructor(public readonly baz: Baz) {} // BAD: readonly implies it's a property which defaults to public +

    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 ({str = 'some default'} = {}, rather than +{str} = {str: 'some default'}), and if a +destructured object is itself optional, it must default to {}.

    + +

    Example:

    + +
    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 = {}) {}
     
    +

    Disallowed:

    + +
    function nestedTooDeeply({x: {num, str}}: {x: Options}) {}
    +function nontrivialDefault({num, str}: Options = {num: 42, str: 'default'}) {}
    +
    + + + + + +

    +

    + +

    Classes

    + +

    Class declarations

    + +

    Class declarations must not be terminated with semicolons:

    +
    class Foo {
    -  bar = new Bar();  // GOOD: public modifier not needed
    -
    -  constructor(public baz: Baz) {}  // public modifier allowed
     }
     
    -

    See also export visibility below.

    +
    class Foo {
    +}; // Unnecessary semicolon
    +
    -

    Constructors

    +

    In contrast, statements that contain class expressions must be terminated with +a semicolon:

    + +
    export const Baz = class extends Bar {
    +  method(): number {
    +    return this.x;
    +  }
    +}; // Semicolon here as this is a statement, not a declaration
    +
    + +
    exports const Baz = class extends Bar {
    +  method(): number {
    +    return this.x;
    +  }
    +}
    +
    + +

    It is neither encouraged nor discouraged to have blank lines separating class +declaration braces from other class content:

    + +
    // 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;
    +  }
    +
    +}
    +
    + +

    Class method declarations

    + +

    Class method declarations must not use a semicolon to separate individual +method declarations:

    + +
    class Foo {
    +  doThing() {
    +    console.log("A");
    +  }
    +}
    +
    + +
    class Foo {
    +  doThing() {
    +    console.log("A");
    +  }; // <-- unnecessary
    +}
    +
    + +

    Method declarations should be separated from surrounding code by a single blank +line:

    + +
    class Foo {
    +  doThing() {
    +    console.log("A");
    +  }
    +
    +  getOtherThing(): number {
    +    return 4;
    +  }
    +}
    +
    + +
    class Foo {
    +  doThing() {
    +    console.log("A");
    +  }
    +  getOtherThing(): number {
    +    return 4;
    +  }
    +}
    +
    + +

    + +
    Overriding toString
    + +

    The toString method may be overridden, but must always succeed and never have +visible side effects.

    + +

    Tip: Beware, in particular, of calling other methods from toString, since +exceptional conditions could lead to infinite loops.

    + +

    + +

    Static methods

    + +
    Avoid private static methods
    + +

    Where it does not interfere with readability, prefer module-local functions over +private static methods.

    + +
    Do not rely on dynamic dispatch
    + +

    Code should not rely on dynamic dispatch of static +methods. Static methods should only be called on the base class +itself (which defines it directly). Static methods should not be called on +variables containing a dynamic instance that may be either the constructor or a +subclass constructor (and must be defined with @nocollapse if this is done), +and must not be called directly on a subclass that doesn’t define the method +itself.

    + +

    Disallowed:

    + +
    // 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();
    +
    +// Disallowed: don't access this in static methods.
    +class MyClass {
    +  static foo() {
    +    return this.staticField;
    +  }
    +}
    +MyClass.staticField = 1;
    +
    + +
    Avoid static this references
    + +

    Code must not use this in a static context.

    + +

    JavaScript allows accessing static fields through this. Different from other +languages, static fields are also inherited.

    + +
    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
    +}
    +
    + +
    + +

    Why?

    + +

    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.

    + +

    This code also encourages an anti-pattern of having substantial static state, +which causes problems with testability.

    + +
    + +

    +

    + +

    Constructors

    Constructor calls must use parentheses, even when no arguments are passed:

    @@ -474,6 +1077,13 @@ constructors).
    const x = new Foo();
     
    +

    Omitting parentheses can lead to subtle mistakes. These two lines are not +equivalent:

    + +
    new Foo().Bar();
    +new Foo.Bar();
    +
    +

    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 @@ -508,9 +1118,32 @@ class NoInstantiation { }

    -

    Class Members

    +

    The constructor should be separated from surrounding code both above and below +by a single blank line:

    -

    No #private fields

    +
    class Foo {
    +  myField = 10;
    +
    +  constructor(private readonly ctorParam) {}
    +
    +  doThing() {
    +    console.log(ctorParam.getThing() + myField);
    +  }
    +}
    +
    + +
    class Foo {
    +  myField = 10;
    +  constructor(private readonly ctorParam) {}
    +  doThing() {
    +    console.log(ctorParam.getThing() + myField);
    +  }
    +}
    +
    + +

    Class members

    + +
    No #private fields

    Do not use private fields (also known as private identifiers):

    @@ -526,8 +1159,9 @@ class NoInstantiation { }
    -
    -Why? +
    + +

    Why?

    Private identifiers cause substantial emit size and performance regressions when down-leveled by TypeScript, and are unsupported @@ -535,14 +1169,14 @@ 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.

    -
    +
    -

    Use readonly

    +
    Use readonly

    Mark properties that are never reassigned outside of the constructor with the readonly modifier (these need not be deeply immutable).

    -

    Parameter properties

    +
    Parameter properties

    Rather than plumbing an obvious initializer through to a class member, use a TypeScript @@ -565,13 +1199,14 @@ TypeScript

    If the parameter property needs documentation, use an @param JSDoc tag.

    -

    Field initializers

    +
    Field initializers

    If a class member is not a parameter, initialize it where it's declared, which sometimes lets you drop the constructor entirely.

    class Foo {
       private readonly userList: string[];
    +
       constructor() {
         this.userList = [];
       }
    @@ -583,7 +1218,12 @@ sometimes lets you drop the constructor entirely.

    }
    -

    Properties used outside of class lexical scope

    +

    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' shape. Optional fields that may be filled in later should be +explicitly initialized to undefined to prevent later shape changes.

    + +
    Properties used outside of class lexical scope

    Properties used from outside the lexical scope of their containing class, such as an Angular component's properties used from a template, must not use @@ -595,12 +1235,11 @@ Angular and AngularJS template properties should use protected, but should use public.

    TypeScript code must not use obj['foo'] to bypass the visibility of a -property. See -testing and private visibility -if you want to access protected fields from a test.

    +property.

    -
    -Why? +
    + +

    Why?

    When a property is private, you are declaring to both automated systems and humans that the property accesses are scoped to the methods of the declaring @@ -609,17 +1248,23 @@ flag a private property that appears to be unused, even if some other file manages to bypass the visibility restriction.

    Though it might appear that obj['foo'] can bypass visibility in the TypeScript -compiler, this pattern can be broken by rearranging the build rules, -and also violates optimization compatibility. -

    +compiler, this pattern can be broken by rearranging the build rules, and also +violates optimization compatibility.

    -

    Getters and Setters (Accessors)

    +
    -

    Getters and setters for class members may be used. The getter method must be -a pure function (i.e., result is -consistent and has no side effects). They are also useful as a means of -restricting the visibility of internal or verbose implementation details (shown -below).

    +

    + +
    Getters and setters
    + + + +

    Getters and setters, also known as accessors, for class members may be used. +The getter method must be a +pure function (i.e., result is +consistent and has no side effects: getters must not change observable state). +They are also useful as a means of restricting the visibility of internal or +verbose implementation details (shown below).

    class Foo {
       constructor(private readonly someService: SomeService) {}
    @@ -634,6 +1279,14 @@ below).

    }
    +
    class Foo {
    +  nextId = 0;
    +  get next() {
    +    return this.nextId++; // Bad: getter changes observable state
    +  }
    +}
    +
    +

    If an accessor is used to hide a class property, the hidden property may be prefixed or suffixed with any whole word, like internal or wrapped. When using these private properties, access the value through the accessor whenever @@ -667,583 +1320,112 @@ just defining a getter with no setter).

    }
    -

    Static this references

    +

    Getters and setters must not be defined using Object.defineProperty, since +this interferes with property renaming.

    -

    Code must not use this in a static context.

    +

    -

    JavaScript allows accessing static fields through this. Different from other -languages, static fields are also inherited.

    +
    Computed properties
    -
    class ShoeStore {
    -  static storage: Storage = ...;
    +

    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 +rationale for not mixing key types. A +[Symbol.iterator] method should be defined for any classes that are logically +iterable. Beyond this, Symbol should be used sparingly.

    - static isAvailable(s: Shoe) { - // Bad: do not use `this` in a static method. - return this.storage.has(s.id); - } -} +

    Tip: be careful of using any other built-in symbols (e.g. +Symbol.isConcatSpreadable) as they are not polyfilled by the compiler and will +therefore not work in older browsers.

    -class EmptyShoeStore extends ShoeStore { - static storage: Storage = EMPTY_STORE; // overrides storage from ShoeStore + + +

    Visibility

    + +

    Restricting visibility of properties, methods, and entire types helps with +keeping code decoupled.

    + +
      +
    • Limit symbol visibility as much as possible.
    • +
    • 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.
    • +
    • TypeScript symbols are public by default. Never use the public modifier +except when declaring non-readonly public parameter properties (in +constructors).
    • +
    + +
    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
     }
     
    -
    -Why? +
    class Foo {
    +  bar = new Bar();  // GOOD: public modifier not needed
     
    -

    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.

    - -

    This code also encourages an anti-pattern of having substantial static state, -which causes problems with testability.

    - -
    - -

    Primitive Types & Wrapper Classes

    - -

    TypeScript code must not instantiate the wrapper classes for the primitive -types String, Boolean, and Number. Wrapper classes have surprising -behavior, such as new Boolean(false) evaluating to true.

    - -
    const s = new String('hello');
    -const b = new Boolean(false);
    -const n = new Number(5);
    -
    - -
    const s = 'hello';
    -const b = false;
    -const n = 5;
    -
    - -

    Array constructor

    - -

    TypeScript code must not use the Array() constructor, with or without new. -It has confusing and contradictory usage:

    - - - -
    const a = new Array(2); // [undefined, undefined]
    -const b = new Array(2, 3); // [2, 3];
    -
    - - - -

    Instead, always use bracket notation to initialize arrays, or from to -initialize an Array with a certain size:

    - -
    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);
    -
    - -

    Type coercion

    - -

    TypeScript code may use the String() and Boolean() (note: no new!) -functions, string template literals, or !! to coerce types.

    - -
    const bool = Boolean(false);
    -const str = String(aNumber);
    -const bool2 = !!str;
    -const str2 = `result: ${bool2}`;
    -
    - -

    Values of enum types (including unions of enum types and other types) must not -be converted to booleans with Boolean() or !!, and must instead be compared -explicitly with comparison operators.

    - -
    enum SupportLevel {
    -  NONE,
    -  BASIC,
    -  ADVANCED,
    -}
    -
    -const level: SupportLevel = ...;
    -let enabled = Boolean(level);
    -
    -const maybeLevel: SupportLevel|undefined = ...;
    -enabled = !!maybeLevel;
    -
    - -
    enum SupportLevel {
    -  NONE,
    -  BASIC,
    -  ADVANCED,
    -}
    -
    -const level: SupportLevel = ...;
    -let enabled = level !== SupportLevel.NONE;
    -
    -const maybeLevel: SupportLevel|undefined = ...;
    -enabled = level !== undefined && level !== SupportLevel.NONE;
    -
    - -
    -Why? - -

    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 does matter are undesirable because they will be -surprising. Such is the case with conversion of enums to booleans; in -particular, by default, the first declared enum value is falsy (because it is 0) -while the others are truthy, which is likely to be unexpected. Readers of code -that uses an enum value may not even know whether it's the first declared value -or not. -

    - -

    Using string concatenation to cast to string is discouraged, as we check that -operands to the plus operator are of matching types.

    - -

    Code must use Number() to parse numeric values, and must check its return -for NaN values explicitly, unless failing to parse is impossible from context.

    - -

    Note: Number(''), Number(' '), and Number('\t') would return 0 instead -of NaN. Number('Infinity') and Number('-Infinity') would return Infinity -and -Infinity respectively. Additionally, exponential notation such as -Number('1e+309') and Number('-1e+309') can overflow into Infinity. These -cases may require special handling.

    - -
    const aNumber = Number('123');
    -if (!isFinite(aNumber)) throw new Error(...);
    -
    - -

    Code must not use unary plus (+) 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.

    - -
    const x = +y;
    -
    - -

    Code also must not use parseInt or parseFloat to parse numbers, except for -non-base-10 strings (see below). Both of those functions ignore trailing -characters in the string, which can shadow error conditions (e.g. parsing 12 -dwarves as 12).

    - -
    const n = parseInt(someString, 10);  // Error prone,
    -const f = parseFloat(someString);    // regardless of passing a radix.
    -
    - -

    Code that requires parsing with a radix must check that its input contains -only appropriate digits for that radix before calling into parseInt;

    - -
    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
    -
    - -

    Use Number() followed by Math.floor or Math.trunc (where available) to -parse integer numbers:

    - -
    let f = Number(someString);
    -if (isNaN(f)) handleError();
    -f = Math.floor(f);
    -
    - -

    Implicit coercion

    - -

    Do not use explicit boolean coercions in conditional clauses that have implicit -boolean coercion. Those are the conditions in an if, for and while -statements.

    - -
    const foo: MyInterface|null = ...;
    -if (!!foo) {...}
    -while (!!foo) {...}
    -
    - -
    const foo: MyInterface|null = ...;
    -if (foo) {...}
    -while (foo) {...}
    -
    - -

    As with explicit conversions, values of enum types (including -unions of enum types and other types) must not be implicitly coerced to -booleans, and must instead be compared explicitly with comparison operators.

    - -
    enum SupportLevel {
    -  NONE,
    -  BASIC,
    -  ADVANCED,
    -}
    -
    -const level: SupportLevel = ...;
    -if (level) {...}
    -
    -const maybeLevel: SupportLevel|undefined = ...;
    -if (level) {...}
    -
    - -
    enum SupportLevel {
    -  NONE,
    -  BASIC,
    -  ADVANCED,
    -}
    -
    -const level: SupportLevel = ...;
    -if (level !== SupportLevel.NONE) {...}
    -
    -const maybeLevel: SupportLevel|undefined = ...;
    -if (level !== undefined && level !== SupportLevel.NONE) {...}
    -
    - -

    Other types of values may be either implicitly coerced to booleans or compared -explicitly with comparison operators:

    - -
    // Explicitly comparing > 0 is OK:
    -if (arr.length > 0) {...}
    -// so is relying on boolean coercion:
    -if (arr.length) {...}
    -
    - -

    Variables

    - -

    Always use const or let to declare variables. Use const by default, unless -a variable needs to be reassigned. Never use var.

    - -
    const foo = otherValue;  // Use if "foo" never changes.
    -let bar = someValue;     // Use if "bar" is ever assigned into later on.
    -
    - -

    const and let are block scoped, like variables in most other languages. -var in JavaScript is function scoped, which can cause difficult to understand -bugs. Don't use it.

    - -
    var foo = someValue;     // Don't use - var scoping is complex and causes bugs.
    -
    - -

    Variables must not be used before their declaration.

    - -

    Exceptions

    - -

    Instantiate Errors using new

    - -

    Always use new Error() when instantiating exceptions, instead of just calling -Error(). Both forms create a new Error instance, but using new is more -consistent with how other objects are instantiated.

    - -
    throw new Error('Foo is not a valid bar.');
    -
    - -
    throw Error('Foo is not a valid bar.');
    -
    - -

    Only throw Errors

    - -

    JavaScript (and thus TypeScript) allow throwing arbitrary values. However if the -thrown value is not an Error, it does not get a stack trace filled in, making -debugging hard.

    - -
    // bad: does not get a stack trace.
    -throw 'oh noes!';
    -
    - -

    Instead, only throw (subclasses of) Error:

    - -
    // Throw only Errors
    -throw new Error('oh noes!');
    -// ... or subtypes of Error.
    -class MyError extends Error {}
    -throw new MyError('my oh noes!');
    -
    - -

    Catching & rethrowing

    - -

    When catching errors, code should assume that all thrown errors are instances -of Error.

    - -
    - -
    try {
    -  doSomething();
    -} catch (e: unknown) {
    -  // All thrown errors must be Error subtypes. Do not handle
    -  // other possible values unless you know they are thrown.
    -  assert(e, isInstanceOf(Error));
    -  displayError(e.message);
    -  // or rethrow:
    -  throw e;
    +  constructor(public baz: Baz) {}  // public modifier allowed
     }
     
    -
    +

    See also export visibility.

    -

    Exception handlers must not defensively handle non-Error types unless the -called API is conclusively known to throw non-Errors in violation of the above -rule. In that case, a comment should be included to specifically identify where -the non-Errors originate.

    +

    Disallowed class patterns

    -
    try {
    -  badApiThrowingStrings();
    -} catch (e: unknown) {
    -  // Note: bad API throws strings instead of errors.
    -  if (typeof e === 'string') { ... }
    +

    + +
    Do not manipulate prototypes directly
    + +

    The class keyword allows clearer and more readable class definitions than +defining prototype properties. Ordinary implementation code has no business +manipulating these objects. Mixins and modifying the prototypes of builtin +objects are explicitly forbidden.

    + +

    Exception: Framework code (such as Polymer, or Angular) may need to use prototypes, and should not resort +to even-worse workarounds to avoid doing so.

    + + + + + +

    + +

    Functions

    + +

    Terminology

    + +

    There are many different types of functions, with subtle distinctions between +them. This guide uses the following terminology, which aligns with +MDN:

    + +
      +
    • function declaration: a declaration (i.e. not an expression) using the +function keyword
    • +
    • function expression: an expression, typically used in an assignment or +passed as a parameter, using the function keyword
    • +
    • arrow function: an expression using the => syntax
    • +
    • block body: right hand side of an arrow function with braces
    • +
    • concise body: right hand side of an arrow function without braces
    • +
    + +

    Methods and classes/constructors are not covered in this section.

    + +

    Prefer function declarations for named functions

    + +

    Prefer function declarations over arrow functions or function expressions when +defining named functions.

    + +
    function foo() {
    +  return 42;
     }
     
    -
    -Why? - -

    Avoid overly defensive programming. Repeating the same -defenses against a problem that will not exist in most code leads to -boiler-plate code that is not useful. -

    - -

    Iterating objects

    - -

    Iterating objects with for (... in ...) is error prone. It will include -enumerable properties from the prototype chain.

    - -

    Do not use unfiltered for (... in ...) statements:

    - -
    for (const x in someObj) {
    -  // x could come from some parent prototype!
    -}
    +
    const foo = () => 42;
     
    -

    Either filter values explicitly with an if statement, or use for (... of -Object.keys(...)).

    - -
    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
    -}
    -
    - -

    Iterating containers

    - -

    Do not use for (... in ...) to iterate over arrays. It will counterintuitively -give the array's indices (as strings!), not values:

    - -
    for (const x in someArray) {
    -  // x is the index!
    -}
    -
    - -

    Prefer for (... of someArr) to iterate over arrays, -go/tsjs-practices/iteration. Array.prototype.forEach and vanilla for loops -are also allowed:

    - -
    for (const x of someArr) {
    -  // x is a value of someArr.
    -}
    -
    -for (let i = 0; i < someArr.length; i++) {
    -  // Explicitly count if the index is needed, otherwise use the for/of form.
    -  const x = someArr[i];
    -  // ...
    -}
    -for (const [i, x] of someArr.entries()) {
    -  // Alternative version of the above.
    -}
    -
    - -

    Using the spread operator

    - -

    Using the spread operator [...foo]; {...bar} is a convenient shorthand for -copying arrays and objects. When using the spread operator on objects, later -values replace earlier values at the same key.

    - -
    const foo = {
    -  num: 1,
    -};
    -
    -const foo2 = {
    -  ...foo,
    -  num: 5,
    -};
    -
    -const foo3 = {
    -  num: 5,
    -  ...foo,
    -}
    -
    -foo2.num === 5;
    -foo3.num === 1;
    -
    -
    - -

    When using the spread operator, the value being spread must match what is -being created. That is, when creating an object, only objects may be used with -the spread operator; when creating an array, only spread iterables. Primitives, -including null and undefined, must not be spread.

    - -
    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};
    -
    - -
    const foo = shouldUseFoo ? {num: 7} : {};
    -const bar = {num: 5, ...foo};
    -const fooStrings = ['a', 'b', 'c'];
    -const ids = [...fooStrings, 'd', 'e'];
    -
    - -

    Control flow statements & blocks

    - -

    Control flow statements always use blocks for the containing code.

    - -
    for (let i = 0; i < x; i++) {
    -  doSomethingWith(i);
    -}
    -
    -if (x) {
    -  doSomethingWithALongMethodNameThatForcesANewLine(x);
    -}
    -
    - -
    if (x)
    -  doSomethingWithALongMethodNameThatForcesANewLine(x);
    -
    -for (let i = 0; i < x; i++) doSomethingWith(i);
    -
    - -

    The exception is that if statements fitting on one line may elide the block.

    - -
    if (x) x.doFoo();
    -
    - -

    Assignment in control statements

    - -

    Prefer to avoid assignment of variables inside control statements. Assignment -can be easily mistaken for equality checks inside control statements.

    - -
    if (x = someFunction()) {
    -  // Assignment easily mistaken with equality check
    -  // ...
    -}
    -
    - -
    x = someFunction();
    -if (x) {
    -  // ...
    -}
    -
    - -

    In cases where assignment inside the control statement is preferred, enclose the -assignment in additional parenthesis to indicate it is intentional.

    - -
    while ((x = someFunction())) {
    -  // Double parenthesis shows assignment is intentional
    -  // ...
    -}
    -
    -
    - -

    Switch Statements

    - -

    All switch statements must contain a default statement group, even if it -contains no code.

    - -
    switch (x) {
    -  case Y:
    -    doSomethingElse();
    -    break;
    -  default:
    -    // nothing to do.
    -}
    -
    - -

    Non-empty statement groups (case ...) must not fall through (enforced by the -compiler):

    - -
    switch (x) {
    -  case X:
    -    doSomething();
    -    // fall through - not allowed!
    -  case Y:
    -    // ...
    -}
    -
    - -

    Empty statement groups are allowed to fall through:

    - -
    switch (x) {
    -  case X:
    -  case Y:
    -    doSomething();
    -    break;
    -  default: // nothing to do.
    -}
    -
    - -

    Equality Checks

    - -

    Always use triple equals (===) and not equals (!==). 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 -JavaScript equality table.

    - -
    if (foo == 'bar' || baz != bam) {
    -  // Hard to understand behaviour due to type coercion.
    -}
    -
    - -
    if (foo === 'bar' || baz !== bam) {
    -  // All good here.
    -}
    -
    - -

    Exception: Comparisons to the literal null value may use the == and -!= operators to cover both null and undefined values.

    - -
    if (foo == null) {
    -  // Will trigger when foo is null or undefined.
    -}
    -
    - -

    Keep try blocks focused

    - -

    Limit the amount of code inside a try block, if this can be done without hurting -readability.

    - -
    try {
    -  const result = methodThatMayThrow();
    -  use(result);
    -} catch (error: unknown) {
    -  // ...
    -}
    -
    - -
    let result;
    -try {
    -  result = methodThatMayThrow();
    -} catch (error: unknown) {
    -  // ...
    -}
    -use(result);
    -
    - -

    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.

    - -

    Exception: There may be performance issues if try blocks are inside a loop. -Widening try blocks to cover a whole loop is ok.

    - -

    Function Declarations

    - -

    Prefer function foo() { ... } to declare top-level named functions.

    - -

    Top-level arrow functions may be used, for example to provide an explicit type -annotation.

    +

    Arrow functions may be used, for example, when an explicit type annotation is +required.

    interface SearchFunction {
       (source: string, subString: string): boolean;
    @@ -1252,18 +1434,23 @@ annotation.

    const fooSearch: SearchFunction = (source, subString) => { ... };
    -
    -

    Note the difference between function declarations (function foo() {}) -discussed here, and function expressions (doSomethingWith(function() -{});) discussed below.

    -
    + -

    Function Expressions

    + -

    Use arrow functions in expressions

    +

    -

    Always use arrow functions instead of pre-ES6 function expressions defined with -the function keyword.

    +

    Nested functions

    + +

    Functions nested within other methods or functions may use function +declarations or arrow functions, as appropriate. In method bodies in particular, +arrow functions are preferred because they have access to the outer this.

    + +

    + +

    Do not use function expressions

    + +

    Do not use function expressions. Use arrow functions instead.

    bar(() => { this.doSomething(); })
     
    @@ -1271,29 +1458,32 @@ the function keyword.

    bar(function() { ... })
     
    -

    Function expressions (defined with the function keyword) may only be used if -code has to dynamically rebind the this pointer, but code should not rebind -the this pointer in general. Code in regular functions (as opposed to arrow -functions and methods) should not access this.

    +

    Exception: Function expressions may be used only if code has to +dynamically rebind this (but this is discouraged), or for +generator functions (which do not have an arrow syntax).

    -

    Expression bodies vs block bodies

    +

    +

    -

    Use arrow functions with expressions or blocks as their body as appropriate.

    +

    Arrow function bodies

    + +

    Use arrow functions with concise bodies (i.e. expressions) or block bodies as +appropriate.

    // Top level functions use function declarations.
     function someFunction() {
    -  // Block arrow function bodies, i.e. bodies with => { }, are fine:
    +  // Block bodies are fine:
       const receipts = books.map((b: Book) => {
         const receipt = payMoney(b.price);
         recordTransaction(receipt);
         return receipt;
       });
     
    -  // Expression bodies are fine, too, if the return value is used:
    +  // 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 don't access `this` in them.
    +    // function declarations are fine, but must not access `this`.
       }
     
       // Nested arrow functions may be assigned to a const.
    @@ -1301,11 +1491,15 @@ function someFunction() {
     }
     
    -

    Only use an expression body if the return value of the function is actually -used.

    +

    Only use a concise body if the return value of the function is actually used. +The block body makes sure the return type is void then and prevents potential +side effects.

    -
    // BAD: use a block ({ ... }) if the return value of the function is not used.
    +
    // 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;
     
    // GOOD: return value is unused, use a block body.
    @@ -1318,13 +1512,18 @@ const transformed = [1, 2, 3].map(v => {
       const more = acrossManyLines(intermediate);
       return worthWrapping(more);
     });
    +// GOOD: explicit `void` ensures no leaked return value
    +myPromise.then(v => void console.log(v));
     
    -

    Rebinding this

    +

    Tip: The void operator can be used to ensure an arrow function with an +expression body returns undefined when the result is unused.

    -

    Function expressions must not use this unless they specifically exist to -rebind the this pointer. Rebinding this can in most cases be avoided by -using arrow functions or explicit parameters.

    +

    Rebinding this

    + +

    Function expressions and function declarations must not use this unless they +specifically exist to rebind the this pointer. Rebinding this can in most +cases be avoided by using arrow functions or explicit parameters.

    function clickHandler() {
       // Bad: what's `this` in this context?
    @@ -1341,7 +1540,40 @@ const setTextFn = (e: HTMLElement) => { e.textContent = 'hello'; };
     document.body.onclick = setTextFn.bind(null, document.body);
     
    -

    Arrow functions as properties

    +

    Prefer arrow functions over other approaches to binding this, such as +f.bind(this), goog.bind(f, this), or const self = this.

    + +

    Prefer passing arrow functions as callbacks

    + +

    Callbacks can be invoked with unexpected arguments that can pass a type check +but still result in logical errors.

    + +

    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.

    + +
    // 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];
    +
    + +

    Instead, prefer passing an arrow-function that explicitly forwards parameters to +the named callback.

    + +
    // GOOD: Arguments are explicitly passed to the callback
    +const numbers = ['11', '5', '3'].map((n) => parseInt(n));
    +// > [11, 5, 3]
    +
    +// 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');
    +}
    +
    +const days = ['tuesday', undefined, 'juice', 'wednesday'].filter(dayFilter);
    +
    + +

    Arrow functions as properties

    Classes usually should not contain properties initialized to arrow functions. Arrow function properties require the calling function to understand that the @@ -1397,7 +1629,7 @@ class DelayHandler { }

    -

    Event Handlers

    +

    Event handlers

    Event handlers may 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 @@ -1446,25 +1678,665 @@ class Component { }

    -

    Automatic Semicolon Insertion

    +

    -

    Do not rely on Automatic Semicolon Insertion (ASI). Explicitly terminate all -statements using a semicolon. This prevents bugs due to incorrect semicolon -insertions and ensures compatibility with tools with limited ASI support (e.g. -clang-format).

    +

    Parameter initializers

    -

    @ts-ignore

    +

    Optional function parameters may be given a default initializer to use when +the argument is omitted. Initializers must not have any observable side +effects. Initializers should be kept as simple as possible.

    -

    Do not use @ts-ignore nor variants @ts-expect-error or @ts-nocheck. They -superficially seem to be an easy way to fix a compiler error, but in practice, -a specific compiler error is often caused by a larger problem that can be fixed -more directly.

    +
    function process(name: string, extraContext: string[] = []) {}
    +function activate(index = 0) {}
    +
    -

    For example, if you are using @ts-ignore 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 how to best use any is useful.

    +
    // BAD: side effect of incrementing the counter
    +let globalCounter = 0;
    +function newId(index = globalCounter++) {}
     
    -

    Type and Non-nullability Assertions

    +// BAD: exposes shared mutable state, which can introduce unintended coupling +// between function calls +class Foo { + private readonly defaultPaths: string[]; + frobnicate(paths = defaultPaths) {} +} +
    + +

    Use default parameters sparingly. Prefer +destructuring to create readable APIs when +there are more than a small handful of optional parameters that do not have a +natural order.

    + + + +

    +

    + +

    Prefer rest and spread when appropriate

    + +

    Use a rest parameter instead of accessing arguments. Never name a local +variable or parameter arguments, which confusingly shadows the built-in name.

    + +
    function variadic(array: string[], ...numbers: number[]) {}
    +
    + + + +

    Use function spread syntax instead of Function.prototype.apply.

    + +

    + +

    Formatting functions

    + +

    Blank lines at the start or end of the function body are not allowed.

    + +

    A single blank line may be used within function bodies sparingly to create +logical groupings of statements.

    + +

    Generators should attach the * to the function and yield keywords, as in +function* foo() and yield* iter, rather than function *foo() or +yield *iter.

    + +

    Parentheses around the left-hand side of a single-argument arrow function are +recommended but not required.

    + +

    Do not put a space after the ... in rest or spread syntax.

    + +
    function myFunction(...elements: number[]) {}
    +myFunction(...array, ...iterable, ...generator());
    +
    + +

    this

    + +

    Only use this in class constructors and methods, functions that have an +explicit this type declared (e.g. function func(this: ThisType, ...)), or in +arrow functions defined in a scope where this may be used.

    + +

    Never use this to refer to the global object, the context of an eval, the +target of an event, or unnecessarily call()ed or apply()ed functions.

    + +
    this.alert('Hello');
    +
    + +

    Interfaces

    + + + + + +

    Primitive literals

    + +

    + +

    String literals

    + +

    + +
    Use single quotes
    + +

    Ordinary string literals are delimited with single quotes ('), rather than +double quotes (").

    + +

    Tip: if a string contains a single quote character, consider using a template +string to avoid having to escape the quote.

    + +

    +

    + +
    No line continuations
    + +

    Do not use line continuations (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.

    + +

    Disallowed:

    + +
    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.';
    +
    + +

    Instead, write

    + +
    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';
    +
    + +

    + +
    Template literals
    + +

    Use template literals (delimited with `) over complex string +concatenation, particularly if multiple string literals are involved. Template +literals may span multiple lines.

    + +

    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.

    + +

    Example:

    + +
    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}`;
    +}
    +
    + +

    + +

    Number literals

    + +

    Numbers may be specified in decimal, hex, octal, or binary. Use exactly 0x, +0o, and 0b prefixes, with lowercase letters, for hex, octal, and binary, +respectively. Never include a leading zero unless it is immediately followed by +x, o, or b.

    + +

    Type coercion

    + +

    TypeScript code may use the String() and Boolean() (note: no new!) +functions, string template literals, or !! to coerce types.

    + +
    const bool = Boolean(false);
    +const str = String(aNumber);
    +const bool2 = !!str;
    +const str2 = `result: ${bool2}`;
    +
    + +

    Values of enum types (including unions of enum types and other types) must not +be converted to booleans with Boolean() or !!, and must instead be compared +explicitly with comparison operators.

    + +
    enum SupportLevel {
    +  NONE,
    +  BASIC,
    +  ADVANCED,
    +}
    +
    +const level: SupportLevel = ...;
    +let enabled = Boolean(level);
    +
    +const maybeLevel: SupportLevel|undefined = ...;
    +enabled = !!maybeLevel;
    +
    + +
    enum SupportLevel {
    +  NONE,
    +  BASIC,
    +  ADVANCED,
    +}
    +
    +const level: SupportLevel = ...;
    +let enabled = level !== SupportLevel.NONE;
    +
    +const maybeLevel: SupportLevel|undefined = ...;
    +enabled = level !== undefined && level !== SupportLevel.NONE;
    +
    + +
    + +

    Why?

    + +

    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 does matter are undesirable because they will be +surprising. Such is the case with conversion of enums to booleans; in +particular, by default, the first declared enum value is falsy (because it is 0) +while the others are truthy, which is likely to be unexpected. Readers of code +that uses an enum value may not even know whether it's the first declared value +or not.

    + +
    + +

    Using string concatenation to cast to string is discouraged, as we check that +operands to the plus operator are of matching types.

    + +

    Code must use Number() to parse numeric values, and must check its return +for NaN values explicitly, unless failing to parse is impossible from context.

    + +

    Note: Number(''), Number(' '), and Number('\t') would return 0 instead +of NaN. Number('Infinity') and Number('-Infinity') would return Infinity +and -Infinity respectively. Additionally, exponential notation such as +Number('1e+309') and Number('-1e+309') can overflow into Infinity. These +cases may require special handling.

    + +
    const aNumber = Number('123');
    +if (!isFinite(aNumber)) throw new Error(...);
    +
    + + + +

    Code must not use unary plus (+) 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.

    + +
    const x = +y;
    +
    + +

    Code also must not use parseInt or parseFloat to parse numbers, except for +non-base-10 strings (see below). Both of those functions ignore trailing +characters in the string, which can shadow error conditions (e.g. parsing 12 +dwarves as 12).

    + +
    const n = parseInt(someString, 10);  // Error prone,
    +const f = parseFloat(someString);    // regardless of passing a radix.
    +
    + +

    Code that requires parsing with a radix must check that its input contains +only appropriate digits for that radix before calling into parseInt;

    + +
    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
    +
    + +

    Use Number() followed by Math.floor or Math.trunc (where available) to +parse integer numbers:

    + +
    let f = Number(someString);
    +if (isNaN(f)) handleError();
    +f = Math.floor(f);
    +
    + +

    + +
    Implicit coercion
    + +

    Do not use explicit boolean coercions in conditional clauses that have implicit +boolean coercion. Those are the conditions in an if, for and while +statements.

    + +
    const foo: MyInterface|null = ...;
    +if (!!foo) {...}
    +while (!!foo) {...}
    +
    + +
    const foo: MyInterface|null = ...;
    +if (foo) {...}
    +while (foo) {...}
    +
    + +

    As with explicit conversions, values of enum types (including +unions of enum types and other types) must not be implicitly coerced to +booleans, and must instead be compared explicitly with comparison operators.

    + +
    enum SupportLevel {
    +  NONE,
    +  BASIC,
    +  ADVANCED,
    +}
    +
    +const level: SupportLevel = ...;
    +if (level) {...}
    +
    +const maybeLevel: SupportLevel|undefined = ...;
    +if (level) {...}
    +
    + +
    enum SupportLevel {
    +  NONE,
    +  BASIC,
    +  ADVANCED,
    +}
    +
    +const level: SupportLevel = ...;
    +if (level !== SupportLevel.NONE) {...}
    +
    +const maybeLevel: SupportLevel|undefined = ...;
    +if (level !== undefined && level !== SupportLevel.NONE) {...}
    +
    + +

    Other types of values may be either implicitly coerced to booleans or compared +explicitly with comparison operators:

    + +
    // Explicitly comparing > 0 is OK:
    +if (arr.length > 0) {...}
    +// so is relying on boolean coercion:
    +if (arr.length) {...}
    +
    + +

    + +

    Control structures

    + +

    +

    + +

    Control flow statements and blocks

    + +

    Control flow statements (if, else, for, do, while, 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.

    + +
    for (let i = 0; i < x; i++) {
    +  doSomethingWith(i);
    +}
    +
    +if (x) {
    +  doSomethingWithALongMethodNameThatForcesANewLine(x);
    +}
    +
    + +
    if (x)
    +  doSomethingWithALongMethodNameThatForcesANewLine(x);
    +
    +for (let i = 0; i < x; i++) doSomethingWith(i);
    +
    + +

    Exception: if statements fitting on one line may elide the block.

    + +
    if (x) x.doFoo();
    +
    + +
    Assignment in control statements
    + +

    Prefer to avoid assignment of variables inside control statements. Assignment +can be easily mistaken for equality checks inside control statements.

    + +
    if (x = someFunction()) {
    +  // Assignment easily mistaken with equality check
    +  // ...
    +}
    +
    + +
    x = someFunction();
    +if (x) {
    +  // ...
    +}
    +
    + +

    In cases where assignment inside the control statement is preferred, enclose the +assignment in additional parenthesis to indicate it is intentional.

    + +
    while ((x = someFunction())) {
    +  // Double parenthesis shows assignment is intentional
    +  // ...
    +}
    +
    +
    + +

    + +
    Iterating containers
    + +

    Prefer for (... of someArr) to iterate over arrays. Array.prototype.forEach and vanilla for +loops are also allowed:

    + +
    for (const x of someArr) {
    +  // x is a value of someArr.
    +}
    +
    +for (let i = 0; i < someArr.length; i++) {
    +  // Explicitly count if the index is needed, otherwise use the for/of form.
    +  const x = someArr[i];
    +  // ...
    +}
    +for (const [i, x] of someArr.entries()) {
    +  // Alternative version of the above.
    +}
    +
    + +

    for-in loops may only be used on dict-style objects (see +below for more info). Do not +use for (... in ...) to iterate over arrays as it will counterintuitively give +the array's indices (as strings!), not values:

    + +
    for (const x in someArray) {
    +  // x is the index!
    +}
    +
    + +

    Object.prototype.hasOwnProperty should be used in for-in loops to exclude +unwanted prototype properties. Prefer for-of with Object.keys, +Object.values, or Object.entries over for-in when possible.

    + +
    for (const key in obj) {
    +  if (!obj.hasOwnProperty(key)) continue;
    +  doWork(key, obj[key]);
    +}
    +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);
    +}
    +
    + +

    + +

    Grouping parentheses

    + +

    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 not +reasonable to assume that every reader has the entire operator precedence table +memorized.

    + +

    Do not use unnecessary parentheses around the entire expression following +delete, typeof, void, return, throw, case, in, of, or yield.

    + + + +

    +

    + +

    Exception handling

    + +

    Exceptions are an important part of the language and should be used whenever +exceptional cases occur.

    + +

    Custom exceptions provide a great way to convey additional error information +from functions. They should be defined and used wherever the native Error type +is insufficient.

    + +

    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).

    + +
    Instantiate errors using new
    + +

    Always use new Error() when instantiating exceptions, instead of just calling +Error(). Both forms create a new Error instance, but using new is more +consistent with how other objects are instantiated.

    + +
    throw new Error('Foo is not a valid bar.');
    +
    + +
    throw Error('Foo is not a valid bar.');
    +
    + +
    Only throw errors
    + +

    JavaScript (and thus TypeScript) allow throwing or rejecting a Promise with +arbitrary values. However if the thrown or rejected value is not an Error, it +does not populate stack trace information, making debugging hard. This treatment +extends to Promise rejection values as Promise.reject(obj) is equivalent to +throw obj; in async functions.

    + +
    // 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!');
    +
    + +

    Instead, only throw (subclasses of) Error:

    + +
    // 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!'));
    +
    + +
    Catching and rethrowing
    + +

    When catching errors, code should assume that all thrown errors are instances +of Error.

    + + + +
    + +
    function assertIsError(e: unknown): asserts e is Error {
    +  if (!(e instanceof Error)) throw new Error("e is not an Error");
    +}
    +
    +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;
    +}
    +
    + +
    + +

    Exception handlers must not defensively handle non-Error types unless the +called API is conclusively known to throw non-Errors in violation of the above +rule. In that case, a comment should be included to specifically identify where +the non-Errors originate.

    + +
    try {
    +  badApiThrowingStrings();
    +} catch (e: unknown) {
    +  // Note: bad API throws strings instead of errors.
    +  if (typeof e === 'string') { ... }
    +}
    +
    + +
    + +

    Why?

    + +

    Avoid +overly defensive programming. +Repeating the same defenses against a problem that will not exist in most code +leads to boiler-plate code that is not useful.

    + +
    + +

    + +
    Empty catch blocks
    + +

    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.

    + +
      try {
    +    return handleNumericResponse(response);
    +  } catch (e: unknown) {
    +    // Response is not numeric. Continue to handle as text.
    +  }
    +  return handleTextResponse(response);
    +
    + +

    Disallowed:

    + +
      try {
    +    shouldFail();
    +    fail('expected an error');
    +  } catch (expected: unknown) {
    +  }
    +
    + +

    Tip: Unlike in some other languages, patterns like the above simply don’t work +since this will catch the error thrown by fail. Use assertThrows() instead.

    + +

    +

    + +

    Switch statements

    + +

    All switch statements must contain a default statement group, even if it +contains no code. The default statement group must be last.

    + +
    switch (x) {
    +  case Y:
    +    doSomethingElse();
    +    break;
    +  default:
    +    // nothing to do.
    +}
    +
    + +

    Within a switch block, each statement group either terminates abruptly with a +break, a return statement, or by throwing an exception. Non-empty statement +groups (case ...) must not fall through (enforced by the compiler):

    + +
    switch (x) {
    +  case X:
    +    doSomething();
    +    // fall through - not allowed!
    +  case Y:
    +    // ...
    +}
    +
    + +

    Empty statement groups are allowed to fall through:

    + +
    switch (x) {
    +  case X:
    +  case Y:
    +    doSomething();
    +    break;
    +  default: // nothing to do.
    +}
    +
    + + + +

    +

    + +

    Equality checks

    + +

    Always use triple equals (===) and not equals (!==). 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 +JavaScript equality table.

    + +
    if (foo == 'bar' || baz != bam) {
    +  // Hard to understand behaviour due to type coercion.
    +}
    +
    + +
    if (foo === 'bar' || baz !== bam) {
    +  // All good here.
    +}
    +
    + +

    Exception: Comparisons to the literal null value may use the == and +!= operators to cover both null and undefined values.

    + +
    if (foo == null) {
    +  // Will trigger when foo is null or undefined.
    +}
    +
    + +

    Type and non-nullability assertions

    Type assertions (x as SomeType) and non-nullability assertions (y!) are unsafe. Both only silence the TypeScript compiler, but do not insert any runtime @@ -1510,7 +2382,7 @@ comments may not be necessary. For example, generated proto code is alw 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.

    -

    Type Assertions Syntax

    +
    Type assertion syntax

    Type assertions must use the as syntax (as opposed to the angle brackets syntax). This enforces parentheses around the assertion when accessing a member.

    @@ -1523,7 +2395,25 @@ const y = <Foo>z.length; const x = (z as Foo).length;
    -

    Type Assertions and Object Literals

    +
    Double assertions
    + +

    From the +TypeScript handbook, +TypeScript only allows type assertions which convert to a more specific or +less specific version of a type. Adding a type assertion (x as Foo) which +does not meet this criteria will give the error: Conversion of type 'X' to type +'Y' may be a mistake because neither type sufficiently overlaps with the other.

    + +

    If you are sure an assertion is safe, you can perform a double assertion. This +involves casting through unknown since it is less specific than all types.

    + +
    // x is a Foo here, because...
    +(x as unknown as Foo).fooMethod();
    +
    + +

    Use unknown (instead of any or {}) as the intermediate type.

    + +
    Type assertions and object literals

    Use type annotations (: Foo) instead of type assertions (as Foo) to specify the type of an object literal. This allows detecting refactoring bugs when the @@ -1565,118 +2455,37 @@ function func(): Foo { }

    -

    Member property declarations

    +

    Keep try blocks focused

    -

    Interface and class declarations must use a semicolon to separate individual -member declarations:

    +

    Limit the amount of code inside a try block, if this can be done without hurting +readability.

    -
    interface Foo {
    -  memberA: string;
    -  memberB: number;
    +
    try {
    +  const result = methodThatMayThrow();
    +  use(result);
    +} catch (error: unknown) {
    +  // ...
     }
     
    -

    Interfaces specifically must not use a comma to separate fields, for symmetry -with class declarations:

    - -
    interface Foo {
    -  memberA: string,
    -  memberB: number,
    +
    let result;
    +try {
    +  result = methodThatMayThrow();
    +} catch (error: unknown) {
    +  // ...
     }
    +use(result);
     
    -

    Inline object type declarations must use a comma as a separator:

    +

    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.

    -
    type SomeTypeAlias = {
    -  memberA: string,
    -  memberB: number,
    -};
    +

    Exception: There may be performance issues if try blocks are inside a loop. +Widening try blocks to cover a whole loop is ok.

    -let someProperty: {memberC: string, memberD: number}; -
    - -

    Optimization compatibility for property access

    - -

    Code must not mix quoted property access with dotted property access:

    - -
    // Bad: code must use either non-quoted or quoted access for any property
    -// consistently across the entire application:
    -console.log(x['someField']);
    -console.log(x.someField);
    -
    - -

    Properties that are external to the application, e.g. properties on JSON objects -or external APIs, must be accessed using .dotted notation, and must be -declared as so-called ambient properties, using the declare modifier. -

    - -
    // Good: using "declare" to declare types that are external to the application,
    -// so that their properties are not renamed.
    -declare interface ServerInfoJson {
    -  appVersion: string;
    -  user: UserJson; // Note: UserJson must also use `declare`!
    -}
    -// serverResponse must be ServerInfoJson as per the application's contract.
    -const data = JSON.parse(serverResponse) as ServerInfoJson;
    -console.log(data.appVersion); // Type safe & renaming safe!
    -
    - -

    Optimization compatibility for module object imports

    - -

    When importing a module object, directly access properties on the module object -rather than passing it around. This ensures that modules can be analyzed and -optimized. Treating -module imports as namespaces is fine.

    - -
    import * as utils from 'utils';
    -class A {
    -  readonly utils = utils;  // <--- BAD: passing around the module object
    -}
    -
    - -
    import * as utils from 'utils';
    -class A {
    -  readonly utils = {method1: utils.method1, method2: utils.method2};
    -}
    -
    - -

    or, more tersely:

    - -
    import {method1, method2} from 'utils';
    -class A {
    -  readonly utils = {method1, method2};
    -}
    -
    - -

    Exception

    - -

    This optimization compatibility rule applies to all web apps. It does not apply -to code that only runs server side (e.g. in NodeJS for a test runner). It is -still strongly encouraged to always declare all types and avoid mixing quoted -and unquoted property access, for code hygiene.

    - -

    Const Enums

    - -

    Code must not use const enum; use plain enum instead.

    - -
    -Why? - -

    TypeScript enums already cannot be mutated; const enum is a separate language -feature related to optimization that makes the enum invisible to -JavaScript users of the module. -

    - -

    Debugger statements

    - -

    Debugger statements must not be included in production code.

    - -
    function debugMe() {
    -  debugger;
    -}
    -
    - -

    Decorators

    +

    Decorators

    Decorators are syntax with an @ prefix, like @MyDecorator.

    @@ -1688,13 +2497,17 @@ frameworks:

  • Polymer (e.g. @property)
  • -
    -Why? + -

    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. -

    +
    + +

    Why?

    + +

    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.

    + +

    When using decorators, the decorator must immediately precede the symbol it decorates, with no empty lines between:

    @@ -1709,386 +2522,372 @@ class MyComp { }
    -

    Source Organization

    +

    Disallowed features

    -

    Modules

    +

    -

    Import Paths

    +

    Wrapper objects for primitive types

    -

    TypeScript code must use paths to import other TypeScript code. Paths may be -relative, i.e. starting with . or .., - or rooted at the base directory, e.g. -root/path/to/file.

    +

    TypeScript code must not instantiate the wrapper classes for the primitive +types String, Boolean, and Number. Wrapper classes have surprising +behavior, such as new Boolean(false) evaluating to true.

    -

    Code should use relative imports (./foo) rather than absolute imports -path/to/foo when referring to files within the same (logical) project as this -allows to move the project around without introducing changes in these imports.

    - -

    Consider limiting the number of parent steps (../../../) as those can make -module and path structures hard to understand.

    - -
    import {Symbol1} from 'path/from/root';
    -import {Symbol2} from '../parent/file';
    -import {Symbol3} from './sibling';
    +
    const s = new String('hello');
    +const b = new Boolean(false);
    +const n = new Number(5);
     
    -

    Namespaces vs Modules

    +

    The wrappers may be called as functions for coercing (which is preferred over +using + or concatenating the empty string) or creating symbols. See +type coercion for more information.

    -

    TypeScript supports two methods to organize code: namespaces and modules, -but namespaces are disallowed. That -is, your code must refer to code in other files using imports and exports of -the form import {foo} from 'bar';

    +

    +

    -

    Your code must not use the namespace Foo { ... } construct. namespaces -may only be used when required to interface with external, third party code. -To semantically namespace your code, use separate files.

    +

    Automatic Semicolon Insertion

    -

    Code must not use require (as in import x = require('...');) for imports. -Use ES6 module syntax.

    +

    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).

    -
    // Bad: do not use namespaces:
    -namespace Rocket {
    -  function launch() { ... }
    -}
    +

    Const enums

    -// Bad: do not use <reference> -/// <reference path="..."/> +

    Code must not use const enum; use plain enum instead.

    -// Bad: do not use require() -import x = require('mydep'); -
    +
    -
    -

    NB: TypeScript namespaces used to be called internal modules and used to use -the module keyword in the form module Foo { ... }. Don't use that either. -Always use ES6 imports.

    -
    +

    Why?

    -

    Exports

    +

    TypeScript enums already cannot be mutated; const enum is a separate language +feature related to optimization that makes the enum invisible to +JavaScript users of the module.

    -

    Use named exports in all code:

    +
    -
    // Use named exports:
    -export class Foo { ... }
    -
    +

    Debugger statements

    -

    Do not use default exports. This ensures that all imports follow a uniform -pattern.

    +

    Debugger statements must not be included in production code.

    -
    // Do not use default exports:
    -export default class Foo { ... } // BAD!
    -
    - -
    -Why? - -

    Default exports provide no canonical name, which makes central maintenance -difficult with relatively little benefit to code owners, including potentially -decreased readability:

    - -
    import Foo from './bar';  // Legal.
    -import Bar from './bar';  // Also legal.
    -
    - -

    Named exports have the benefit of erroring when import statements try to import -something that hasn't been declared. In foo.ts:

    - -
    const foo = 'blah';
    -export default foo;
    -
    - -

    And in bar.ts:

    - -
    import {fizz} from './foo';
    -
    - -

    Results in error TS2614: Module '"./foo"' has no exported member 'fizz'. While -bar.ts:

    - -
    import fizz from './foo';
    -
    - -

    Results in fizz === foo, which is probably unexpected and difficult to debug.

    - -

    Additionally, default exports encourage people to put everything into one big -object to namespace it all together:

    - -
    export default class Foo {
    -  static SOME_CONSTANT = ...
    -  static someHelpfulFunction() { ... }
    -  ...
    +
    function debugMe() {
    +  debugger;
     }
     
    -

    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 Foo) that can be -ambiguously used as both a type and a value in other files.

    +

    -

    Instead, prefer use of file scope for namespacing, as well as named exports:

    +

    with

    -
    export const SOME_CONSTANT = ...
    -export function someHelpfulFunction()
    -export class Foo {
    -  // only class stuff here
    -}
    +

    Do not use the with keyword. It makes your code harder to understand and +has been banned in strict mode since ES5.

    + +

    + +

    Dynamic code evaluation

    + +

    Do not use eval or the Function(...string) constructor (except for code +loaders). These features are potentially dangerous and simply do not work in +environments using strict +Content Security Policies.

    + +

    + +

    Non-standard features

    + +

    Do not use non-standard ECMAScript or Web Platform features.

    + +

    This includes:

    + +
      +
    • Old features that have been marked deprecated or removed entirely from +ECMAScript / the Web Platform (see +MDN)
    • +
    • New ECMAScript features that are not yet standardized +
        +
      • Avoid using features that are in current TC39 working draft or currently +in the proposal process
      • +
      • Use only ECMAScript features defined in the current ECMA-262 +specification
      • +
    • +
    • Proposed but not-yet-complete web standards: +
    • +
    • Non-standard language “extensions” (such as those provided by some external +transpilers)
    • +
    + +

    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.

    + +

    + +

    Modifying builtin objects

    + +

    Never modify builtin types, either by adding methods to their constructors or to +their prototypes. Avoid depending on libraries that do +this.

    + +

    Do not add symbols to the global object unless absolutely necessary (e.g. +required by a third-party API).

    + +

    +

    + +

    Naming

    + +

    Identifiers

    + +

    Identifiers must use only ASCII letters, digits, underscores (for constants +and structured test method names), and (rarely) the '$' sign.

    + +

    Naming style

    + +

    TypeScript expresses information in types, so names should not be decorated +with information that is included in the type. (See also +Testing Blog + for more about what +not to include.)

    + +

    Some concrete examples of this rule:

    + +
      +
    • Do not use trailing or leading underscores for private properties or +methods.
    • +
    • Do not use the opt_ prefix for optional parameters. +
    • +
    • Do not mark interfaces specially (IMyInterface or +MyFooInterface) 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. class TodoItem and interface +TodoItemStorage if the interface expresses the format used for +storage/serialization in JSON).
    • +
    • Suffixing Observables with $ 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 should be consistent within projects.
    • +
    + +

    + +

    Descriptive names

    + +

    Names must 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.

    + +
      +
    • Exception: Variables that are in scope for 10 lines or fewer, including +arguments that are not part of an exported API, may use short (e.g. +single letter) variable names.
    • +
    + +
    // 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.
     
    -
    - -

    Export visibility

    - -

    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.

    - -

    Mutable Exports

    - -

    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 export let is not allowed.

    - -
    - -
    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 */);
    +
    // 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".
     
    -
    +

    -

    If one needs to support externally accessible and mutable bindings, they -should instead use explicit getter functions.

    +

    Camel case

    -
    let foo = 3;
    -window.setTimeout(() => {
    -  foo = 4;
    -}, 1000 /* ms */);
    -// Use an explicit getter to access the mutable export.
    -export function getFoo() { return foo; };
    -
    +

    +Treat abbreviations like acronyms in names as whole words, i.e. use +loadHttpUrl, not loadHTTPURL, unless required by a platform name (e.g. +XMLHttpRequest).

    -

    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.

    +

    Dollar sign

    -
    function pickApi() {
    -  if (useOtherApi()) return OtherApi;
    -  return RegularApi;
    -}
    -export const SomeApi = pickApi();
    -
    +

    Identifiers should not generally use $, except when required by naming +conventions for third party frameworks. See above for more on +using $ with Observable values.

    -

    Container Classes

    +

    + + + + + +

    -

    Do not create container classes with static methods or properties for the sake -of namespacing.

    +

    Rules by identifier type

    -
    export class Container {
    -  static FOO = 1;
    -  static bar() { return 1; }
    -}
    -
    - -

    Instead, export individual constants and functions:

    - -
    export const FOO = 1;
    -export function bar() { return 1; }
    -
    - -

    Imports

    - -

    There are four variants of import statements in ES6 and TypeScript:

    - -
    +

    Most identifier names should follow the casing in the table below, based on the +identifier's type.

    - - - + + - - - - - - - - - - - - - - - - - + + + - + + + + + + + + +
    Import typeExampleUse forStyleCategory
    module -import * as foo from -'...';TypeScript imports -
    destructuring -import {SomeThing} from -'...';TypeScript imports -
    default -import SomeThing from -'...';Only for other external code that -requires them
    side-effect +UpperCamelCase import '...'; - +class / interface / type / enum / decorator / type +parameters / component functions in TSX / JSXElement type +parameter
    lowerCamelCase Only to import libraries for -their side-effects on load (such -as custom elements)variable / parameter / function / method / property / +module alias
    CONSTANT_CASE +global constant values, including enum values. See +Constants below.
    #identprivate identifiers are never used.
    -
    // 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'; +

    Type parameters

    -// Sometimes needed to import libraries for their side effects: -import 'jasmine'; -import '@polymer/paper-button'; +

    Type parameters, like in Array<T>, may use a single upper case character +(T) or UpperCamelCase.

    + + + +

    Test names

    + +

    Test method names inxUnit-style test frameworks may be structured with _ separators, e.g. +testX_whenY_doesZ().

    + +

    _ prefix/suffix

    + +

    Identifiers must not use _ as a prefix or suffix.

    + +

    This also means that _ must not be used as an identifier by itself (e.g. to +indicate a parameter is unused).

    + +
    +

    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:

    + +
    const [a, , b] = [1, 5, 10];  // a <- 1, b <- 10
    +
    +
    + + + +

    Imports

    + +

    Module namespace imports are lowerCamelCase while files are snake_case, +which means that imports correctly will not match in casing style, such as

    + +
    import * as fooBar from './foo_bar';
     
    -
    +

    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:

    -

    Module versus destructuring imports

    + -

    Both module and destructuring imports have advantages depending on the -situation.

    +

    Constants

    -

    Despite the *, a module import is not comparable to a wildcard import as -seen in other languages. Instead, module imports give a name to the entire -module and each symbol reference mentions the module, which can make code more -readable and gives autocompletion on all symbols in a module. They also require -less import churn (all symbols are available), fewer name collisions, and allow -terser names in the module that's imported. Module imports are particularly -useful when using many different symbols from large APIs.

    +

    Immutable: CONSTANT_CASE indicates that a value is intended to not be +changed, and may 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.

    -

    Destructuring imports give local names for each imported symbol. They allow -terser code when using the imported symbol, which is particularly useful for -very commonly used symbols, such as Jasmine's describe and it.

    - -
    // Bad: overlong import statement of needlessly namespaced names.
    -import {TableViewItem, TableViewHeader, TableViewRow, TableViewModel,
    -  TableViewRenderer} from './tableview';
    -let item: TableViewItem = ...;
    +
    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.
     
    -
    // Better: use the module for namespacing.
    -import * as tableview from './tableview';
    -let item: tableview.Item = ...;
    +

    A constant can also be a static readonly property of a class.

    + +
    class Foo {
    +  private static readonly MY_SPECIAL_NUMBER = 5;
    +
    +  bar() {
    +    return 2 * Foo.MY_SPECIAL_NUMBER;
    +  }
    +}
     
    -
    import * as testing from './testing';
    +

    Global: Only symbols declared on the module level, static fields of module +level classes, and values of module level enums, may use CONST_CASE. 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 must use lowerCamelCase.

    -// All tests will use the same three functions repeatedly. -// When importing only a few symbols that are used very frequently, also -// consider importing the symbols directly (see below). -testing.describe('foo', () => { - testing.it('bar', () => { - testing.expect(...); - testing.expect(...); - }); -}); +

    If a value is an arrow function that implements an interface, then it may be +declared lowerCamelCase.

    + + + + + +

    + +

    Aliases

    + +

    When creating a local-scope alias of an existing symbol, use the format of the +existing identifier. The local alias must match the existing naming and format +of the source. For variables use const for your local aliases, and for class +fields use the readonly attribute.

    + +
    +

    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 +access modifiers.

    +
    + +
    const {BrewStateEnum} = SomeType;
    +const CAPACITY = 5;
    +
    +class Teapot {
    +  readonly BrewStateEnum = BrewStateEnum;
    +  readonly CAPACITY = CAPACITY;
    +}
     
    -
    // Better: give local names for these common functions.
    -import {describe, it, expect} from './testing';
    +

    Type system

    -describe('foo', () => { - it('bar', () => { - expect(...); - expect(...); - }); -}); -... -
    - -

    Renaming imports

    - -

    Code should fix name collisions by using a module import or renaming the -exports themselves. Code may rename imports (import {SomeThing as -SomeOtherThing}) if needed.

    - -

    Three examples where renaming can be helpful:

    - -
      -
    1. If it's necessary to avoid collisions with other imported symbols.
    2. -
    3. If the imported symbol name is generated.
    4. -
    5. If importing symbols whose names are unclear by themselves, renaming can -improve code clarity. For example, when using RxJS the from function might -be more readable when renamed to observableFrom.
    6. -
    - -

    Import & export type

    - -

    Do not use import type {...} or export type {...}.

    - -
    import type {Foo};
    -export type {Bar};
    -export type {Bar} from './bar';
    -
    - -

    Instead, just use regular imports and exports:

    - -
    import {Foo} from './foo';
    -export {Bar} from './bar';
    -
    - -

    Note: this does not apply to export as applied to a type definition, i.e. -export type Foo = ...;.

    - -
    export type Foo = string;
    -
    - -

    TypeScript tooling automatically distinguishes symbols used as types vs symbols -used as values and only generates runtime loads for the latter.

    - -
    -Why? - -

    TypeScript tooling automatically handles the distinction and does not insert -runtime loads for type references. This gives a better developer UX: toggling -back and forth between import type and import is bothersome. At the same -time, import type gives no guarantees: your code might still have a hard -dependency on some import through a different transitive path.

    - -

    If you need to force a runtime load for side effects, use import '...';. See -

    - -

    export type might seem useful to avoid ever exporting a value symbol for an -API. However it does not give guarantees either: downstream code might still -import an API through a different path. A better way to split & guarantee type -vs value usages of an API is to actually split the symbols into e.g. -UserService and AjaxUserService. This is less error prone and also better -communicates intent.

    - -
    - -

    Organize By Feature

    - -

    Organize packages by feature, not by type. For example, an online shop should -have packages named products, checkout, backend, not views, models, -controllers.

    - -

    Type System

    - -

    Type Inference

    +

    Type inference

    Code may rely on type inference as implemented by the TypeScript compiler for all type expressions (variables, fields, return types, etc). @@ -2108,6 +2907,10 @@ expression.

    const x: Set<string> = new Set();
    +

    Explicitly specifying types may be required to prevent generic type parameters +from being inferred as unknown. For example, initializing generic types with +no values (e.g. empty arrays, objects, Maps, or Sets).

    +
    const x = new Set<string>();
     
    @@ -2124,7 +2927,9 @@ const value: string[] = await rpc.getSomeValue().transform();

    Whether an annotation is required is decided by the code reviewer.

    -

    Return types

    + + +

    Return types

    Whether to include return type annotations for functions and methods is up to the code author. Reviewers may ask for annotations to clarify complex return @@ -2140,11 +2945,15 @@ functions and methods:

    that change the return type of the function. -

    Null vs Undefined

    +

    -

    TypeScript supports null and undefined types. Nullable types can be +

    Undefined and null

    + +

    TypeScript supports undefined and null types. Nullable types can be constructed as a union type (string|null); similarly with undefined. There -is no special syntax for unions of null and undefined.

    +is no special syntax for unions of undefined and null.

    + +

    TypeScript code can use either undefined or null to denote absence of a value, there is no general guidance to prefer one over the other. Many @@ -2152,7 +2961,7 @@ JavaScript APIs use undefined (e.g. Map.get), while ma use null (e.g. Element.getAttribute), so the appropriate absent value depends on the context.

    -

    Nullable/undefined type aliases

    +

    Nullable/undefined type aliases

    Type aliases must not include |null or |undefined in a union type. Nullable aliases typically indicate that null values are being passed around @@ -2180,17 +2989,9 @@ class CoffeeService { }

    -
    // Best
    -type CoffeeResponse = Latte|Americano;
    +

    -class CoffeeService { - getLatte(): CoffeeResponse { - return assert(fetchResponse(), 'Coffee maker is broken, file a ticket'); - }; -} -
    - -

    Optionals vs |undefined type

    +

    Prefer optional over |undefined

    In addition, TypeScript supports a special construct for optional parameters and fields, using ?:

    @@ -2219,17 +3020,14 @@ fields as possible.

    }
    -

    Structural Types vs Nominal Types

    +

    + +

    Use structural types

    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.

    -

    Use structural typing where appropriate in your code. Outside of test code, use -interfaces to define structural types, not classes. In test code it can be -useful to have mock implementations structurally match the code under test -without introducing an extra interface.

    -

    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).

    @@ -2246,8 +3044,33 @@ reporting).

    }
    -
    -Why? +

    Use interfaces to define structural types, not classes

    + +
    interface Foo {
    +  a: number;
    +  b: string;
    +}
    +
    +const foo: Foo = {
    +  a: 123,
    +  b: 'abc',
    +}
    +
    + +
    class Foo {
    +  readonly a: number;
    +  readonly b: number;
    +}
    +
    +const foo: Foo = {
    +  a: 123,
    +  b: 'abc',
    +}
    +
    + +
    + +

    Why?

    The badFoo object above relies on type inference. Additional fields could be added to badFoo and the type is inferred based on the object itself.

    @@ -2294,9 +3117,11 @@ makeSound(dog); makeSound(horse);
    - + -

    Interfaces vs Type Aliases

    +

    + +

    Prefer interfaces over type literal aliases

    TypeScript supports type aliases @@ -2318,8 +3143,9 @@ alias for the object literal expression.

    }
    -
    -Why? +
    + +

    Why?

    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 @@ -2327,37 +3153,49 @@ are also interesting technical reasons to prefer interface. That page quotes the TypeScript team lead: Honestly, my take is that it should really just be interfaces for anything that they can model. There is no benefit -to type aliases when there are so many issues around display/perf. -

    +to type aliases when there are so many issues around display/perf.

    -

    Array<T> Type

    +
    + +

    Array<T> Type

    For simple types (containing just alphanumeric characters and dot), use the -syntax sugar for arrays, T[], rather than the longer form Array<T>.

    +syntax sugar for arrays, T[] or readonly T[], rather than the longer form +Array<T> or ReadonlyArray<T>.

    + +

    For multi-dimensional non-readonly arrays of simple types, use the syntax +sugar form (T[][], T[][][], and so on) rather than the longer form.

    For anything more complex, use the longer form Array<T>.

    These rules apply at each level of nesting, i.e. a simple T[] nested in a more complex type would still be spelled as T[], using the syntax sugar.

    -

    This also applies for readonly T[] vs ReadonlyArray<T>.

    - -
    const a: string[];
    -const b: readonly string[];
    -const c: ns.MyObj[];
    -const d: Array<string|number>;
    -const e: ReadonlyArray<string|number>;
    -const f: InjectionToken<string[]>;  // Use syntax sugar for nested types.
    +
    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[]>;
     
    -
    const a: Array<string>;            // the syntax sugar is shorter
    -const b: ReadonlyArray<string>;
    -const c: {n: number, s: string}[]; // the braces/parens make it harder to read
    -const d: (string|number)[];
    -const e: readonly (string|number)[];
    +
    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[])[];
     
    -

    Indexable Types / index signatures ({[key: string]: T})

    +

    Indexable types / index signatures ({[key: string]: T})

    In JavaScript, it's common to use an object as an associative array (aka map, hash, or dict). Such objects can be typed using an @@ -2390,7 +3228,7 @@ with a defined set of keys. This is distinct from associative arrays in that the keys are statically known. See advice on that below.

    -

    Mapped & Conditional Types

    +

    Mapped and conditional types

    TypeScript's mapped types @@ -2434,10 +3272,9 @@ term cost of complex type expressions.

  • Mapped & conditional types may be used, subject to these considerations.
  • -
    -For example, TypeScript's builtin Pick<T, Keys> type allows creating a new +

    For example, TypeScript's builtin Pick<T, Keys> type allows creating a new type by subsetting another type T, but simple interface extension can often be -easier to understand. +easier to understand.

    interface User {
       shoeSize: number;
    @@ -2469,20 +3306,23 @@ interface User extends FoodPreferences {
     
     

    Using interfaces here makes the grouping of properties explicit, improves IDE support, allows better optimization, and arguably makes the code easier to -understand. -

    +understand.

    -

    any Type

    +

    any Type

    TypeScript's any type is a super and subtype of all other types, and allows dereferencing all properties. As such, any is dangerous - it can mask severe programming errors, and its use undermines the value of having static types in the first place.

    -
    -Consider not to use any. In circumstances where you -want to use any, consider one of: -
    + + +
    + +

    Consider not to use any. In circumstances where you want to use any, +consider one of:

    + +
    -

    Providing a more specific type

    +

    Providing a more specific type

    Use interfaces , an inline object type, or a type alias:

    @@ -2519,7 +3359,7 @@ function nicestElement<T>(items: T[]): T { }
    -

    Using unknown over any

    +

    Using unknown over any

    The any type allows assignment into any other type and dereferencing any property off it. Often this behaviour is not necessary or desirable, and code @@ -2536,12 +3376,16 @@ const val: unknown = value; danger.whoops(); // This access is completely unchecked!

    -
    -To safely use unknown values, narrow the type using a -type guard -
    +
    -

    Suppressing any lint warnings

    +

    To safely use unknown values, narrow the type using a +type guard

    + +
    + + + +

    Suppressing any lint warnings

    Sometimes using any is legitimate, for example in tests to construct a mock object. In such cases, add a comment that suppresses the lint warning, and @@ -2557,7 +3401,47 @@ const mockBookService = ({get() { return mockBook; }} as any) as BookService; const component = new MyComponent(mockBookService, /* unused ShoppingCart */ null as any);

    -

    Tuple Types

    + + +

    {} Type

    + +

    The {} type, also known as an empty interface 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.

    + +
    let player: {};
    +
    +player = {
    +  health: 50,
    +}; // Allowed.
    +
    +console.log(player.health) // Property 'health' does not exist on type '{}'.
    +
    + +
    function takeAnything(obj:{}) {
    +
    +}
    +
    +takeAnything({});
    +takeAnything({ a: 1, b: 2 });
    +
    + +

    Google3 code should not use {} for most use cases. {} represents any +non-nullish primitive or object type, which is rarely appropriate. Prefer one of +the following more-descriptive types:

    + + + +

    Tuple types

    If you are tempted to create a Pair type, instead use a tuple type:

    @@ -2597,7 +3481,7 @@ use(address.port); const {host, port} = splitHostPort(userAddress); -

    Wrapper types

    +

    Wrapper types

    There are a few types related to JavaScript primitives that should not ever be used:

    @@ -2614,20 +3498,474 @@ three mentioned above, plus symbol and bigint).

    Further, never invoke the wrapper types as constructors (with new).

    -

    Return type only generics

    +

    Return type only generics

    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.

    -

    Consistency

    + + +

    + +

    Toolchain requirements

    + +

    Google style requires using a number of tools in specific ways, outlined here.

    + +

    + + + + + + + + + + + + + + + + +

    + + + +

    TypeScript compiler

    + +

    All TypeScript files must pass type checking using the standard + tool chain.

    + +

    @ts-ignore

    + +

    Do not use @ts-ignore nor the variants @ts-expect-error or @ts-nocheck.

    + +
    + +

    Why?

    + +

    They superficially seem to be an easy way to fix a compiler error, but in +practice, a specific compiler error is often caused by a larger problem that can +be fixed more directly.

    + +

    For example, if you are using @ts-ignore 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 how to best use any is useful.

    + +
    + + + +

    You may use @ts-expect-error in unit tests, though you generally should not. +@ts-expect-error suppresses all errors. It's easy to accidentally over-match +and suppress more serious errors. Consider one of:

    + + + + + + + +

    Conformance

    + +

    Google TypeScript includes several conformance frameworks, + +tsetse and +tsec.

    + + + +

    These rules are commonly used to enforce critical restrictions (such as defining +globals, which could break the codebase) and security patterns (such as using +eval or assigning to innerHTML), or more loosely to improve code quality.

    + +

    Google-style TypeScript must abide by any applicable global or framework-local +conformance rules.

    + + + + + +

    +

    + +

    Comments and documentation

    + + + +

    JSDoc versus comments

    + +

    There are two types of comments, JSDoc (/** ... */) and non-JSDoc ordinary +comments (// ... or /* ... */).

    + + + +

    JSDoc comments are understood by tools (such as editors and documentation +generators), while ordinary comments are only for other humans.

    + +

    + +

    Multi-line comments

    + +

    Multi-line comments are indented at the same level as the surrounding code. They +must use multiple single-line comments (//-style), not block comment style +(/* */).

    + +
    // This is
    +// fine
    +
    + +
    /*
    + * This should
    + * use multiple
    + * single-line comments
    + */
    +
    +/* This should use // */
    +
    + +

    Comments are not enclosed in boxes drawn with asterisks or other characters.

    + + + +

    JSDoc general form

    + +

    The basic formatting of JSDoc comments is as seen in this example:

    + +
    /**
    + * Multiple lines of JSDoc text are written here,
    + * wrapped normally.
    + * @param arg A number to do something to.
    + */
    +function doSomething(arg: number) { … }
    +
    + +

    or in this single-line example:

    + +
    /** This short jsdoc describes the function. */
    +function doSomething(arg: number) { … }
    +
    + +

    If a single-line comment overflows into multiple lines, it must use the +multi-line style with /** and */ on their own lines.

    + +

    Many tools extract metadata from JSDoc comments to perform code validation and +optimization. As such, these comments must be well-formed.

    + +

    Markdown

    + +

    JSDoc is written in Markdown, though it may include HTML when necessary.

    + +

    This means that tooling parsing JSDoc will ignore plain text formatting, so if +you did this:

    + +
    /**
    + * Computes weight based on three factors:
    + *   items sent
    + *   items received
    + *   last timestamp
    + */
    +
    + +

    it will be rendered like this:

    + +
    Computes weight based on three factors: items sent items received last timestamp
    +
    + +

    Instead, write a Markdown list:

    + +
    /**
    + * Computes weight based on three factors:
    + *
    + * - items sent
    + * - items received
    + * - last timestamp
    + */
    +
    + +

    JSDoc tags

    + +

    Google style allows a subset of JSDoc tags. Most tags must occupy their own line, with the tag at the beginning +of the line.

    + +
    /**
    + * 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) { ... }
    +
    + +
    /**
    + * The "param" tag must occupy its own line and may not be combined.
    + * @param left @param right
    + */
    +function add(left: number, right: number) { ... }
    +
    + + + +

    Line wrapping

    + +

    Line-wrapped block tags are indented four spaces. Wrapped description text may +be lined up with the description on previous lines, but this horizontal +alignment is discouraged.

    + +
    /**
    + * 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;
    +};
    +
    + +

    Do not indent when wrapping a @desc or @fileoverview description.

    + +

    Document all top-level exports of modules

    + +

    Use /** JSDoc */ comments to communicate information to the users of your +code. Avoid merely restating the property or parameter name. You should also +document all properties and methods (exported/public or not) whose purpose is +not immediately obvious from their name, as judged by your reviewer.

    + +

    Exception: Symbols that are only exported to be consumed by tooling, such as +@NgModule classes, do not require comments.

    + +

    Class comments

    + +

    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.

    + + + +

    Method and function comments

    + +

    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.

    + +

    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 This method ... before it.

    + +

    Parameter property comments

    + +

    A +parameter property +is a constructor parameter that is prefixed by one of the modifiers private, +protected, public, or readonly. A parameter property declares both a +parameter and an instance property, and implicitly assigns into it. For example, +constructor(private readonly foo: Foo), declares that the constructor takes a +parameter foo, but also declares a private readonly property foo, and +assigns the parameter into that property before executing the remainder of the +constructor.

    + +

    To document these fields, use JSDoc's @param annotation. Editors display the +description on constructor calls and property accesses.

    + + + +
    /** 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[]) {}
    +}
    +
    + +
    /** 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;
    +  }
    +}
    +
    + +

    +

    + +

    JSDoc type annotations

    + +

    JSDoc type annotations are redundant in TypeScript source code. Do not declare +types in @param or @return blocks, do not write @implements, @enum, +@private, @override etc. on code that uses the implements, enum, +private, override etc. keywords.

    + + + +

    Make comments that actually add information

    + +

    For non-exported symbols, sometimes the name and type of the function or +parameter is enough. Code will usually benefit from more documentation than +just variable names though!

    + + + +

    + +

    Comments when calling a function

    + +

    “Parameter name” comments should be used whenever the method name and parameter +value do not sufficiently convey the meaning of the parameter.

    + +

    Before adding these comments, consider refactoring the method to instead accept +an interface and destructure it to greatly improve call-site +readability.

    + +

    Parameter name comments go before the parameter value, and include the +parameter name and a = suffix:

    + +
    someFunction(obviousParam, /* shouldRender= */ true, /* name= */ 'hello');
    +
    + +

    Existing code may use a legacy parameter name comment style, which places these +comments ~after~ the parameter value and omits the =. Continuing to use this +style within the file for consistency is acceptable.

    + +
    someFunction(obviousParam, true /* shouldRender */, 'hello' /* name */);
    +
    + +

    Place documentation prior to decorators

    + +

    When a class, method, or property have both decorators like @Component and +JsDoc, please make sure to write the JsDoc before the decorator.

    + + + + + +

    Policies

    + +

    +

    + +

    Consistency

    For any style question that isn't settled definitively by this specification, do what the other code in the same file is already doing (be consistent). If that doesn't resolve the question, consider emulating the other files in the same directory.

    -

    Goals

    +

    Brand new files must 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 below. If this reformatting is not +done, then new code should be as consistent as possible with existing code in +the same file, but must not violate the style guide.

    + +

    +

    + +

    Reformatting existing code

    + +

    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.

    + +

    When updating the style of existing code, follow these guidelines.

    + +
      +
    1. 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.
    2. +
    3. 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.
    4. +
    + + + + + +

    + +

    Deprecation

    + +

    Mark deprecated methods, classes or interfaces with an @deprecated JSDoc +annotation. A deprecation comment must include simple, clear directions for +people to fix their call sites.

    + + + +

    + +

    Generated code: mostly exempt

    + +

    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.

    + + + +

    + +

    Style guide goals

    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 @@ -2640,20 +3978,7 @@ global rules. Evaluate your style guide proposal against the following:

  • Code should avoid patterns that are known to cause problems, especially for users new to the language.

    -

    Examples:

    - -
  • +
  • Code across projects should be consistent across irrelevant variations.

    @@ -2662,9 +3987,6 @@ irrelevant variations.

    should consider choosing one just so we don't divergently evolve for no reason and avoid pointless debates in code reviews.

    -

    We should usually match JavaScript style as well, because people often write -both languages together.

    -

    Examples:

  • - \ No newline at end of file +