bazelbuild / bazelbuild/rules_closure

Weird issues with externs

Open
#509 2 comments 0 reactions 0 assignees View on GitHub
Dominant language
Java
Stars
159
Forks
111
PR merge metrics
No merged PRs in 30d

Description

When trying to use [Google Maps externs](https://github.com/google/closure-compiler/blob/master/contrib/externs/maps/google_maps_api_v3_43.js), I get some very strange behavior that doesn't seem to have a remedy except for a bug fix.

consider the following property initializer in an arbitrary JS class and accompanying Bazel targets:

**`WORKSPACE`**:
```starlark
# ...

http_file(
name = "com_google_gmaps",
downloaded_file_path = "gmaps-extern.js",
urls = ["https://raw.githubusercontent.com/google/closure-compiler/db22839eee034c1b302879f64a2984407459b0f2/contrib/externs/maps/google_maps_api_v3_43.js"],
)

# ...
```

**`BUILD.bazel`**:
```starlark
closure_js_library(
name = "gmaps",
srcs = ["@com_google_gmaps//file"],
)

closure_js_library(
name = "some-thing",
srcs = ["some-thing.js"],
deps = [":gmaps"],
)

closure_js_binary(
name = "some-js",
deps = [":some-thing"],
dependency_mode = "PRUNE_LEGACY",
entry_points = ["repro.some_thing"],
)
```

**`some-thing.js`:**
```js
goog.module('repro.some_thing');

class SomeThing {
constructor() {
/**
* Holds a reference to the managed Maps marker.
*
* @const
* @type {!google.maps.Marker}
* @private
*/
this.marker_ = new google.maps.Marker({});
}
}
```

simple, right? however, Closure Compiler fails to recognize the symbols as externs:
```
(17:00:49) INFO: Current date is 2021-01-16
(17:00:49) INFO: Analyzed target //:some-js.js (0 packages loaded, 0 targets configured).
(17:00:49) INFO: Found 1 target...
(17:00:56) ERROR: /...project path.../BUILD.bazel:207:7: Compiling N JavaScript files to some-js.js failed (Exit 1): ClosureWorker failed: error executing command bazel-out/host/bin/external/io_bazel_rules_closure/java/io/bazel/rules/closure/ClosureWorker @@bazel-out/darwin-fastbuild/bin/some-js.js-0.params
some-thing.js:12: ERROR - could not determine the type of this expression
this.marker_ = new google.maps.Marker({});
^
ProTip: "JSC_UNKNOWN_EXPR_TYPE" or "missingSourcesWarnings" or "reportUnknownTypes" can be added to the `suppress` attribute of:
//:some-thing
Alternatively /** @suppress {reportUnknownTypes} */ can be added to the source file.

1 error(s), 0 warning(s), 97.6% typed

Target //:some-js failed to build
Use --verbose_failures to see the command lines of failed build steps.
(17:00:56) INFO: Elapsed time: 6.756s, Critical Path: 6.29s
(17:00:56) INFO: 3 processes: 2 internal, 1 worker.
(17:00:56) FAILED: Build did NOT complete successfully
```

## Diagnosis

Okay, so, let's start in the [extern (`google_maps_api_v3_43.js`)](https://github.com/google/closure-compiler/blob/master/contrib/externs/maps/google_maps_api_v3_43.js).

**`google_maps_api_v3_43.js:4845`:**
```js
/**
* @param {google.maps.MarkerOptions=} opts
* @extends {google.maps.MVCObject}
* @constructor
*/
google.maps.Marker = function(opts) {};
```

So it's in the extern. So maybe the extern isn't getting loaded by `rules_closure` / `JsChecker`? Let's check:

```
> grep gmaps bazel-out/darwin-fastbuild/bin/some-js.js-0.params
bazel-out/darwin-fastbuild/bin/tools/externs/libs/gmaps.pbtxt
tools/externs/libs/gmaps.js
```

So it's making it into the build ☹️

To make matters even more interesting, with the Firebase externs, I had to change the `firebase = {};` `@namespace` to `const =` for it to work:

**`firebase-externs.js`:**
```js
/**
* @fileoverview Firebase namespace and Firebase App API.
* @externs
*/

/**
* firebase is a global namespace from which all the Firebase
* services are accessed.
*
* @namespace
*/
const firebase = {};
```

No idea why, and I'm still suspicious of this change (i.e. maybe it just busted a build cache somewhere), but changing `var =` to `const =` fixed my extern issues with Firebase. Trying that with Google Maps wasn't fruitful:

**`google_maps_api_v3_43.js:4845`:**
```
/**
* @fileoverview Externs for the Google Maps v3 API.
* @see https://developers.google.com/maps/documentation/javascript/reference
* @externs
*/

/**
* @const
* @suppress {const,duplicate,strictMissingProperties}
*/
const google = {};
```

Yields:

```
(17:17:02) INFO: Current date is 2021-01-16
(17:17:02) INFO: Analyzed target //:some-js.js (0 packages loaded, 0 targets configured).
(17:17:02) INFO: Found 1 target...
(17:17:08) ERROR: /Volumes/VANTAGE/platform/cookies/frontend/BUILD.bazel:207:7: Compiling N JavaScript files to some-js.js failed (Exit 1): ClosureWorker failed: error executing command bazel-out/host/bin/external/io_bazel_rules_closure/java/io/bazel/rules/closure/ClosureWorker @@bazel-out/darwin-fastbuild/bin/some-js.js-0.params
(gmaps-extern):27: ERROR - Illegal redeclared variable: google
const google = {};
^
ProTip: "JSC_REDECLARED_VARIABLE_ERROR" can be added to the `suppress` attribute of:
//:gmaps

1 error(s), 0 warning(s)

Target //:some-js.js failed to build
Use --verbose_failures to see the command lines of failed build steps.
(17:18:26) INFO: Elapsed time: 1.793s, Critical Path: 1.33s
(17:18:26) INFO: 3 processes: 2 internal, 1 worker.
(17:18:26) FAILED: Build did NOT complete successfully
```

This is already weird because I don't have a `google =` anywhere in my code (in an extern or otherwise). As far as I can tell, none of my upstream dependencies do, either. But if I add that suppression:

```
# ...
ERROR: Bad --suppress value: JSC_REDECLARED_VARIABLE_ERROR
# ...
```

Going back to our Google Maps sample, what's even more interesting is, it will find certain symbols in the gmaps extern, for instance:
- `google.maps.LatLng`
- `google.maps.Point`
- `google.maps.Map` (it seems to find this _sometimes_)

So, I'm stuck at this point, because (1) there appears to (maybe) be a builtin `google` that I cannot re-define, because of (2) a warning from JSC that I cannot suppress or (3) perhaps an inability to re-declare this namespace for my use.

Any ideas? Is anyone else experiencing the same thing?

Contributor guide

Open the contributing guide

Research direction

Start with the WORKSPACE and BUILD.bazel reproducer, then inspect some-thing.js and the referenced google_maps_api_v3_43.js extern. Run the Bazel target and compare the generated some-js.js-0.params entries with JsChecker's diagnostics. Done means the extern symbols resolve consistently without the redeclaration or unknown-type errors.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript
Domain
build-system, tooling
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
32/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.