JavaScript annotation for Closure compiler questions

I have the following typedef in an externs file:

/** @typedef ({eventNameArray: Array.<string>,eventArrayIndex: number}) */ var triggerNextData; 

I would like it to be used as the passed parameter for the triggerNext function (which will trigger the next event). Both eventNameArray (an array of strings) and eventArrayIndex are required.

Here is the triggerNext function that expects this type:

 /** * @type {function(triggerNextData)} */ triggerNext: function(data){ ... } 

When I call it like this:

 mediator.triggerNext("hi there"); 

I get the warning as expected, but the warning says that eventNameArray is optional:

found: string required: {eventArrayIndex: number, eventNameArray: (Array. | Null)} mediator.triggerNext ("hello there");

Be that as it may, this does not require an array of strings (an array of type not shown), and the array is optional.

The following is a warning:

 mediator.triggerNext({eventNameArray:null,eventArrayIndex:0}); mediator.triggerNext({eventNameArray:[22,33],eventArrayIndex:0}); 

I would like to know how to introduce eventNameArray as a required array of strings, can this be done? If so, how do I do this?

+4
source share
2 answers

copied verbatim from clostools.blogspot.co.uk/2012/02/type-checking-tips.html due to "Thank you for your suggestion. Too bad blogpot is blocked here in China, so I can not read it. see a lot articles about it on blogs, but they’re not visible. "

The Closure Compiler programming language is a bit more complicated. It has unions ("the variable x can be A or B"), structural functions ("the variable x is a function that returns a number") and record types ("the variable x is any object with the properties foo and bar").

Many have told us that this is still not expressive enough. There are many ways to write JavaScript that are not suitable for our type system. People suggested that we should add mixins, traits, and post-hoc names to anonymous objects.

This is not surprising to us. JavaScript object rules are a bit like Calvinball rules. You can change something and come up with new rules. Many people think that a good type system gives you a powerful way to describe how your program is structured. But it also gives you a set of rules. The type system ensures that everyone agrees on what a "class" is and what an "interface" is and what it means "is." When you try to add type annotations to untyped JS, you will inevitably run into problems when the rules in my head don't quite match the rules in your head. This is normal.

But we were surprised that when we gave people this type system, they often found several ways to express the same thing. Some methods worked better than others. I thought Id wrote this to describe some things that people tried, and how they worked.

Function versus Function ()

There are two ways to describe a function. One of them is to use the type {Function}, which the compiler literally interprets as “any object x, where“ x instanceof Function is true. ”A {Function} intentionally soft. It can take any arguments and return anything. You can even use “new on it.” The compiler will let you call it, but you want it without warning.

The structural function is much more specific and gives you small-scale control over what the function can do. A {function()} takes no arguments, but we don't care what it returns. A {function(?): number} returns a number and takes exactly one argument, but we do not care about the type of this argument. A {function(new:Array)} creates an array when you call it with "new". Our type documentation and JavaScript style guide have more examples of using structure functions.

Many people have asked us if {Function} discouraged because it is less specific. Actually, this is very useful. For example, consider the definition of Function.prototype.bind . It allows you to execute functions: you can give it a function and an argument list, and it will return you a new function with these pre-filled in arguments. It is impossible for our type system to express that the return type of a function is a type conversion of the first argument. So the JSDoc on Function.prototype.bind says that it returns {Function} , and the compiler should have manually encoded logic to determine the real type.

There are also many cases where you want to pass a callback function to collect results, but the results depend on the context.

 rpc.get('MyObject', function(x) { // process MyObject }); 

The "rpc.get" method is much more awkward if the callback argument you pass must enter-cast whatever it receives. Therefore, it is often easier to specify the parameter a {Function} and trust that the type of the caller is not worth checking the type.

Object vs Anonymous Objects

Many JS libraries define one global object with many methods. What type annotation should this object have?

 var bucket = {}; /** @param {number} stuff */ bucket.fill = function(stuff) {}; 

If you came from Java, you might be tempted to just give it a {Object} type.

 /** @type {Object} */ var bucket = {}; /** @param {number} stuff */ bucket.fill = function(stuff) {}; 

This is usually not what you want. If you add the annotation "@type {Object}", you are not just saying to the compiler "bucket is an object." You say that "the bucket is allowed to be any object." Therefore, the compiler should assume that anyone can assign any bucket object, and the program will still be type safe.

Instead, you're often better off using @const.

 /** @const */ var bucket = {}; /** @param {number} stuff */ bucket.fill = function(stuff) {}; 

Now we know that the bucket cannot be attached to any other object, and the output mechanism of compilers can greatly strengthen the verification of the bucket and its methods.

Can everything be a record type?

The JavaScripts type system is not so complex. It has 8 types with special syntax: null, undefined, boolean, number, string, Object, Array and Function. Some people have noticed that record types allow you to define an "object with x, y, and z properties," and that typedefs let you specify a name for an expression of any type. Thus, between the two, you should be able to define any custom type with record types and typedef. Is that all we need?

Record types are great when you need a function to accept a large number of optional parameters. So, if you have this function:

 /** * @param {boolean=} withKetchup * @param {boolean=} withLettuce * @param {boolean=} withOnions */ function makeBurger(withKetchup, withLettuce, withOnions) {} 

you can make it a little easier, for example:

 /** * @param {{withKetchup: (boolean|undefined), withLettuce: (boolean|undefined), withOnions: (boolean|undefined)}=} options */ function makeBurger(options) {} 

It works well. But when you use the same type of recording in many places in the program, things can get a little hairy. Suppose you created a type for the makeBurgers parameter:

 /** @typedef {{withKetchup: (boolean|undefined), withLettuce: (boolean|undefined), withOnions: (boolean|undefined)}=} */ var BurgerToppings; /** @const */ var bobsBurgerToppings = {withKetchup: true}; function makeBurgerForBob() { return makeBurger(bobsBurgerToppings); } 

Alice later builds a restaurant app on top of the Bobs library. In a separate file, she tries to add a bow, but bolts the API.

 bobsBurgerToppings.withOnions = 3; 

The Closure Compiler will notice that bobsBurgerToppings no longer matches the BurgerToppings record type. But he will not complain about Alice's code. He will complain that the Bobs code is creating a type error. For non-trivial programs, it can be very difficult for Bob to understand why the types no longer match.

A good type system does not just express type contracts. It also gives us a good way to assign blame when the code breaks the contract. Since a class is usually defined in one place, the compiler can determine who is responsible for violating the class definition. But when you have an anonymous object that is passed to many different functions and has properties set from many disparate places, it is much harder for both people and compilers to find out who violates type contracts.

Posted by Nick Santos, Software Engineer

+2
source

By default, all objects can be null :

All object types are NULL by default, regardless of whether they are declared using the Nullable operator.

You can use ! , to say that it must be a nonzero value:

 @typedef ({eventNameArray:!Array.<string>, eventArrayIndex:number}) 

As for the elements of the array, which are strings, I don't know (yet).

+3
source

All Articles