Colorize the log in the Google Chrome console

It's very usual to log some data using the console API. When your application grows, the console may becomes unreadable. Thus, you may miss an important message. In Chromium based browsers (and maybe other browsers), you can add style to your logs. This means you can change the color, background, font size, and almost anything you want as long as you can express it in CSS.

Use %c in the message and set the css in the second argument of the method.

Here's some examples:

console.log('%c Message with color', 'background: red; color: white;');

console.log('%cMessage with %ccolor', 'color: blue;', 'color: red;');

console.log('%cMessage with %cdifferent sizes', 'color: blue;', 'color: red; font-size:20px');

Don't overuse this trick if you don't want to transform your console into a work of art of Picasso!

Detect missing await in TypeScript

Using promises in JavaScript/TypeScript is very common. More and more APIs use them, including the ones provided by the browser such as fetch or Service worker registration. Using the async/await syntax, promises are very convenient to use.

function delay(ms: number) {
    return new Promise(resolve => {
        setTimeout(resolve, ms);
    });
}

async function simulateLongComputation() {
    await delay(100);
    return 42;
}

However, it's also very easy to forget awaiting a promise, mainly if you are refactoring your code. Even when you don't await a promise, the ts file is perfectly valid, so the TypeScript compiler can compile without error. But the code does not behave as expected. This pattern is called fire and forget because you start a promise but you'll never get the result of the promise (success or failure). This is not ofen what you want.

function checkValueAsync(value: any) {
    return new Promise((resolve, reject) => {
        if (value) {
            resolve();
        } else {
            reject();
        }
    });
}

try {
    checkValueAsync(null); // missing await here
} catch (ex) {
    console.log(ex); // no error, because the promise is not awaited
}

The only information is an error in the console if the promise failed:

Google Chrome Developer tools error message

If you are familiar with C#, you know there is a compilation warning to prevent this mistake. The TypeScript compiler doesn't provide such option. So, you have to use another solution. Since the version 4.4, TSLint can check your code does not contain floating promise.

Using TSLint to detect floating promises

TSLint is an extensible static analysis tool that checks TypeScript code for readability, maintainability, and functionality errors. It is widely supported across modern editors & build systems and can be customized with your own lint rules, configurations, and formatters.

  1. Download TSLint: npm install -g tslint typescript
  2. Configure TSLint:

TSLint provides lots of rules. In this post, we'll only use the one to detect floating promises.

Create a file named tslint.json int the root directory of your project with the following content:

{
    "extends": "tslint:recommended",
    "rules": {
        "no-floating-promises": true
    }
}

Note: If you are using JQuery promises or any other alternative to standard promises, you have to adapt the configuration with something similar to: "no-floating-promises": [true, "JQueryPromise"].

  1. Run tslint: tslint --project tsconfig.json
PS F:\Samples> tslint --project tsconfig.json

ERROR: F:/Samples/index.ts[12, 5]: Promises must be handled appropriately

You can now easily detect where you forgot to await promises thanks to TSLint 😃

BTW, if you actually want the fire and forget behavior, you can disable the tslint rule for a specific line:

// tslint:disable-next-line:no-floating-promises
checkValueAsync(null);

Or you can change the return type of the statement using void:

void checkValueAsync(null);

Conclusion

TSLint is a valuable tool to add to your toolbox. You can easily integrate it in your build pipeline. In this post, I show only one rule of TSLint, but you can activate more than 100 rules to ensure your code is well-written!

Additional links:

Detect common JavaScript errors with TypeScript

TypeScript can help you writing less bugs! Each version improve the detection of common errors. However, all the checks are not enabled by default. In this blog post, we'll see the differents compiler options and the kind of errors they help to catch. There are no specific order beacuse all of them are important.

If you're still not using TypeScript, you can read my previous post: Still not using TypeScript?.

Invalid arguments, unknown method / property, or typo

TypeScript knows what is valid or not as it knows the types of the variables, or functions. This prevents typo or using an unknown method or property, or using an argument of type string instead of number, etc. So, you don't need to execute your code to detect this kind of issue.

var author = { nickname: "meziantou", firstname: "Gérald", lastname: "Barré" };
console.log(author.lastName);     // Error: property 'lastName' does not exist on type '...'. Did you mean 'lastname'?
author.nickname.trimStart();      // Error: property 'trimStart' doest not exist on type 'string'.
author.firstname.charCodeAt("1"); // Error: Argument of type 'string' is not assignable to parameter of type 'number'.

Null- and undefined-aware types

I think strictNullChecks is the most important TypeScript compiler flag. This will help you detect lots of potential errors. We often suppose a value is not null or undefined, which is not always true. The strictNullChecks option considers null and undefined as different types. So, if a variable can be null, you must explicitly declare it as type | null;

{
    "compilerOptions": {
        "strictNullChecks": true
    }
}
function a(s: string) {
    console.log(s.length); // ok
}

a(null); // error: null is not assignable to string

function b(s: string | null) {
    console.log(s.length); // error: s is probably 'null'
}

function c(s?: string) {
    console.log(s.length); // error: s is probably 'undefined'
}

Report error when not all code paths in function return a value

The noImplicitReturns compiler option ensures you always write a return statement in all branches of a method, if at least one branch contains a return statement with a value.

{
    "compilerOptions": {
        "noImplicitReturns": true
    }
}
// Error: Not all code paths return a value.
function noImplicitReturns(a: number) {
    if (a > 10) {
        return a;
    }
    // No return in this branch
}

Report error on unreachable code.

The allowUnreachableCode compiler option ensures you don't have dead code in a function. It detects unreachable code such as if (constant) or code after returns. Note that in JavaScript, the return statement ends at the end of the line. So, if you write the value of the return statement on the next line, the function will return undefined, and the next line is never executed.

{
    "compilerOptions": {
        "allowUnreachableCode": false
    }
}
function allowUnreachableCode() {
    if (false) {
        console.log("Unreachable code");
    }

    var a = 1;
    if (a > 0) {
        return
            10; // Unreachable code
    }

    return 0;
    console.log("Unreachable code");
}

Report errors on unused locals or unused parameters

The noUnusedLocals and noUnusedParameters compiler options ensure you don't have unused variables or parameters. This may occur after code refactoring, and just because you forget a part of your algorithm. For the compiler, an unused parameter or variable is a parameter or a variable with no read access.

{
    "compilerOptions": {
        "noUnusedLocals": true,
        "noUnusedParameters": true
    }
}
function f() {
    var a = "foo"; // Error: 'a' is declared but its value is never read
    return "bar";
}

function f(n: number) {
    n = 0; // Never read
}

class C {
    private m: number; // this.m is never read
    constructor() {
        this.m = 0;
    }
}

Report errors on excess property for object literals

The suppressExcessPropertyErrors compiler option check your don't have unexpected properties in an object. This may help you detecting typo in your code.

{
    "compilerOptions": {
        "suppressExcessPropertyErrors": false
    }
}
var x: { foo: number };
x = { foo: 1, baz: 2 };  // Error, excess property 'baz'

var y: { foo: number, bar?: number };
y = { foo: 1, baz: 2 };  // Error, excess property 'baz'

Report errors on this expressions with an implied any type

A function's this keyword behaves a little differently in JavaScript compared to other languages. In most cases, the value of this is determined by how a function is called. It can't be set by assignment during execution, and it may be different each time the function is called.

You can read more about the this in the MDN documentation.

TypeScript can't detect automatically the type of this in a function because the value of this depends on the way the function is called. There, it's type is any and you can't check its usage correctly. You can prevent this behavior by forcing the typing of this. Thus, your code will be typed, and you'll get all the benefices of that.

{
    "compilerOptions": {
        "noImplicitThis": true
    }
}
function noImplicitThis() {
    return this.length; // Error: 'this' implicitly has type 'any' because it does not have a type annotation
}

You can correct your code by typing this:

function noImplicitThis(this: string[]) {
    return this.length; // ok
}

Report errors for fallthrough cases in switch statement

Sometimes, you can forget the break keywork in a switch case. This may lead to undesired behavior. With the noFallthroughCasesInSwitch compiler option, TypeScript detects the missing break and report an error.

{
    "compilerOptions": {
        "noFallthroughCasesInSwitch": true
    }
}

Disable bivariant parameter checking for function types

The strictFunctionTypes compiler option ensures you are using actually compatible functions (arguments and return type).

{
    "compilerOptions": {
        "strictFunctionTypes": true
    }
}
interface Animal { name: string; };
interface Dog extends Animal { breed: string; };

var f = (animal: Animal) => animal.name;
var g = (dog: Dog) => dog.breed;

f = g; // error: g is not compatible with f
// error: Type '(dog: Dog) => any' is not assignable to type '(animal: Animal) => any'.
// Types of parameters 'dog' and 'animal' are incompatible.
//   Type 'Animal' is not assignable to type 'Dog'.
//     Property 'dogProp' is missing in type 'Animal'.

The following blog post explains in details why those 2 functions are incompatible: https://blogs.msdn.microsoft.com/typescript/2017/10/31/announcing-typescript-2-6/

  • Is it okay for a value of type (dog: Dog) => any to say it can be used in place of a (animal: Animal) => any?
    • Is it okay to say my function only expects an Animal when it may use properties that on Dog?
      • Only if an Animal can be used in place of a Dog – so is Animal assignable to Dog?
        • No! It’s missing dogProp.

Report errors for indexing objects lacking index signatures

The suppressImplicitAnyIndexErrors option prevents you from accessing properties using the indexer syntax, unless the property is actually defined or an indexer is defined.

{
    "compilerOptions": {
        "suppressImplicitAnyIndexErrors": false
    }
}
var x = { a: 0 };
x["a"] = 1; // ok
x["b"] = 1; // Error, type '{ a: number; }' has no index signature.

Parse in strict mode and emit "use strict" for each source file

The alwaysStrict compiler option indicates the compiler to always parse the file in strict mode and to generate "use strict";, so you don't have to set it in every file. If you don't know what is the strict mode, I should read the great post from John Resig: https://johnresig.com/blog/ecmascript-5-strict-mode-json-and-more/

Strict mode helps out in a couple ways:

  • It catches some common coding bloopers, throwing exceptions.
  • It prevents, or throws errors, when relatively "unsafe" actions are taken (such as gaining access to the global object).
  • It disables features that are confusing or poorly thought out.
{
    "compilerOptions": {
        "alwaysStrict": true
    }
}

Report errors on unused labels

The usage of labels is very uncommon. If you don't know them, it's ok. If you are curious, you can jump to the documentation. TypeScript provides a compiler option to ensure you don't have unused label in your code.

{
    "compilerOptions": {
        "allowUnusedLabels": false
    }
}
loop1:
for (let i = 0; i < 3; i++) {
    loop2: // Error: Unused label.
    for (let j = 0; j < 3; j++) {
        break loop1;
    }
}

Report errors on expressions and declarations with an implied any type

When the TypeScript compiler cannot determine the type of an object, it uses any. Therefore, you cannot get advantages of the type checker. This is not what you want when you use TypeScript, so the compiler raises an error. You can fix this error by specifying the type.

{
    "compilerOptions": {
        "noImplicitAny": true
    }
}
function noImplicitAny(args) { // Error: Parameter 'args' implicitly has an 'any' type.
    console.log(args);
}
function noImplicitAny(args: string[]) { // ok with the type information
    console.log(args);
}

Report errors in *.js files

If you use js files along with ts files, you can check them using TypeScript. The compiler will use information from JSDoc, imports, and usages to validate your code. The verification won't be as powerful as for ts files, but this is a good start. You can read more about how the compiler handle js files in the wiki: https://github.com/Microsoft/TypeScript/wiki/Type-Checking-JavaScript-Files

{
    "compilerOptions": {
        "allowJs": true,
        "checkJs": true
    }
}

Conclusion

TypeScript has a lot of options to increase the robustness of your code. By enabling all of them, you'll reduce the number of errors at runtime. Then, you can use some nice technics to help you writing even better code, such as the pseudo nameof operator.

TypeScript - nameof operator equivalent

A lot of JavaScript frameworks involve computation based on the property name of an object. So, you end with magic strings in your code. Magic strings are bad because it makes your code hard to refactor, you cannot find all references, etc. In C# 6, you can use the nameof operator. This is very useful. However, this is not possible in TypeScript yet. There are many issues asking to support this operator. In the meanwhile, you can create a function that simulates the nameof operator, at least for the most common case, using the keyof operator.

function nameof<T>(key: keyof T, instance?: T): keyof T {
    return key;
}

You can use the function using 2 ways:

function test(user: User) {
    nameof<User>("id"); // returns "id"
    nameof<User>("Id"); // Error
    nameof("id", user); // returns "id", without specifying the generic type
}

The nameof function is very cool because it checks the string is valid and it also provides autocompletion 😃

TypeScript autocompletion

TypeScript error

You'll find lots of great resources in the advanced types section of the documentation:

GitHub issues about nameof (or related) in TypeScript: