Search code examples
javascriptgoogle-closure-compilergoogle-closuregoogle-closure-library

closure compiler externs solves an issue but i didn't understand why?


I compile my source with closure compiler and when i call a function that got an event object from network the application throws an error in console.

The function called is:

/**
 * @param {goog.events.Event} event Socket.io-Wrapper Event.
 */
de.my.app.admin.prototype.onSaved = function(event){ 
  var category = event.data[0].category; //<-- here it throws the error because category get compiled.
  var id       = event.data[0].id;
  var oldid    = event.data[0].oldid;
[...]
}

the event object looks like this

{ data:{
    0: {
      category: 'someString',
      id:       5,
      oldid:    -5
  } } 
[...someMoreValuesAddedBySocketIO...]
}

that is the behavior i expected.

now i add an externs declaration like this to my externs file but i didn't alter the type declaration of the @param at the function and the error disappears:

var xterns;
/**
 * @typedef {{
 *   category : string,
 *   oldid    : number,
 *   id       : number
 * }}
 */
xterns.NOTUSEDNAME;

/**
 * @type {string}
 */
xterns.NOTUSEDNAME.prototype.category;

/**
 * @type {number}
 */

xterns.NOTUSEDNAME.prototype.oldid;

/**
 * @type {number}
 */
xterns.NOTUSEDNAME.prototype.id;  

In short: I have a @param {goog.events.Event} event declaration and an extern for xterns.NOTUSEDNAME solves the compiler problems... Can anyone explain why this happens?


Solution

  • This is a common misconception. Closure-compiler will not rename a property if any extern object contains a property of the same name. See the FAQ. If the type based optimizations are enabled, then this is no longer true and I would expect your code to break again.

    To make this code type safe and compile without warnings, you would need to either:

    1. Reference the data properties using quoted syntax event.data[0]['category']. Your properties will never be renamed by the compiler using this method (which is often used by JSON Data).

    2. Extend the goog.events.Event type with a custom object that defines the data object as a strongly typed array.

    Example:

    /**
     * @constructor
     * @extends {goog.events.Event}
     */
    de.my.app.AdminEvent = function() {};
    goog.inherits(de.my.app.AdminEvent, goog.events.Event);
    
    /** @type {Array.<{category:string, id:number, oldid:number}>} */
    de.my.app.AdminEvent.prototype.data = []; 
    

    Depending on your exact situation, an interface might be a better option.