google / google/closure-compiler

ordering of externs silently changes behavior by removing some definitions

Open
#3,586 7 comments 0 reactions 0 assignees View on GitHub
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.