protobufjs 7.4.0 → 7.5.1

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/index.d.ts CHANGED
@@ -179,6 +179,9 @@ export class Enum extends ReflectionObject {
179
179
  /** Values options, if any */
180
180
  public valuesOptions?: { [k: string]: { [k: string]: any } };
181
181
 
182
+ /** Resolved values features, if any */
183
+ public _valuesFeatures?: { [k: string]: { [k: string]: any } };
184
+
182
185
  /** Reserved ranges, if any. */
183
186
  public reserved: (number[]|string)[];
184
187
 
@@ -267,9 +270,24 @@ export class Field extends FieldBase {
267
270
  */
268
271
  public static fromJSON(name: string, json: IField): Field;
269
272
 
270
- /** Determines whether this field is packed. Only relevant when repeated and working with proto2. */
273
+ /** Determines whether this field is required. */
274
+ public readonly required: boolean;
275
+
276
+ /** Determines whether this field is not required. */
277
+ public readonly optional: boolean;
278
+
279
+ /**
280
+ * Determines whether this field uses tag-delimited encoding. In proto2 this
281
+ * corresponded to group syntax.
282
+ */
283
+ public readonly delimited: boolean;
284
+
285
+ /** Determines whether this field is packed. Only relevant when repeated. */
271
286
  public readonly packed: boolean;
272
287
 
288
+ /** Determines whether this field tracks presence. */
289
+ public readonly hasPresence: boolean;
290
+
273
291
  /**
274
292
  * Field decorator (TypeScript).
275
293
  * @param fieldId Field id
@@ -314,12 +332,6 @@ export class FieldBase extends ReflectionObject {
314
332
  /** Extended type if different from parent. */
315
333
  public extend?: string;
316
334
 
317
- /** Whether this field is required. */
318
- public required: boolean;
319
-
320
- /** Whether this field is optional. */
321
- public optional: boolean;
322
-
323
335
  /** Whether this field is repeated. */
324
336
  public repeated: boolean;
325
337
 
@@ -369,6 +381,14 @@ export class FieldBase extends ReflectionObject {
369
381
  * @throws {Error} If any reference cannot be resolved
370
382
  */
371
383
  public resolve(): Field;
384
+
385
+ /**
386
+ * Infers field features from legacy syntax that may have been specified differently.
387
+ * in older editions.
388
+ * @param edition The edition this proto is on, or undefined if pre-editions
389
+ * @returns The feature values to override
390
+ */
391
+ public _inferLegacyProtoFeatures(edition: (string|undefined)): object;
372
392
  }
373
393
 
374
394
  /** Field descriptor. */
@@ -730,6 +750,9 @@ export abstract class NamespaceBase extends ReflectionObject {
730
750
  /** Nested objects by name. */
731
751
  public nested?: { [k: string]: ReflectionObject };
732
752
 
753
+ /** Whether or not objects contained in this namespace need feature resolution. */
754
+ protected _needsRecursiveFeatureResolution: boolean;
755
+
733
756
  /** Nested objects of this namespace as an array for iteration. */
734
757
  public readonly nestedArray: ReflectionObject[];
735
758
 
@@ -877,6 +900,21 @@ export abstract class ReflectionObject {
877
900
  /** Unique name within its namespace. */
878
901
  public name: string;
879
902
 
903
+ /** The edition specified for this object. Only relevant for top-level objects. */
904
+ public _edition: string;
905
+
906
+ /**
907
+ * The default edition to use for this object if none is specified. For legacy reasons,
908
+ * this is proto2 except in the JSON parsing case where it was proto3.
909
+ */
910
+ public _defaultEdition: string;
911
+
912
+ /** Resolved Features. */
913
+ public _features: object;
914
+
915
+ /** Whether or not features have been resolved. */
916
+ public _featuresResolved: boolean;
917
+
880
918
  /** Parent namespace. */
881
919
  public parent: (Namespace|null);
882
920
 
@@ -919,6 +957,27 @@ export abstract class ReflectionObject {
919
957
  */
920
958
  public resolve(): ReflectionObject;
921
959
 
960
+ /**
961
+ * Resolves this objects editions features.
962
+ * @param edition The edition we're currently resolving for.
963
+ * @returns `this`
964
+ */
965
+ public _resolveFeaturesRecursive(edition: string): ReflectionObject;
966
+
967
+ /**
968
+ * Resolves child features from parent features
969
+ * @param edition The edition we're currently resolving for.
970
+ */
971
+ public _resolveFeatures(edition: string): void;
972
+
973
+ /**
974
+ * Infers features from legacy syntax that may have been specified differently.
975
+ * in older editions.
976
+ * @param edition The edition this proto is on, or undefined if pre-editions
977
+ * @returns The feature values to override
978
+ */
979
+ public _inferLegacyProtoFeatures(edition: (string|undefined)): object;
980
+
922
981
  /**
923
982
  * Gets an option value.
924
983
  * @param name Option name
@@ -933,7 +992,7 @@ export abstract class ReflectionObject {
933
992
  * @param [ifNotSet] Sets the option only if it isn't currently set
934
993
  * @returns `this`
935
994
  */
936
- public setOption(name: string, value: any, ifNotSet?: boolean): ReflectionObject;
995
+ public setOption(name: string, value: any, ifNotSet?: (boolean|undefined)): ReflectionObject;
937
996
 
938
997
  /**
939
998
  * Sets a parsed option.
@@ -957,6 +1016,12 @@ export abstract class ReflectionObject {
957
1016
  * @returns Class name[, space, full name]
958
1017
  */
959
1018
  public toString(): string;
1019
+
1020
+ /**
1021
+ * Converts the edition this object is pinned to for JSON format.
1022
+ * @returns The edition string for JSON representation
1023
+ */
1024
+ public _editionToJSON(): (string|undefined);
960
1025
  }
961
1026
 
962
1027
  /** Reflected oneof. */
@@ -1010,6 +1075,13 @@ export class OneOf extends ReflectionObject {
1010
1075
  */
1011
1076
  public remove(field: Field): OneOf;
1012
1077
 
1078
+ /**
1079
+ * Determines whether this field corresponds to a synthetic oneof created for
1080
+ * a proto3 optional field. No behavioral logic should depend on this, but it
1081
+ * can be relevant for reflection.
1082
+ */
1083
+ public readonly isProto3Optional: boolean;
1084
+
1013
1085
  /**
1014
1086
  * OneOf decorator (TypeScript).
1015
1087
  * @param fieldNames Field names
@@ -1055,9 +1127,6 @@ export interface IParserResult {
1055
1127
  /** Weak imports, if any */
1056
1128
  weakImports: (string[]|undefined);
1057
1129
 
1058
- /** Syntax, if specified (either `"proto2"` or `"proto3"`) */
1059
- syntax: (string|undefined);
1060
-
1061
1130
  /** Populated root instance */
1062
1131
  root: Root;
1063
1132
  }
@@ -1255,7 +1324,7 @@ export class Root extends NamespaceBase {
1255
1324
 
1256
1325
  /**
1257
1326
  * Loads a namespace descriptor into a root namespace.
1258
- * @param json Nameespace descriptor
1327
+ * @param json Namespace descriptor
1259
1328
  * @param [root] Root namespace, defaults to create a new one if omitted
1260
1329
  * @returns Root namespace
1261
1330
  */
@@ -2194,9 +2263,10 @@ export namespace util {
2194
2263
  * @param dst Destination object
2195
2264
  * @param path dot '.' delimited path of the property to set
2196
2265
  * @param value the value to set
2266
+ * @param [ifNotSet] Sets the option only if it isn't currently set
2197
2267
  * @returns Destination object
2198
2268
  */
2199
- function setProperty(dst: { [k: string]: any }, path: string, value: object): { [k: string]: any };
2269
+ function setProperty(dst: { [k: string]: any }, path: string, value: object, ifNotSet?: (boolean|undefined)): { [k: string]: any };
2200
2270
 
2201
2271
  /** Decorator root (TypeScript). */
2202
2272
  let decorateRoot: Root;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "protobufjs",
3
- "version": "7.4.0",
3
+ "version": "7.5.1",
4
4
  "versionScheme": "~",
5
5
  "description": "Protocol Buffers for JavaScript (& TypeScript).",
6
6
  "author": "Daniel Wirtz <dcode+protobufjs@dcode.io>",
@@ -33,7 +33,9 @@
33
33
  "build:bundle": "gulp --gulpfile scripts/gulpfile.js",
34
34
  "build:types": "node cli/bin/pbts --main --global protobuf --out index.d.ts src/ lib/aspromise/index.js lib/base64/index.js lib/codegen/index.js lib/eventemitter/index.js lib/float/index.js lib/fetch/index.js lib/inquire/index.js lib/path/index.js lib/pool/index.js lib/utf8/index.js",
35
35
  "changelog": "node scripts/changelog -w",
36
- "coverage": "nyc tape -r ./lib/tape-adapter tests/*.js tests/node/*.js",
36
+ "coverage": "npm run coverage:test && npm run coverage:report",
37
+ "coverage:test": "nyc --silent tape -r ./lib/tape-adapter tests/*.js tests/node/*.js",
38
+ "coverage:report": "nyc report --reporter=lcov --reporter=text",
37
39
  "docs": "jsdoc -c config/jsdoc.json -R README.md --verbose --pedantic",
38
40
  "lint": "npm run lint:sources && npm run lint:types",
39
41
  "lint:sources": "eslint \"**/*.js\" -c config/eslint.json",
package/src/decoder.js CHANGED
@@ -16,16 +16,14 @@ function missing(field) {
16
16
  */
17
17
  function decoder(mtype) {
18
18
  /* eslint-disable no-unexpected-multiline */
19
- var gen = util.codegen(["r", "l"], mtype.name + "$decode")
19
+ var gen = util.codegen(["r", "l", "e"], mtype.name + "$decode")
20
20
  ("if(!(r instanceof Reader))")
21
21
  ("r=Reader.create(r)")
22
22
  ("var c=l===undefined?r.len:r.pos+l,m=new this.ctor" + (mtype.fieldsArray.filter(function(field) { return field.map; }).length ? ",k,value" : ""))
23
23
  ("while(r.pos<c){")
24
- ("var t=r.uint32()");
25
- if (mtype.group) gen
26
- ("if((t&7)===4)")
27
- ("break");
28
- gen
24
+ ("var t=r.uint32()")
25
+ ("if(t===e)")
26
+ ("break")
29
27
  ("switch(t>>>3){");
30
28
 
31
29
  var i = 0;
@@ -91,15 +89,15 @@ function decoder(mtype) {
91
89
  ("}else");
92
90
 
93
91
  // Non-packed
94
- if (types.basic[type] === undefined) gen(field.resolvedType.group
95
- ? "%s.push(types[%i].decode(r))"
92
+ if (types.basic[type] === undefined) gen(field.delimited
93
+ ? "%s.push(types[%i].decode(r,undefined,((t&~7)|4)))"
96
94
  : "%s.push(types[%i].decode(r,r.uint32()))", ref, i);
97
95
  else gen
98
96
  ("%s.push(r.%s())", ref, type);
99
97
 
100
98
  // Non-repeated
101
- } else if (types.basic[type] === undefined) gen(field.resolvedType.group
102
- ? "%s=types[%i].decode(r)"
99
+ } else if (types.basic[type] === undefined) gen(field.delimited
100
+ ? "%s=types[%i].decode(r,undefined,((t&~7)|4))"
103
101
  : "%s=types[%i].decode(r,r.uint32())", ref, i);
104
102
  else gen
105
103
  ("%s=r.%s()", ref, type);
package/src/encoder.js CHANGED
@@ -15,7 +15,7 @@ var Enum = require("./enum"),
15
15
  * @ignore
16
16
  */
17
17
  function genTypePartial(gen, field, fieldIndex, ref) {
18
- return field.resolvedType.group
18
+ return field.delimited
19
19
  ? gen("types[%i].encode(%s,w.uint32(%i)).uint32(%i)", fieldIndex, ref, (field.id << 3 | 3) >>> 0, (field.id << 3 | 4) >>> 0)
20
20
  : gen("types[%i].encode(%s,w.uint32(%i).fork()).ldelim()", fieldIndex, ref, (field.id << 3 | 2) >>> 0);
21
21
  }
package/src/enum.js CHANGED
@@ -56,6 +56,12 @@ function Enum(name, values, options, comment, comments, valuesOptions) {
56
56
  */
57
57
  this.valuesOptions = valuesOptions;
58
58
 
59
+ /**
60
+ * Resolved values features, if any
61
+ * @type {Object<string, Object<string, *>>|undefined}
62
+ */
63
+ this._valuesFeatures = {};
64
+
59
65
  /**
60
66
  * Reserved ranges, if any.
61
67
  * @type {Array.<number[]|string>}
@@ -72,6 +78,21 @@ function Enum(name, values, options, comment, comments, valuesOptions) {
72
78
  this.valuesById[ this.values[keys[i]] = values[keys[i]] ] = keys[i];
73
79
  }
74
80
 
81
+ /**
82
+ * @override
83
+ */
84
+ Enum.prototype._resolveFeatures = function _resolveFeatures(edition) {
85
+ edition = this._edition || edition;
86
+ ReflectionObject.prototype._resolveFeatures.call(this, edition);
87
+
88
+ Object.keys(this.values).forEach(key => {
89
+ var parentFeaturesCopy = Object.assign({}, this._features);
90
+ this._valuesFeatures[key] = Object.assign(parentFeaturesCopy, this.valuesOptions && this.valuesOptions[key] && this.valuesOptions[key].features);
91
+ });
92
+
93
+ return this;
94
+ };
95
+
75
96
  /**
76
97
  * Enum descriptor.
77
98
  * @interface IEnum
@@ -89,6 +110,9 @@ function Enum(name, values, options, comment, comments, valuesOptions) {
89
110
  Enum.fromJSON = function fromJSON(name, json) {
90
111
  var enm = new Enum(name, json.values, json.options, json.comment, json.comments);
91
112
  enm.reserved = json.reserved;
113
+ if (json.edition)
114
+ enm._edition = json.edition;
115
+ enm._defaultEdition = "proto3"; // For backwards-compatibility.
92
116
  return enm;
93
117
  };
94
118
 
@@ -100,6 +124,7 @@ Enum.fromJSON = function fromJSON(name, json) {
100
124
  Enum.prototype.toJSON = function toJSON(toJSONOptions) {
101
125
  var keepComments = toJSONOptions ? Boolean(toJSONOptions.keepComments) : false;
102
126
  return util.toObject([
127
+ "edition" , this._editionToJSON(),
103
128
  "options" , this.options,
104
129
  "valuesOptions" , this.valuesOptions,
105
130
  "values" , this.values,
package/src/field.js CHANGED
@@ -35,7 +35,11 @@ var ruleRe = /^required|optional|repeated$/;
35
35
  * @throws {TypeError} If arguments are invalid
36
36
  */
37
37
  Field.fromJSON = function fromJSON(name, json) {
38
- return new Field(name, json.id, json.type, json.rule, json.extend, json.options, json.comment);
38
+ var field = new Field(name, json.id, json.type, json.rule, json.extend, json.options, json.comment);
39
+ if (json.edition)
40
+ field._edition = json.edition;
41
+ field._defaultEdition = "proto3"; // For backwards-compatibility.
42
+ return field;
39
43
  };
40
44
 
41
45
  /**
@@ -105,18 +109,6 @@ function Field(name, id, type, rule, extend, options, comment) {
105
109
  */
106
110
  this.extend = extend || undefined; // toJSON
107
111
 
108
- /**
109
- * Whether this field is required.
110
- * @type {boolean}
111
- */
112
- this.required = rule === "required";
113
-
114
- /**
115
- * Whether this field is optional.
116
- * @type {boolean}
117
- */
118
- this.optional = !this.required;
119
-
120
112
  /**
121
113
  * Whether this field is repeated.
122
114
  * @type {boolean}
@@ -183,13 +175,6 @@ function Field(name, id, type, rule, extend, options, comment) {
183
175
  */
184
176
  this.declaringField = null;
185
177
 
186
- /**
187
- * Internally remembers whether this field is packed.
188
- * @type {boolean|null}
189
- * @private
190
- */
191
- this._packed = null;
192
-
193
178
  /**
194
179
  * Comment for this field.
195
180
  * @type {string|null}
@@ -198,17 +183,69 @@ function Field(name, id, type, rule, extend, options, comment) {
198
183
  }
199
184
 
200
185
  /**
201
- * Determines whether this field is packed. Only relevant when repeated and working with proto2.
186
+ * Determines whether this field is required.
187
+ * @name Field#required
188
+ * @type {boolean}
189
+ * @readonly
190
+ */
191
+ Object.defineProperty(Field.prototype, "required", {
192
+ get: function() {
193
+ return this._features.field_presence === "LEGACY_REQUIRED";
194
+ }
195
+ });
196
+
197
+ /**
198
+ * Determines whether this field is not required.
199
+ * @name Field#optional
200
+ * @type {boolean}
201
+ * @readonly
202
+ */
203
+ Object.defineProperty(Field.prototype, "optional", {
204
+ get: function() {
205
+ return !this.required;
206
+ }
207
+ });
208
+
209
+ /**
210
+ * Determines whether this field uses tag-delimited encoding. In proto2 this
211
+ * corresponded to group syntax.
212
+ * @name Field#delimited
213
+ * @type {boolean}
214
+ * @readonly
215
+ */
216
+ Object.defineProperty(Field.prototype, "delimited", {
217
+ get: function() {
218
+ return this.resolvedType instanceof Type &&
219
+ this._features.message_encoding === "DELIMITED";
220
+ }
221
+ });
222
+
223
+ /**
224
+ * Determines whether this field is packed. Only relevant when repeated.
202
225
  * @name Field#packed
203
226
  * @type {boolean}
204
227
  * @readonly
205
228
  */
206
229
  Object.defineProperty(Field.prototype, "packed", {
207
230
  get: function() {
208
- // defaults to packed=true if not explicity set to false
209
- if (this._packed === null)
210
- this._packed = this.getOption("packed") !== false;
211
- return this._packed;
231
+ return this._features.repeated_field_encoding === "PACKED";
232
+ }
233
+ });
234
+
235
+ /**
236
+ * Determines whether this field tracks presence.
237
+ * @name Field#hasPresence
238
+ * @type {boolean}
239
+ * @readonly
240
+ */
241
+ Object.defineProperty(Field.prototype, "hasPresence", {
242
+ get: function() {
243
+ if (this.repeated || this.map) {
244
+ return false;
245
+ }
246
+ return this.partOf || // oneofs
247
+ this.declaringField || this.extensionField || // extensions
248
+ this._features.field_presence !== "IMPLICIT";
212
249
  }
213
250
  });
214
251
 
@@ -216,8 +253,6 @@ Object.defineProperty(Field.prototype, "packed", {
216
253
  * @override
217
254
  */
218
255
  Field.prototype.setOption = function setOption(name, value, ifNotSet) {
219
- if (name === "packed") // clear cached before setting
220
- this._packed = null;
221
256
  return ReflectionObject.prototype.setOption.call(this, name, value, ifNotSet);
222
257
  };
223
258
 
@@ -245,6 +280,7 @@ Field.prototype.setOption = function setOption(name, value, ifNotSet) {
245
280
  Field.prototype.toJSON = function toJSON(toJSONOptions) {
246
281
  var keepComments = toJSONOptions ? Boolean(toJSONOptions.keepComments) : false;
247
282
  return util.toObject([
283
+ "edition" , this._editionToJSON(),
248
284
  "rule" , this.rule !== "optional" && this.rule || undefined,
249
285
  "type" , this.type,
250
286
  "id" , this.id,
@@ -284,7 +320,7 @@ Field.prototype.resolve = function resolve() {
284
320
 
285
321
  // remove unnecessary options
286
322
  if (this.options) {
287
- if (this.options.packed === true || this.options.packed !== undefined && this.resolvedType && !(this.resolvedType instanceof Enum))
323
+ if (this.options.packed !== undefined && this.resolvedType && !(this.resolvedType instanceof Enum))
288
324
  delete this.options.packed;
289
325
  if (!Object.keys(this.options).length)
290
326
  this.options = undefined;
@@ -322,6 +358,46 @@ Field.prototype.resolve = function resolve() {
322
358
  return ReflectionObject.prototype.resolve.call(this);
323
359
  };
324
360
 
361
+ /**
362
+ * Infers field features from legacy syntax that may have been specified differently.
363
+ * in older editions.
364
+ * @param {string|undefined} edition The edition this proto is on, or undefined if pre-editions
365
+ * @returns {object} The feature values to override
366
+ */
367
+ Field.prototype._inferLegacyProtoFeatures = function _inferLegacyProtoFeatures(edition) {
368
+ if (edition !== "proto2" && edition !== "proto3") {
369
+ return {};
370
+ }
371
+
372
+ var features = {};
373
+
374
+ if (this.rule === "required") {
375
+ features.field_presence = "LEGACY_REQUIRED";
376
+ }
377
+ if (this.parent && types.defaults[this.type] === undefined) {
378
+ // We can't use resolvedType because types may not have been resolved yet. However,
379
+ // legacy groups are always in the same scope as the field so we don't have to do a
380
+ // full scan of the tree.
381
+ var type = this.parent.get(this.type.split(".").pop());
382
+ if (type && type instanceof Type && type.group) {
383
+ features.message_encoding = "DELIMITED";
384
+ }
385
+ }
386
+ if (this.getOption("packed") === true) {
387
+ features.repeated_field_encoding = "PACKED";
388
+ } else if (this.getOption("packed") === false) {
389
+ features.repeated_field_encoding = "EXPANDED";
390
+ }
391
+ return features;
392
+ };
393
+
394
+ /**
395
+ * @override
396
+ */
397
+ Field.prototype._resolveFeatures = function _resolveFeatures(edition) {
398
+ return ReflectionObject.prototype._resolveFeatures.call(this, this._edition || edition);
399
+ };
400
+
325
401
  /**
326
402
  * Decorator function as returned by {@link Field.d} and {@link MapField.d} (TypeScript).
327
403
  * @typedef FieldDecorator
package/src/namespace.js CHANGED
@@ -108,10 +108,33 @@ function Namespace(name, options) {
108
108
  * @private
109
109
  */
110
110
  this._nestedArray = null;
111
+
112
+ /**
113
+ * Cache lookup calls for any objects contains anywhere under this namespace.
114
+ * This drastically speeds up resolve for large cross-linked protos where the same
115
+ * types are looked up repeatedly.
116
+ * @type {Object.<string,ReflectionObject|null>}
117
+ * @private
118
+ */
119
+ this._lookupCache = {};
120
+
121
+ /**
122
+ * Whether or not objects contained in this namespace need feature resolution.
123
+ * @type {boolean}
124
+ * @protected
125
+ */
126
+ this._needsRecursiveFeatureResolution = true;
111
127
  }
112
128
 
113
129
  function clearCache(namespace) {
114
130
  namespace._nestedArray = null;
131
+ namespace._lookupCache = {};
132
+
133
+ // Also clear parent caches, since they include nested lookups.
134
+ var parent = namespace;
135
+ while(parent = parent.parent) {
136
+ parent._lookupCache = {};
137
+ }
115
138
  return namespace;
116
139
  }
117
140
 
@@ -240,6 +263,23 @@ Namespace.prototype.add = function add(object) {
240
263
  }
241
264
  }
242
265
  this.nested[object.name] = object;
266
+
267
+ if (!(this instanceof Type || this instanceof Service || this instanceof Enum || this instanceof Field)) {
268
+ // This is a package or a root namespace.
269
+ if (!object._edition) {
270
+ // Make sure that some edition is set if it hasn't already been specified.
271
+ object._edition = object._defaultEdition;
272
+ }
273
+ }
274
+
275
+ this._needsRecursiveFeatureResolution = true;
276
+
277
+ // Also clear parent caches, since they need to recurse down.
278
+ var parent = this;
279
+ while(parent = parent.parent) {
280
+ parent._needsRecursiveFeatureResolution = true;
281
+ }
282
+
243
283
  object.onAdd(this);
244
284
  return clearCache(this);
245
285
  };
@@ -302,12 +342,29 @@ Namespace.prototype.define = function define(path, json) {
302
342
  */
303
343
  Namespace.prototype.resolveAll = function resolveAll() {
304
344
  var nested = this.nestedArray, i = 0;
345
+ this.resolve();
305
346
  while (i < nested.length)
306
347
  if (nested[i] instanceof Namespace)
307
348
  nested[i++].resolveAll();
308
349
  else
309
350
  nested[i++].resolve();
310
- return this.resolve();
351
+ return this;
352
+ };
353
+
354
+ /**
355
+ * @override
356
+ */
357
+ Namespace.prototype._resolveFeaturesRecursive = function _resolveFeaturesRecursive(edition) {
358
+ if (!this._needsRecursiveFeatureResolution) return this;
359
+ this._needsRecursiveFeatureResolution = false;
360
+
361
+ edition = this._edition || edition;
362
+
363
+ ReflectionObject.prototype._resolveFeaturesRecursive.call(this, edition);
364
+ this.nestedArray.forEach(nested => {
365
+ nested._resolveFeaturesRecursive(edition);
366
+ });
367
+ return this;
311
368
  };
312
369
 
313
370
  /**
@@ -318,7 +375,6 @@ Namespace.prototype.resolveAll = function resolveAll() {
318
375
  * @returns {ReflectionObject|null} Looked up object or `null` if none could be found
319
376
  */
320
377
  Namespace.prototype.lookup = function lookup(path, filterTypes, parentAlreadyChecked) {
321
-
322
378
  /* istanbul ignore next */
323
379
  if (typeof filterTypes === "boolean") {
324
380
  parentAlreadyChecked = filterTypes;
@@ -337,25 +393,48 @@ Namespace.prototype.lookup = function lookup(path, filterTypes, parentAlreadyChe
337
393
  if (path[0] === "")
338
394
  return this.root.lookup(path.slice(1), filterTypes);
339
395
 
396
+ var found = this._lookupImpl(path);
397
+ if (found && (!filterTypes || filterTypes.indexOf(found.constructor) > -1)) {
398
+ return found;
399
+ }
400
+
401
+ // If there hasn't been a match, try again at the parent
402
+ if (this.parent === null || parentAlreadyChecked)
403
+ return null;
404
+ return this.parent.lookup(path, filterTypes);
405
+ };
406
+
407
+ /**
408
+ * Internal helper for lookup that handles searching just at this namespace and below along with caching.
409
+ * @param {string[]} path Path to look up
410
+ * @returns {ReflectionObject|null} Looked up object or `null` if none could be found
411
+ * @private
412
+ */
413
+ Namespace.prototype._lookupImpl = function lookup(path) {
414
+ var flatPath = path.join(".");
415
+ if(Object.prototype.hasOwnProperty.call(this._lookupCache, flatPath)) {
416
+ return this._lookupCache[flatPath];
417
+ }
418
+
340
419
  // Test if the first part matches any nested object, and if so, traverse if path contains more
341
420
  var found = this.get(path[0]);
421
+ var exact = null;
342
422
  if (found) {
343
423
  if (path.length === 1) {
344
- if (!filterTypes || filterTypes.indexOf(found.constructor) > -1)
345
- return found;
346
- } else if (found instanceof Namespace && (found = found.lookup(path.slice(1), filterTypes, true)))
347
- return found;
424
+ exact = found;
425
+ } else if (found instanceof Namespace && (found = found._lookupImpl(path.slice(1))))
426
+ exact = found;
348
427
 
349
428
  // Otherwise try each nested namespace
350
- } else
429
+ } else {
351
430
  for (var i = 0; i < this.nestedArray.length; ++i)
352
- if (this._nestedArray[i] instanceof Namespace && (found = this._nestedArray[i].lookup(path, filterTypes, true)))
353
- return found;
431
+ if (this._nestedArray[i] instanceof Namespace && (found = this._nestedArray[i]._lookupImpl(path)))
432
+ exact = found;
433
+ }
354
434
 
355
- // If there hasn't been a match, try again at the parent
356
- if (this.parent === null || parentAlreadyChecked)
357
- return null;
358
- return this.parent.lookup(path, filterTypes);
435
+ // Set this even when null, so that when we walk up the tree we can quickly bail on repeated checks back down.
436
+ this._lookupCache[flatPath] = exact;
437
+ return exact;
359
438
  };
360
439
 
361
440
  /**