Search code examples
javascriptarraysgoogle-closure-compiler

Why does google closure compiler warn about length of an array?


I have an object, with a property which is always an array or a null value like so:

/** @constructor */
function MyObject() {
    // optionalProperty always contains an array or null
    this.optionalProperty = null;
}

MyObject.prototype.initialize = function() {
     this.optionalProperty = [1,2,3];
};

var foo = new MyObject();
foo.initialize();

if(Array.isArray(foo.optionalProperty)) {
    var length = foo.optionalProperty.length;
}

Google closure compiler warns that property length is never defined on foo.optionalProperty, even though it's clearly an array if the line of code checking the length gets executed, and of course arrays have a length property. Suggestions for eliminating/suppressing this warning?

UPDATE: OK, so I was kind of an idiot. I tried to prepare a minimal example of the problem from my codebase, but this example actually does NOT throw a compiler warning, as Chad pointed out. So I went digging, and found another place in my codebase where I had treated the property as an object rather than an array! Here's a better code snippet to see the warning:

/** @constructor */
function MyObject() {
    // optionalProperty always contains an array or null
    this.optionalProperty = null;
}

MyObject.prototype.initialize = function() {
    this.optionalProperty = {};
    this.optionalProperty.a = 1;
};

MyObject.prototype.otherMethod = function() {
    if(Array.isArray(this.optionalProperty)) {
        for (var i = 0; i < this.optionalProperty.length; i++) {
            console.log(i);
        }
    }
};

var foo = new MyObject();
foo.initialize();
foo.otherMethod();

Thus, the warning that the 'length' property was not previously defined was legit. And I never would have had this confusion in the first place if I had typed this.optionalProperty as owler points out, because the compiler warns if you try to assign an array to something that's supposed to be an object. I still maintain that the compiler could be smarter about type-checking inside a block like if(Array.isArray(something)) {} but the problem here was definitely user error.


Solution

  • You need to tell the compiler the type of the property, something like this

    function MyObject() {
        /** optionalProperty always contains an array or null
        * @type {?Array}
        */
        this.optionalProperty = null;
    }
    
    MyObject.prototype.initialize = function() {
         this.optionalProperty = [1,2,3];
    }
    
    foo = new MyObject();
    foo.initialize();
    
    if (this.optionalProperty != null) {
        var length = foo.optionalProperty.length;
    }
    

    You could also use goog.isArray() there. I don't know if the compiler will recognize Array.isArray, it might. But from the definition if it's not null then it is an array, and the compiler knows that.

    The closure wiki has a good page called Annotating JavaScript for the Closure Compiler