Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why doesn't Closure Compiler recognize type declarations inside a self-executing anonymous function?

I'm getting a lot of "Unknown type" warnings when running a fairly large library through Closure Compiler, and they seem to occur when my types are declared in self-executing anonymous functions. There's nothing exotic about this, but if I strip the self-executing functions out, the type declarations seem to work (at least in this simple test).

I'm not sure if there's something wrong with my code annotations or if there's anything illegal in the code, but I think this is all kosher and the standard way to modularize an API.

The following test code creates a namespace (just a plain old JS object) and attaches an enum (an object literal) and a function to it.

var mynamespace = {};
(function (mynamespace) {
    /**
     * Some enum.
     * @enum {number}
     */
    mynamespace.SomeEnum = {
        FOO: 1,
        BAR: 2
    };

    /**
     * Frazzle some type.
     * @param {mynamespace.SomeEnum} qux The type to frazzle.
     * @return {boolean} whether the operation succeeded.
     */
    mynamespace.frazzle = function(qux) {
        return true;
    }
}(mynamespace));

// call it
mynamespace.frazzle(mynamespace.SomeEnum.FOO);

Looks fine, right? The closure compiler errors:

[jscomp] Compiling 1 file(s) with 37 extern(s)
[jscomp] X:\dev\solclientjs\sdk\tools\jscomptest.js:14: WARNING - Parse error. Unknown type mynamespace.SomeEnum

[jscomp]      * @param {mynamespace.SomeEnum} qux The type to frazzle.
like image 236
jpdaigle Avatar asked Mar 03 '11 02:03

jpdaigle


People also ask

What is the function of a closure compiler?

The Closure Compiler is a tool for making JavaScript download and run faster. Instead of compiling from a source language to machine code, it compiles from JavaScript to better JavaScript. It parses your JavaScript, analyzes it, removes dead code and rewrites and minimizes what's left.

How do you execute an anonymous function?

Another use case of anonymous functions is to invoke the function immediately after initialization, this is also known as Self Executing Function. This can be done by adding parenthesis we can immediately execute the anonymous function.

Can an anonymous function be assigned to a variable?

An anonymous function in javascript is not accessible after its initial creation. Therefore, we need to assign it to a variable, so that we can use its value later. They are always invoked (called) using the variable name. Also, we create anonymous functions in JavaScript, where we want to use functions as values.

Why are anonymous functions bad?

Anonymous functions bound everywhere are a pain. They're difficult to debug, maintain, test, or reuse. Instead, use an object literal to organize and name your handlers and callbacks.


2 Answers

Edit:

Original answer was totally off.

This definitely appears to be a bug in the compiler. I haven't found a bug report with this exact issue, but I found two bug reports that appear to address the inverse of this issue (compiler should be throwing a warning, but it won't unless you unwrap the anonymous function).

http://code.google.com/p/closure-compiler/issues/detail?id=134

http://code.google.com/p/closure-compiler/issues/detail?id=61

In any case it looks like anonymous functions are wonky when used with type expressions.

like image 194
Bryan Downing Avatar answered Oct 12 '22 14:10

Bryan Downing


In your example, the argument "mynamespace" is being passed an "alias" to the global mynamespace object. Having an alias (local mynamespace) to your global object (global mynamespace) prevents optimizations on the entire tree under the global object. This is a BAD BAD idea for the Closure Compiler in Advanced mode.

Any function defined under a local variable is volatile. The compiler has no idea (without deep code flow analysis) the local "mynamespace" is an alias to the global "mynamespace". Therefore, it won't automatically associate anything you define under the local variable to the object that it aliases.

The proper ways to do this are:

Option #1, using the global object directly:

(function() {
    mynamespace.someEnum = ...
})();

Option #2, use a goog.scope alias (assuming you are using Advanced mode):

goog.scope(function() {
    var somevar = mynamespace;

    (function() {
        somevar.someEnum = ...
    })();
});

Both options should get you your desired result. However, there is currently no way to do what you want via an argument into a wrapper closure.

like image 27
Stephen Chung Avatar answered Oct 12 '22 13:10

Stephen Chung