Skip to content

Weird issues with externs #509

Open
Open
@sgammon

Description

@sgammon

When trying to use Google Maps externs, 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:

# ...

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:

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:

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).

google_maps_api_v3_43.js:4845:

/**
 * @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:

/**
 * @fileoverview Firebase namespace and Firebase App API.
 * @externs
 */

/**
 * <code>firebase</code> 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?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions