diff --git a/.bazelrc b/.bazelrc new file mode 100644 index 0000000..3ce91d2 --- /dev/null +++ b/.bazelrc @@ -0,0 +1 @@ +common --enable_bzlmod diff --git a/.github/workflows/ci.bazelrc b/.github/workflows/ci.bazelrc new file mode 100644 index 0000000..a21ece4 --- /dev/null +++ b/.github/workflows/ci.bazelrc @@ -0,0 +1,13 @@ +# This file contains Bazel settings to apply on CI only. +# It is referenced with a --bazelrc option in the call to bazel in ci.yaml + +# Debug where options came from +build --announce_rc +# This directory is configured in GitHub actions to be persisted between runs. +build --disk_cache=~/.cache/bazel +build --repository_cache=~/.cache/bazel-repo +# Don't rely on test logs being easily accessible from the test runner, +# though it makes the log noisier. +test --test_output=errors +# Allows tests to run bazelisk-in-bazel, since this is the cache folder used +test --test_env=XDG_CACHE_HOME diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..2cce537 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,88 @@ +name: CI + +# Controls when the action will run. +on: + # Triggers the workflow on push or pull request events but only for the main branch + push: + branches: [main, es6] + pull_request: + branches: [main, es6] + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +concurrency: + # Cancel previous actions from the same PR: https://stackoverflow.com/a/72408109 + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + # matrix-prep-* steps generate JSON used to create a dynamic actions matrix. + # Insanely complex for how simple this requirement is inspired from + # https://stackoverflow.com/questions/65384420/how-to-make-a-github-action-matrix-element-conditional + + matrix-prep-bazelversion: + # Prepares the 'bazelversion' axis of the test matrix + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - id: bazel_from_bazelversion + run: echo "bazelversion=$(head -n 1 .bazelversion)" >> $GITHUB_OUTPUT + # bazel 5 testing disabled for now due to + # https://github.com/aspect-build/bazel-lib/issues/392 + # - id: bazel_5 + # run: echo "bazelversion=5.3.2" >> $GITHUB_OUTPUT + outputs: + # Will look like [""] + bazelversions: ${{ toJSON(steps.*.outputs.bazelversion) }} + + bazel-build: + # The type of runner that the job will run on + runs-on: ubuntu-latest + + needs: + - matrix-prep-bazelversion + + # Run bazel test in each workspace with each version of Bazel supported + strategy: + fail-fast: false + matrix: + bazelversion: ${{ fromJSON(needs.matrix-prep-bazelversion.outputs.bazelversions) }} + folder: + - "." + + # Steps represent a sequence of tasks that will be executed as part of the job + steps: + # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it + - uses: actions/checkout@v3 + + # Cache build and external artifacts so that the next ci build is incremental. + # Because github action caches cannot be updated after a build, we need to + # store the contents of each build in a unique cache key, then fall back to loading + # it on the next ci run. We use hashFiles(...) in the key and restore-keys- with + # the prefix to load the most recent cache for the branch on a cache miss. You + # should customize the contents of hashFiles to capture any bazel input sources, + # although this doesn't need to be perfect. If none of the input sources change + # then a cache hit will load an existing cache and bazel won't have to do any work. + # In the case of a cache miss, you want the fallback cache to contain most of the + # previously built artifacts to minimize build time. The more precise you are with + # hashFiles sources the less work bazel will have to do. + - name: Mount bazel caches + uses: actions/cache@v3 + with: + path: | + ~/.cache/bazel + ~/.cache/bazel-repo + key: bazel-cache-${{ hashFiles('**/BUILD.bazel', '**/*.bzl', 'WORKSPACE') }} + restore-keys: bazel-cache- + + - name: Configure Bazel version + working-directory: ${{ matrix.folder }} + run: echo "${{ matrix.bazelversion }}" > .bazelversion + + - name: bazel build //generator/... + env: + # Bazelisk will download bazel to here, ensure it is cached between runs. + XDG_CACHE_HOME: ~/.cache/bazel-repo + working-directory: ${{ matrix.folder }} + run: bazel --bazelrc=$GITHUB_WORKSPACE/.github/workflows/ci.bazelrc --bazelrc=.github/workflows/ci.bazelrc build //generator/... diff --git a/.gitignore b/.gitignore index 99c6e65..1db47c8 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,14 @@ bazel-* /testproto_libs1.js /testproto_libs2.js /google-protobuf.js +/google-protobuf.js.map + +### Automatically added by Hedron's Bazel Compile Commands Extractor: https://github.com/hedronvision/bazel-compile-commands-extractor +# Ignore the `external` link (that is added by `bazel-compile-commands-extractor`). The link differs between macOS/Linux and Windows, so it shouldn't be checked in. The pattern must not end with a trailing `/` because it's a symlink on macOS/Linux. +/external +# Ignore links to Bazel's output. The pattern needs the `*` because people can change the name of the directory into which your repository is cloned (changing the `bazel-` symlink), and must not end with a trailing `/` because it's a symlink on macOS/Linux. +/bazel-* +# Ignore generated output. Although valuable (after all, the primary purpose of `bazel-compile-commands-extractor` is to produce `compile_commands.json`!), it should not be checked in. +/compile_commands.json +# Ignore the directory in which `clangd` stores its local index. +/.cache/ diff --git a/BUILD.bazel b/BUILD.bazel index 5483303..9837c9b 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -21,7 +21,7 @@ pkg_files( name = "dist_files", srcs = glob([ "google/protobuf/*.js", - "google/protobuf/compiler/*.js" + "google/protobuf/compiler/*.js", ]) + [ "google-protobuf.js", "package.json", @@ -58,5 +58,5 @@ filegroup( srcs = [ ":dist_tar", ":dist_zip", - ] + ], ) diff --git a/MODULE.bazel b/MODULE.bazel new file mode 100644 index 0000000..7048166 --- /dev/null +++ b/MODULE.bazel @@ -0,0 +1,23 @@ +module( + name = "protobuf_javascript_gonzojive", + version = "3.21.5", +) + +bazel_dep(name = "protobuf", version = "21.7", repo_name = "com_google_protobuf") +bazel_dep(name = "abseil-cpp", version = "20211102.0") +bazel_dep(name = "rules_pkg", version = "0.7.0") + +# For VS Code autocompletion: +# https://github.com/hedronvision/bazel-compile-commands-extractor#usage +# +# Run bazel run @hedron_compile_commands//:refresh_all to get autocomplete +# working in VS Code and other editors. +# Not currently working due to https://github.com/hedronvision/bazel-compile-commands-extractor/issues/199 +bazel_dep(name = "hedron_compile_commands", dev_dependency = True) +git_override( + module_name = "hedron_compile_commands", + commit = "0e990032f3c5a866e72615cf67e5ce22186dcb97", + remote = "https://github.com/hedronvision/bazel-compile-commands-extractor.git", + # Replace the commit hash (above) with the latest (https://github.com/hedronvision/bazel-compile-commands-extractor/commits/main). + # Even better, set up Renovate and let it do the work for you (see "Suggestion: Updates" in the README). +) diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock new file mode 100644 index 0000000..f5cd531 --- /dev/null +++ b/MODULE.bazel.lock @@ -0,0 +1,110 @@ +{ + "lockFileVersion": 11, + "registryFileHashes": { + "https://bcr.bazel.build/bazel_registry.json": "8a28e4aff06ee60aed2a8c281907fb8bcbf3b753c91fb5a5c57da3215d5b3497", + "https://bcr.bazel.build/modules/abseil-cpp/20210324.2/MODULE.bazel": "7cd0312e064fde87c8d1cd79ba06c876bd23630c83466e9500321be55c96ace2", + "https://bcr.bazel.build/modules/abseil-cpp/20211102.0/MODULE.bazel": "70390338f7a5106231d20620712f7cccb659cd0e9d073d1991c038eb9fc57589", + "https://bcr.bazel.build/modules/abseil-cpp/20211102.0/source.json": "7e3a9adf473e9af076ae485ed649d5641ad50ec5c11718103f34de03170d94ad", + "https://bcr.bazel.build/modules/apple_support/1.5.0/MODULE.bazel": "50341a62efbc483e8a2a6aec30994a58749bd7b885e18dd96aa8c33031e558ef", + "https://bcr.bazel.build/modules/apple_support/1.5.0/source.json": "eb98a7627c0bc486b57f598ad8da50f6625d974c8f723e9ea71bd39f709c9862", + "https://bcr.bazel.build/modules/bazel_features/1.11.0/MODULE.bazel": "f9382337dd5a474c3b7d334c2f83e50b6eaedc284253334cf823044a26de03e8", + "https://bcr.bazel.build/modules/bazel_features/1.11.0/source.json": "c9320aa53cd1c441d24bd6b716da087ad7e4ff0d9742a9884587596edfe53015", + "https://bcr.bazel.build/modules/bazel_skylib/1.0.3/MODULE.bazel": "bcb0fd896384802d1ad283b4e4eb4d718eebd8cb820b0a2c3a347fb971afd9d8", + "https://bcr.bazel.build/modules/bazel_skylib/1.2.1/MODULE.bazel": "f35baf9da0efe45fa3da1696ae906eea3d615ad41e2e3def4aeb4e8bc0ef9a7a", + "https://bcr.bazel.build/modules/bazel_skylib/1.3.0/MODULE.bazel": "20228b92868bf5cfc41bda7afc8a8ba2a543201851de39d990ec957b513579c5", + "https://bcr.bazel.build/modules/bazel_skylib/1.6.1/MODULE.bazel": "8fdee2dbaace6c252131c00e1de4b165dc65af02ea278476187765e1a617b917", + "https://bcr.bazel.build/modules/bazel_skylib/1.6.1/source.json": "082ed5f9837901fada8c68c2f3ddc958bb22b6d654f71dd73f3df30d45d4b749", + "https://bcr.bazel.build/modules/buildozer/7.1.2/MODULE.bazel": "2e8dd40ede9c454042645fd8d8d0cd1527966aa5c919de86661e62953cd73d84", + "https://bcr.bazel.build/modules/buildozer/7.1.2/source.json": "c9028a501d2db85793a6996205c8de120944f50a0d570438fcae0457a5f9d1f8", + "https://bcr.bazel.build/modules/googletest/1.11.0/MODULE.bazel": "3a83f095183f66345ca86aa13c58b59f9f94a2f81999c093d4eeaa2d262d12f4", + "https://bcr.bazel.build/modules/googletest/1.11.0/source.json": "c73d9ef4268c91bd0c1cd88f1f9dfa08e814b1dbe89b5f594a9f08ba0244d206", + "https://bcr.bazel.build/modules/platforms/0.0.4/MODULE.bazel": "9b328e31ee156f53f3c416a64f8491f7eb731742655a47c9eec4703a71644aee", + "https://bcr.bazel.build/modules/platforms/0.0.5/MODULE.bazel": "5733b54ea419d5eaf7997054bb55f6a1d0b5ff8aedf0176fef9eea44f3acda37", + "https://bcr.bazel.build/modules/platforms/0.0.6/MODULE.bazel": "ad6eeef431dc52aefd2d77ed20a4b353f8ebf0f4ecdd26a807d2da5aa8cd0615", + "https://bcr.bazel.build/modules/platforms/0.0.7/MODULE.bazel": "72fd4a0ede9ee5c021f6a8dd92b503e089f46c227ba2813ff183b71616034814", + "https://bcr.bazel.build/modules/platforms/0.0.9/MODULE.bazel": "4a87a60c927b56ddd67db50c89acaa62f4ce2a1d2149ccb63ffd871d5ce29ebc", + "https://bcr.bazel.build/modules/platforms/0.0.9/source.json": "cd74d854bf16a9e002fb2ca7b1a421f4403cda29f824a765acd3a8c56f8d43e6", + "https://bcr.bazel.build/modules/protobuf/21.7/MODULE.bazel": "a5a29bb89544f9b97edce05642fac225a808b5b7be74038ea3640fae2f8e66a7", + "https://bcr.bazel.build/modules/protobuf/21.7/source.json": "bbe500720421e582ff2d18b0802464205138c06056f443184de39fbb8187b09b", + "https://bcr.bazel.build/modules/protobuf/3.19.0/MODULE.bazel": "6b5fbb433f760a99a22b18b6850ed5784ef0e9928a72668b66e4d7ccd47db9b0", + "https://bcr.bazel.build/modules/protobuf/3.19.6/MODULE.bazel": "9233edc5e1f2ee276a60de3eaa47ac4132302ef9643238f23128fea53ea12858", + "https://bcr.bazel.build/modules/rules_cc/0.0.1/MODULE.bazel": "cb2aa0747f84c6c3a78dad4e2049c154f08ab9d166b1273835a8174940365647", + "https://bcr.bazel.build/modules/rules_cc/0.0.2/MODULE.bazel": "6915987c90970493ab97393024c156ea8fb9f3bea953b2f3ec05c34f19b5695c", + "https://bcr.bazel.build/modules/rules_cc/0.0.8/MODULE.bazel": "964c85c82cfeb6f3855e6a07054fdb159aced38e99a5eecf7bce9d53990afa3e", + "https://bcr.bazel.build/modules/rules_cc/0.0.9/MODULE.bazel": "836e76439f354b89afe6a911a7adf59a6b2518fafb174483ad78a2a2fde7b1c5", + "https://bcr.bazel.build/modules/rules_cc/0.0.9/source.json": "1f1ba6fea244b616de4a554a0f4983c91a9301640c8fe0dd1d410254115c8430", + "https://bcr.bazel.build/modules/rules_java/4.0.0/MODULE.bazel": "5a78a7ae82cd1a33cef56dc578c7d2a46ed0dca12643ee45edbb8417899e6f74", + "https://bcr.bazel.build/modules/rules_java/7.6.1/MODULE.bazel": "2f14b7e8a1aa2f67ae92bc69d1ec0fa8d9f827c4e17ff5e5f02e91caa3b2d0fe", + "https://bcr.bazel.build/modules/rules_java/7.6.1/source.json": "8f3f3076554e1558e8e468b2232991c510ecbcbed9e6f8c06ac31c93bcf38362", + "https://bcr.bazel.build/modules/rules_jvm_external/4.4.2/MODULE.bazel": "a56b85e418c83eb1839819f0b515c431010160383306d13ec21959ac412d2fe7", + "https://bcr.bazel.build/modules/rules_jvm_external/4.4.2/source.json": "a075731e1b46bc8425098512d038d416e966ab19684a10a34f4741295642fc35", + "https://bcr.bazel.build/modules/rules_license/0.0.3/MODULE.bazel": "627e9ab0247f7d1e05736b59dbb1b6871373de5ad31c3011880b4133cafd4bd0", + "https://bcr.bazel.build/modules/rules_license/0.0.7/MODULE.bazel": "088fbeb0b6a419005b89cf93fe62d9517c0a2b8bb56af3244af65ecfe37e7d5d", + "https://bcr.bazel.build/modules/rules_license/0.0.7/source.json": "355cc5737a0f294e560d52b1b7a6492d4fff2caf0bef1a315df5a298fca2d34a", + "https://bcr.bazel.build/modules/rules_pkg/0.7.0/MODULE.bazel": "df99f03fc7934a4737122518bb87e667e62d780b610910f0447665a7e2be62dc", + "https://bcr.bazel.build/modules/rules_pkg/0.7.0/source.json": "c2557066e0c0342223ba592510ad3d812d4963b9024831f7f66fd0584dd8c66c", + "https://bcr.bazel.build/modules/rules_proto/4.0.0/MODULE.bazel": "a7a7b6ce9bee418c1a760b3d84f83a299ad6952f9903c67f19e4edd964894e06", + "https://bcr.bazel.build/modules/rules_proto/5.3.0-21.7/MODULE.bazel": "e8dff86b0971688790ae75528fe1813f71809b5afd57facb44dad9e8eca631b7", + "https://bcr.bazel.build/modules/rules_proto/5.3.0-21.7/source.json": "d57902c052424dfda0e71646cb12668d39c4620ee0544294d9d941e7d12bc3a9", + "https://bcr.bazel.build/modules/rules_python/0.10.2/MODULE.bazel": "cc82bc96f2997baa545ab3ce73f196d040ffb8756fd2d66125a530031cd90e5f", + "https://bcr.bazel.build/modules/rules_python/0.22.1/MODULE.bazel": "26114f0c0b5e93018c0c066d6673f1a2c3737c7e90af95eff30cfee38d0bbac7", + "https://bcr.bazel.build/modules/rules_python/0.22.1/source.json": "57226905e783bae7c37c2dd662be078728e48fa28ee4324a7eabcafb5a43d014", + "https://bcr.bazel.build/modules/rules_python/0.4.0/MODULE.bazel": "9208ee05fd48bf09ac60ed269791cf17fb343db56c8226a720fbb1cdf467166c", + "https://bcr.bazel.build/modules/stardoc/0.5.1/MODULE.bazel": "1a05d92974d0c122f5ccf09291442580317cdd859f07a8655f1db9a60374f9f8", + "https://bcr.bazel.build/modules/stardoc/0.5.1/source.json": "a96f95e02123320aa015b956f29c00cb818fa891ef823d55148e1a362caacf29", + "https://bcr.bazel.build/modules/upb/0.0.0-20220923-a547704/MODULE.bazel": "7298990c00040a0e2f121f6c32544bab27d4452f80d9ce51349b1a28f3005c43", + "https://bcr.bazel.build/modules/upb/0.0.0-20220923-a547704/source.json": "f1ef7d3f9e0e26d4b23d1c39b5f5de71f584dd7d1b4ef83d9bbba6ec7a6a6459", + "https://bcr.bazel.build/modules/zlib/1.2.11/MODULE.bazel": "07b389abc85fdbca459b69e2ec656ae5622873af3f845e1c9d80fe179f3effa0", + "https://bcr.bazel.build/modules/zlib/1.2.12/MODULE.bazel": "3b1a8834ada2a883674be8cbd36ede1b6ec481477ada359cd2d3ddc562340b27", + "https://bcr.bazel.build/modules/zlib/1.3/MODULE.bazel": "6a9c02f19a24dcedb05572b2381446e27c272cd383aed11d41d99da9e3167a72", + "https://bcr.bazel.build/modules/zlib/1.3/source.json": "b6b43d0737af846022636e6e255fd4a96fee0d34f08f3830e6e0bac51465c37c" + }, + "selectedYankedVersions": {}, + "moduleExtensions": { + "@@apple_support~//crosstool:setup.bzl%apple_cc_configure_extension": { + "general": { + "bzlTransitiveDigest": "PjIds3feoYE8SGbbIq2SFTZy3zmxeO2tQevJZNDo7iY=", + "usagesDigest": "aLmqbvowmHkkBPve05yyDNGN7oh7QE9kBADr3QIZTZs=", + "recordedFileInputs": {}, + "recordedDirentsInputs": {}, + "envVariables": {}, + "generatedRepoSpecs": { + "local_config_apple_cc": { + "bzlFile": "@@apple_support~//crosstool:setup.bzl", + "ruleClassName": "_apple_cc_autoconf", + "attributes": {} + }, + "local_config_apple_cc_toolchains": { + "bzlFile": "@@apple_support~//crosstool:setup.bzl", + "ruleClassName": "_apple_cc_autoconf_toolchains", + "attributes": {} + } + }, + "recordedRepoMappingEntries": [ + [ + "apple_support~", + "bazel_tools", + "bazel_tools" + ] + ] + } + }, + "@@platforms//host:extension.bzl%host_platform": { + "general": { + "bzlTransitiveDigest": "xelQcPZH8+tmuOHVjL9vDxMnnQNMlwj0SlvgoqBkm4U=", + "usagesDigest": "meSzxn3DUCcYEhq4HQwExWkWtU4EjriRBQLsZN+Q0SU=", + "recordedFileInputs": {}, + "recordedDirentsInputs": {}, + "envVariables": {}, + "generatedRepoSpecs": { + "host_platform": { + "bzlFile": "@@platforms//host:extension.bzl", + "ruleClassName": "host_platform_repo", + "attributes": {} + } + }, + "recordedRepoMappingEntries": [] + } + } + } +} diff --git a/WORKSPACE b/WORKSPACE deleted file mode 100644 index 6902897..0000000 --- a/WORKSPACE +++ /dev/null @@ -1,16 +0,0 @@ -workspace(name = "com_google_protobuf_javascript") - -load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") - -http_archive( - name = "com_google_protobuf", - strip_prefix = "protobuf-21.3", - urls = ["https://github.com/protocolbuffers/protobuf/archive/refs/tags/v21.3.zip"], -) - -load("@com_google_protobuf//:protobuf_deps.bzl", "protobuf_deps") - -protobuf_deps() - -load("@rules_pkg//:deps.bzl", "rules_pkg_dependencies") -rules_pkg_dependencies() diff --git a/generator/BUILD.bazel b/generator/BUILD.bazel index b865ca4..cfdd909 100644 --- a/generator/BUILD.bazel +++ b/generator/BUILD.bazel @@ -9,8 +9,8 @@ cc_binary( ], visibility = ["//visibility:public"], deps = [ + "@abseil-cpp//absl/strings:str_format", "@com_google_protobuf//:protobuf", "@com_google_protobuf//:protoc_lib", ], ) - diff --git a/generator/js_generator.cc b/generator/js_generator.cc index 97b5844..86891fd 100644 --- a/generator/js_generator.cc +++ b/generator/js_generator.cc @@ -37,7 +37,7 @@ #include #include #include -#include +#include "absl/strings/str_format.h" #include #include @@ -76,16 +76,6 @@ static const int kNumKeyword = sizeof(kKeyword) / sizeof(char*); namespace { -// The mode of operation for bytes fields. Historically JSPB always carried -// bytes as JS {string}, containing base64 content by convention. With binary -// and proto3 serialization the new convention is to represent it as binary -// data in Uint8Array. See b/26173701 for background on the migration. -enum BytesMode { - BYTES_DEFAULT, // Default type for getBytesField to return. - BYTES_B64, // Explicitly coerce to base64 string where needed. - BYTES_U8, // Explicitly coerce to Uint8Array where needed. -}; - bool IsReserved(const std::string& ident) { for (int i = 0; i < kNumKeyword; i++) { if (ident == kKeyword[i]) { @@ -152,6 +142,15 @@ std::string ModuleAlias(const std::string& filename) { // file descriptor's package. std::string GetNamespace(const GeneratorOptions& options, const FileDescriptor* file) { + // if (options.import_style == GeneratorOptions::kImportEs6) { + // //std::string dotSeparated = "proto." + file->package(); + // // Use $ because it's not valid in proto package names + // // (https://developers.google.com/protocol-buffers/docs/reference/proto3-spec#identifiers). + // // If we used _, "foo.a_b" would be equivalent to "foo.a.b". + // //ReplaceCharacters(&dotSeparated, ".", '$'); + // //return dotSeparated; + // } + if (!options.namespace_prefix.empty()) { return options.namespace_prefix; } else if (!file->package().empty()) { @@ -237,11 +236,11 @@ std::string MaybeCrossFileRef(const GeneratorOptions& options, } } -std::string SubmessageTypeRef(const GeneratorOptions& options, - const FieldDescriptor* field) { - GOOGLE_CHECK(field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE); - return MaybeCrossFileRef(options, field->file(), field->message_type()); -} +// std::string SubmessageTypeRef(const GeneratorOptions& options, +// const FieldDescriptor* field) { +// GOOGLE_CHECK(field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE); +// return MaybeCrossFileRef(options, field->file(), field->message_type()); +// } // - Object field name: LOWER_UNDERSCORE -> LOWER_CAMEL, except for group fields // (UPPER_CAMEL -> LOWER_CAMEL), with "List" (or "Map") appended if appropriate, @@ -680,9 +679,9 @@ bool EscapeJSString(const std::string& in, std::string* out) { if (codepoint >= 0x20 && codepoint <= 0x7e) { *out += static_cast(codepoint); } else if (codepoint >= 0x100) { - *out += StringPrintf("\\u%04x", codepoint); + *out += absl::StrFormat("\\u%04x", codepoint); } else { - *out += StringPrintf("\\x%02x", codepoint); + *out += absl::StrFormat("\\x%02x", codepoint); } break; } @@ -1146,12 +1145,12 @@ bool HasRepeatedFields(const GeneratorOptions& options, return false; } -static const char* kRepeatedFieldArrayName = ".repeatedFields_"; +static const char* kRepeatedFieldArrayName = "repeatedFields_"; std::string RepeatedFieldsArrayName(const GeneratorOptions& options, const Descriptor* desc) { return HasRepeatedFields(options, desc) - ? (GetMessagePath(options, desc) + kRepeatedFieldArrayName) + ? (desc->name() + "." + kRepeatedFieldArrayName) : "null"; } @@ -1164,12 +1163,12 @@ bool HasOneofFields(const Descriptor* desc) { return false; } -static const char* kOneofGroupArrayName = ".oneofGroups_"; +static const char* kOneofGroupArrayName = "oneofGroups_"; std::string OneofFieldsArrayName(const GeneratorOptions& options, const Descriptor* desc) { return HasOneofFields(desc) - ? (GetMessagePath(options, desc) + kOneofGroupArrayName) + ? (desc->name() + "." + kOneofGroupArrayName) : "null"; } @@ -1241,7 +1240,9 @@ std::string RelativeTypeName(const FieldDescriptor* field) { std::string JSExtensionsObjectName(const GeneratorOptions& options, const FileDescriptor* from_file, const Descriptor* desc) { - if (desc->full_name() == "google.protobuf.bridge.MessageSet") { + if (options.WantEs6()) { + return TypeNames::JsName(desc->name()) + ".extensions"; + } else if (desc->full_name() == "google.protobuf.bridge.MessageSet") { // TODO(haberman): fix this for the kImportCommonJs case. return "jspb.Message.messageSetExtensions"; } else { @@ -1275,7 +1276,7 @@ std::string FieldDefinition(const GeneratorOptions& options, } else { value_type = ProtoTypeName(options, value_field); } - return StringPrintf("map<%s, %s> %s = %d;", key_type.c_str(), + return absl::StrFormat("map<%s, %s> %s = %d;", key_type.c_str(), value_type.c_str(), field->name().c_str(), field->number()); } else { @@ -1294,7 +1295,7 @@ std::string FieldDefinition(const GeneratorOptions& options, type = ProtoTypeName(options, field); name = field->name(); } - return StringPrintf("%s %s %s = %d;", qualifier.c_str(), type.c_str(), + return absl::StrFormat("%s %s %s = %d;", qualifier.c_str(), type.c_str(), name.c_str(), field->number()); } } @@ -1726,6 +1727,9 @@ void Generator::GenerateProvides(const GeneratorOptions& options, it != provided->end(); ++it) { if (options.import_style == GeneratorOptions::kImportClosure) { printer->Print("goog.provide('$name$');\n", "name", *it); + } else if (options.WantEs6()) { + // In ES6 mode, we do not construct the tree of objects + // using goog.exportSymbol. } else { // We aren't using Closure's import system, but we use goog.exportSymbol() // to construct the expected tree of objects, eg. @@ -1778,7 +1782,9 @@ void Generator::GenerateRequiresForLibrary( const GeneratorOptions& options, io::Printer* printer, const std::vector& files, std::set* provided) const { - GOOGLE_CHECK_EQ(options.import_style, GeneratorOptions::kImportClosure); + GOOGLE_CHECK_OK( + options.import_style == GeneratorOptions::kImportClosure || + options.import_style == GeneratorOptions::kImportEs6); // For Closure imports we need to import every message type individually. std::set required; std::set forwards; @@ -1850,6 +1856,12 @@ void Generator::GenerateRequiresImpl(const GeneratorOptions& options, std::set* provided, bool require_jspb, bool require_extension, bool require_map) const { + + if (options.WantEs6()) { + // In ES6 mode, imports are handled by GenerateFile and + // goog.* isn't used. + return; + } if (require_jspb) { required->insert("jspb.Message"); required->insert("jspb.BinaryReader"); @@ -1952,14 +1964,19 @@ void Generator::GenerateTestOnly(const GeneratorOptions& options, } void Generator::GenerateClassesAndEnums(const GeneratorOptions& options, + const TypeNames& type_names, io::Printer* printer, const FileDescriptor* file) const { - for (int i = 0; i < file->message_type_count(); i++) { - GenerateClassConstructorAndDeclareExtensionFieldInfo(options, printer, - file->message_type(i)); + // In ES6 module mode, class constructors are generated within + // GenerateClass. + if (options.import_style != GeneratorOptions::kImportEs6) { + for (int i = 0; i < file->message_type_count(); i++) { + GenerateClassConstructorAndDeclareExtensionFieldInfo(options, printer, + file->message_type(i)); + } } for (int i = 0; i < file->message_type_count(); i++) { - GenerateClass(options, printer, file->message_type(i)); + GenerateClass(options, type_names, printer, file->message_type(i)); } for (int i = 0; i < file->enum_type_count(); i++) { GenerateEnum(options, printer, file->enum_type(i)); @@ -1967,8 +1984,13 @@ void Generator::GenerateClassesAndEnums(const GeneratorOptions& options, } void Generator::GenerateClass(const GeneratorOptions& options, + const TypeNames& type_names, io::Printer* printer, const Descriptor* desc) const { + if (options.import_style == GeneratorOptions::kImportEs6) { + GenerateClassEs6(options, type_names, printer, desc); + return; + } if (IgnoreMessage(desc)) { return; } @@ -1977,13 +1999,21 @@ void Generator::GenerateClass(const GeneratorOptions& options, printer->Print("\n"); GenerateClassFieldInfo(options, printer, desc); - GenerateClassToObject(options, printer, desc); + GenerateClassToObject(options, type_names, printer, desc); // These must come *before* the extension-field info generation in // GenerateClassRegistration so that references to the binary // serialization/deserialization functions may be placed in the extension // objects. - GenerateClassDeserializeBinary(options, printer, desc); - GenerateClassSerializeBinary(options, printer, desc); + GenerateClassDeserializeBinary(options, type_names, printer, desc); + GenerateClassSerializeBinary(options, type_names, printer, desc); + + // Emit well-known type methods. + for (FileToc* toc = well_known_types_js; toc->name != NULL; toc++) { + std::string name = std::string("google/protobuf/") + toc->name; + if (name == StripProto(desc->file()->name()) + ".js") { + printer->Print(toc->data); + } + } } // Recurse on nested types. These must come *before* the extension-field @@ -1993,55 +2023,143 @@ void Generator::GenerateClass(const GeneratorOptions& options, GenerateEnum(options, printer, desc->enum_type(i)); } for (int i = 0; i < desc->nested_type_count(); i++) { - GenerateClass(options, printer, desc->nested_type(i)); + GenerateClass(options, type_names, printer, desc->nested_type(i)); } if (!NamespaceOnly(desc)) { - GenerateClassRegistration(options, printer, desc); - GenerateClassFields(options, printer, desc); + GenerateClassRegistration(options, type_names, printer, desc); + GenerateClassFields(options, type_names, printer, desc); if (options.import_style != GeneratorOptions::kImportClosure) { for (int i = 0; i < desc->extension_count(); i++) { - GenerateExtension(options, printer, desc->extension(i)); + GenerateExtension(options, type_names, printer, desc->extension(i)); } } } } +void Generator::GenerateClassEs6(const GeneratorOptions& options, + const TypeNames& type_names, + io::Printer* printer, + const Descriptor* desc) const { + if (IgnoreMessage(desc)) { + return; + } + + + std::string prefix = (desc->containing_type() == nullptr) ? + "export " : ("static " + desc->name() + " = "); + + printer->Print("\n"); + printer->Print( + "/**\n" + " * ES6 class generated by js_generator.cc.\n" + " *\n" + " * @param {Array=} opt_data Optional initial data array, typically " + "from a\n" + " * server response, or constructed directly in Javascript. The array " + "is used\n" + " * in place and becomes part of the constructed object. It is not " + "cloned.\n" + " * If no data is provided, the constructed object will be empty, but " + "still\n" + " * valid.\n" + " * @extends {jspb.Message}\n" + " * @constructor\n" + " */\n" + "$prefix$class $classname$ extends jspb.Message {\n", + "prefix", prefix, + "classname", desc->name()); + + printer->Indent(); + + GenerateClassConstructorAndDeclareExtensionFieldInfo(options, printer, desc); + + GenerateClassFieldInfo(options, printer, desc); + + GenerateClassToObject(options, type_names, printer, desc); + + // These must come *before* the extension-field info generation in + // GenerateClassRegistration so that references to the binary + // serialization/deserialization functions may be placed in the extension + // objects. + GenerateClassDeserializeBinary(options, type_names, printer, desc); + GenerateClassSerializeBinary(options, type_names, printer, desc); + + // Recurse on nested types. These must come *before* the extension-field + // info generation in GenerateClassRegistration so that extensions that + // reference nested types proceed the definitions of the nested types. + for (int i = 0; i < desc->enum_type_count(); i++) { + GenerateEnum(options, printer, desc->enum_type(i)); + } + for (int i = 0; i < desc->nested_type_count(); i++) { + GenerateClass(options, type_names, printer, desc->nested_type(i)); + } + + GenerateClassRegistration(options, type_names, printer, desc); + GenerateClassFields(options, type_names, printer, desc); + for (int i = 0; i < desc->extension_count(); i++) { + GenerateExtension(options, type_names, printer, desc->extension(i)); + } + + printer->Outdent(); + printer->Print("} // end class $classname$\n", + "classname", desc->name()); // end class $classname$ extends jspb.Message +} + void Generator::GenerateClassConstructor(const GeneratorOptions& options, io::Printer* printer, const Descriptor* desc) const { + const std::string classSymbol = GetMessagePath(options, desc); + const std::string methodStart = MethodStart( + options, classSymbol.c_str(), "constructor"); + const char * methodEnd = options.WantEs6() ? "}" : "};"; + printer->Print( - "/**\n" - " * Generated by JsPbCodeGenerator.\n" - " * @param {Array=} opt_data Optional initial data array, typically " - "from a\n" - " * server response, or constructed directly in Javascript. The array " - "is used\n" - " * in place and becomes part of the constructed object. It is not " - "cloned.\n" - " * If no data is provided, the constructed object will be empty, but " - "still\n" - " * valid.\n" - " * @extends {jspb.Message}\n" - " * @constructor\n" - " */\n" - "$classprefix$$classname$ = function(opt_data) {\n", - "classprefix", GetMessagePathPrefix(options, desc), "classname", - desc->name()); - printer->Annotate("classname", desc); - std::string message_id = GetMessageId(desc); - printer->Print( - " jspb.Message.initialize(this, opt_data, $messageId$, $pivot$, " - "$rptfields$, $oneoffields$);\n", - "messageId", - !message_id.empty() ? ("'" + message_id + "'") - : (IsResponse(desc) ? "''" : "0"), - "pivot", GetPivot(desc), "rptfields", - RepeatedFieldsArrayName(options, desc), "oneoffields", - OneofFieldsArrayName(options, desc)); + "/**\n" + " * Generated by JsPbCodeGenerator.\n" + " * @param {Array=} opt_data Optional initial data array, typically " + "from a\n" + " * server response, or constructed directly in Javascript. The array " + "is used\n" + " * in place and becomes part of the constructed object. It is not " + "cloned.\n" + " * If no data is provided, the constructed object will be empty, but " + "still\n" + " * valid.\n" + " * @extends {jspb.Message}\n" + " * @constructor\n" + " */\n" + "$methodstart$(opt_data) {\n", + "methodstart", methodStart); + +printer->Annotate("classname", desc); + +if (options.WantEs6()) { + printer->Print(" super(...arguments);\n"); +} + +// Body of constructor. +std::string message_id = GetMessageId(desc); +printer->Print( + " jspb.Message.initialize(this, opt_data, $messageId$, $pivot$, " + "$rptfields$, $oneoffields$);\n", + "messageId", + !message_id.empty() ? ("'" + message_id + "'") + : (IsResponse(desc) ? "''" : "0"), + "pivot", GetPivot(desc), "rptfields", + RepeatedFieldsArrayName(options, desc), "oneoffields", + OneofFieldsArrayName(options, desc)); +printer->Print( + "$methodend$\n\n", + "methodend", methodEnd); + + // Additional closure stuff. + if (options.WantEs6()) { + // Skip the displayName for now for ES6. + return; + } printer->Print( - "};\n" "goog.inherits($classname$, jspb.Message);\n" "if (goog.DEBUG && !COMPILED) {\n" // displayName overrides Function.prototype.displayName @@ -2065,17 +2183,12 @@ void Generator::GenerateClassConstructorAndDeclareExtensionFieldInfo( GenerateClassExtensionFieldInfo(options, printer, desc); } } - for (int i = 0; i < desc->nested_type_count(); i++) { - if (!IgnoreMessage(desc->nested_type(i))) { - GenerateClassConstructorAndDeclareExtensionFieldInfo( - options, printer, desc->nested_type(i)); - } - } } void Generator::GenerateClassFieldInfo(const GeneratorOptions& options, io::Printer* printer, const Descriptor* desc) const { + const std::string className = GetMessagePath(options, desc); if (HasRepeatedFields(options, desc)) { printer->Print( "/**\n" @@ -2083,14 +2196,21 @@ void Generator::GenerateClassFieldInfo(const GeneratorOptions& options, " * @private {!Array}\n" " * @const\n" " */\n" - "$classname$$rptfieldarray$ = $rptfields$;\n" + "$lhs$ = $rptfields$;\n" "\n", - "classname", GetMessagePath(options, desc), "rptfieldarray", - kRepeatedFieldArrayName, "rptfields", - RepeatedFieldNumberList(options, desc)); + "lhs", StaticMemberAssignmentLhs( + options, className.c_str(), kRepeatedFieldArrayName), + "rptfields", RepeatedFieldNumberList(options, desc)); } if (HasOneofFields(desc)) { + const std::string assignment = ( + options.import_style == GeneratorOptions::kImportEs6 + ) ? ( + std::string("static ") + kOneofGroupArrayName + " = " + OneofGroupList(desc) + ";" + ) : ( + className + "." + kOneofGroupArrayName + " = " + OneofGroupList(desc) + ";" + ); printer->Print( "/**\n" " * Oneof group definitions for this message. Each group defines the " @@ -2104,10 +2224,10 @@ void Generator::GenerateClassFieldInfo(const GeneratorOptions& options, " * @private {!Array>}\n" " * @const\n" " */\n" - "$classname$$oneofgrouparray$ = $oneofgroups$;\n" + "$assignment$\n" "\n", - "classname", GetMessagePath(options, desc), "oneofgrouparray", - kOneofGroupArrayName, "oneofgroups", OneofGroupList(desc)); + "classname", className, + "assignment", assignment); for (int i = 0; i < desc->oneof_decl_count(); i++) { if (IgnoreOneof(desc->oneof_decl(i))) { @@ -2131,14 +2251,22 @@ void Generator::GenerateClassXid(const GeneratorOptions& options, void Generator::GenerateOneofCaseDefinition( const GeneratorOptions& options, io::Printer* printer, const OneofDescriptor* oneof) const { + + const std::string className = GetMessagePath(options, oneof->containing_type()); + + const std::string oneofCaseName = options.WantEs6() ? ( + "static " + JSOneofName(oneof) + "Case" + ) : ( + className + "." + JSOneofName(oneof) + "Case" + ); printer->Print( "/**\n" " * @enum {number}\n" " */\n" - "$classname$.$oneof$Case = {\n" + "$lhs$ = {\n" " $upcase$_NOT_SET: 0", - "classname", GetMessagePath(options, oneof->containing_type()), "oneof", - JSOneofName(oneof), "upcase", ToEnumCase(oneof->name())); + "lhs", oneofCaseName, + "upcase", ToEnumCase(oneof->name())); for (int i = 0; i < oneof->field_count(); i++) { if (IgnoreField(oneof->field(i))) { @@ -2159,23 +2287,39 @@ void Generator::GenerateOneofCaseDefinition( "\n" "/**\n" " * @return {$class$.$oneof$Case}\n" - " */\n" - "$class$.prototype.get$oneof$Case = function() {\n" + " */\n", + "class", className, + "oneof", JSOneofName(oneof)); + + GenerateMethodStart(options, printer, className.c_str(), + (std::string("get") + JSOneofName(oneof) + "Case").c_str()); + + printer->Print( + "() {\n" " return /** @type {$class$.$oneof$Case} */(jspb.Message." - "computeOneofCase(this, $class$.oneofGroups_[$oneofindex$]));\n" - "};\n" - "\n", - "class", GetMessagePath(options, oneof->containing_type()), "oneof", - JSOneofName(oneof), "oneofindex", JSOneofIndex(oneof)); + "computeOneofCase(this, $class$.oneofGroups_[$oneofindex$]));\n", + "class", oneof->containing_type()->name(), + "oneof", JSOneofName(oneof), + "oneofindex", JSOneofIndex(oneof)); + GenerateMethodEnd(options, printer); + printer->Print( + "\n" + "\n"); } void Generator::GenerateClassToObject(const GeneratorOptions& options, + const TypeNames& type_names, io::Printer* printer, const Descriptor* desc) const { + + const char * if_guard_start = options.WantEs6() ? "" : "if (jspb.Message.GENERATE_TO_OBJECT) {\n"; + const char * if_guard_end = options.WantEs6() ? "" : "}\n"; + const std::string classSymbol = options.WantEs6() ? desc->name() : GetMessagePath(options, desc); + printer->Print( "\n" "\n" - "if (jspb.Message.GENERATE_TO_OBJECT) {\n" + "$if_guard_start$" "/**\n" " * Creates an object representation of this proto.\n" " * Field names that are reserved in JavaScript and will be renamed to " @@ -2190,9 +2334,9 @@ void Generator::GenerateClassToObject(const GeneratorOptions& options, " * http://goto/soy-param-migration\n" " * @return {!Object}\n" " */\n" - "$classname$.prototype.toObject = function(opt_includeInstance) {\n" + "$methodstart$(opt_includeInstance) {\n" " return $classname$.toObject(opt_includeInstance, this);\n" - "};\n" + "}\n" "\n" "\n" "/**\n" @@ -2205,9 +2349,12 @@ void Generator::GenerateClassToObject(const GeneratorOptions& options, " * @return {!Object}\n" " * @suppress {unusedLocalVariables} f is only used for nested messages\n" " */\n" - "$classname$.toObject = function(includeInstance, msg) {\n" + "$classmethodstart$(includeInstance, msg) {\n" " var f, obj = {", - "classname", GetMessagePath(options, desc)); + "if_guard_start", if_guard_start, + "methodstart", MethodStart(options, classSymbol.c_str(), "toObject"), + "classmethodstart", MethodStartStatic(options, classSymbol.c_str(), "toObject"), + "classname", classSymbol); bool first = true; for (int i = 0; i < desc->field_count(); i++) { @@ -2223,7 +2370,7 @@ void Generator::GenerateClassToObject(const GeneratorOptions& options, first = false; } - GenerateClassFieldToObject(options, printer, field); + GenerateClassFieldToObject(options, type_names, printer, field); } if (!first) { @@ -2247,11 +2394,12 @@ void Generator::GenerateClassToObject(const GeneratorOptions& options, " obj.$$jspbMessageInstance = msg;\n" " }\n" " return obj;\n" - "};\n" "}\n" + "$if_guard_end$" "\n" "\n", - "classname", GetMessagePath(options, desc)); + "classname", GetMessagePath(options, desc), + "if_guard_end", if_guard_end); } void Generator::GenerateFieldValueExpression(io::Printer* printer, @@ -2303,6 +2451,7 @@ void Generator::GenerateFieldValueExpression(io::Printer* printer, } void Generator::GenerateClassFieldToObject(const GeneratorOptions& options, + const TypeNames& type_names, io::Printer* printer, const FieldDescriptor* field) const { printer->Print("$fieldname$: ", "fieldname", @@ -2331,14 +2480,14 @@ void Generator::GenerateClassFieldToObject(const GeneratorOptions& options, "jspb.Message.toObjectList(msg.get$getter$(),\n" " $type$.toObject, includeInstance)", "getter", JSGetterName(options, field), "type", - SubmessageTypeRef(options, field)); + type_names.SubmessageTypeRef(field)); } } else { printer->Print( "(f = msg.get$getter$()) && " "$type$.toObject(includeInstance, f)", "getter", JSGetterName(options, field), "type", - SubmessageTypeRef(options, field)); + type_names.SubmessageTypeRef(field)); } } else if (field->type() == FieldDescriptor::TYPE_BYTES) { // For bytes fields we want to always return the B64 data. @@ -2406,6 +2555,7 @@ void Generator::GenerateObjectTypedef(const GeneratorOptions& options, } void Generator::GenerateClassFromObject(const GeneratorOptions& options, + const TypeNames& type_names, io::Printer* printer, const Descriptor* desc) const { printer->Print("if (jspb.Message.GENERATE_FROM_OBJECT) {\n\n"); @@ -2426,7 +2576,7 @@ void Generator::GenerateClassFromObject(const GeneratorOptions& options, for (int i = 0; i < desc->field_count(); i++) { const FieldDescriptor* field = desc->field(i); if (!IgnoreField(field)) { - GenerateClassFieldFromObject(options, printer, field); + GenerateClassFieldFromObject(options, type_names, printer, field); } } @@ -2437,7 +2587,9 @@ void Generator::GenerateClassFromObject(const GeneratorOptions& options, } void Generator::GenerateClassFieldFromObject( - const GeneratorOptions& options, io::Printer* printer, + const GeneratorOptions& options, + const TypeNames& type_names, + io::Printer* printer, const FieldDescriptor* field) const { if (field->is_map()) { const FieldDescriptor* value_field = MapFieldValue(field); @@ -2472,14 +2624,15 @@ void Generator::GenerateClassFieldFromObject( " $fieldclass$.fromObject));\n", "name", JSObjectFieldName(options, field), "index", JSFieldIndex(field), "fieldclass", - SubmessageTypeRef(options, field)); + type_names.SubmessageTypeRef(field)); } } else { printer->Print( " obj.$name$ && jspb.Message.setWrapperField(\n" " msg, $index$, $fieldclass$.fromObject(obj.$name$));\n", "name", JSObjectFieldName(options, field), "index", - JSFieldIndex(field), "fieldclass", SubmessageTypeRef(options, field)); + JSFieldIndex(field), "fieldclass", + type_names.SubmessageTypeRef(field)); } } else { // Simple (primitive) field. @@ -2492,34 +2645,42 @@ void Generator::GenerateClassFieldFromObject( } void Generator::GenerateClassRegistration(const GeneratorOptions& options, + const TypeNames& type_names, io::Printer* printer, const Descriptor* desc) const { // Register any extensions defined inside this message type. for (int i = 0; i < desc->extension_count(); i++) { const FieldDescriptor* extension = desc->extension(i); if (ShouldGenerateExtension(extension)) { - GenerateExtension(options, printer, extension); + GenerateExtension(options, type_names, printer, extension); } } } void Generator::GenerateClassFields(const GeneratorOptions& options, + const TypeNames& type_names, io::Printer* printer, const Descriptor* desc) const { for (int i = 0; i < desc->field_count(); i++) { if (!IgnoreField(desc->field(i))) { - GenerateClassField(options, printer, desc->field(i)); + GenerateClassField(options, type_names, printer, desc->field(i)); } } } -void GenerateBytesWrapper(const GeneratorOptions& options, io::Printer* printer, - const FieldDescriptor* field, BytesMode bytes_mode) { +void Generator::GenerateBytesWrapper(const GeneratorOptions& options, io::Printer* printer, + const FieldDescriptor* field, BytesMode bytes_mode) const { std::string type = JSFieldTypeAnnotation(options, field, /* is_setter_argument = */ false, /* force_present = */ false, /* singular_if_not_packed = */ false, bytes_mode); + const std::string classSymbol = GetMessagePath(options, field->containing_type()); + const std::string methodStart = MethodStart( + options, classSymbol.c_str(), + (std::string("get") + JSGetterName(options, field, bytes_mode)).c_str()); + const char * methodEnd = options.WantEs6() ? "}" : "};"; + printer->Print( "/**\n" " * $fielddef$\n" @@ -2527,24 +2688,31 @@ void GenerateBytesWrapper(const GeneratorOptions& options, io::Printer* printer, " * This is a type-conversion wrapper around `get$defname$()`\n" " * @return {$type$}\n" " */\n" - "$class$.prototype.get$name$ = function() {\n" + "$methodstart$() {\n" " return /** @type {$type$} */ (jspb.Message.bytes$list$As$suffix$(\n" " this.get$defname$()));\n" - "};\n" + "$methodend$\n" "\n" "\n", - "fielddef", FieldDefinition(options, field), "comment", - FieldComments(field, bytes_mode), "type", type, "class", - GetMessagePath(options, field->containing_type()), "name", - JSGetterName(options, field, bytes_mode), "list", - field->is_repeated() ? "List" : "", "suffix", - JSByteGetterSuffix(bytes_mode), "defname", - JSGetterName(options, field, BYTES_DEFAULT)); + "fielddef", FieldDefinition(options, field), + "comment", FieldComments(field, bytes_mode), + "type", type, + "methodstart", methodStart, + "methodend", methodEnd, + "class", GetMessagePath(options, field->containing_type()), + "name", JSGetterName(options, field, bytes_mode), + "list", field->is_repeated() ? "List" : "", + "suffix", JSByteGetterSuffix(bytes_mode), + "defname", JSGetterName(options, field, BYTES_DEFAULT)); } void Generator::GenerateClassField(const GeneratorOptions& options, + const TypeNames& type_names, io::Printer* printer, const FieldDescriptor* field) const { +const std::string classSymbol = GetMessagePath(options, field->containing_type()); +const char * methodEndBrace = options.WantEs6() ? "}" : "};"; + if (field->is_map()) { const FieldDescriptor* key_field = MapFieldKey(field); const FieldDescriptor* value_field = MapFieldValue(field); @@ -2567,14 +2735,28 @@ void Generator::GenerateClassField(const GeneratorOptions& options, " * empty, instead returning `undefined`\n" " * @return {!jspb.Map<$keytype$,$valuetype$>}\n" " */\n", - "fielddef", FieldDefinition(options, field), "keytype", key_type, + "fielddef", FieldDefinition(options, field), + "keytype", key_type, "valuetype", value_type); + + // Function start + if (options.import_style == GeneratorOptions::kImportEs6) { + printer->Print( + "$gettername$(opt_noLazyCreate) {\n", + "gettername", "get" + JSGetterName(options, field)); + } else { + printer->Print( + "$class$.prototype.$gettername$ = function(opt_noLazyCreate) {\n", + "class", classSymbol, + "gettername", "get" + JSGetterName(options, field)); + } + + // Begin function body contents. printer->Print( - "$class$.prototype.$gettername$ = function(opt_noLazyCreate) {\n" " return /** @type {!jspb.Map<$keytype$,$valuetype$>} */ (\n", - "class", GetMessagePath(options, field->containing_type()), - "gettername", "get" + JSGetterName(options, field), "keytype", key_type, + "keytype", key_type, "valuetype", value_type); + printer->Annotate("gettername", field); printer->Print( " jspb.Message.getMapField(this, $index$, opt_noLazyCreate", @@ -2594,9 +2776,13 @@ void Generator::GenerateClassField(const GeneratorOptions& options, printer->Print("));\n"); printer->Print( - "};\n" + "$endbrace$\n" "\n" - "\n"); + "\n", + "endbrace", methodEndBrace); + + // End function body contents. + } else if (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) { // Message field: special handling in order to wrap the underlying data // array with a message object. @@ -2613,53 +2799,77 @@ void Generator::GenerateClassField(const GeneratorOptions& options, /* is_setter_argument = */ false, /* force_present = */ false, /* singular_if_not_packed = */ false)); + + + // Function definition begin.. depends on ES6 style or not + if (options.import_style == GeneratorOptions::kImportEs6) { + printer->Print( + "$gettername$() {\n", + "gettername", "get" + JSGetterName(options, field)); + } else { + printer->Print( + "$class$.prototype.$gettername$ = function() {\n", + "class", classSymbol, + "gettername", "get" + JSGetterName(options, field)); + } + printer->Print( - "$class$.prototype.$gettername$ = function() {\n" " return /** @type{$type$} */ (\n" " jspb.Message.get$rpt$WrapperField(this, $wrapperclass$, " "$index$$required$));\n" - "};\n" + "$endbrace$\n" "\n" "\n", - "class", GetMessagePath(options, field->containing_type()), - "gettername", "get" + JSGetterName(options, field), "type", - JSFieldTypeAnnotation(options, field, + "type", JSFieldTypeAnnotation(options, field, /* is_setter_argument = */ false, /* force_present = */ false, /* singular_if_not_packed = */ false), "rpt", (field->is_repeated() ? "Repeated" : ""), "index", - JSFieldIndex(field), "wrapperclass", SubmessageTypeRef(options, field), + JSFieldIndex(field), "wrapperclass", + type_names.SubmessageTypeRef(field), "required", - (field->label() == FieldDescriptor::LABEL_REQUIRED ? ", 1" : "")); + (field->label() == FieldDescriptor::LABEL_REQUIRED ? ", 1" : ""), + "endbrace", methodEndBrace); printer->Annotate("gettername", field); + printer->Print( "/**\n" " * @param {$optionaltype$} value\n" " * @return {!$class$} returns this\n" - "*/\n" - "$class$.prototype.$settername$ = function(value) {\n" + "*/\n", + "optionaltype", + JSFieldTypeAnnotation(options, field, + /* is_setter_argument = */ true, + /* force_present = */ false, + /* singular_if_not_packed = */ false), + "class", classSymbol); + GenerateMethodStart(options, printer, classSymbol.c_str(), + ("set" + JSGetterName(options, field)).c_str()); + printer->Print( + "(value) {\n" " return jspb.Message.set$oneoftag$$repeatedtag$WrapperField(", "optionaltype", JSFieldTypeAnnotation(options, field, /* is_setter_argument = */ true, /* force_present = */ false, /* singular_if_not_packed = */ false), - "class", GetMessagePath(options, field->containing_type()), - "settername", "set" + JSGetterName(options, field), "oneoftag", + "class", classSymbol,"oneoftag", (InRealOneof(field) ? "Oneof" : ""), "repeatedtag", (field->is_repeated() ? "Repeated" : "")); printer->Annotate("settername", field); printer->Print( - "this, $index$$oneofgroup$, value);\n" - "};\n" + "this, $index$$oneofgroup$, value);\n", + "index", JSFieldIndex(field), + "oneofgroup", (InRealOneof(field) ? (", " + JSOneofArray(options, field)) : "")); + GenerateMethodEnd(options, printer); + printer->Print( "\n" - "\n", - "index", JSFieldIndex(field), "oneofgroup", - (InRealOneof(field) ? (", " + JSOneofArray(options, field)) : "")); + "\n" + "\n"); if (field->is_repeated()) { - GenerateRepeatedMessageHelperMethods(options, printer, field); + GenerateRepeatedMessageHelperMethods(options, type_names, printer, field); } } else { @@ -2696,11 +2906,13 @@ void Generator::GenerateClassField(const GeneratorOptions& options, FieldComments(field, bytes_mode), "type", typed_annotation); } - printer->Print("$class$.prototype.$gettername$ = function() {\n", "class", - GetMessagePath(options, field->containing_type()), - "gettername", "get" + JSGetterName(options, field)); + GenerateMethodStart(options, printer, classSymbol.c_str(), + ("get" + JSGetterName(options, field)).c_str()); + // TODO delete this annotate call? printer->Annotate("gettername", field); + printer->Print("() {\n"); + if (untyped) { printer->Print(" return "); } else { @@ -2722,20 +2934,15 @@ void Generator::GenerateClassField(const GeneratorOptions& options, } GenerateFieldValueExpression(printer, "this", field, use_default); - if (untyped) { - printer->Print( - ";\n" - "};\n" - "\n" - "\n"); + printer->Print(";\n"); } else { - printer->Print( - ");\n" - "};\n" - "\n" - "\n"); + printer->Print(");\n"); } + GenerateMethodEnd(options, printer); + printer->Print( + "\n" + "\n"); if (field->type() == FieldDescriptor::TYPE_BYTES && !untyped) { GenerateBytesWrapper(options, printer, field, BYTES_B64); @@ -2747,7 +2954,7 @@ void Generator::GenerateClassField(const GeneratorOptions& options, " * @param {$optionaltype$} value\n" " * @return {!$class$} returns this\n" " */\n", - "class", GetMessagePath(options, field->containing_type()), + "class", classSymbol, "optionaltype", untyped ? "*" : JSFieldTypeAnnotation(options, field, @@ -2758,28 +2965,38 @@ void Generator::GenerateClassField(const GeneratorOptions& options, if (field->file()->syntax() == FileDescriptor::SYNTAX_PROTO3 && !field->is_repeated() && !field->is_map() && !HasFieldPresence(options, field)) { + // Proto3 non-repeated and non-map fields without presence use the // setProto3*Field function. + GenerateMethodStart(options, printer, classSymbol.c_str(), + ("set" + JSGetterName(options, field)).c_str()); + printer->Print( - "$class$.prototype.$settername$ = function(value) {\n" + "(value) {\n" " return jspb.Message.setProto3$typetag$Field(this, $index$, " - "value);" - "\n" - "};\n" + "value);\n", + "typetag", JSTypeTag(field), + "index", JSFieldIndex(field)); + + GenerateMethodEnd(options, printer); + printer->Print( "\n" - "\n", - "class", GetMessagePath(options, field->containing_type()), - "settername", "set" + JSGetterName(options, field), "typetag", - JSTypeTag(field), "index", JSFieldIndex(field)); - printer->Annotate("settername", field); + "\n"); + + // DO NOT SUBMIT: Can the Annotate call be safely removed? + // printer->Annotate("settername", field); + } else { + + GenerateMethodStart(options, printer, classSymbol.c_str(), + ("set" + JSGetterName(options, field)).c_str()); // Otherwise, use the regular setField function. printer->Print( - "$class$.prototype.$settername$ = function(value) {\n" + "(value) {\n" " return jspb.Message.set$oneoftag$Field(this, $index$", - "class", GetMessagePath(options, field->containing_type()), - "settername", "set" + JSGetterName(options, field), "oneoftag", - (InRealOneof(field) ? "Oneof" : ""), "index", JSFieldIndex(field)); + "class", classSymbol, + "oneoftag", (InRealOneof(field) ? "Oneof" : ""), + "index", JSFieldIndex(field)); printer->Annotate("settername", field); printer->Print( "$oneofgroup$, $type$value$rptvalueinit$$typeclose$);\n" @@ -2799,7 +3016,7 @@ void Generator::GenerateClassField(const GeneratorOptions& options, " * Clears the value.\n" " * @return {!$class$} returns this\n" " */\n", - "class", GetMessagePath(options, field->containing_type())); + "class", classSymbol); } if (field->is_repeated()) { @@ -2807,6 +3024,10 @@ void Generator::GenerateClassField(const GeneratorOptions& options, } } + const std::string clearerName = "clear" + JSGetterName(options, field); + const std::string clearerMethodStart = MethodStart( + options, classSymbol.c_str(), clearerName.c_str()); + // Generate clearFoo() method for map fields, repeated fields, and other // fields with presence. if (field->is_map()) { @@ -2816,14 +3037,15 @@ void Generator::GenerateClassField(const GeneratorOptions& options, " * Clears values from the map. The map will be non-null.\n" " * @return {!$class$} returns this\n" " */\n" - "$class$.prototype.$clearername$ = function() {\n" + "$methodstart$() {\n" " this.$gettername$().clear();\n" " return this;\n" - "};\n" + "$methodend$\n" "\n" "\n", - "class", GetMessagePath(options, field->containing_type()), - "clearername", "clear" + JSGetterName(options, field), + "class", classSymbol, + "methodstart", clearerMethodStart, + "methodend", methodEndBrace, "gettername", "get" + JSGetterName(options, field)); // clang-format on printer->Annotate("clearername", field); @@ -2837,7 +3059,7 @@ void Generator::GenerateClassField(const GeneratorOptions& options, " * $jsdoc$\n" " * @return {!$class$} returns this\n" " */\n" - "$class$.prototype.$clearername$ = function() {\n" + "$methodstart$() {\n" " return this.$settername$($clearedvalue$);\n" "};\n" "\n" @@ -2845,8 +3067,8 @@ void Generator::GenerateClassField(const GeneratorOptions& options, "jsdoc", field->is_repeated() ? "Clears the list making it empty but non-null." : "Clears the message field making it undefined.", - "class", GetMessagePath(options, field->containing_type()), - "clearername", "clear" + JSGetterName(options, field), + "class", classSymbol, + "methodstart", clearerMethodStart, "settername", "set" + JSGetterName(options, field), "clearedvalue", (field->is_repeated() ? "[]" : "undefined")); // clang-format on @@ -2860,11 +3082,11 @@ void Generator::GenerateClassField(const GeneratorOptions& options, " * Clears the field making it undefined.\n" " * @return {!$class$} returns this\n" " */\n" - "$class$.prototype.$clearername$ = function() {\n" + "$methodstart$() {\n" " return jspb.Message.set$maybeoneof$Field(this, " "$index$$maybeoneofgroup$, ", - "class", GetMessagePath(options, field->containing_type()), - "clearername", "clear" + JSGetterName(options, field), + "class", classSymbol, + "methodstart", clearerMethodStart, "maybeoneof", (InRealOneof(field) ? "Oneof" : ""), "maybeoneofgroup", (InRealOneof(field) ? (", " + JSOneofArray(options, field)) @@ -2881,18 +3103,24 @@ void Generator::GenerateClassField(const GeneratorOptions& options, } if (HasFieldPresence(options, field)) { + const std::string haserName = "has" + JSGetterName(options, field); + const std::string haserMethodStart = MethodStart( + options, classSymbol.c_str(), haserName.c_str()); + printer->Print( "/**\n" " * Returns whether this field is set.\n" " * @return {boolean}\n" " */\n" - "$class$.prototype.$hasername$ = function() {\n" + "$methodstart$() {\n" " return jspb.Message.getField(this, $index$) != null;\n" - "};\n" + "$methodend$\n" "\n" "\n", - "class", GetMessagePath(options, field->containing_type()), "hasername", - "has" + JSGetterName(options, field), "index", JSFieldIndex(field)); + "class", classSymbol, + "methodstart", haserMethodStart, + "methodend", methodEndBrace, + "index", JSFieldIndex(field)); printer->Annotate("hasername", field); } } @@ -2900,19 +3128,28 @@ void Generator::GenerateClassField(const GeneratorOptions& options, void Generator::GenerateRepeatedPrimitiveHelperMethods( const GeneratorOptions& options, io::Printer* printer, const FieldDescriptor* field, bool untyped) const { + + const std::string classSymbol = GetMessagePath(options, field->containing_type()); + const std::string adderName = std::string("add") + + JSGetterName(options, field, BYTES_DEFAULT, /* drop_list = */ true); + const std::string adderMethodStart = MethodStart( + options, classSymbol.c_str(), adderName.c_str()); + // clang-format off printer->Print( "/**\n" + " * Adds a value to the repeated field $field_name$ \n" + " *\n" " * @param {$optionaltype$} value\n" " * @param {number=} opt_index\n" " * @return {!$class$} returns this\n" " */\n" - "$class$.prototype.$addername$ = function(value, opt_index) {\n" + "$methodstart$(value, opt_index) {\n" " return jspb.Message.addToRepeatedField(this, " "$index$", - "class", GetMessagePath(options, field->containing_type()), "addername", - "add" + JSGetterName(options, field, BYTES_DEFAULT, - /* drop_list = */ true), + "methodstart", adderMethodStart, + "class", classSymbol, + "addername", adderName, "optionaltype", JSFieldTypeAnnotation( options, field, @@ -2937,31 +3174,43 @@ void Generator::GenerateRepeatedPrimitiveHelperMethods( } void Generator::GenerateRepeatedMessageHelperMethods( - const GeneratorOptions& options, io::Printer* printer, + const GeneratorOptions& options, + const TypeNames& type_names, + io::Printer* printer, const FieldDescriptor* field) const { + + const std::string classSymbol = GetMessagePath(options, field->containing_type()); + const std::string adderName = std::string("add") + + JSGetterName(options, field, BYTES_DEFAULT, /* drop_list = */ true); + const std::string adderMethodStart = MethodStart( + options, classSymbol.c_str(), adderName.c_str()); + printer->Print( "/**\n" + " * Adds a value to the repeated field $field_name$ \n" + " *\n" " * @param {!$optionaltype$=} opt_value\n" " * @param {number=} opt_index\n" " * @return {!$optionaltype$}\n" " */\n" - "$class$.prototype.$addername$ = function(opt_value, opt_index) {\n" + "$methodstart$(opt_value, opt_index) {\n" " return jspb.Message.addTo$repeatedtag$WrapperField(", - "optionaltype", JSTypeName(options, field, BYTES_DEFAULT), "class", - GetMessagePath(options, field->containing_type()), "addername", - "add" + JSGetterName(options, field, BYTES_DEFAULT, - /* drop_list = */ true), + "optionaltype", JSTypeName(options, field, BYTES_DEFAULT), + "field_name", field->name(), + "class", classSymbol, + "methodstart", adderMethodStart, + "addername", adderName, "repeatedtag", (field->is_repeated() ? "Repeated" : "")); printer->Annotate("addername", field); printer->Print( - "this, $index$$oneofgroup$, opt_value, $ctor$, opt_index);\n" + "this, $index$$oneofgroup$, opt_value, $class$, opt_index);\n" "};\n" "\n" "\n", "index", JSFieldIndex(field), "oneofgroup", - (InRealOneof(field) ? (", " + JSOneofArray(options, field)) : ""), "ctor", - GetMessagePath(options, field->message_type())); + (InRealOneof(field) ? (", " + JSOneofArray(options, field)) : ""), + "class", type_names.SubmessageTypeRef(field)); } void Generator::GenerateClassExtensionFieldInfo(const GeneratorOptions& options, @@ -2985,9 +3234,8 @@ void Generator::GenerateClassExtensionFieldInfo(const GeneratorOptions& options, " *\n" " * @type {!Object}\n" " */\n" - "$class$.extensions = {};\n" - "\n", - "class", GetMessagePath(options, desc)); + "static extensions = {};\n" + "\n"); printer->Print( "\n" @@ -3006,29 +3254,39 @@ void Generator::GenerateClassExtensionFieldInfo(const GeneratorOptions& options, " *\n" " * @type {!Object}\n" " */\n" - "$class$.extensionsBinary = {};\n" - "\n", - "class", GetMessagePath(options, desc)); + "static extensionsBinary = {};\n" + "\n"); } } void Generator::GenerateClassDeserializeBinary(const GeneratorOptions& options, + const TypeNames& type_names, io::Printer* printer, const Descriptor* desc) const { // TODO(cfallin): Handle lazy decoding when requested by field option and/or // by default for 'bytes' fields and packed repeated fields. + const std::string classSymbol = desc->name(); + printer->Print( "/**\n" " * Deserializes binary data (in protobuf wire format).\n" " * @param {jspb.ByteSource} bytes The bytes to deserialize.\n" " * @return {!$class$}\n" - " */\n" - "$class$.deserializeBinary = function(bytes) {\n" + " */\n", + "class", classSymbol); + printer->Print( + "$methodstart$(bytes) {\n" " var reader = new jspb.BinaryReader(bytes);\n" " var msg = new $class$;\n" - " return $class$.deserializeBinaryFromReader(msg, reader);\n" - "};\n" + " return $class$.deserializeBinaryFromReader(msg, reader);\n", + "methodstart", this->MethodStartStatic(options, classSymbol.c_str(), "deserializeBinary"), + "class", classSymbol); + + GenerateMethodEnd(options, printer); + +printer->Print( + "\n" "\n" "\n" "/**\n" @@ -3037,20 +3295,21 @@ void Generator::GenerateClassDeserializeBinary(const GeneratorOptions& options, " * @param {!$class$} msg The message object to deserialize into.\n" " * @param {!jspb.BinaryReader} reader The BinaryReader to use.\n" " * @return {!$class$}\n" - " */\n" - "$class$.deserializeBinaryFromReader = function(msg, reader) {\n" - " while (reader.nextField()) {\n", - "class", GetMessagePath(options, desc)); + " */\n", + "class", classSymbol); printer->Print( + "$methodstart$(msg, reader) {\n" + " while (reader.nextField()) {\n" " if (reader.isEndGroup()) {\n" " break;\n" " }\n" " var field = reader.getFieldNumber();\n" - " switch (field) {\n"); + " switch (field) {\n", + "methodstart", MethodStartStatic(options, classSymbol.c_str(), "deserializeBinaryFromReader")); for (int i = 0; i < desc->field_count(); i++) { if (!IgnoreField(desc->field(i))) { - GenerateClassDeserializeBinaryField(options, printer, desc->field(i)); + GenerateClassDeserializeBinaryField(options, type_names, printer, desc->field(i)); } } @@ -3074,14 +3333,20 @@ void Generator::GenerateClassDeserializeBinary(const GeneratorOptions& options, printer->Print( " }\n" - " return msg;\n" - "};\n" + " return msg;\n"); + + GenerateMethodEnd(options, printer); + + printer->Print( + "\n" "\n" "\n"); } void Generator::GenerateClassDeserializeBinaryField( - const GeneratorOptions& options, io::Printer* printer, + const GeneratorOptions& options, + const TypeNames& type_names, + io::Printer* printer, const FieldDescriptor* field) const { printer->Print(" case $num$:\n", "num", StrCat(field->number())); @@ -3122,7 +3387,8 @@ void Generator::GenerateClassDeserializeBinaryField( " var value = new $fieldclass$;\n" " reader.read$msgOrGroup$($grpfield$value," "$fieldclass$.deserializeBinaryFromReader);\n", - "fieldclass", SubmessageTypeRef(options, field), "msgOrGroup", + "fieldclass", + type_names.SubmessageTypeRef(field), "msgOrGroup", (field->type() == FieldDescriptor::TYPE_GROUP) ? "Group" : "Message", "grpfield", (field->type() == FieldDescriptor::TYPE_GROUP) @@ -3172,18 +3438,28 @@ void Generator::GenerateClassDeserializeBinaryField( } void Generator::GenerateClassSerializeBinary(const GeneratorOptions& options, + const TypeNames& type_names, io::Printer* printer, const Descriptor* desc) const { + + const std::string classSymbol = desc->name(); + printer->Print( "/**\n" " * Serializes the message to binary data (in protobuf wire format).\n" " * @return {!Uint8Array}\n" - " */\n" - "$class$.prototype.serializeBinary = function() {\n" + " */\n"); + printer->Print( + "$methodstart$() {\n" " var writer = new jspb.BinaryWriter();\n" - " $class$.serializeBinaryToWriter(this, writer);\n" - " return writer.getResultBuffer();\n" - "};\n" + " this.constructor.serializeBinaryToWriter(this, writer);\n" + " return writer.getResultBuffer();\n", + "methodstart", MethodStart(options, classSymbol.c_str(), "serializeBinary"), + "class", classSymbol); + + GenerateMethodEnd(options, printer); + printer->Print( + "\n" "\n" "\n" "/**\n" @@ -3193,14 +3469,14 @@ void Generator::GenerateClassSerializeBinary(const GeneratorOptions& options, " * @param {!jspb.BinaryWriter} writer\n" " * @suppress {unusedLocalVariables} f is only used for nested messages\n" " */\n" - "$class$.serializeBinaryToWriter = function(message, " - "writer) {\n" + "$methodstart$(message, writer) {\n" " var f = undefined;\n", - "class", GetMessagePath(options, desc)); + "methodstart", MethodStartStatic(options, classSymbol.c_str(), "serializeBinaryToWriter"), + "class", classSymbol); - for (int i = 0; i < desc->field_count(); i++) { +for (int i = 0; i < desc->field_count(); i++) { if (!IgnoreField(desc->field(i))) { - GenerateClassSerializeBinaryField(options, printer, desc->field(i)); + GenerateClassSerializeBinaryField(options, type_names, printer, desc->field(i)); } } @@ -3212,14 +3488,18 @@ void Generator::GenerateClassSerializeBinary(const GeneratorOptions& options, GetMessagePath(options, desc)); } + GenerateMethodEnd(options, printer); + printer->Print( - "};\n" + "\n" "\n" "\n"); } void Generator::GenerateClassSerializeBinaryField( - const GeneratorOptions& options, io::Printer* printer, + const GeneratorOptions& options, + const TypeNames& type_names, + io::Printer* printer, const FieldDescriptor* field) const { if (HasFieldPresence(options, field) && field->cpp_type() != FieldDescriptor::CPPTYPE_MESSAGE) { @@ -3319,7 +3599,7 @@ void Generator::GenerateClassSerializeBinaryField( printer->Print( ",\n" " $submsg$.serializeBinaryToWriter\n", - "submsg", SubmessageTypeRef(options, field)); + "submsg", type_names.SubmessageTypeRef(field)); } else { printer->Print("\n"); } @@ -3334,13 +3614,24 @@ void Generator::GenerateClassSerializeBinaryField( void Generator::GenerateEnum(const GeneratorOptions& options, io::Printer* printer, const EnumDescriptor* enumdesc) const { + + const bool is_toplevel = enumdesc->containing_type() == nullptr; + const std::string enumNamePrefix = is_toplevel ? "export const " : "static "; + + // TODO(reddaly): If the enum is defined at top-level, we need + // 'const = ' instead of ' = ' + const std::string enumNameForDefinition = options.WantEs6() ? ( + enumNamePrefix + enumdesc->name() + ) : ( + GetEnumPathPrefix(options, enumdesc) + enumdesc->name() + ); printer->Print( "/**\n" " * @enum {number}\n" " */\n" - "$enumprefix$$name$ = {\n", - "enumprefix", GetEnumPathPrefix(options, enumdesc), "name", - enumdesc->name()); + "$defname$ = {\n", + "defname", enumNameForDefinition, + "name", enumdesc->name()); printer->Annotate("name", enumdesc); std::set used_name; @@ -3366,24 +3657,26 @@ void Generator::GenerateEnum(const GeneratorOptions& options, } void Generator::GenerateExtension(const GeneratorOptions& options, + const TypeNames& type_names, io::Printer* printer, const FieldDescriptor* field) const { - std::string extension_scope = - (field->extension_scope() - ? GetMessagePath(options, field->extension_scope()) - : GetNamespace(options, field->file())); + std::string extension_scope_name = + (field->containing_type() + ? TypeNames::JsName(field->containing_type()->name()) + : GetNamespace(options, field->file())); + std::string extension_object_name = + JSExtensionsObjectName(options, field->file(), field->containing_type()); + std::string extension_object_field_name = JSObjectFieldName(options, field); - const std::string extension_object_name = JSObjectFieldName(options, field); printer->Print( "\n" "/**\n" " * A tuple of {field number, class constructor} for the extension\n" - " * field named `$nameInComment$`.\n" + " * field named `$name$`.\n" " * @type {!jspb.ExtensionFieldInfo<$extensionType$>}\n" " */\n" "$class$.$name$ = new jspb.ExtensionFieldInfo(\n", - "nameInComment", extension_object_name, "name", extension_object_name, - "class", extension_scope, "extensionType", + "class", extension_scope_name, "name", extension_object_field_name, "extensionType", JSFieldTypeAnnotation(options, field, /* is_setter_argument = */ false, /* force_present = */ true, @@ -3397,13 +3690,13 @@ void Generator::GenerateExtension(const GeneratorOptions& options, "!Object} */ (\n" " $toObject$),\n" " $repeated$);\n", - "index", StrCat(field->number()), "name", extension_object_name, "ctor", + "index", StrCat(field->number()), "name", extension_object_field_name, "ctor", (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE - ? SubmessageTypeRef(options, field) + ? type_names.SubmessageTypeRef(field) : std::string("null")), "toObject", (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE - ? (SubmessageTypeRef(options, field) + ".toObject") + ? (type_names.SubmessageTypeRef(field) + ".toObject") : std::string("null")), "repeated", (field->is_repeated() ? "1" : "0")); @@ -3415,18 +3708,17 @@ void Generator::GenerateExtension(const GeneratorOptions& options, " $binaryWriterFn$,\n" " $binaryMessageSerializeFn$,\n" " $binaryMessageDeserializeFn$,\n", - "extendName", - JSExtensionsObjectName(options, field->file(), field->containing_type()), - "index", StrCat(field->number()), "class", extension_scope, "name", - extension_object_name, "binaryReaderFn", - JSBinaryReaderMethodName(options, field), "binaryWriterFn", - JSBinaryWriterMethodName(options, field), "binaryMessageSerializeFn", + "extendName", extension_object_name, "index", StrCat(field->number()), + "class", extension_scope_name, "name", extension_object_field_name, + "binaryReaderFn", JSBinaryReaderMethodName(options, field), + "binaryWriterFn", JSBinaryWriterMethodName(options, field), + "binaryMessageSerializeFn", (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) - ? (SubmessageTypeRef(options, field) + ".serializeBinaryToWriter") + ? (type_names.SubmessageTypeRef(field) + ".serializeBinaryToWriter") : "undefined", "binaryMessageDeserializeFn", (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) - ? (SubmessageTypeRef(options, field) + ".deserializeBinaryFromReader") + ? (type_names.SubmessageTypeRef(field) + ".deserializeBinaryFromReader") : "undefined"); printer->Print(" $isPacked$);\n", "isPacked", @@ -3437,10 +3729,8 @@ void Generator::GenerateExtension(const GeneratorOptions& options, "// toObject() will function correctly.\n" "$extendName$[$index$] = $class$.$name$;\n" "\n", - "extendName", - JSExtensionsObjectName(options, field->file(), field->containing_type()), - "index", StrCat(field->number()), "class", extension_scope, "name", - extension_object_name); + "extendName", extension_object_name, "index", StrCat(field->number()), + "class", extension_scope_name, "name", extension_object_field_name); } bool GeneratorOptions::ParseFromOptions( @@ -3544,6 +3834,10 @@ GeneratorOptions::OutputMode GeneratorOptions::output_mode() const { return kOneOutputFilePerSCC; } +bool GeneratorOptions::WantEs6() const { + return this->import_style == GeneratorOptions::kImportEs6; +} + void Generator::GenerateFilesInDepOrder( const GeneratorOptions& options, io::Printer* printer, const std::vector& files) const { @@ -3558,9 +3852,15 @@ void Generator::GenerateFilesInDepOrder( } void Generator::GenerateFileAndDeps( - const GeneratorOptions& options, io::Printer* printer, + const GeneratorOptions& options, + io::Printer* printer, const FileDescriptor* root, std::set* all_files, std::set* generated) const { + // ES6 must use kOneOutputFilePerInputFile. + GOOGLE_CHECK_NE(GeneratorOptions::kOneOutputFilePerInputFile, + options.output_mode()); + TypeNames type_names = TypeNames::NonEs6TypeNames(options); + // Skip if already generated. if (generated->find(root) != generated->end()) { return; @@ -3577,7 +3877,7 @@ void Generator::GenerateFileAndDeps( // original set requested to be generated; i.e., don't take all transitive // deps down to the roots. if (all_files->find(root) != all_files->end()) { - GenerateClassesAndEnums(options, printer, root); + GenerateClassesAndEnums(options, type_names, printer, root); } } @@ -3611,13 +3911,239 @@ bool Generator::GenerateFile(const FileDescriptor* file, return true; } +TypeNames TypeNames::NonEs6TypeNames(const GeneratorOptions& options) { + return TypeNames(options, nullptr, std::map()); +} + +void ReservedForLocalIdentifiers(const Descriptor* desc, std::set& reserved_identifiers); + +void ReservedForLocalIdentifiers(const EnumDescriptor* desc, std::set& reserved_identifiers); + +void ReservedForLocalIdentifiers(const Descriptor* desc, std::set& reserved_identifiers) { + reserved_identifiers.insert(TypeNames::JsName(desc->full_name())); + for (int j = 0; j < desc->nested_type_count(); j++) { + ReservedForLocalIdentifiers(desc->nested_type(j), reserved_identifiers); + } + for (int j = 0; j < desc->enum_type_count(); j++) { + ReservedForLocalIdentifiers(desc->enum_type(j), reserved_identifiers); + } +} + +void ReservedForLocalIdentifiers(const EnumDescriptor* desc, std::set& reserved_identifiers) { + reserved_identifiers.insert(TypeNames::JsName(desc->full_name())); +} + + +/** + * ReservedForLocalIdentifiers computes the set of symbols that should not be + * used for imports because they might conflict with definitions (class names, + * etc.). + */ +void ReservedForLocalIdentifiers(const FileDescriptor* desc, std::set& reserved_identifiers) { + for (int j = 0; j < desc->message_type_count(); j++) { + ReservedForLocalIdentifiers(desc->message_type(j), reserved_identifiers); + } + for (int j = 0; j < desc->enum_type_count(); j++) { + ReservedForLocalIdentifiers(desc->enum_type(j), reserved_identifiers); + } +} + +/** + * ReservedForLocalIdentifiers computes the set of symbols that should not be + * used for imports because they might conflict with definitions (class names, + * etc.). +void RegisterTypesDefinedInGeneratedFile( + const FileDescriptor* desc, + std::map& full_name_to_alias) { + for (int j = 0; j < desc->message_type_count(); j++) { + ReservedForLocalIdentifiers(desc->message_type(j), reserved_identifiers); + } + for (int j = 0; j < desc->enum_type_count(); j++) { + ReservedForLocalIdentifiers(desc->enum_type(j), reserved_identifiers); + } +} + */ + +TypeNames TypeNames::Es6TypeNames( + const GeneratorOptions& options, + const FileDescriptor* codegen_file) { + + // Full proto name -> local alias in JS codegen file. + std::map full_name_to_alias; + // Local aliases that are already reserved + std::set reserved_aliases; + ReservedForLocalIdentifiers(codegen_file, reserved_aliases); + //RegisterTypesDefinedInGeneratedFile(codegen_file, full_name_to_alias); + + auto pick_name = [&](const std::string full_name, const std::string ideal_name) -> void { + std::string base_candidate = ideal_name; + for (int i = -1; i < 10000; i++) { + if (i == 0) { + base_candidate = full_name; + ReplaceCharacters(&base_candidate, ".", '_'); + } + std::string candidate = base_candidate; + if (i > 0) { + candidate += std::to_string(i); + } + + if (reserved_aliases.find(candidate) == reserved_aliases.end()) { + reserved_aliases.insert(candidate); + full_name_to_alias.insert(std::make_pair(full_name, candidate)); + return; + } + } + full_name_to_alias.insert(std::make_pair(full_name, "PICK_NAME_FAILED_INTERNAL_ERROR")); + }; + + auto register_types = [&](const FileDescriptor* file) -> void { + for (int j = 0; j < file->message_type_count(); j++) { + auto message_type = file->message_type(j); + if (file == codegen_file) { + // Ensure that messages and enums declared at top level in the .proto + // file corresponding to the currently generaed code get identifiers + // equal to their message identifiers. + // reserved_aliases was already updated. + full_name_to_alias.insert(std::make_pair(message_type->full_name(), message_type->name())); + } else { + pick_name(message_type->full_name(), message_type->name()); + } + } + + for (int j = 0; j < file->enum_type_count(); j++) { + auto enum_type = file->enum_type(j); + if (file == codegen_file) { + // Ensure that messages and enums declared at top level in the .proto + // file corresponding to the currently generaed code get identifiers + // equal to their message identifiers. + // reserved_aliases was already updated. + full_name_to_alias.insert(std::make_pair(enum_type->full_name(), enum_type->name())); + } else { + pick_name(enum_type->full_name(), enum_type->name()); + } + } + }; + + register_types(codegen_file); + // Loop through all dependencies and add their types. + for (int i = 0; i < codegen_file->dependency_count(); i++) { + auto dep_file = codegen_file->dependency(i); + register_types(dep_file); + } + + // TODO(reddaly): Replace conflicting identifiers. + return TypeNames(options, codegen_file, full_name_to_alias); +} + +/** + * Returns the JavaScript expression for referring to the passed message type. + */ +std::string TypeNames::JsExpression(const google::protobuf::Descriptor& desc) const { + if (this->options.WantEs6()) { + return this->JsExpression(desc.full_name()); + } + + return MaybeCrossFileRef(this->options, this->codegen_file, &desc); +} + +/** + * Returns the JavaScript expression for referring to the given enum type. + */ +std::string TypeNames::JsExpression(const google::protobuf::EnumDescriptor& desc) const { + if (this->options.WantEs6()) { + return this->JsExpression(desc.full_name()); + } + return GetEnumPath(this->options, &desc); +} + +std::string TypeNames::SubmessageTypeRef(const FieldDescriptor* field) const { + GOOGLE_CHECK(field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE); + GOOGLE_CHECK(this->codegen_file == nullptr || + this->codegen_file == field->file()); + GOOGLE_CHECK_NOTNULL(field->message_type()); + return JsExpression(*field->message_type()); +} + +std::string TypeNames::JsExpression(const std::string& full_name) const { + GOOGLE_CHECK_OK(this->options.WantEs6()); + + auto iter = this->map_.find(full_name); + if (iter != this->map_.end()) { + return iter->second; + } + // See if the parent full_name is available. If it is, use it as the prefix. + auto parts = google::protobuf::Split(full_name, ".", false); + if (parts.size() > 1) { + std::vector parent_parts = {parts.begin(), parts.end() - 1}; + auto parent_path = google::protobuf::JoinStrings( + parent_parts, + "."); + return this->JsExpression(parent_path) + "." + parts[parts.size() - 1]; + } + return std::string("INVALID TYPE NAME ") + full_name; +} + +std::string TypeNames::JsName(const std::string& full_name) { + // TODO(reddaly): There should probably be some logic to rename messages that + // conflict with reserved names, right? + auto parts = google::protobuf::Split(full_name, ".", false); + return parts[parts.size()-1]; +} + +/** + * Returns the import alias for all the top-level messages and enums + * in the given dependency file. +*/ +std::vector ImportAliases( + const TypeNames& type_names, + const FileDescriptor& dep_file) { + std::vector out; + for (int j = 0; j < dep_file.message_type_count(); j++) { + auto message_type = dep_file.message_type(j); + auto alias = type_names.JsExpression(*message_type); + auto exported_name = TypeNames::JsName(message_type->full_name()); + if (alias == exported_name) { + out.push_back(alias); + } else { + out.push_back(exported_name + " as " + alias); + } + } + + for (int j = 0; j < dep_file.enum_type_count(); j++) { + auto enum_type = dep_file.enum_type(j); + out.push_back(type_names.JsExpression(*enum_type)); + } + return out; +} + void Generator::GenerateFile(const GeneratorOptions& options, io::Printer* printer, const FileDescriptor* file) const { GenerateHeader(options, file, printer); + auto type_names = options.WantEs6() ? + TypeNames::Es6TypeNames(options, file) : + TypeNames::NonEs6TypeNames(options); + // Generate "require" statements. - if ((options.import_style == GeneratorOptions::kImportCommonJs || + if (options.import_style == GeneratorOptions::kImportEs6) { + printer->Print("import jspb from \"google-protobuf\";\n"); + + for (int i = 0; i < file->dependency_count(); i++) { + std::string aliases_comma_delimited = + google::protobuf::JoinStrings( + ImportAliases(type_names, *file->dependency(i)), + ", "); + const std::string& name = file->dependency(i)->name(); + printer->Print( + "import {$aliases$} from \"$file$\"; // proto import: \"$proto_filename$\"\n", + "aliases", aliases_comma_delimited, + "modulealias", ModuleAlias(name), + "file", GetRootPath(file->name(), name) + GetJSFilename(options, name), + "proto_filename", name); + } + + } else if ((options.import_style == GeneratorOptions::kImportCommonJs || options.import_style == GeneratorOptions::kImportCommonJsStrict)) { printer->Print("var jspb = require('google-protobuf');\n"); printer->Print("var goog = jspb;\n"); @@ -3677,17 +4203,18 @@ void Generator::GenerateFile(const GeneratorOptions& options, GenerateProvides(options, printer, &provided); std::vector files; files.push_back(file); - if (options.import_style == GeneratorOptions::kImportClosure) { + if (options.import_style == GeneratorOptions::kImportClosure || + options.import_style == GeneratorOptions::kImportEs6) { GenerateRequiresForLibrary(options, printer, files, &provided); } - GenerateClassesAndEnums(options, printer, file); + GenerateClassesAndEnums(options, type_names, printer, file); // Generate code for top-level extensions. Extensions nested inside messages // are emitted inside GenerateClassesAndEnums(). for (std::set::const_iterator it = extensions.begin(); it != extensions.end(); ++it) { - GenerateExtension(options, printer, *it); + GenerateExtension(options, type_names, printer, *it); } // if provided is empty, do not export anything @@ -3699,14 +4226,6 @@ void Generator::GenerateFile(const GeneratorOptions& options, printer->Print("goog.object.extend(exports, proto);\n", "package", GetNamespace(options, file)); } - - // Emit well-known type methods. - for (FileToc* toc = well_known_types_js; toc->name != NULL; toc++) { - std::string name = std::string("google/protobuf/") + toc->name; - if (name == StripProto(file->name()) + ".js") { - printer->Print(toc->data); - } - } } bool Generator::GenerateAll(const std::vector& files, @@ -3721,6 +4240,7 @@ bool Generator::GenerateAll(const std::vector& files, } if (options.output_mode() == GeneratorOptions::kEverythingInOneFile) { + auto type_names = TypeNames::NonEs6TypeNames(options); // All output should go in a single file. std::string filename = options.output_dir + "/" + options.library + options.GetFileNameExtension(); @@ -3760,7 +4280,7 @@ bool Generator::GenerateAll(const std::vector& files, for (int i = 0; i < extensions.size(); i++) { if (ShouldGenerateExtension(extensions[i])) { - GenerateExtension(options, &printer, extensions[i]); + GenerateExtension(options, type_names, &printer, extensions[i]); } } @@ -3771,6 +4291,7 @@ bool Generator::GenerateAll(const std::vector& files, EmbedCodeAnnotations(annotations, &printer); } } else if (options.output_mode() == GeneratorOptions::kOneOutputFilePerSCC) { + TypeNames type_names = TypeNames::NonEs6TypeNames(options); std::set have_printed; SCCAnalyzer analyzer; std::map allowed_map; @@ -3829,7 +4350,7 @@ bool Generator::GenerateAll(const std::vector& files, } for (auto one_desc : scc->descriptors) { if (one_desc->containing_type() == nullptr) { - GenerateClass(options, &printer, one_desc); + GenerateClass(options, type_names, &printer, one_desc); } } @@ -3912,7 +4433,7 @@ bool Generator::GenerateAll(const std::vector& files, for (int j = 0; j < files[i]->extension_count(); j++) { if (ShouldGenerateExtension(files[i]->extension(j))) { - GenerateExtension(options, &printer, files[i]->extension(j)); + GenerateExtension(options, type_names, &printer, files[i]->extension(j)); } } if (options.annotate_code) { @@ -3939,6 +4460,56 @@ bool Generator::GenerateAll(const std::vector& files, return true; } +// Prints the beginning/end of a method of some class. +void Generator::GenerateMethodStart(const GeneratorOptions& options, + io::Printer* printer, + const char * classSymbol, + const char * methodName) const { + if(options.import_style == GeneratorOptions::kImportEs6) { + printer->PrintRaw(MethodStart(options, classSymbol, methodName)); + } +} + +const std::string Generator::MethodStart(const GeneratorOptions& options, + const char * classSymbol, + const char * methodName) const { + if (options.WantEs6()) { + return methodName; + } else { + return std::string(classSymbol) + ".prototype." + methodName + " = function"; + } +} + +const std::string Generator::MethodStartStatic(const GeneratorOptions& options, + const char * classSymbol, + const char * methodName) const { + if (options.WantEs6()) { + return std::string("static ") + methodName; + } else { + return std::string(classSymbol) + "." + methodName + " = function"; + } +} + +void Generator::GenerateMethodEnd(const GeneratorOptions& options, + io::Printer* printer) const { + if(options.import_style == GeneratorOptions::kImportEs6) { + printer->Print("}"); + } else { + printer->Print("};"); + } +} + +const std::string Generator::StaticMemberAssignmentLhs( + const GeneratorOptions& options, + const char * classSymbol, + const char * fieldName) const { + if (options.WantEs6()) { + return std::string("static ") + fieldName; + } else { + return std::string("") + classSymbol + "." + fieldName; + } + } + } // namespace js } // namespace compiler } // namespace protobuf diff --git a/generator/js_generator.h b/generator/js_generator.h index c7a942a..40198f2 100644 --- a/generator/js_generator.h +++ b/generator/js_generator.h @@ -59,6 +59,18 @@ class Printer; namespace compiler { namespace js { +class TypeNames; + +// The mode of operation for bytes fields. Historically JSPB always carried +// bytes as JS {string}, containing base64 content by convention. With binary +// and proto3 serialization the new convention is to represent it as binary +// data in Uint8Array. See b/26173701 for background on the migration. +enum BytesMode { + BYTES_DEFAULT, // Default type for getBytesField to return. + BYTES_B64, // Explicitly coerce to base64 string where needed. + BYTES_U8, // Explicitly coerce to Uint8Array where needed. +}; + struct GeneratorOptions { // Output path. std::string output_dir; @@ -108,6 +120,11 @@ struct GeneratorOptions { // Indicates how to output the generated code based on the provided options. OutputMode output_mode() const; + // True if the code generator is in ES6 module generation mode. + // + // In this mode, ES6 classes and module-style imports will be used. + bool WantEs6() const; + // The remaining options are only relevant when we are using kImportClosure. // Add a `goog.requires()` call for each enum type used. If not set, a @@ -130,6 +147,86 @@ struct GeneratorOptions { bool annotate_code; }; +/** + * Maps known protobuf type names for enums, messages to a JavaScript + * expresseion used to reference that type. + */ +class TypeNames { +public: + /** + * Returns a TypeNames namer for naming types while generating code + * for the given proto file. Assumes kOneOutputFilePerInputFile. + */ + static TypeNames Es6TypeNames( + const GeneratorOptions& options, + const FileDescriptor* codegen_file); + + /** + * Returns a TypeNames object for naming types while generating + * code in non-es6-mode. Use dot-delmited type names and + * goog.provide/goog.requires. + */ + static TypeNames NonEs6TypeNames(const GeneratorOptions& options); + + /** + * Returns the JavaScript expression that is exported by the ES6 module + * that defines the type with the given full name as obtained from the + * type descriptor. If the symbol is not directly exported by the + * ES6 module, the empty string should be returned. + */ + static std::string JsName(const std::string& full_name); + + /** + * Returns the JavaScript expression for referring to the passed message type. + */ + std::string JsExpression(const google::protobuf::Descriptor& desc) const; + + /** + * Returns the JavaScript expression for referring to the given enum type. + */ + std::string JsExpression(const google::protobuf::EnumDescriptor& desc) const; + + /** + * Returns the JavaScript expression for referring to type of the + * given field, which must be a message field. + */ + std::string SubmessageTypeRef(const FieldDescriptor* field) const; + +private: + TypeNames( + GeneratorOptions options_, + const FileDescriptor* codegen_file_, + const std::map& map) : + options(options_), + codegen_file(codegen_file_), + map_(map) {} + + GeneratorOptions options; + + // The proto file for which code is being generated. + // + // If in ES6 mode, this will always be set. Otherwise, this may + // be null if in kOneOutputFilePerSCC or kEverythingInOneFile mode. + const FileDescriptor* codegen_file; + + // Maps a fully qualified proto type name (as returned from + // Descriptor::full_name()) to a JavaScript expression to use to refer to that + // type within the generated code. + std::map map_; + + // True for non-ES6 mode. Use dot-delimited identifiers to refer + // to protos. e.g., "proto.foo.bar.Baz.Bim" for nested message Bim + // within message Baz within package "foo.bar". + //bool UseDotDelimitedNames(); + + /** + * Returns the JavaScript expression for referring to the Enum + * or Message with the provided full name (as obtained from the type + * descriptor). + */ + std::string JsExpression(const std::string& full_name) const; +}; + // CodeGenerator implementation which generates a JavaScript source file and // header. If you create your own protocol compiler binary and you want it to // support JavaScript output, you can do so by registering an instance of this @@ -238,6 +335,7 @@ class PROTOC_EXPORT Generator : public CodeGenerator { // Generate definitions for all message classes and enums. void GenerateClassesAndEnums(const GeneratorOptions& options, + const TypeNames& type_names, io::Printer* printer, const FileDescriptor* file) const; @@ -247,8 +345,14 @@ class PROTOC_EXPORT Generator : public CodeGenerator { bool use_default) const; // Generate definition for one class. - void GenerateClass(const GeneratorOptions& options, io::Printer* printer, + void GenerateClass(const GeneratorOptions& options, + const TypeNames& type_names, + io::Printer* printer, const Descriptor* desc) const; + void GenerateClassEs6(const GeneratorOptions& options, + const TypeNames& type_names, + io::Printer* printer, + const Descriptor* desc) const; void GenerateClassConstructor(const GeneratorOptions& options, io::Printer* printer, const Descriptor* desc) const; @@ -267,40 +371,53 @@ class PROTOC_EXPORT Generator : public CodeGenerator { io::Printer* printer, const Descriptor* desc) const; void GenerateClassToObject(const GeneratorOptions& options, + const TypeNames& type_names, io::Printer* printer, const Descriptor* desc) const; void GenerateClassFieldToObject(const GeneratorOptions& options, + const TypeNames& type_names, io::Printer* printer, const FieldDescriptor* field) const; void GenerateClassFromObject(const GeneratorOptions& options, + const TypeNames& type_names, io::Printer* printer, const Descriptor* desc) const; void GenerateClassFieldFromObject(const GeneratorOptions& options, + const TypeNames& type_names, io::Printer* printer, const FieldDescriptor* field) const; void GenerateClassRegistration(const GeneratorOptions& options, + const TypeNames& type_names, io::Printer* printer, const Descriptor* desc) const; void GenerateClassFields(const GeneratorOptions& options, + const TypeNames& type_names, io::Printer* printer, const Descriptor* desc) const; - void GenerateClassField(const GeneratorOptions& options, io::Printer* printer, + void GenerateClassField(const GeneratorOptions& options, + const TypeNames& type_names, + io::Printer* printer, const FieldDescriptor* desc) const; void GenerateClassExtensionFieldInfo(const GeneratorOptions& options, io::Printer* printer, const Descriptor* desc) const; void GenerateClassDeserialize(const GeneratorOptions& options, + const TypeNames& type_names, io::Printer* printer, const Descriptor* desc) const; void GenerateClassDeserializeBinary(const GeneratorOptions& options, + const TypeNames& type_names, io::Printer* printer, const Descriptor* desc) const; void GenerateClassDeserializeBinaryField(const GeneratorOptions& options, + const TypeNames& type_names, io::Printer* printer, const FieldDescriptor* field) const; void GenerateClassSerializeBinary(const GeneratorOptions& options, + const TypeNames& type_names, io::Printer* printer, const Descriptor* desc) const; void GenerateClassSerializeBinaryField(const GeneratorOptions& options, + const TypeNames& type_names, io::Printer* printer, const FieldDescriptor* field) const; @@ -309,7 +426,9 @@ class PROTOC_EXPORT Generator : public CodeGenerator { const EnumDescriptor* enumdesc) const; // Generate an extension definition. - void GenerateExtension(const GeneratorOptions& options, io::Printer* printer, + void GenerateExtension(const GeneratorOptions& options, + const TypeNames& type_names, + io::Printer* printer, const FieldDescriptor* field) const; // Generate addFoo() method for repeated primitive fields. @@ -320,9 +439,33 @@ class PROTOC_EXPORT Generator : public CodeGenerator { // Generate addFoo() method for repeated message fields. void GenerateRepeatedMessageHelperMethods(const GeneratorOptions& options, + const TypeNames& type_names, io::Printer* printer, const FieldDescriptor* field) const; + void GenerateBytesWrapper(const GeneratorOptions& options, io::Printer* printer, + const FieldDescriptor* field, BytesMode bytes_mode) const; + + // Prints the beginning/end of a method of some class. + void GenerateMethodStart(const GeneratorOptions& options, + io::Printer* printer, + const char * classSymbol, + const char * methodName) const; + void GenerateMethodEnd(const GeneratorOptions& options, + io::Printer* printer) const; + + const std::string MethodStart(const GeneratorOptions& options, + const char * classSymbol, + const char * methodName) const; + const std::string MethodStartStatic(const GeneratorOptions& options, + const char * classSymbol, + const char * methodName) const; + + const std::string StaticMemberAssignmentLhs( + const GeneratorOptions& options, + const char * classSymbol, + const char * fieldName) const; + GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(Generator); }; diff --git a/generator/well_known_types_embed.cc b/generator/well_known_types_embed.cc index ef18854..688ec3b 100644 --- a/generator/well_known_types_embed.cc +++ b/generator/well_known_types_embed.cc @@ -39,7 +39,7 @@ struct FileToc well_known_types_js[] = { " * Returns the type name contained in this instance, if any.\n" " * @return {string|undefined}\n" " */\n" - "proto.google.protobuf.Any.prototype.getTypeName = function() {\n" + "getTypeName() {\n" " return this.getTypeUrl().split('/').pop();\n" "};\n" "\n" @@ -51,7 +51,7 @@ struct FileToc well_known_types_js[] = { " * @param {string} name The type name of this message object.\n" " * @param {string=} opt_typeUrlPrefix the type URL prefix.\n" " */\n" - "proto.google.protobuf.Any.prototype.pack = function(serialized, name,\n" + "pack(serialized, name,\n" " opt_typeUrlPrefix) " "{\n" " if (!opt_typeUrlPrefix) {\n" @@ -65,7 +65,7 @@ struct FileToc well_known_types_js[] = { " }\n" "\n" " this.setValue(serialized);\n" - "};\n" + "}\n" "\n" "\n" "/**\n" @@ -79,14 +79,14 @@ struct FileToc well_known_types_js[] = { "deserialized\n" " * object, otherwise returns null.\n" " */\n" - "proto.google.protobuf.Any.prototype.unpack = function(deserialize, name) " + "unpack(deserialize, name) " "{\n" " if (this.getTypeName() == name) {\n" " return deserialize(this.getValue_asU8());\n" " } else {\n" " return null;\n" " }\n" - "};\n" + "}\n" }, {"timestamp.js", "/* This code will be inserted into generated code for\n" @@ -96,7 +96,7 @@ struct FileToc well_known_types_js[] = { " * Returns a JavaScript 'Date' object corresponding to this Timestamp.\n" " * @return {!Date}\n" " */\n" - "proto.google.protobuf.Timestamp.prototype.toDate = function() {\n" + "toDate() {\n" " var seconds = this.getSeconds();\n" " var nanos = this.getNanos();\n" "\n" @@ -108,7 +108,7 @@ struct FileToc well_known_types_js[] = { " * Sets the value of this Timestamp object to be the given Date.\n" " * @param {!Date} value The value to set.\n" " */\n" - "proto.google.protobuf.Timestamp.prototype.fromDate = function(value) {\n" + "fromDate(value) {\n" " this.setSeconds(Math.floor(value.getTime() / 1000));\n" " this.setNanos(value.getMilliseconds() * 1000000);\n" "};\n" @@ -120,7 +120,7 @@ struct FileToc well_known_types_js[] = { " * @param {!Date} value The value to set.\n" " * @return {!proto.google.protobuf.Timestamp}\n" " */\n" - "proto.google.protobuf.Timestamp.fromDate = function(value) {\n" + "fromDate(value) {\n" " var timestamp = new proto.google.protobuf.Timestamp();\n" " timestamp.fromDate(value);\n" " return timestamp;\n" @@ -142,7 +142,7 @@ struct FileToc well_known_types_js[] = { " * @return {?proto.google.protobuf.JavaScriptValue} a plain JavaScript\n" " * value representing this Struct.\n" " */\n" - "proto.google.protobuf.Value.prototype.toJavaScript = function() {\n" + "toJavaScript() {\n" " var kindCase = proto.google.protobuf.Value.KindCase;\n" " switch (this.getKindCase()) {\n" " case kindCase.NULL_VALUE:\n" @@ -169,7 +169,7 @@ struct FileToc well_known_types_js[] = { " * convert.\n" " * @return {!proto.google.protobuf.Value} The newly constructed value.\n" " */\n" - "proto.google.protobuf.Value.fromJavaScript = function(value) {\n" + "fromJavaScript(value) {\n" " var ret = new proto.google.protobuf.Value();\n" " switch (goog.typeOf(value)) {\n" " case 'string':\n" @@ -204,7 +204,7 @@ struct FileToc well_known_types_js[] = { " * Converts this ListValue object to a plain JavaScript array.\n" " * @return {!Array} a plain JavaScript array representing this List.\n" " */\n" - "proto.google.protobuf.ListValue.prototype.toJavaScript = function() {\n" + "toJavaScript() {\n" " var ret = [];\n" " var values = this.getValuesList();\n" "\n" @@ -221,7 +221,7 @@ struct FileToc well_known_types_js[] = { " * @param {!Array} array a plain JavaScript array\n" " * @return {proto.google.protobuf.ListValue} a new ListValue object\n" " */\n" - "proto.google.protobuf.ListValue.fromJavaScript = function(array) {\n" + "fromJavaScript(array) {\n" " var ret = new proto.google.protobuf.ListValue();\n" "\n" " for (var i = 0; i < array.length; i++) {\n" @@ -239,7 +239,7 @@ struct FileToc well_known_types_js[] = { "plain\n" " * JavaScript object representing this Struct.\n" " */\n" - "proto.google.protobuf.Struct.prototype.toJavaScript = function() {\n" + "toJavaScript() {\n" " var ret = {};\n" "\n" " this.getFieldsMap().forEach(function(value, key) {\n" @@ -255,7 +255,7 @@ struct FileToc well_known_types_js[] = { " * @param {!Object} obj a plain JavaScript object\n" " * @return {proto.google.protobuf.Struct} a new Struct object\n" " */\n" - "proto.google.protobuf.Struct.fromJavaScript = function(obj) {\n" + "fromJavaScript(obj) {\n" " var ret = new proto.google.protobuf.Struct();\n" " var map = ret.getFieldsMap();\n" "\n"