“This is the 16th day of my participation in the Gwen Challenge in November. Check out the details: The Last Gwen Challenge in 2021.”

JavaScript file type checking

Versions after TypeScript 2.3 support using –checkJs for type checking and error notification of.js files

You can ignore type checking by adding the // @ts-nocheck comment; Instead, you can optionally check some.js files by removing the –checkJs setting and adding a // @ts-check comment. You can also use // @ts-ignore to ignore errors on this line. If you use tsconfig.json, JS checks will follow strict checks such as noImplicitAny, strictNullChecks, etc. But because JS checking is relatively lax, there can be some surprises when using strict tags

When comparing.js files with.ts files in type checking, note the following:

Type information is represented by JSDoc types

In.js files, types can be inferred as in.ts files. Similarly, when types cannot be inferred, they can be specified by JSDoc, as in.ts files. Like TypeScript, –noImplicitAny reports errors where the compiler cannot infer the type. (Except in the case of object literals; More on that later)

JSDoc annotated declarations are set to the type of the declaration. Such as:

/ * *@type {number} * /
var x;

x = 0;      // OK
x = false;  // Error: boolean is not assignable to number
Copy the code

You can find all JSDoc supported modes here, JSDoc documentation.

Attributes are inferred from assignment statements within the class

ES2015 does not provide methods for declaring class attributes. Properties are dynamically assigned, just like object literals.

In the.js file, the compiler deduces the attribute type from the attribute assignment statement inside the class. The type of an attribute is the type of the value assigned in the constructor, unless it is not defined in the constructor or is undefined or null in the constructor. In this case, the type will be the combined type of all assigned values. Properties defined in constructors are considered permanent, whereas properties defined in methods and accessors are considered optional.

class C {
    constructor() {
        this.constructorOnly = 0
        this.constructorUnknown = undefined
    }
    method() {
        this.constructorOnly = false // error, constructorOnly is a number
        this.constructorUnknown = "plunkbat" // ok, constructorUnknown is string | undefined
        this.methodOnly = 'ok'  // ok, but y could also be undefined
    }
    method2() {
        this.methodOnly = true  // also, ok, y's type is string | boolean | undefined}}Copy the code

If a property has never been set within a class, they are treated as unknown.

If a class’s attributes are for reading only, declare its type in JSDoc in the constructor. If it is initialized later, you don’t even need to assign it a value in the constructor:

class C {
    constructor() {
        / * *@type {number | undefined} * /
        this.prop = undefined;
        / * *@type {number | undefined} * /
        this.count; }}let c = new C();
c.prop = 0;          // OK
c.count = "string";  // Error: string is not assignable to number|undefined
Copy the code

Constructors are equivalent to classes

Before ES2015, Javascript used constructors instead of classes. The compiler supports this pattern and recognizes the constructor as an ES2015 class. The attribute type inference mechanism is the same as described above.

function C() {
    this.constructorOnly = 0
    this.constructorUnknown = undefined
}
C.prototype.method = function() {
    this.constructorOnly = false // error
    this.constructorUnknown = "plunkbat" // OK, the type is string | undefined
}
Copy the code

CommonJS module is supported

TypeScript recognizes CommonJS modules in.js files. Assignments to exports and module.exports are recognized as export declarations. Similarly, the require function call is recognized as a module import. Such as:

// same as `import module "fs"`
const fs = require("fs");

// same as `export function readFile`
module.exports.readFile = function(f) {
  return fs.readFileSync(f);
}
Copy the code

Support for module syntax in JavaScript files is much broader than in TypeScript. Most assignments and declarations are allowed.

Class, function, and object literals are namespaces

The classes in.js files are namespaces. It can be used for nested classes, such as:

class C {
}
C.D = class {}Copy the code

Code prior to ES2015, which can be used to simulate static methods:

function Outer() {
  this.y = 2
}
Outer.Inner = function() {
  this.yy = 2
}
Copy the code

It can also be used to create simple namespaces:

var ns = {}
ns.C = class {
}
ns.func = function() {}Copy the code

Other changes are also supported:

// The function expression that is called immediately
var ns = (function (n) {
  returnn || {}; }) (); ns.CONST =1

// defaulting to global
var assign = assign || function() {
  // code goes here
}
assign.extra = 1
Copy the code

Object literals are open

The.ts file initializes a variable with an object literal and also declares its type. New members can no longer be added to object literals. This rule is relaxed in the.js file; Object literals have open types that allow you to add and access properties that were not previously defined. Such as:

var obj = { a: 1 };
obj.b = 2;  // Allowed
Copy the code

Object literals behave as if they had a default index signature [x: String]: any, and they can be treated as open mappings rather than closed objects.

Similar to other JS checking behavior, this behavior can be changed by specifying the JSDoc type, for example:

/ * *@type {{a: number}} * /
var obj = { a: 1 };
obj.b = 2;  // Error, type {a: number} does not have property b
Copy the code

Null, undefined, and empty arrays are of type any or any[]

Any variable, parameter, or attribute initialized with null, undefined, that is of type any, even in strict NULL checking mode. Any variable, parameter, or attribute initialized with [] is of type any[], even in strict null checking mode. The only exception is a property that has multiple initializers like the one above.

function Foo(i = null) {
    if(! i) i =1;
    var j = undefined;
    j = 2;
    this.l = [];
}
var foo = new Foo();
foo.l.push(foo.i);
foo.l.push("end");
Copy the code

Function arguments are optional by default

Since optional arguments cannot be specified before ES2015, all function arguments in the.js file are treated as optional. It is permissible to call a function with fewer arguments than expected.

One thing to note is that calling a function with too many arguments gets you an error.

Such as:

function bar(a, b) {
  console.log(a + "" + b);
}

bar(1);       // OK, second argument considered optional
bar(1.2);
bar(1.2.3); // Error, too many arguments
Copy the code

Functions that use JSDoc annotations are removed from this rule. Use the JSDoc optional parameter syntax to indicate optionality. Such as:

/ * * *@param {string} [somebody] - Somebody's name.
 */
function sayHello(somebody) {
    if(! somebody) { somebody ='John Doe';
    }
    console.log('Hello ' + somebody);
}

sayHello();
Copy the code

byargumentsInferred var-args parameter declaration

If a function has a reference to arguments in its function body, the function is implicitly assumed to have a var-arg argument (e.g. :(… Arg: any[]) => any)). Use JSDoc’s var-arg syntax to specify the types of arguments.

/ * *@param {... number} args */
function sum(/* numbers */) {
    var total = 0
    for (var i = 0; i < arguments.length; i++) {
      total += arguments[i]
    }
    return total
}
Copy the code

Unspecified type parameters default toany

Since there is no natural syntax in JavaScript for specifying generic parameters, the default parameter type is any.

In the extends statement:

For example, react.componentis defined to have two type parameters, Props and State. There is no legal way to specify them in an extends statement in a.js file. The default parameter type is any:

import { Component } from "react";

class MyComponent extends Component {
    render() {
        this.props.b; // Allowed, since this.props is of type any}}Copy the code

Use JSDoc’s @Augments to specify the type explicitly. Such as:

import { Component } from "react";

/ * * *@augments {Component<{a: number}, State>}
 */
class MyComponent extends Component {
    render() {
        this.props.b; // Error: b does not exist on {a:number}}}Copy the code

In the JSDoc reference:

Unspecified type arguments in JSDoc default to any:

/ * *@type{Array} * /
var x = [];

x.push(1);        // OK
x.push("string"); // OK, x is of type Array<any>

/ * *@type{Array.<number>} * /
var y = [];

y.push(1);        // OK
y.push("string"); // Error, string is not assignable to number
Copy the code

In a function call

Calls to generic functions use arguments to infer generic arguments. Sometimes, the process fails to infer a type, mostly because of a lack of sources for inference; In this case, the type parameter type defaults to any. Such as:

var p = new Promise((resolve, reject) = > { reject() });

p; // Promise<any>;
Copy the code

Support the JSDoc

The following list lists the previously supported JSDoc annotations that you can use to add type information to JavaScript files.

Note that tags not listed below (such as @async) are not yet supported.

  • @type
  • @param (or @arg or @argument)
  • @returns (or @return)
  • @typedef
  • @callback
  • @template
  • @class (or @constructor)
  • @this
  • @extends (or @augments)
  • @enum

The meanings they represent are usually the same as those given at usejsdoc.org or are supersets of them. The following code describes the differences and gives some examples.

@type

You can use the @type tag and refer to a type name (primitive, declared in TypeScript, or specified in the @typedef tag in JSDoc) you can use any TypeScript type and most JSDoc types.

/ * * *@type {string}* /
var s;

/ * *@type {Window} * /
var win;

/ * *@type {PromiseLike<string>} * /
var promisedString;

// You can specify an HTML Element with DOM properties
/ * *@type {HTMLElement} * /
var myElement = document.querySelector(selector);
element.dataset.myData = ' ';
Copy the code

@type can specify the union type – for example, the union of string and Boolean types.

/ * * *@type {(string | boolean)}* /
var sb;
Copy the code

Note that parentheses are optional.

/ * * *@type {string | boolean}* /
var sb;
Copy the code

There are several ways to specify an array type:

/ * *@type {number[]} * /
var ns;
/ * *@type {Array.<number>} * /
var nds;
/ * *@type {Array<number>} * /
var nas;
Copy the code

You can also specify object literal types. For example, an object with attributes a (string) and b (number) uses the following syntax:

/ * *@type {{ a: string, b: number }} * /
var var9;
Copy the code

You can specify map-like and array-like objects using string and numeric index signatures, using standard JSDoc or TypeScript syntax.

/**
 * A map-like object that maps arbitrary `string` properties to `number`s.
 *
 * @type {Object.<string, number>}* /
var stringToNumber;

/ * *@type {Object.<number, object>} * /
var arrayLike;
Copy the code

These two types are equivalent to {[x: string]: number} and {[x: number]: any} in TypeScript. The compiler recognizes these two grammars.

Function types can be specified using TypeScript or Closure syntax.

/ * *@type {function(string, boolean): number} Closure syntax */
var sbn;
/ * *@type {(s: string, b: boolean) => number} Typescript syntax */
var sbn2;
Copy the code

Or use an unspecified Function type:

/ * *@type {Function} * /
var fn7;
/ * *@type {function} * /
var fn6;
Copy the code

Other types of Closure can also be used:

/ * * *@type {*} - can be 'any' type
 */
var star;
/ * * *@type {? } - unknown type (same as 'any')
 */
var question;
Copy the code

conversion

TypeScript borrows from Closure’s transformation syntax. You can convert one type to another by using the @type tag in front of a parenthesized expression

/ * * *@type {number | string}* /
var numberOrString = Math.random() < 0.5 ? "hello" : 100;
var typeAssertedNumber = / * *@type {number} * / (numberOrString)
Copy the code

The import type

You can use import types to import declarations from other files. This syntax is specific to TypeScript and differs from the JSDoc standard:

/ * * *@param p { import("./a").Pet }
 */
function walk(p) {
    console.log(`Walking ${p.name}. `);
}
Copy the code

Import types can also be used in type alias declarations:

/ * * *@typedef Pet { import("./a").Pet }
 */

/ * * *@type {Pet}* /
var myPet;
myPet.name;
Copy the code

Import types can be used to get a value from a module.

/ * * *@type {typeof import("./a").x }* /
var x = require("./a").x;
Copy the code

@paramand@returns

@param has the same syntax as @type, but adds a parameter name. Parameters can be declared optional with [] :

// Parameters may be declared in a variety of syntactic forms
/ * * *@param {string}  p1 - A string param.
 * @param {string=} p2 - An optional param (Closure syntax)
 * @param {string} [p3] - Another optional param (JSDoc syntax).
 * @param {string} [p4="test"] - An optional param with a default value
 * @return {string} This is the result
 */
function stringsStringStrings(p1, p2, p3, p4){
  // TODO
}
Copy the code

The return type of the function is similar:

/ * * *@return {PromiseLike<string>}* /
function ps(){}

/ * * *@returns {{ a: string, b: number }} - May use '@returns' as well as '@return'
 */
function ab(){}
Copy the code

@typedef.@callback, and@param

A @typedef can be used to declare complex types. Similar syntax to @param.

/ * * *@typedef {Object} SpecialType - creates a new type named 'SpecialType'
 * @property {string} prop1 - a string property of SpecialType
 * @property {number} prop2 - a number property of SpecialType
 * @property {number=} prop3 - an optional number property of SpecialType
 * @prop {number} [prop4] - an optional number property of SpecialType
 * @prop {number} [prop5=42] - an optional number property of SpecialType with default
 */
/ * *@type {SpecialType} * /
var specialTypeObject;
Copy the code

You can use object or object on the first line.

/ * * *@typedef {object} SpecialType1 - creates a new type named 'SpecialType'
 * @property {string} prop1 - a string property of SpecialType
 * @property {number} prop2 - a number property of SpecialType
 * @property {number=} prop3 - an optional number property of SpecialType
 */
/ * *@type {SpecialType1} * /
var specialTypeObject1;
Copy the code

@param allows similar syntax. Note that nested attribute names must be prefixed with parameter names:

/ * * *@param {Object} options - The shape is the same as SpecialType above
 * @param {string} options.prop1
 * @param {number} options.prop2
 * @param {number=} options.prop3
 * @param {number} [options.prop4]
 * @param {number} [options.prop5=42]
 */
function special(options) {
  return (options.prop4 || 1001) + options.prop5;
}
Copy the code

@callback is similar to @typedef, but it specifies a function type instead of an object type:

/ * * *@callback Predicate
 * @param {string} data
 * @param {number} [index]
 * @returns {boolean}* /
/ * *@type {Predicate} * /
const ok = s= >! (s.length %2);
Copy the code

Of course, all of these types can be declared on a single line using TypeScript’s @typedef syntax:

/ * *@typedef {{ prop1: string, prop2: string, prop3? : number }} SpecialType */
/ * *@typedef {(data: string, index? : number) => boolean} Predicate */
Copy the code

@template

Declare generics with @template:

/ * * *@template T
 * @param {T} p1 - A generic parameter that flows through to the return type
 * @return {T}* /
function id(x){ return x }
Copy the code

To declare multiple type arguments with commas or tags:

/ * * *@template T,U,V
 * @template W,X
 */
Copy the code

You can also specify type constraints before parameter names. Only the first type parameter of the list is constrained:

/ * * *@template {string} K - K must be a string or string literal
 * @template {{ serious(): string }} Seriousalizable - must have a serious method
 * @param {K} key
 * @param {Seriousalizable} object* /
function seriousalize(key, object) {
  / /??????
}
Copy the code

@constructor

The compiler deduces the constructor from the assignment of this, but you can make the checking more rigorous and the hint friendlier by adding a @constructor tag:

/ * * *@constructor
 * @param {number} data* /
function C(data) {
  this.size = 0;
  this.initialize(data); // Should error, initializer expects a string
}
/ * * *@param {string} s* /
C.prototype.initialize = function (s) {
  this.size = s.length
}

var c = new C(0);
var result = C(1); // C should only be called with new
Copy the code

With @constructor, this will be checked in constructor C, so you get a prompt in the initialize method and an error if you pass in a number. If you call C directly instead of constructing it, you’ll also get an error.

Unfortunately, this means that constructors that can either be constructed or called directly cannot use @constructor.

@this

The compiler can usually infer the type of this from the context. But you can specify its type explicitly using @this:

/ * * *@this {HTMLElement}
 * @param {*} e* /
function callbackForLater(e) {
    this.clientHeight = parseInt(e) // should be fine!
}
Copy the code

@extends

When a JavaScript class inherits a base class, there is no way to specify the type of the type parameter. The @extends tag provides a way to:

/ * * *@template T
 * @extends {Set<T>}* /
class SortableSet extends Set {
  // ...
}
Copy the code

Note that @extends only applies to classes. Currently, the constructor cannot be implemented to inherit a class.

@enum

The @enum tag allows you to create an object literal whose members have certain types. Unlike most object literals in JavaScript, it does not allow additional members.

/ * *@enum {number} * /
const JSDocState = {
  BeginningOfLine: 0.SawAsterisk: 1.SavingComments: 2,}Copy the code

Note that @enum is very different from @enum in TypeScript, which is much simpler. However, unlike TypeScript enumerations, @enum can be of any type:

/ * *@enum {function(number): number} * /
const Math = {
  add1: n= > n + 1.id: n= > -n,
  sub1: n= > n - 1,}Copy the code

More examples

var someObj = {
  / * * *@param {string} param1 - Docs on property assignments work
   */
  x: function(param1){}};/**
 * As do docs on variable assignments
 * @return {Window}* /
let someFunc = function(){};

/**
 * And class methods
 * @param {string} greeting The greeting to use
 */
Foo.prototype.sayHi = (greeting) = > console.log("Hi!");

/**
 * And arrow functions expressions
 * @param {number} x - A multiplier
 */
let myArrow = x= > x * x;

/**
 * Which means it works for stateless function components in JSX too
 * @param {{a: string, b: number}} test - Some param
 */
var sfc = (test) = > <div>{test.a.charAt(0)}</div>;

/**
 * A parameter can be a class constructor, using Closure syntax.
 *
 * @param {{new(... args: any[]): object}} C - The class to register
 */
function registerClass(C) {}

/ * * *@param {... string} p1 - A 'rest' arg (array) of strings. (treated as 'any')
 */
function fn10(p1){}

/ * * *@param {... string} p1 - A 'rest' arg (array) of strings. (treated as 'any')
 */
function fn9(p1) {
  return p1.join();
}
Copy the code

An unsupported schema is known

It is not possible to treat an object as a type in a value space unless the object has created a type, such as a constructor.

function aNormalFunction() {}/ * * *@type {aNormalFunction}* /
var wrong;
/**
 * Use 'typeof' instead:
 * @type {typeof aNormalFunction}* /
var right;
Copy the code

The = suffix on an object literal attribute does not specify that the attribute is optional:

/ * * *@type {{ a: string, b: number= }} * /
var wrong;
/**
 * Use postfix question on the property name instead:
 * @type {{ a: string, b? : number }} * /
var right;
Copy the code

The Nullable type is used only when strictNullChecks are enabled:

/ * * *@type {? number}
 * With strictNullChecks: true -- number | null
 * With strictNullChecks: off  -- number
 */
var nullable;
Copy the code

Non-nullable types have no meaning and are treated as the original type:

/ * * *@type {! number}
 * Just has type number
 */
var normal;
Copy the code

Unlike the JSDoc type system, TypeScript only allows types to be marked as packages that do not contain NULL. There is no explicit non-nullable – if strictNullChecks is enabled, then the number is non-null. If not enabled, number can be null.