Skip to content

Commit 9c5a178

Browse files
Fix file metadata problem by storing edition/features on top-level objects
1 parent b2c6867 commit 9c5a178

13 files changed

+351
-72
lines changed

cli/targets/proto.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -330,7 +330,7 @@ function buildOptions(object, ignore = []) {
330330
return;
331331
first = true;
332332
Object.keys(object.options).forEach(function(key) {
333-
if (ignore.includes(key) || key.startsWith("features.")) return;
333+
if (ignore.includes(key) || key.startsWith("features.") || key === "edition") return;
334334
if (first) {
335335
first = false;
336336
push("");

index.d.ts

+39-10
Original file line numberDiff line numberDiff line change
@@ -279,9 +279,24 @@ export class Field extends FieldBase {
279279
*/
280280
public static fromJSON(name: string, json: IField): Field;
281281

282-
/** Determines whether this field is packed. Only relevant when repeated and working with proto2. */
282+
/** Determines whether this field is required. */
283+
public readonly required: boolean;
284+
285+
/** Determines whether this field is not required. */
286+
public readonly optional: boolean;
287+
288+
/**
289+
* Determines whether this field uses tag-delimited encoding. In proto2 this
290+
* corresponded to group syntax.
291+
*/
292+
public readonly delimited: boolean;
293+
294+
/** Determines whether this field is packed. Only relevant when repeated. */
283295
public readonly packed: boolean;
284296

297+
/** Determines whether this field tracks presence. */
298+
public readonly hasPresence: boolean;
299+
285300
/**
286301
* Field decorator (TypeScript).
287302
* @param fieldId Field id
@@ -326,12 +341,6 @@ export class FieldBase extends ReflectionObject {
326341
/** Extended type if different from parent. */
327342
public extend?: string;
328343

329-
/** Whether this field is required. */
330-
public required: boolean;
331-
332-
/** Whether this field is optional. */
333-
public optional: boolean;
334-
335344
/** Whether this field is repeated. */
336345
public repeated: boolean;
337346

@@ -381,6 +390,14 @@ export class FieldBase extends ReflectionObject {
381390
* @throws {Error} If any reference cannot be resolved
382391
*/
383392
public resolve(): Field;
393+
394+
/**
395+
* Infers field features from legacy syntax that may have been specified differently.
396+
* in older editions.
397+
* @param edition The edition this proto is on, or undefined if pre-editions
398+
* @returns The feature values to override
399+
*/
400+
public _inferLegacyProtoFeatures(edition: (string|undefined)): object;
384401
}
385402

386403
/** Field descriptor. */
@@ -940,6 +957,14 @@ export abstract class ReflectionObject {
940957
/** Resolves child features from parent features */
941958
public _resolveFeatures(): void;
942959

960+
/**
961+
* Infers features from legacy syntax that may have been specified differently.
962+
* in older editions.
963+
* @param edition The edition this proto is on, or undefined if pre-editions
964+
* @returns The feature values to override
965+
*/
966+
public _inferLegacyProtoFeatures(edition: (string|undefined)): object;
967+
943968
/**
944969
* Gets an option value.
945970
* @param name Option name
@@ -1031,6 +1056,13 @@ export class OneOf extends ReflectionObject {
10311056
*/
10321057
public remove(field: Field): OneOf;
10331058

1059+
/**
1060+
* Determines whether this field corresponds to a synthetic oneof created for
1061+
* a proto3 optional field. No behavioral logic should depend on this, but it
1062+
* can be relevant for reflection.
1063+
*/
1064+
public readonly isProto3Optional: boolean;
1065+
10341066
/**
10351067
* OneOf decorator (TypeScript).
10361068
* @param fieldNames Field names
@@ -1076,9 +1108,6 @@ export interface IParserResult {
10761108
/** Weak imports, if any */
10771109
weakImports: (string[]|undefined);
10781110

1079-
/** Syntax, if specified (either `"proto2"` or `"proto3"`) */
1080-
syntax: (string|undefined);
1081-
10821111
/** Populated root instance */
10831112
root: Root;
10841113
}

src/enum.js

+10-7
Original file line numberDiff line numberDiff line change
@@ -85,21 +85,20 @@ function Enum(name, values, options, comment, comments, valuesOptions) {
8585
}
8686

8787
/**
88-
* Resolves value features
89-
* @returns {Enum} `this`
88+
* @override
9089
*/
91-
Enum.prototype.resolve = function resolve() {
92-
ReflectionObject.prototype.resolve.call(this);
90+
Enum.prototype._resolveFeatures = function _resolveFeatures(edition) {
91+
var edition = this._edition || edition;
92+
ReflectionObject.prototype._resolveFeatures.call(this, edition);
9393

94-
for (var key of Object.keys(this._valuesProtoFeatures)) {
94+
Object.keys(this._valuesProtoFeatures).forEach(key => {
9595
var parentFeaturesCopy = Object.assign({}, this._features);
9696
this._valuesFeatures[key] = Object.assign(parentFeaturesCopy, this._valuesProtoFeatures[key] || {});
97-
}
97+
});
9898

9999
return this;
100100
};
101101

102-
103102
/**
104103
* Enum descriptor.
105104
* @interface IEnum
@@ -117,6 +116,9 @@ Enum.prototype.resolve = function resolve() {
117116
Enum.fromJSON = function fromJSON(name, json) {
118117
var enm = new Enum(name, json.values, json.options, json.comment, json.comments);
119118
enm.reserved = json.reserved;
119+
if (json.edition)
120+
enm._edition = json.edition;
121+
enm._defaultEdition = "proto3"; // For backwards-compatibility.
120122
return enm;
121123
};
122124

@@ -128,6 +130,7 @@ Enum.fromJSON = function fromJSON(name, json) {
128130
Enum.prototype.toJSON = function toJSON(toJSONOptions) {
129131
var keepComments = toJSONOptions ? Boolean(toJSONOptions.keepComments) : false;
130132
return util.toObject([
133+
"edition" , this._editionToJSON(),
131134
"options" , this.options,
132135
"valuesOptions" , this.valuesOptions,
133136
"values" , this.values,

src/field.js

+17-2
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,11 @@ var ruleRe = /^required|optional|repeated$/;
3535
* @throws {TypeError} If arguments are invalid
3636
*/
3737
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;
3943
};
4044

4145
/**
@@ -276,6 +280,7 @@ Field.prototype.setOption = function setOption(name, value, ifNotSet) {
276280
Field.prototype.toJSON = function toJSON(toJSONOptions) {
277281
var keepComments = toJSONOptions ? Boolean(toJSONOptions.keepComments) : false;
278282
return util.toObject([
283+
"edition" , this._editionToJSON(),
279284
"rule" , this.rule !== "optional" && this.rule || undefined,
280285
"type" , this.type,
281286
"id" , this.id,
@@ -360,9 +365,12 @@ Field.prototype.resolve = function resolve() {
360365
* @returns {object} The feature values to override
361366
*/
362367
Field.prototype._inferLegacyProtoFeatures = function _inferLegacyProtoFeatures(edition) {
363-
if (edition) return {};
368+
if (edition !== "proto2" && edition !== "proto3") {
369+
return;
370+
}
364371

365372
var features = {};
373+
this.resolve();
366374
if (this.rule === "required") {
367375
features.field_presence = "LEGACY_REQUIRED";
368376
}
@@ -377,6 +385,13 @@ Field.prototype._inferLegacyProtoFeatures = function _inferLegacyProtoFeatures(e
377385
return features;
378386
};
379387

388+
/**
389+
* @override
390+
*/
391+
Field.prototype._resolveFeatures = function _resolveFeatures(edition) {
392+
return ReflectionObject.prototype._resolveFeatures.call(this, this._edition || edition);
393+
};
394+
380395
/**
381396
* Decorator function as returned by {@link Field.d} and {@link MapField.d} (TypeScript).
382397
* @typedef FieldDecorator

src/namespace.js

+22
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,15 @@ Namespace.prototype.add = function add(object) {
240240
}
241241
}
242242
this.nested[object.name] = object;
243+
244+
if (!(this instanceof Type || this instanceof Service || this instanceof Enum || this instanceof Field)) {
245+
// This is a package or a root namespace.
246+
if (!object._edition) {
247+
// Make sure that some edition is set if it hasn't already been specified.
248+
object._edition = object._defaultEdition;
249+
}
250+
}
251+
243252
object.onAdd(this);
244253
return clearCache(this);
245254
};
@@ -311,6 +320,19 @@ Namespace.prototype.resolveAll = function resolveAll() {
311320
return this;
312321
};
313322

323+
/**
324+
* @override
325+
*/
326+
Namespace.prototype._resolveFeaturesRecursive = function _resolveFeaturesRecursive(edition) {
327+
var edition = this._edition || edition;
328+
329+
ReflectionObject.prototype._resolveFeaturesRecursive.call(this, edition);
330+
this.nestedArray.forEach(nested => {
331+
nested._resolveFeaturesRecursive(edition);
332+
});
333+
return this;
334+
};
335+
314336
/**
315337
* Recursively looks up the reflection object matching the specified path in the scope of this namespace.
316338
* @param {string|string[]} path Path to look up

src/object.js

+57-7
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,31 @@ function ReflectionObject(name, options) {
4848
*/
4949
this.name = name;
5050

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+
5164
/**
5265
* Resolved Features.
66+
* @type {object}
5367
*/
5468
this._features = {};
5569

70+
/**
71+
* Whether or not features have been resolved.
72+
* @type {boolean}
73+
*/
74+
this._featuresResolved = false;
75+
5676
/**
5777
* Unresolved Features.
5878
*/
@@ -163,25 +183,41 @@ ReflectionObject.prototype.onRemove = function onRemove(parent) {
163183
ReflectionObject.prototype.resolve = function resolve() {
164184
if (this.resolved)
165185
return this;
166-
var edition = this.getOption("edition");
167-
if ((this instanceof Namespace && edition) || (this.parent && this.parent.resolved)) {
168-
this._resolveFeatures();
186+
if (this instanceof Root) {
187+
this._resolveFeaturesRecursive(this._edition);
169188
this.resolved = true;
170189
}
171190
return this;
172191
};
173192

193+
/**
194+
* Resolves this objects editions features.
195+
* @param {string} edition The edition we're currently resolving for.
196+
* @returns {ReflectionObject} `this`
197+
*/
198+
ReflectionObject.prototype._resolveFeaturesRecursive = function _resolveFeaturesRecursive(edition) {
199+
return this._resolveFeatures(this._edition || edition);
200+
};
201+
174202
/**
175203
* Resolves child features from parent features
204+
* @param {string} edition The edition we're currently resolving for.
176205
* @returns {undefined}
177206
*/
178-
ReflectionObject.prototype._resolveFeatures = function _resolveFeatures() {
207+
ReflectionObject.prototype._resolveFeatures = function _resolveFeatures(edition) {
208+
if (this._featuresResolved) {
209+
return;
210+
}
211+
179212
var defaults = {};
180213

214+
if (!edition) {
215+
throw new Error("Unknown edition for " + this.fullName);
216+
}
217+
181218
var protoFeatures = Object.assign(Object.assign({}, this._protoFeatures), this._inferLegacyProtoFeatures(edition));
182219

183-
var edition = this.getOption("edition");
184-
if (this instanceof Namespace && edition) {
220+
if (this._edition) {
185221
// For a namespace marked with a specific edition, reset defaults.
186222
if (edition === "proto2") {
187223
defaults = Object.assign({}, proto2Defaults);
@@ -212,15 +248,16 @@ ReflectionObject.prototype._resolveFeatures = function _resolveFeatures() {
212248
if (this.extensionField) {
213249
// Sister fields should have the same features as their extensions.
214250
this.extensionField._features = this._features;
251+
this.extensionField._featuresResolved = true;
215252
}
253+
this._featuresResolved = true;
216254
};
217255

218256
/**
219257
* Infers features from legacy syntax that may have been specified differently.
220258
* in older editions.
221259
* @param {string|undefined} edition The edition this proto is on, or undefined if pre-editions
222260
* @returns {object} The feature values to override
223-
* @abstract
224261
*/
225262
ReflectionObject.prototype._inferLegacyProtoFeatures = function _inferLegacyProtoFeatures(/*edition*/) {
226263
return {};
@@ -323,6 +360,19 @@ ReflectionObject.prototype.toString = function toString() {
323360
return className;
324361
};
325362

363+
/**
364+
* Converts the edition this object is pinned to for JSON format.
365+
* @returns {string|undefined} The edition string for JSON representation
366+
*/
367+
ReflectionObject.prototype._editionToJSON = function _editionToJSON() {
368+
if (!this._edition || this._edition === "proto3") {
369+
// Avoid emitting proto3 since we need to default to it for backwards
370+
// compatibility anyway.
371+
return undefined;
372+
}
373+
return this._edition;
374+
}
375+
326376
// Sets up cyclic dependencies (called in index-light)
327377
ReflectionObject._configure = function(Root_, Namespace_) {
328378
Root = Root_;

0 commit comments

Comments
 (0)