Blog-Archiv

Samstag, 30. Juni 2018

How to Make TypeScript Check JavaScript

Experienced programmers know that projects written in languages that do not support typing get uncontrollable at a certain size, although this is widely ignored. Now we have this problem with user-interface development in JavaScript, no it's more, it's the whole presentation layer that changed from the server to the web-browser side. A presentation layer is a big thing. And of course, with all our legacy applications, there is no time to rewrite thousands of JavaScript source lines to TypeScript.

But can we make the TS compiler check our JS sources? Would it find type incompatibilities and other mistakes? The TS handbook promises it, so let's verify.

Compiler Switches

The tsc compiler provides an abundance of switches. To make it check the example checkme.js source we must use three of them (I refer here to tsc 2.7.2):

tsc --checkJs --allowJs --noEmit   checkme.js
  1. --checkJs: check also .js sources for correctness
  2. --allowJs: allows the file extension .js, the compiler insists on that when having --checkJs
  3. --noEmit: do not generate JS source, without this switch you would receive the error message
    error TS5055: Cannot write file 'checkme.js' because it would overwrite input file.
    

Open a commandline terminal window, change to the directory where your JS sources reside, and launch the commandline above. The tsc compiler will now check them.

Plain JS Sources

Let's try to compile following JS source code that obviously contains typing mistakes:

let count = 123;
count = "Hello";

let myName = "Garfield";
myName = 321;

const countList = [];
countList.push("World");
countList.push(1);

You should not assign a string value to the variable count that previously contained a number. The same for string-variable myName that receives a number. Then we have an array that we fill with both a number and a string, which is a mistake normally.

Put that code into a checkme.js file and compile it with tsc --checkJs --allowJs --noEmit checkme.js. In the following I have documented the resulting error-messages in the source lines that cause them:

let count = 123;
count = "Hello";  // error TS2322: Type '"Hello"' is not assignable to type 'number'.

let myName = "Garfield";
myName = 321;  // error TS2322: Type '321' is not assignable to type 'string'.

const countList = [];
countList.push("World");
countList.push(1);  // not an error!

The compiler detected the re-typing of the variables, but did not report the inconsistent array contents. For function calls we would get no results at all. So this is quite a poor check, but TS provides a better solution.

JS Sources with JsDoc Typing

They named it "type annotations" because the type-hints are prefixed by an ampersand "@", nevertheless these hints are inside JsDoc /** comments */. It looks like the following:

/** @type {number} */
let count = 0;

/**
 * @param {string} message - required, the message to display.
 * @param {string} [prefix] - optional, the prefix for the message.
 * @return {number} the number of characters in given message.
 */
function sayHello(message, prefix) {
    console.log(prefix+": "+message);
    return message.length;
}

These JsDoc annotations fully type the variable and the function, and all abuses will be reported by the TS compiler!

Here comes a buggy implementation, together with the resulting compiler messages:

/** @type {number} */
const count = "Hello";  // error TS2322: Type '"Hello"' is not assignable to type 'number'.

/**
 * @param {string} message - required, the message to display.
 * @param {string} [prefix] - optional, the prefix for the message.
 * @return {number} the number of characters in given message.
 */
function sayHello(message, prefix) {
    prefix = prefix || 1234;  // error TS2322: Type 'string | 1234' is not assignable to type 'string'.
    console.log(prefix+": "+message);
    return message;  // error TS2322: Type 'string' is not assignable to type 'number'.
}

/** @type {string} */
const result = sayHello(1, 2);  // error TS2345: Argument of type '1' is not assignable to parameter of type 'string'.
                                // error TS2322: Type 'number' is not assignable to type 'string'.

The variable count is typed to be a number by a JsDoc type annotation. When it receives a string value, the compiler detects and reports that pitfall. Inside function sayHello(), the string parameter prefix receives a default value that is a number, which is a mistake. The function returns a string, but should return a number. Finally the function is called with two number parameters, but both should be string. Its return is assigned to a string variable, but the return is number.

Put this into some .js file and compile it with tsc --checkJs --allowJs --noEmit *.js to see these error messages. The exit code of the compiler is 1 (non-zero), thus you can detect the problem in an automated build process.

Here comes array typing with JsDoc, and the resulting error message. The countList is typed to be a number-array, but receives a string, which is reported to be an error.

/** @type {number[]} */
const countList = [];
countList.push("World");  // error TS2345: Argument of type '"World"' is not assignable to parameter of type 'number'.

Not bad, isn't it?

Conclusion

You can also type classes and object literals. Find more information in the JsDoc-Support and Type-Checking Wiki articles on the TypeScript web page. Another quite useful TS page is the FAQ.




1 Kommentar:

James Alter hat gesagt…

Wow, great blog. I loved how you talked about the importance of freelance developers and the importance of hire a dedicated developer in India. I knew about Eiliana.com. It is one of the emerging freelance platforms you can look for projects there.