closure-compiler
closure-compiler copied to clipboard
Property that is a class is not type checkable without being marked as @const
Suppose collection.js:
export class A {}
export default {A}
and index.js:
import {A} from './collection.js';
import collection from './collection.js';
class B extends A {}
class C extends collection.A {}
Compiling this produces a warning:
$ closure-compiler --js collection.js --js index.js --warning_level VERBOSE --checks_only
index.js: WARNING - Bad type annotation. Unknown type $jscompDefaultExport$$module$collection.A
0 error(s), 1 warning(s), 92.5% typed
closure-compiler is the latest java release.
Closure Compiler (http://github.com/google/closure-compiler)
Version: v20180716
Built on: 2018-07-17 22:09
Class B that extends explicitly imported A works fine, but class C that extends A imported as a property of default object produces warning.
The code compiles and works as expected despite the warning. Also note that the warning is missing line information and is not pointing to any particular point in the code. There weren't any annotations in original code either, so the message is quite misleading.
Referencing collection.A in jsdoc annotations also produces a warning:
WARNING - Bad type annotation. Unknown type collection$$module$collection.A
* @type {collection.A}
^
This might to be related to https://github.com/google/closure-compiler/issues/2236 and/or https://github.com/google/closure-compiler/issues/2257 but I could not reproduce the former with the current version of compiler while the latter issue seems to be explicitly about transitive exports.
This is not limited to classes.
collection.js:
/**
* @typedef {number}
*/
export var TYPE;
export default {TYPE}
and index.js:
import {TYPE} from './collection.js';
import collection from './collection.js';
/**
* @type {TYPE}
*/
let a = '';
/**
* @type {collection.TYPE}
*/
let b = '';
Produces:
index.js:7: WARNING - initializing variable
found : string
required: number
let a = '';
^^
index.js:10: WARNING - Bad type annotation. Unknown type collection$$module$collection.TYPE
* @type {collection.TYPE}
^
0 error(s), 2 warning(s), 100.0% typed
Note how compiler understood the type of a and performed a type check producing a correct warning when it was assigned a wrong value (string instead of number) while it failed to do so with b.
This is easy to repro: https://closure-compiler-debugger.appspot.com/#input0%3Dexport%2520class%2520A%2520%257B%257D%250Aexport%2520default%2520%257BA%257D%26input1%3Dimport%2520%257BA%257D%2520from%2520'.%252Finput0'%253B%250Aimport%2520collection%2520from%2520'.%252Finput0'%253B%250A%250Aclass%2520B%2520extends%2520A%2520%257B%257D%250Aclass%2520C%2520extends%2520collection.A%2520%257B%257D%26conformanceConfig%26externs%26refasterjs-template%26includeDefaultExterns%3Dtrue%26CHECK_TYPES%3Dtrue%26TRANSPILE%3Dtrue%26CLOSURE_PASS%3Dtrue%26PRESERVE_TYPE_ANNOTATIONS%3Dtrue%26PRETTY_PRINT%3Dtrue
@jplaisted, Can you take a look?
Looks like a type inlining issue. If you mark it as @const it won't complain.
export class A {}
export default {/** @const */ A}
https://closure-compiler-debugger.appspot.com/#input0%3Dexport%2520class%2520A%2520%257B%257D%250Aexport%2520default%2520%257B%252F**%2520%2540const%2520*%252F%2520A%257D%26input1%3Dimport%2520%257BA%257D%2520from%2520'.%252Finput0'%253B%250Aimport%2520collection%2520from%2520'.%252Finput0'%253B%250A%250Aclass%2520B%2520extends%2520A%2520%257B%257D%250Aclass%2520C%2520extends%2520collection.A%2520%257B%257D%26conformanceConfig%26externs%26refasterjs-template%26includeDefaultExterns%3Dtrue%26CHECK_TYPES%3Dtrue%26TRANSPILE%3Dtrue%26CLOSURE_PASS%3Dtrue%26PRESERVE_TYPE_ANNOTATIONS%3Dtrue%26PRETTY_PRINT%3Dtrue
In fact it has nothing to do with ES6 modules at all. This has the same error:
class A {}
const collection = {A}
class B extends A {}
class C extends collection.A {}
Ah, good point. Thanks for the workaround.
Also @l1bbcsg: kind of off topic, but what's with that style? Why not just import * as collection rather than duplicate the entire module as a default export? Seems like a lot of work to avoid 5 characters :)
Apologizes for bringing modules into the equation, looks like I jumped to conclusions.
The workaround is good enough for classes, but it doesn't seem to work for the second example with typedefs: https://github.com/google/closure-compiler/issues/3043#issuecomment-410230627
As for codestyle, this is mostly an abstract issue that I stumbled upon while trying to migrate a really complicated Closure project from namespaces to modules. I'm doing this through AST transformations, so codestyle is a mess for now since the two approaches are quite different, but we'll think about it later. Thanks for the suggestion though, it looks like a good idea.
Apologizes for bringing modules into the equation, looks like I jumped to conclusions.
No worries! Thanks for filing. A bug is a bug :)
The workaround is good enough for classes, but it doesn't seem to work for the second example with typedefs: #3043 (comment)
We appear to do the same thing for the typedef we're doing for classes, so I'm not sure why it doesn't work. In the end the annotation is still module.TYPE, even if I make TYPE a class.
https://closure-compiler-debugger.appspot.com/#input0%3D%252F**%250A%2520*%2520%2540typedef%2520%257Bnumber%257D%250A%2520*%252F%250Avar%2520TYPE%253B%250Aconst%2520module%2520%253D%2520%257B%252F**%2520%2540const%2520*%252F%2520TYPE%257D%253B%250A%250A%250A%252F**%250A%2520*%2520%2540type%2520%257Bmodule.TYPE%257D%250A%2520*%252F%250Alet%2520b%2520%253D%2520''%253B%26input1%3D%250A%250A%26conformanceConfig%26externs%26refasterjs-template%26includeDefaultExterns%3Dtrue%26CHECK_TYPES%3Dtrue%26TRANSPILE%3Dtrue%26CLOSURE_PASS%3Dtrue%26PRESERVE_TYPE_ANNOTATIONS%3Dtrue%26PRETTY_PRINT%3Dtrue
I just wanted to report that I had the same problem while adapting the pako zip library to work with GCC. Here's what my use case boiled down to:
var lib = {};
(function () {
class Greeter {
sayHello () {
alert("hello");
}
}
lib.Greeter = Greeter;
}());
/** @type {lib.Greeter?} */
var myGreeter = null;
if (Math.random() < 0.2) {
myGreeter = new lib.Greeter();
myGreeter.sayHello();
}
It gives this error:
JSC_UNRECOGNIZED_TYPE_ERROR: Bad type annotation. Unknown type lib.Greeter at line 10 character 11
/** @type {lib.Greeter?} */
but adding /** @const */ to the lib.Greeter line silences the warning.
Thanks to @jplaisted for the @const workaround - it worked for me as well.