Closure Compiler Externs for Function with static fields - javascript

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.

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 annotations for an object containing a constructor function

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.

Why isn't Closure Compiler warning about violations of #package visibility?

When I use the #package annotation and then try to access it from a file in another directory I receive no warning on compiling. The compiler is given a warning_level argument of 'VERBOSE' (and I receive warnings for #private violations).
See https://developers.google.com/closure/compiler/docs/js-for-compiler
For example:
In ns/pkg/foo.js
/**
* #constructor
*/
ns.pkg.Foo = function(){}
/**
* #package
*/
ns.pkg.Foo.prototype.modify = function() {}
In ns/view.js
/**
* Factory method
*/
ns.View.create = function() {
var myFoo = new ns.pkg.Foo();
myFoo.modify(); // ***no compiler error***
//...
}
Am I missing a compiler flag or misunderstanding closure docs? Or is this a bug in the compiler?

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).

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