Exposing local types in Closure Compiler - javascript

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.

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.

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

How can I correctly document instance members added via Object.defineProperties?

I have a class which defines a few instance properties via Object.defineProperties and I'm having great difficulty getting JSDoc 3 to recognize that they belong to their class.
Here's a simplified version of what I'm working with:
/** #exports mymodule */
function mymodule(exports) {
/** #constructor
* #param {String} foo A foo.
* #param {String} bar A bar.
* #classdesc Has a foo and a bar.
*/
function Example(foo, bar) {
Object.defineProperties(this, {
/** A foo and a bar
* #memberof Example
*/
foobar: { enumerable: false, value: foo + bar, writable: false }
});
}
exports.Example = Example;
}
When I run JSDoc, I get output for mymodule, Example, foo, and bar, but not foobar. If I remove the #memberof tag for foobar, it get registered as a global. I've tried #memberof mymmodule~Example, adding #lends to both the Object.defineProperties call and the object passed to it, and converting it to Object.defineProperty, but the results don't change.
How can I document foobar as belonging to Example?
After digging through every example I could find, I finally assembled the necessary incantation — #memberof is indeed the trick, but JSDoc seems to require that modules being used in namepaths be explicitly marked as such. The following worked perfectly:
/** A foo and a bar
*
* #type String
* #instance
* #memberof module:mymodule~Example
*/
You could also try the #lends annotation in place of #memberOf like this :
Object.defineProperties(this, /** #lends Example# */{
/** A foo and a bar */
foobar: { enumerable: false, value: foo + bar, writable: false }
});
Don't forget the sharp symbol after the class name to tell jsdoc members are instance members and not static members.
After fiddling around for many hours, I finally got it to work with #memberOf!. Note the uppercase "O" and the bang "!"
/**
* Description
* #memberOf! MyClass
* #type {String}
*/
var myString;
In case you want to use defineProperty instead, a #type annotation is sufficient (at least for IntelliJ to infer the proper type for the property):
/** #type SomeType */
Object.defineProperty(this, "someProperty", {
get: () => new SomeType()
});

Commenting JavaScript for Google Closure Compiler for Singleton

I am trying to understand the JSDoc style for documenting JavaScript that is used with the JavaScript Closure Compiler. I have the JavaScript code below
// ==ClosureCompiler==
// #compilation_level ADVANCED_OPTIMIZATIONS
// ==/ClosureCompiler==
(function(){
/**
* #type Array.<string>
* #private
*/
var sb = [];
/**
* #const
* #type{{append: function(string): SingletonStringBuffer, toString: function(): string}}
*/
window['SingletonStringBuffer'] = {
/**
* #param {string} text
* #return {SingletonStringBuffer}
*/
append: function(text){
sb.push(text);
return SingletonStringBuffer;
},
/**
* #return {string}
*/
toString: function(){
return sb.join("");
}
};
}());
When I do an advanced compile on this code I am receiving 2 warnings.
JSC_TYPE_PARSE_ERROR: Bad type annotation. Unknown type SingletonStringBuffer at line 10 character 35
* #type{{append: function(string): SingletonStringBuffer, toString: function()...
^ JSC_TYPE_PARSE_ERROR: Bad type annotation. Unknown type SingletonStringBuffer at line 15 character 11
* #return {SingletonStringBuffer}
^
The function append returns a deference to the encapsulating object. The variable that it is returning ,SingletonStringBuffer, is declared... so I am not sure what is wrong or how to correct it.
You haven't created a named type as far as the compiler is concerned. For this case, I would expect you to create an interface:
/** #interface */
function StringBuffer() {
}
/**
* #param {string} text
* #return {StringBuffer}
*/
StringBuffer.prototype.append;
etc
This can be declared either in the code (if you are using advanced mode it will be stripped) or in your extern files (if you want the type without the code in simple mode).
You can then use it like so (in your case):
(function(){
/**
* #type Array.<string>
* #private
*/
var sb = [];
/**
* #const
* #type {StringBuffer}
*/
window['SingltonStringBuffer'] = {
/**
* #param {string} text
* #return {StringBuffer}
*/
append: function(text){
sb.push(text);
return SingltonStringBuffer;
},
/**
* #return {string}
*/
toString: function(){
return sb.join("");
}
};
}());
singletons work differently in closure. I have not seen an explicit annotation for it, but the compiler (in advanced mode) has some understanding of certain built-in functions
Singletons would be declared via the goog.addSingletonGetter function, here is a code sample
/**
* #constructor
* #extends {path.to.BaseClass}
*/
path.to.MyClass = function() {
goog.base(this);
};
goog.inherits(path.to.MyClass, path.to.BaseClass);
goog.addSingletonGetter(path.to.MyClass);
and that be it.
PS
you are getting the bad annotation because {SingltonStringBuffer} is never declared as a class.
PPS.
Some rambling on post the fact.
I suspect (but this is untested) that making the constructer private might work. Notice the trailing underscore in the example
/**
* #private -> NOTE THIS IS IN NO WAY VERIFIED
* #constructor
* #extends {path.to.BaseClass}
*/
path.to.MyClass_ = function() {
goog.base(this);
};
goog.inherits(path.to.MyClass, path.to.BaseClass);
goog.addSingletonGetter(path.to.MyClass);

Categories