protobufjs 7.3.3 → 7.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/object.js CHANGED
@@ -3,10 +3,17 @@ module.exports = ReflectionObject;
3
3
 
4
4
  ReflectionObject.className = "ReflectionObject";
5
5
 
6
+ const OneOf = require("./oneof");
6
7
  var util = require("./util");
7
8
 
8
9
  var Root; // cyclic
9
10
 
11
+ /* eslint-disable no-warning-comments */
12
+ // TODO: Replace with embedded proto.
13
+ var editions2023Defaults = {enum_type: "OPEN", field_presence: "EXPLICIT", json_format: "ALLOW", message_encoding: "LENGTH_PREFIXED", repeated_field_encoding: "PACKED", utf8_validation: "VERIFY"};
14
+ var proto2Defaults = {enum_type: "CLOSED", field_presence: "EXPLICIT", json_format: "LEGACY_BEST_EFFORT", message_encoding: "LENGTH_PREFIXED", repeated_field_encoding: "EXPANDED", utf8_validation: "NONE"};
15
+ var proto3Defaults = {enum_type: "OPEN", field_presence: "IMPLICIT", json_format: "ALLOW", message_encoding: "LENGTH_PREFIXED", repeated_field_encoding: "PACKED", utf8_validation: "VERIFY"};
16
+
10
17
  /**
11
18
  * Constructs a new reflection object instance.
12
19
  * @classdesc Base class of all reflection objects.
@@ -41,6 +48,25 @@ function ReflectionObject(name, options) {
41
48
  */
42
49
  this.name = name;
43
50
 
51
+ /**
52
+ * The edition specified for this object. Only relevant for top-level objects.
53
+ * @type {string}
54
+ */
55
+ this._edition = null;
56
+
57
+ /**
58
+ * The default edition to use for this object if none is specified. For legacy reasons,
59
+ * this is proto2 except in the JSON parsing case where it was proto3.
60
+ * @type {string}
61
+ */
62
+ this._defaultEdition = "proto2";
63
+
64
+ /**
65
+ * Resolved Features.
66
+ * @type {object}
67
+ */
68
+ this._features = {};
69
+
44
70
  /**
45
71
  * Parent namespace.
46
72
  * @type {Namespace|null}
@@ -146,11 +172,84 @@ ReflectionObject.prototype.onRemove = function onRemove(parent) {
146
172
  ReflectionObject.prototype.resolve = function resolve() {
147
173
  if (this.resolved)
148
174
  return this;
149
- if (this.root instanceof Root)
150
- this.resolved = true; // only if part of a root
175
+ if (this instanceof Root) {
176
+ this._resolveFeaturesRecursive(this._edition);
177
+ this.resolved = true;
178
+ }
151
179
  return this;
152
180
  };
153
181
 
182
+ /**
183
+ * Resolves this objects editions features.
184
+ * @param {string} edition The edition we're currently resolving for.
185
+ * @returns {ReflectionObject} `this`
186
+ */
187
+ ReflectionObject.prototype._resolveFeaturesRecursive = function _resolveFeaturesRecursive(edition) {
188
+ return this._resolveFeatures(this._edition || edition);
189
+ };
190
+
191
+ /**
192
+ * Resolves child features from parent features
193
+ * @param {string} edition The edition we're currently resolving for.
194
+ * @returns {undefined}
195
+ */
196
+ ReflectionObject.prototype._resolveFeatures = function _resolveFeatures(edition) {
197
+ var defaults = {};
198
+
199
+ /* istanbul ignore if */
200
+ if (!edition) {
201
+ throw new Error("Unknown edition for " + this.fullName);
202
+ }
203
+
204
+ var protoFeatures = Object.assign(this.options ? Object.assign({}, this.options.features) : {},
205
+ this._inferLegacyProtoFeatures(edition));
206
+
207
+ if (this._edition) {
208
+ // For a namespace marked with a specific edition, reset defaults.
209
+ /* istanbul ignore else */
210
+ if (edition === "proto2") {
211
+ defaults = Object.assign({}, proto2Defaults);
212
+ } else if (edition === "proto3") {
213
+ defaults = Object.assign({}, proto3Defaults);
214
+ } else if (edition === "2023") {
215
+ defaults = Object.assign({}, editions2023Defaults);
216
+ } else {
217
+ throw new Error("Unknown edition: " + edition);
218
+ }
219
+ this._features = Object.assign(defaults, protoFeatures || {});
220
+ return;
221
+ }
222
+
223
+ // fields in Oneofs aren't actually children of them, so we have to
224
+ // special-case it
225
+ /* istanbul ignore else */
226
+ if (this.partOf instanceof OneOf) {
227
+ var lexicalParentFeaturesCopy = Object.assign({}, this.partOf._features);
228
+ this._features = Object.assign(lexicalParentFeaturesCopy, protoFeatures || {});
229
+ } else if (this.declaringField) {
230
+ // Skip feature resolution of sister fields.
231
+ } else if (this.parent) {
232
+ var parentFeaturesCopy = Object.assign({}, this.parent._features);
233
+ this._features = Object.assign(parentFeaturesCopy, protoFeatures || {});
234
+ } else {
235
+ throw new Error("Unable to find a parent for " + this.fullName);
236
+ }
237
+ if (this.extensionField) {
238
+ // Sister fields should have the same features as their extensions.
239
+ this.extensionField._features = this._features;
240
+ }
241
+ };
242
+
243
+ /**
244
+ * Infers features from legacy syntax that may have been specified differently.
245
+ * in older editions.
246
+ * @param {string|undefined} edition The edition this proto is on, or undefined if pre-editions
247
+ * @returns {object} The feature values to override
248
+ */
249
+ ReflectionObject.prototype._inferLegacyProtoFeatures = function _inferLegacyProtoFeatures(/*edition*/) {
250
+ return {};
251
+ };
252
+
154
253
  /**
155
254
  * Gets an option value.
156
255
  * @param {string} name Option name
@@ -166,12 +265,19 @@ ReflectionObject.prototype.getOption = function getOption(name) {
166
265
  * Sets an option.
167
266
  * @param {string} name Option name
168
267
  * @param {*} value Option value
169
- * @param {boolean} [ifNotSet] Sets the option only if it isn't currently set
268
+ * @param {boolean|undefined} [ifNotSet] Sets the option only if it isn't currently set
170
269
  * @returns {ReflectionObject} `this`
171
270
  */
172
271
  ReflectionObject.prototype.setOption = function setOption(name, value, ifNotSet) {
173
- if (!ifNotSet || !this.options || this.options[name] === undefined)
174
- (this.options || (this.options = {}))[name] = value;
272
+ if (!this.options)
273
+ this.options = {};
274
+ if (/^features\./.test(name)) {
275
+ util.setProperty(this.options, name, value, ifNotSet);
276
+ } else if (!ifNotSet || this.options[name] === undefined) {
277
+ if (this.getOption(name) !== value) this.resolved = false;
278
+ this.options[name] = value;
279
+ }
280
+
175
281
  return this;
176
282
  };
177
283
 
@@ -195,10 +301,11 @@ ReflectionObject.prototype.setParsedOption = function setParsedOption(name, valu
195
301
  });
196
302
  if (opt) {
197
303
  // If we found an existing option - just merge the property value
304
+ // (If it's a feature, will just write over)
198
305
  var newValue = opt[name];
199
306
  util.setProperty(newValue, propName, value);
200
307
  } else {
201
- // otherwise, create a new option, set it's property and add it to the list
308
+ // otherwise, create a new option, set its property and add it to the list
202
309
  opt = {};
203
310
  opt[name] = util.setProperty({}, propName, value);
204
311
  parsedOptions.push(opt);
@@ -209,6 +316,7 @@ ReflectionObject.prototype.setParsedOption = function setParsedOption(name, valu
209
316
  newOpt[name] = value;
210
317
  parsedOptions.push(newOpt);
211
318
  }
319
+
212
320
  return this;
213
321
  };
214
322
 
@@ -237,6 +345,19 @@ ReflectionObject.prototype.toString = function toString() {
237
345
  return className;
238
346
  };
239
347
 
348
+ /**
349
+ * Converts the edition this object is pinned to for JSON format.
350
+ * @returns {string|undefined} The edition string for JSON representation
351
+ */
352
+ ReflectionObject.prototype._editionToJSON = function _editionToJSON() {
353
+ if (!this._edition || this._edition === "proto3") {
354
+ // Avoid emitting proto3 since we need to default to it for backwards
355
+ // compatibility anyway.
356
+ return undefined;
357
+ }
358
+ return this._edition;
359
+ };
360
+
240
361
  // Sets up cyclic dependencies (called in index-light)
241
362
  ReflectionObject._configure = function(Root_) {
242
363
  Root = Root_;
package/src/oneof.js CHANGED
@@ -171,6 +171,25 @@ OneOf.prototype.onRemove = function onRemove(parent) {
171
171
  ReflectionObject.prototype.onRemove.call(this, parent);
172
172
  };
173
173
 
174
+ /**
175
+ * Determines whether this field corresponds to a synthetic oneof created for
176
+ * a proto3 optional field. No behavioral logic should depend on this, but it
177
+ * can be relevant for reflection.
178
+ * @name OneOf#isProto3Optional
179
+ * @type {boolean}
180
+ * @readonly
181
+ */
182
+ Object.defineProperty(OneOf.prototype, "isProto3Optional", {
183
+ get: function() {
184
+ if (this.fieldsArray == null || this.fieldsArray.length !== 1) {
185
+ return false;
186
+ }
187
+
188
+ var field = this.fieldsArray[0];
189
+ return field.options != null && field.options["proto3_optional"] === true;
190
+ }
191
+ });
192
+
174
193
  /**
175
194
  * Decorator function as returned by {@link OneOf.d} (TypeScript).
176
195
  * @typedef OneOfDecorator
package/src/parse.js CHANGED
@@ -13,6 +13,7 @@ var tokenize = require("./tokenize"),
13
13
  Enum = require("./enum"),
14
14
  Service = require("./service"),
15
15
  Method = require("./method"),
16
+ ReflectionObject = require("./object"),
16
17
  types = require("./types"),
17
18
  util = require("./util");
18
19
 
@@ -24,8 +25,7 @@ var base10Re = /^[1-9][0-9]*$/,
24
25
  base8NegRe = /^-?0[0-7]+$/,
25
26
  numberRe = /^(?![eE])[0-9]*(?:\.[0-9]*)?(?:[eE][+-]?[0-9]+)?$/,
26
27
  nameRe = /^[a-zA-Z_][a-zA-Z_0-9]*$/,
27
- typeRefRe = /^(?:\.?[a-zA-Z_][a-zA-Z_0-9]*)(?:\.[a-zA-Z_][a-zA-Z_0-9]*)*$/,
28
- fqTypeRefRe = /^(?:\.[a-zA-Z_][a-zA-Z_0-9]*)+$/;
28
+ typeRefRe = /^(?:\.?[a-zA-Z_][a-zA-Z_0-9]*)(?:\.[a-zA-Z_][a-zA-Z_0-9]*)*$/;
29
29
 
30
30
  /**
31
31
  * Result object returned from {@link parse}.
@@ -33,7 +33,6 @@ var base10Re = /^[1-9][0-9]*$/,
33
33
  * @property {string|undefined} package Package name, if declared
34
34
  * @property {string[]|undefined} imports Imports, if any
35
35
  * @property {string[]|undefined} weakImports Weak imports, if any
36
- * @property {string|undefined} syntax Syntax, if specified (either `"proto2"` or `"proto3"`)
37
36
  * @property {Root} root Populated root instance
38
37
  */
39
38
 
@@ -81,13 +80,25 @@ function parse(source, root, options) {
81
80
  pkg,
82
81
  imports,
83
82
  weakImports,
84
- syntax,
85
- isProto3 = false;
83
+ edition = "proto2";
86
84
 
87
85
  var ptr = root;
88
86
 
87
+ var topLevelObjects = [];
88
+ var topLevelOptions = {};
89
+
89
90
  var applyCase = options.keepCase ? function(name) { return name; } : util.camelCase;
90
91
 
92
+ function resolveFileFeatures() {
93
+ topLevelObjects.forEach(obj => {
94
+ obj._edition = edition;
95
+ Object.keys(topLevelOptions).forEach(opt => {
96
+ if (obj.getOption(opt) !== undefined) return;
97
+ obj.setOption(opt, topLevelOptions[opt], true);
98
+ });
99
+ });
100
+ }
101
+
91
102
  /* istanbul ignore next */
92
103
  function illegal(token, name, insideTryCatch) {
93
104
  var filename = parse.filename;
@@ -126,7 +137,6 @@ function parse(source, root, options) {
126
137
  try {
127
138
  return parseNumber(token, /* insideTryCatch */ true);
128
139
  } catch (e) {
129
-
130
140
  /* istanbul ignore else */
131
141
  if (acceptTypeRef && typeRefRe.test(token))
132
142
  return token;
@@ -139,10 +149,23 @@ function parse(source, root, options) {
139
149
  function readRanges(target, acceptStrings) {
140
150
  var token, start;
141
151
  do {
142
- if (acceptStrings && ((token = peek()) === "\"" || token === "'"))
143
- target.push(readString());
144
- else
145
- target.push([ start = parseId(next()), skip("to", true) ? parseId(next()) : start ]);
152
+ if (acceptStrings && ((token = peek()) === "\"" || token === "'")) {
153
+ var str = readString();
154
+ target.push(str);
155
+ if (edition >= 2023) {
156
+ throw illegal(str, "id");
157
+ }
158
+ } else {
159
+ try {
160
+ target.push([ start = parseId(next()), skip("to", true) ? parseId(next()) : start ]);
161
+ } catch (err) {
162
+ if (acceptStrings && typeRefRe.test(token) && edition >= 2023) {
163
+ target.push(token);
164
+ } else {
165
+ throw err;
166
+ }
167
+ }
168
+ }
146
169
  } while (skip(",", true));
147
170
  var dummy = {options: undefined};
148
171
  dummy.setOption = function(name, value) {
@@ -219,7 +242,6 @@ function parse(source, root, options) {
219
242
  }
220
243
 
221
244
  function parsePackage() {
222
-
223
245
  /* istanbul ignore if */
224
246
  if (pkg !== undefined)
225
247
  throw illegal("package");
@@ -231,6 +253,7 @@ function parse(source, root, options) {
231
253
  throw illegal(pkg, "name");
232
254
 
233
255
  ptr = ptr.define(pkg);
256
+
234
257
  skip(";");
235
258
  }
236
259
 
@@ -256,20 +279,28 @@ function parse(source, root, options) {
256
279
 
257
280
  function parseSyntax() {
258
281
  skip("=");
259
- syntax = readString();
260
- isProto3 = syntax === "proto3";
282
+ edition = readString();
261
283
 
262
284
  /* istanbul ignore if */
263
- if (!isProto3 && syntax !== "proto2")
264
- throw illegal(syntax, "syntax");
285
+ if (edition < 2023)
286
+ throw illegal(edition, "syntax");
265
287
 
266
- // Syntax is needed to understand the meaning of the optional field rule
267
- // Otherwise the meaning is ambiguous between proto2 and proto3
268
- root.setOption("syntax", syntax);
288
+ skip(";");
289
+ }
290
+
291
+ function parseEdition() {
292
+ skip("=");
293
+ edition = readString();
294
+ const supportedEditions = ["2023"];
295
+
296
+ /* istanbul ignore if */
297
+ if (!supportedEditions.includes(edition))
298
+ throw illegal(edition, "edition");
269
299
 
270
300
  skip(";");
271
301
  }
272
302
 
303
+
273
304
  function parseCommon(parent, token) {
274
305
  switch (token) {
275
306
 
@@ -337,14 +368,19 @@ function parse(source, root, options) {
337
368
  break;
338
369
 
339
370
  case "required":
371
+ if (edition !== "proto2")
372
+ throw illegal(token);
373
+ /* eslint-disable no-fallthrough */
340
374
  case "repeated":
341
375
  parseField(type, token);
342
376
  break;
343
377
 
344
378
  case "optional":
345
379
  /* istanbul ignore if */
346
- if (isProto3) {
380
+ if (edition === "proto3") {
347
381
  parseField(type, "proto3_optional");
382
+ } else if (edition !== "proto2") {
383
+ throw illegal(token);
348
384
  } else {
349
385
  parseField(type, "optional");
350
386
  }
@@ -364,8 +400,9 @@ function parse(source, root, options) {
364
400
 
365
401
  default:
366
402
  /* istanbul ignore if */
367
- if (!isProto3 || !typeRefRe.test(token))
403
+ if (edition === "proto2" || !typeRefRe.test(token)) {
368
404
  throw illegal(token);
405
+ }
369
406
 
370
407
  push(token);
371
408
  parseField(type, "optional");
@@ -373,6 +410,9 @@ function parse(source, root, options) {
373
410
  }
374
411
  });
375
412
  parent.add(type);
413
+ if (parent === ptr) {
414
+ topLevelObjects.push(type);
415
+ }
376
416
  }
377
417
 
378
418
  function parseField(parent, rule, extend) {
@@ -399,6 +439,7 @@ function parse(source, root, options) {
399
439
  var name = next();
400
440
 
401
441
  /* istanbul ignore if */
442
+
402
443
  if (!nameRe.test(name))
403
444
  throw illegal(name, "name");
404
445
 
@@ -406,6 +447,7 @@ function parse(source, root, options) {
406
447
  skip("=");
407
448
 
408
449
  var field = new Field(name, parseId(next()), type, rule, extend);
450
+
409
451
  ifBlock(field, function parseField_block(token) {
410
452
 
411
453
  /* istanbul ignore else */
@@ -428,15 +470,15 @@ function parse(source, root, options) {
428
470
  } else {
429
471
  parent.add(field);
430
472
  }
431
-
432
- // JSON defaults to packed=true if not set so we have to set packed=false explicity when
433
- // parsing proto2 descriptors without the option, where applicable. This must be done for
434
- // all known packable types and anything that could be an enum (= is not a basic type).
435
- if (!isProto3 && field.repeated && (types.packed[type] !== undefined || types.basic[type] === undefined))
436
- field.setOption("packed", false, /* ifNotSet */ true);
473
+ if (parent === ptr) {
474
+ topLevelObjects.push(field);
475
+ }
437
476
  }
438
477
 
439
478
  function parseGroup(parent, rule) {
479
+ if (edition >= 2023) {
480
+ throw illegal("group");
481
+ }
440
482
  var name = next();
441
483
 
442
484
  /* istanbul ignore if */
@@ -459,7 +501,6 @@ function parse(source, root, options) {
459
501
  parseOption(type, token);
460
502
  skip(";");
461
503
  break;
462
-
463
504
  case "required":
464
505
  case "repeated":
465
506
  parseField(type, token);
@@ -467,7 +508,7 @@ function parse(source, root, options) {
467
508
 
468
509
  case "optional":
469
510
  /* istanbul ignore if */
470
- if (isProto3) {
511
+ if (edition === "proto3") {
471
512
  parseField(type, "proto3_optional");
472
513
  } else {
473
514
  parseField(type, "optional");
@@ -565,6 +606,7 @@ function parse(source, root, options) {
565
606
 
566
607
  case "reserved":
567
608
  readRanges(enm.reserved || (enm.reserved = []), true);
609
+ if(enm.reserved === undefined) enm.reserved = [];
568
610
  break;
569
611
 
570
612
  default:
@@ -572,6 +614,9 @@ function parse(source, root, options) {
572
614
  }
573
615
  });
574
616
  parent.add(enm);
617
+ if (parent === ptr) {
618
+ topLevelObjects.push(enm);
619
+ }
575
620
  }
576
621
 
577
622
  function parseEnumValue(parent, token) {
@@ -585,10 +630,14 @@ function parse(source, root, options) {
585
630
  dummy = {
586
631
  options: undefined
587
632
  };
633
+ dummy.getOption = function(name) {
634
+ return this.options[name];
635
+ };
588
636
  dummy.setOption = function(name, value) {
589
- if (this.options === undefined)
590
- this.options = {};
591
- this.options[name] = value;
637
+ ReflectionObject.prototype.setOption.call(dummy, name, value);
638
+ };
639
+ dummy.setParsedOption = function() {
640
+ return undefined;
592
641
  };
593
642
  ifBlock(dummy, function parseEnumValue_block(token) {
594
643
 
@@ -602,34 +651,42 @@ function parse(source, root, options) {
602
651
  }, function parseEnumValue_line() {
603
652
  parseInlineOptions(dummy); // skip
604
653
  });
605
- parent.add(token, value, dummy.comment, dummy.options);
654
+ parent.add(token, value, dummy.comment, dummy.parsedOptions || dummy.options);
606
655
  }
607
656
 
608
657
  function parseOption(parent, token) {
609
- var isCustom = skip("(", true);
610
-
611
- /* istanbul ignore if */
612
- if (!typeRefRe.test(token = next()))
613
- throw illegal(token, "name");
614
-
615
- var name = token;
616
- var option = name;
617
- var propName;
658
+ var option;
659
+ var propName;
660
+ var isOption = true;
661
+ if (token === "option") {
662
+ token = next();
663
+ }
618
664
 
619
- if (isCustom) {
620
- skip(")");
621
- name = "(" + name + ")";
622
- option = name;
623
- token = peek();
624
- if (fqTypeRefRe.test(token)) {
625
- propName = token.slice(1); //remove '.' before property name
626
- name += token;
627
- next();
665
+ while (token !== "=") {
666
+ if (token === "(") {
667
+ var parensValue = next();
668
+ skip(")");
669
+ token = "(" + parensValue + ")";
670
+ }
671
+ if (isOption) {
672
+ isOption = false;
673
+ if (token.includes(".") && !token.includes("(")) {
674
+ var tokens = token.split(".");
675
+ option = tokens[0] + ".";
676
+ token = tokens[1];
677
+ continue;
678
+ }
679
+ option = token;
680
+ } else {
681
+ propName = propName ? propName += token : token;
682
+ }
683
+ token = next();
628
684
  }
629
- }
630
- skip("=");
631
- var optionValue = parseOptionValue(parent, name);
632
- setParsedOption(parent, option, optionValue, propName);
685
+ var name = propName ? option.concat(propName) : option;
686
+ var optionValue = parseOptionValue(parent, name);
687
+ propName = propName && propName[0] === "." ? propName.slice(1) : propName;
688
+ option = option && option[option.length - 1] === "." ? option.slice(0, -1) : option;
689
+ setParsedOption(parent, option, optionValue, propName);
633
690
  }
634
691
 
635
692
  function parseOptionValue(parent, name) {
@@ -651,12 +708,12 @@ function parse(source, root, options) {
651
708
 
652
709
  skip(":", true);
653
710
 
654
- if (peek() === "{")
655
- value = parseOptionValue(parent, name + "." + token);
656
- else if (peek() === "[") {
711
+ if (peek() === "{") {
657
712
  // option (my_option) = {
658
713
  // repeated_value: [ "foo", "bar" ]
659
714
  // };
715
+ value = parseOptionValue(parent, name + "." + token);
716
+ } else if (peek() === "[") {
660
717
  value = [];
661
718
  var lastValue;
662
719
  if (skip("[", true)) {
@@ -696,6 +753,10 @@ function parse(source, root, options) {
696
753
  }
697
754
 
698
755
  function setOption(parent, name, value) {
756
+ if (ptr === parent && /^features\./.test(name)) {
757
+ topLevelOptions[name] = value;
758
+ return;
759
+ }
699
760
  if (parent.setOption)
700
761
  parent.setOption(name, value);
701
762
  }
@@ -723,8 +784,9 @@ function parse(source, root, options) {
723
784
 
724
785
  var service = new Service(token);
725
786
  ifBlock(service, function parseService_block(token) {
726
- if (parseCommon(service, token))
787
+ if (parseCommon(service, token)) {
727
788
  return;
789
+ }
728
790
 
729
791
  /* istanbul ignore else */
730
792
  if (token === "rpc")
@@ -733,6 +795,9 @@ function parse(source, root, options) {
733
795
  throw illegal(token);
734
796
  });
735
797
  parent.add(service);
798
+ if (parent === ptr) {
799
+ topLevelObjects.push(service);
800
+ }
736
801
  }
737
802
 
738
803
  function parseMethod(parent, token) {
@@ -802,7 +867,7 @@ function parse(source, root, options) {
802
867
 
803
868
  case "optional":
804
869
  /* istanbul ignore if */
805
- if (isProto3) {
870
+ if (edition === "proto3") {
806
871
  parseField(parent, "proto3_optional", reference);
807
872
  } else {
808
873
  parseField(parent, "optional", reference);
@@ -811,7 +876,7 @@ function parse(source, root, options) {
811
876
 
812
877
  default:
813
878
  /* istanbul ignore if */
814
- if (!isProto3 || !typeRefRe.test(token))
879
+ if (edition === "proto2" || !typeRefRe.test(token))
815
880
  throw illegal(token);
816
881
  push(token);
817
882
  parseField(parent, "optional", reference);
@@ -851,10 +916,16 @@ function parse(source, root, options) {
851
916
  parseSyntax();
852
917
  break;
853
918
 
854
- case "option":
919
+ case "edition":
920
+ /* istanbul ignore if */
921
+ if (!head)
922
+ throw illegal(token);
923
+ parseEdition();
924
+ break;
855
925
 
926
+ case "option":
856
927
  parseOption(ptr, token);
857
- skip(";");
928
+ skip(";", true);
858
929
  break;
859
930
 
860
931
  default:
@@ -870,12 +941,13 @@ function parse(source, root, options) {
870
941
  }
871
942
  }
872
943
 
944
+ resolveFileFeatures();
945
+
873
946
  parse.filename = null;
874
947
  return {
875
948
  "package" : pkg,
876
949
  "imports" : imports,
877
950
  weakImports : weakImports,
878
- syntax : syntax,
879
951
  root : root
880
952
  };
881
953
  }