Closure compiler annotations for an object containing a constructor function - javascript

I'm trying to typecheck a library that returns an Object of constructor functions. With the following code Closure errors with:
./my-app.js:11: ERROR - Cannot call non-function type Foo.Wheel
const wheel = new Foo.Wheel();
^
Here's the code structure:
my-app-code.js - The code I'm using
const Foo = /** #type{!Foo.Module} */ require('foo');
const wheel = new Foo.Wheel();
wheel.rotate();
externs-foo.js - Closure externs for the Foo library
/** #const */
const Foo = {};
/** #record */
Foo.Module = function() {};
/** #type {!Foo.Wheel} */
Foo.Module.prototype.Wheel;
/** #constructor */
Foo.Wheel = function() {};
/**
* #returns {void}
*/
Foo.Wheel.prototype.rotate = function() {};
foo/index.js - corresponds to Foo.Module type.
module.exports = {
Wheel: require("./wheel"),
};
foo/wheel.js - corresponds to Foo.Wheel.
function Wheel() {}
Wheel.prototype.rotate = function() {};
module.exports = Wheel;
I tried one variation on externs-foo.js with the following results.
Make Foo.module.prototype.Wheel a function
/** #return {!Foo.Wheel} */
Foo.Module.prototype.Wheel = function() {};
Errors with:
my-app.js:11: ERROR - Expected a constructor but found type function(this:Foo.Module):Foo.Wheel.
const wheel = new Foo.Wheel();
my-app.js:13: ERROR - Property rotate never defined on module$myapp_Foo of type Foo.Module
wheel.rotate();

I'm aware of two solutions to this issue:
Declaring Foo.Module.prototype.Wheel = Foo.Wheel; in the externs file.
Use #type {function(new:Foo.Wheel)}, which says that the function is in fact a constructor that instantiates a Foo.Wheel object.
I prefer solution #1 because it declares a reference to the constructor function, so the compiler will allow me to access properties of the constructor (e.g. static methods). IIRC this can't be done in solution #2.
The annotations #type {!Foo.Wheel} and #return {!Foo.Wheel} won't work because they refer to an instance of Foo.Wheel and what you actually want is the constructor itself.

Related

Describe destructed property with JSDoc

If I do like this I can desribe my property and get intellisense when later using myObject (using Visual Studio Code).
/**
* #type {MyObject}
*/
const myObject = object.nestedObject.myObject;
But what if I destruct an object like this
/**
* #type {MyObject}
* #type {MyObjectTwo}
*/
const { myObject, myObjectTwo } = object.nestedObject;
How can I use JSDoc to understand this way of assigning properties (deconstructing an object) so that I can get intellisense on myObject and myObjectTwo?

Closure Compiler Externs for Function with static fields

What is the correct type for a constructor function that also has properties on the function object with Google's closure compiler?
Here's a runnable first attempt on the Closure compiler debugger.
Application Code
const Mocha = /** #type {!MochaJS} */ (require('mocha'));
const mochaInstance = new Mocha();
const Suite = Mocha.Suite;
Closure Externs
/** #constructor */
const MochaJS = function() {};
/** #type {!MochaJS.Suite} */
MochaJS.prototype.Suite;
/** #record */
MochaJS.Suite = function() {};
The difficulty comes because Closure-compiler doesn't handle external module definitions well. Also, dont' confuse the constructor/namespace with an instance. They are different.
Application
// A constructor type for Mocha
const Mocha = /** #type {!function(new:MochaJS)} */ (require('mocha'));
const mochaInstance = new Mocha();
const Suite = /** #type {!MochaJSSuite} */ (Mocha.Suite);
Externs
/** #constructor */
const MochaJS = function() {};
/** #function */
MochaJSSuite = function() {};
This is just rough guesses for the types - I'm not familiar enough with Mocha to write the externs without going to hunt down the documentation reference. Hopefully it will point you in the right direction though.

Closure Compiler does not always enforce type safety?

I'm having an issue with closure compiler where it's not enforcing strict type checks for some strange reason. It correctly checks type safety when setting a variable where it's declared, but is failing to throw an type error when passing an object.
/**
* #public
* #param x {number}
*/
SomeClass.prototype.setterMethod = function(x) {
this.var1 = x;
};
var a = new SomeClass();
a.setterMethod({}); // SHOULD THROW AN ERROR!!!!!!
Why isn't closure compiler enforcing type safety here? It correctly functions if I declare the variable:
this.var1 = {}; // correctly throws an error
However it is not enforcing strict type safety checks on function parameters, or when setting the class member variable outside of the member declaration. I've posted the full code and output below. Is there a way to force closure to force these types of checks? Or am I doing something wrong here?
// ==ClosureCompiler==
// #compilation_level ADVANCED_OPTIMIZATIONS
// #warning_level VERBOSE
// #output_file_name default.js
// ==/ClosureCompiler==
/**
* #class SomeClass
* #constructor
*/
function SomeClass() {
/**
* #protected
* #type {number}
*/
this.var1;
};
/**
* #public
* #param x {number}
*/
SomeClass.prototype.setterMethod = function(x) {
this.var1 = x;
};
/**
* #public
* #returns {number}
*/
SomeClass.prototype.getterMethod = function() {
return this.var1;
};
/**
* #type {SomeClass}
*/
var a = new SomeClass();
a.setterMethod({});
console.log(a.getterMethod());
// output: -- NO WARNINGS!!!!
// var a=new function(){};a.a={};console.log(a.a);
There are two issues occurring:
The Closure-compiler web service runs in a type of "demo" mode and assumes all undeclared variables are external. There is currently no way to disable this. Testing with the command line compiler doesn't show the same issues.
Your JSDoc paramater annotation is not correct. It should be #param {number} x (you have the type and name reversed).

Exposing local types in Closure Compiler

I'm trying to expose the return type of a function where the return type is defined within the function's own scope.
// ==ClosureCompiler==
// #compilation_level ADVANCED_OPTIMIZATIONS
// ==/ClosureCompiler==
/** #return {MyType} */
function func(){
/** #constructor */
function MyType(){}
return new MyType();
}
/** #type {MyType} */
var instance = func();
When I compile this here, I get two type errors one for the #return annotation and one for the #type annotation. Both errors say Unknown type MyType. Is there a way to get this pattern to compile with type-checking?
Maybe it can't find the type because MyType as a type is not available when you create instance (can't do new MyType() or instance instanceof MyType).
This can be fixed by adding a typedef in the same scope as instance or create instance in the same scope as MyType.
Both:
/** #typedef {Object} */
var MyType;
/** #return {MyType} Returns new MyType **/
function func(){
/** #constructor */
function MyType(){
}
return new MyType();
}
/** #type {MyType} */
var instance = func();
and:
function func(){
/** #constructor */
function MyType(){
}
/** #type {MyType} */
var instance = new MyType();
return new MyType();
}
Won't produce any warning
[update]
To provide a complex type through annotation is going to be painfully hard. You can make the type as complex as you want. Using externs would not work because externs variables and their functions won't be renamed to shorter names (without some extra compile parameters) and you can't re define the type. So I guess you're basically stuck with providing a typedef annotation:
/** #typedef {{hands:number,doSomething:function(string):boolean}} */
var MyType;
/** #return {MyType} Returns new MyType **/
function func(){
/** #constructor */
function MyType(){
this.hands=22;
}
MyType.prototype.doSomething=function(msg){
return true;
}
return new MyType();
}
/** #type {MyType} */
var instance = func();
/** #type string */
var myString=instance.hands;//warning:found number required string
/** #type Array */
var myArray=instance.doSomething("hi");//found boolean required Array
instance.doSomething(22);//found number required string
instance.doSomething();//at leas 1 argument(s)
The problem is that "MyType" is scoped with "func" and is not available outside. You might do better to define an #interface in the global scope and just use that type outside the defining function.

Closure Compiler #extends doesn't work when using "Class" container objects

I'm in the process of annotating all my javascript for closure compiler, however -- the code I have currently heavily depends on defining classes within objects i.e:
Class.SomeClass = function() {};
Class.SomeOtherClass = function() {};
rather than:
function SomeClass() {};
SomeClass.prototype = {};
However, it gives me an warning when trying to annotate an extends ... the compiler is stating that i can't determine what type Class.SomeClass is:
JSC_TYPE_PARSE_ERROR: Bad type annotation. Unknown type Class.SomeObject
* #extends Class.SomeObject
Paste the following code below into closure compiler with ADVANCED_OPTIMIZATIONS:
// ==ClosureCompiler==
// #output_file_name default.js
// #compilation_level ADVANCED_OPTIMIZATIONS
// ==/ClosureCompiler==
(function($) {
"use strict";
var Class = {};
/**
* #constructor
*/
Class.ObjectA = function() {};
Class.ObjectA.prototype = {};
/**
* #constructor
* #extends Class.ObjectA
*/
Class.ObjectB = function() {};
Class.ObjectB.prototype = $.extend(new Class.ObjectA(), {
initialize: function() {}
});
window.test = new Class.ObjectB();
window.test.initialize();
})(jQuery);
The answer isn't obvious. You simply need to add an #const to your Class namespace.
/** #const */
var Class = {};
well the easiest, since you will be compiling it in advance mode anyway is to maybe use goo.provide and just link the base.js in. then obviously you should use goog.inherits for your inheritance since advanced mode understand the base.js functions a lot better than say $.extends.
so my code to achieve the same would look like this:
// ==ClosureCompiler==
// #output_file_name default.js
// #compilation_level ADVANCED_OPTIMIZATIONS
// #use_closure_library true
// ==/ClosureCompiler==
goog.provide('Class.ObjectA')
/**
* #constructor
*/
Class.ObjectA = function() {};
goog.provide('Class.ObjectB');
/**
* #constructor
* #extends Class.ObjectA
*/
Class.ObjectB = function() {};
Class.ObjectB.prototype =
{
initialize: function() {}
}
goog.inherits(Class.ObjectB, Class.ObjectA);
window.test = new Class.ObjectB();
window.test.initialize();
In the compiler ui, you have to select the option to add the closure library which will add goog.base.
Now you also have several jqueury style foo's in your approach like (function($) {})(jQuery); which i would reassess the use of if you are starting down the advanced compilation route (i would personally reasses the use of jquery versus the closure library, but I do know of people who continued using jquery with advanced). going down the advanced mode I would also recomend that you look at a build system like plovr.

Categories