google / google/closure-compiler
ordering of externs silently changes behavior by removing some definitions
- Dominant language
- JavaScript
- Stars
- 7.7k
- Forks
- 1.2k
- Avg merge
- 2d 12h
- Merged PRs (30d)
- 6
Description
depending on the order of externs passed to closure-compiler, type definitions might be silently lost!
while i wouldn't disagree that i should be taking better care with externs, it seems like an easy trap to fall into: my tooling was using a glob.glob('*.js') to build up the externs, and the code was passing locally w/out any errors. but it would fail on *some* other systems seemingly at random, and it took quite a while to track down/realize it was due to the externs order being different due to dirent ordering in the underlying FS.
normally closure-compiler complains when it sees duplicate definitions (which is great), so it seems like it should have issued errors or diagnostics of some sort when it clobbered/lost some type information.
the example below is basically what i had: a `externs-base.js` file that defined the common APIs from a diff module, and `externs-addons.js` that included local extensions and built off of the common API. so logically, addons should be imported after base.
```sh
$ closure-compiler --version
Closure Compiler (http://github.com/google/closure-compiler)
Version: v20200406
Built on: 2020-04-09 19:44
$ closure-compiler --checks-only '--jscomp_error=*' \
--externs=externs-addons.js --externs=externs-base.js code.js
< no errors !? >
$ ../libdot/bin/closure-compiler --checks-only '--jscomp_error=*' \
--externs=externs-base.js --externs=externs-addons.js code.js
code.js:7: ERROR - [JSC_TYPE_MISMATCH] actual parameter 1 of APIEvent.prototype.addListener does not match formal parameter
found : function(Object, function(string): ?): undefined
required: function({id: string}, function(number): ?): ?
API.onOpen.addListener(onOpen);
^^^^^^
1 error(s), 0 warning(s), 85.7% typed
```
here are the sample inputs
* externs-base.js
```js
/** @externs */
var API = {};
/**
* @interface
* @template T
*/
function APIEvent() {}
/** @param {T} callback */
APIEvent.prototype.addListener = function(callback) {};
```
* externs-addons.js
```js
/**
* @interface
* @extends {APIEvent}
*/
API.Event;
/** @type {!API.Event} */
API.onOpen;
```
* code.js
```js
/**
* @param {!Object} options
* @param {function(string)} onSuccess
*/
const onOpen = function(options, onSuccess) {};
API.onOpen.addListener(onOpen);
```
Contributor guide
Assessment
This issue has not been assessed yet.