Search code examples
javascripttypesmodulegoogle-closure-compiler

Closure Compiler externs - Referencing a complex function


How can I refer to a complex function type with the Google closure compiler and not an instance of the constructor function?

externs.js - Externs for CoolLibrary.matchSomething

/** @externs */

/** @const */
const CoolLibrary = {};

/**
 * @param {!Object} object The object to inspect.
 * @param {!Object} source The object of property values to match.
 * @param {!function(!Object): !Object} customizer The function to customize
 *     comparisons.
 * @return {boolean} Returns `true` if `object` is a match, else `false`.
 * @constructor
 */
CoolLibrary.matchSomething = function(object, source, customizer) {};


/**
 * @param {string} src
 * @return {!Object}
 */
function require(src) {}

foo.js - application code

goog.module('foo');

const isMatchWith = /** @type {!CoolLibrary.matchSomething} */ (require('coollibrary.matchsomething'));

const foo = isMatchWith({}, {}, (val) => {});

I'm invoking it like so:

java -jar closure-compiler-v20161201.jar --js=foo.js --externs=externs.js --new_type_inf

Here's a runnable version at the Closure Compiler Debugger

It errors with:

foo.js:3: WARNING - Bad type annotation. Unknown type Lodash.isMatchWith
const isMatchWith = /** @type {!Lodash.isMatchWith} */ (require('lodash.ismatchwith'));
                                ^
0 error(s), 1 warning(s), 72.7% typed

It works if I use @typedef but that loses most of the information. Is there a better way to add type information than using typedef like below?

/** @typedef {function(!Object, !Object, function(!Object):!Object):boolean} */
CoolLibrary.matchSomething;

Solution

  • Function definitions are not type names. You can use a typedef to prevent retyping this data if you import the function it in multiple places. However, if you only import the information in a single place, then a typedef would be overkill.

    For a single import, you would simply duplicate the function annotation in the type cast at your require call.

    const isMatchWith =
      /** @type {function(!Object, !Object, function(!Object):!Object):boolean} */
      (require('lodash.ismatchwith'));
    

    The compiler handles these cases for you when module bundling is used, but that requires all of the source files to be compatible with the compiler and provided as part of the compilation. This is not currently possible for an external library.