Closure-compiler: @typedef and @template don't work together

Created on 25 Mar 2015  路  5Comments  路  Source: google/closure-compiler

The following code does not work:

/**
 * @template S, T
 * @typedef {{
 *             type: string,
 *             humanName: string,
 *             keyboard: TriResourceRef,
 *             serverElems: Array.<S>,
 *             getSrvId: function(S): string,
 *             getTitle: function(S): string,
 *             getDescriptions: function(S): Array.<string>,
 *             getUseForGrouping: function(S): boolean,
 *             getDeleted: function(S): boolean,
 *             getCliId: function(T): string,
 *             convertToSrv: function(T): S,
 *             convertToCli: function(S): T,
 *             makeNewCli: function(string, Date): T,
 *             mergeSrvCli: function(S, T): S,
 *             getUpdateId: function(T): string,
 *             isSent: function(T): boolean,
 *             insertDescriptionIfMissing: function(S): S
 *          }}
 */
var DrgInterf;

Using the recent compiler version, it results this:

/var/folders/m0/8vx31n6j775g80_0pqhh47k40000gn/T/tmpbDmD3e:77568: ERROR - Bad type annotation. Unknown type S
 *             serverElems: Array.<S>,
                                   ^

Splitting the template in two template annotations does not help either.

enhancement

Most helpful comment

Is this ever going to be supported? We've got:

/**
 * @param {!function(F, null=, (Object<string,*>|null)=): !goog.soy.data.SanitizedContent} template
 * @template F
 */

pasted all over our codebase because it's impossible to have a parameterized @typedef 馃槖

All 5 comments

we should warn about a misplaced annotation with the use of @template described here. The @record proposal might allow this.

Is this ever going to be supported? We've got:

/**
 * @param {!function(F, null=, (Object<string,*>|null)=): !goog.soy.data.SanitizedContent} template
 * @template F
 */

pasted all over our codebase because it's impossible to have a parameterized @typedef 馃槖

maybe, but it's not high priority so it might be a while

I think TypeScript allows adding some sort of "call" property to their interfaces. That might be one way to handle this, but allowing to define an @record (which can, of course, be templated) with a typed call method. In any case, if any approach is taken, it's not going to happen until after NTI is fully rolled out.

I'm also running into this issue. I'm trying to translate the TypeScript type annotations in Preact to Closure Compiler-compatible JsDoc (to be able to use Preact from a project which is compiled with Closure Compiler).

However, I cannot translate some of the types completely due to limitations with Closure Compiler support for @typedef and @template.

For example, consider the following TypeScript type in Preact:

type ComponentType<P = {}> = ComponentClass<P> | FunctionComponent<P>;

I would like to translate this to JsDoc as follows (note: I'm using a namespace preact for my @externs file):

/**
 * @typedef {preact.ComponentClass<P> | preact.FunctionComponent<P>}
 * @template P
 */
preact.ComponentType;

This file is then used as an @externs file, but it fails compilation with:

ERROR - Bad type annotation. Type name(s) for @template annotation declared twice. See https://github.com/google/closure-compiler/wiki/Annotating-JavaScript-for-the-Closure-Compiler for more information.

Unfortunately, that page does not say anything on the topic:

  • the @template section doesn't talk about @typedef
  • the @typedef section doesn't mention @template

Also, https://github.com/google/closure-compiler/wiki/Generic-Types doesn't mention anything about the interaction between @template and @typedef.

Removing the @template P annotation results in a different error:

ERROR - Bad type annotation. Unknown type P

As a result, I'm stuck, because I don't see a way to make it work.


If you'd like an isolated test case independent of Preact, consider the following file, similar to the above, without any @externs or other complexities:

/**
 * @typedef {Array<P> | Array<Q>}
 * @template P, Q
 */
var customArray;

Now, let's try to compile it:

$ java -jar closure-compiler-v20200719.jar --compilation_level=ADVANCED --dependency_mode=PRUNE \
      foo.js --entry_point foo.js \
foo.js:2: WARNING - [JSC_UNRECOGNIZED_TYPE_ERROR] Bad type annotation. Unknown type P
  2|  * @typedef {Array<P> | Array<Q>}
                        ^

foo.js:2: WARNING - [JSC_UNRECOGNIZED_TYPE_ERROR] Bad type annotation. Unknown type Q
  2|  * @typedef {Array<P> | Array<Q>}
                                   ^

foo.js:3: WARNING - [JSC_TYPE_PARSE_ERROR] Bad type annotation. Type name(s) for @template annotation declared twice. See https://github.com/google/closure-compiler/wiki/Annotating-JavaScript-for-the-Closure-Compiler for more information.
  3|  * @template P, Q
        ^

0 error(s), 3 warning(s)

I found some test cases that appear to generate the errors that I'm seeing, and I'm confused as to why they're considered errors:

https://github.com/google/closure-compiler/blob/3a22cab6d6d66642c1fd3b78040973a9a293e34c/test/com/google/javascript/jscomp/parsing/JsDocInfoParserTest.java#L2659-L2673

To me, these code samples look like exactly the correct thing for someone to write and expect to work when composing the use of @typedef with @template.

Was this page helpful?
0 / 5 - 0 ratings