Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 68b5339

Browse filesBrowse files
committed
feat: add feature resolution and tests
1 parent 8dabd5e commit 68b5339
Copy full SHA for 68b5339

File tree

Expand file treeCollapse file tree

8 files changed

+237
-79
lines changed
Filter options
Expand file treeCollapse file tree

8 files changed

+237
-79
lines changed

‎src/enum.js

Copy file name to clipboardExpand all lines: src/enum.js
+24-14Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ var Namespace = require("./namespace"),
2020
* @param {Object.<string,string>} [comments] The value comments for this enum
2121
* @param {Object.<string,Object<string,*>>|undefined} [valuesOptions] The value options for this enum
2222
*/
23-
function Enum(name, values, options, comment, comments, valuesOptions, valuesFeatures) {
23+
function Enum(name, values, options, comment, comments, valuesOptions) {
2424
ReflectionObject.call(this, name, options);
2525

2626
if (values && typeof values !== "object")
@@ -56,11 +56,7 @@ function Enum(name, values, options, comment, comments, valuesOptions, valuesFea
5656
*/
5757
this.valuesOptions = valuesOptions;
5858

59-
/**
60-
* Values features, if any
61-
* @type {Object<string, Object<string, *>>|undefined}
62-
*/
63-
this.valuesFeatures = valuesFeatures;
59+
this._valuesFeatures = {};
6460

6561
/**
6662
* Reserved ranges, if any.
@@ -125,7 +121,7 @@ Enum.prototype.toJSON = function toJSON(toJSONOptions) {
125121
* @throws {TypeError} If arguments are invalid
126122
* @throws {Error} If there is already a value with this name or id
127123
*/
128-
Enum.prototype.add = function add(name, id, comment, options, features) {
124+
Enum.prototype.add = function add(name, id, comment, options) {
129125
// utilized by the parser but not by .fromJSON
130126

131127
if (!util.isString(name))
@@ -154,14 +150,30 @@ Enum.prototype.add = function add(name, id, comment, options, features) {
154150
if (this.valuesOptions === undefined)
155151
this.valuesOptions = {};
156152
this.valuesOptions[name] = options || null;
157-
}
158153

159-
if (features) {
160-
if (this.valuesFeatures === undefined)
161-
this.valuesFeatures = {};
162-
this.valuesFeatures[name] = features || null;
154+
// console.log(options)
155+
// if (/features/.test(options)) {
156+
// var features = Object.keys(this.valuesOptions).find(x => {return x.hasOwnProperty("features")});
157+
// this._valuesFeatures[name] = features ?? {};
158+
// console.log(this._valuesFeatures[name])
159+
// }
160+
161+
console.log(this.valuesOptions)
162+
for (var key in this.valuesOptions) {
163+
console.log(this.valuesOptions[key])
164+
var features = Array.isArray(this.valuesOptions) ? this.valuesOptions[key].find(x => {return x.hasOwnProperty("features")}) : this.valuesOptions[key] === "features";
165+
if (features) {
166+
if (!this._valuesFeatures) {
167+
this._valuesFeatures = {};
168+
}
169+
this._valuesFeatures[key] = features.features || {};
170+
}
171+
}
163172
}
164173

174+
175+
// console.log(this.valuesOptions)
176+
165177
this.comments[name] = comment || null;
166178
return this;
167179
};
@@ -188,8 +200,6 @@ Enum.prototype.remove = function remove(name) {
188200
if (this.valuesOptions)
189201
delete this.valuesOptions[name];
190202

191-
if (this.valuesFeatures)
192-
delete this.valuesFeatures[name];
193203
return this;
194204
};
195205

‎src/object.js

Copy file name to clipboardExpand all lines: src/object.js
+21-6Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ function ReflectionObject(name, options) {
4444
/**
4545
* Resolved Features.
4646
*/
47-
this.features = null;
47+
this._features = null;
4848

4949
/**
5050
* Parent namespace.
@@ -151,11 +151,19 @@ ReflectionObject.prototype.onRemove = function onRemove(parent) {
151151
ReflectionObject.prototype.resolve = function resolve() {
152152
if (this.resolved)
153153
return this;
154-
if (this.root instanceof Root)
155-
this.resolved = true; // only if part of a root
154+
this._resolveFeatures();
155+
// if (this.root instanceof Root)
156+
this.resolved = true;
156157
return this;
157158
};
158159

160+
ReflectionObject.prototype._resolveFeatures = function _resolveFeatures() {
161+
this._features = {...this.parent?._features ?? {}, ...this._features};
162+
if (this.parent) {
163+
this.parent._resolveFeatures();
164+
}
165+
}
166+
159167
/**
160168
* Gets an option value.
161169
* @param {string} name Option name
@@ -202,6 +210,7 @@ ReflectionObject.prototype.setParsedOption = function setParsedOption(name, valu
202210
if (!this.parsedOptions) {
203211
this.parsedOptions = [];
204212
}
213+
var isFeature = /features/.test(name);
205214
var parsedOptions = this.parsedOptions;
206215
if (propName) {
207216
// If setting a sub property of an option then try to merge it
@@ -211,12 +220,13 @@ ReflectionObject.prototype.setParsedOption = function setParsedOption(name, valu
211220
});
212221
if (opt) {
213222
// If we found an existing option - just merge the property value
223+
// (If it's a feature, will just write over)
214224
var newValue = opt[name];
215-
util.setProperty(newValue, propName, value);
225+
util.setProperty(newValue, propName, value, isFeature);
216226
} else {
217-
// otherwise, create a new option, set it's property and add it to the list
227+
// otherwise, create a new option, set its property and add it to the list
218228
opt = {};
219-
opt[name] = util.setProperty({}, propName, value);
229+
opt[name] = util.setProperty({}, propName, value, isFeature);
220230
parsedOptions.push(opt);
221231
}
222232
} else {
@@ -225,6 +235,11 @@ ReflectionObject.prototype.setParsedOption = function setParsedOption(name, valu
225235
newOpt[name] = value;
226236
parsedOptions.push(newOpt);
227237
}
238+
239+
if (isFeature) {
240+
var features = parsedOptions.find(x => {return x.hasOwnProperty("features")});
241+
this._features = features.features ?? {};
242+
}
228243
return this;
229244
};
230245

‎src/parse.js

Copy file name to clipboardExpand all lines: src/parse.js
+82-33Lines changed: 82 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ var tokenize = require("./tokenize"),
1313
Enum = require("./enum"),
1414
Service = require("./service"),
1515
Method = require("./method"),
16+
ReflectionObject = require("./object"),
1617
types = require("./types"),
1718
util = require("./util");
1819

@@ -26,7 +27,11 @@ var base10Re = /^[1-9][0-9]*$/,
2627
nameRe = /^[a-zA-Z_][a-zA-Z_0-9]*$/,
2728
typeRefRe = /^(?:\.?[a-zA-Z_][a-zA-Z_0-9]*)(?:\.[a-zA-Z_][a-zA-Z_0-9]*)*$/,
2829
fqTypeRefRe = /^(?:\.[a-zA-Z_][a-zA-Z_0-9]*)+$/,
29-
featuresRefRe = /features\.([a-zA-Z_]*)/;
30+
featuresTypeRefRe = /^(features)(.*)/;
31+
32+
var editions2023Defaults = {features: {enum_type: 'OPEN', field_presence: 'EXPLICIT', json_format: 'ALLOW', message_encoding: 'LENGTH_PREFIXED', repeated_field_encoding: 'PACKED', utf8_validation: 'VERIFY'}}
33+
var proto2Defaults = {features: {enum_type: 'CLOSED', field_presence: 'EXPLICIT', json_format: 'LEGACY_BEST_EFFORT', message_encoding: 'LENGTH_PREFIXED', repeated_field_encoding: 'EXPANDED', utf8_validation: 'NONE'}}
34+
var proto3Defaults = {features: {enum_type: 'OPEN', field_presence: 'IMPLICIT', json_format: 'ALLOW', message_encoding: 'LENGTH_PREFIXED', repeated_field_encoding: 'PACKED', utf8_validation: 'VERIFY'}}
3035

3136
/**
3237
* Result object returned from {@link parse}.
@@ -269,6 +274,16 @@ function parse(source, root, options) {
269274
// Otherwise the meaning is ambiguous between proto2 and proto3
270275
root.setOption("syntax", syntax);
271276

277+
if (isProto3) {
278+
for (var key of Object.keys(proto3Defaults)) {
279+
setParsedOption(root, key, proto3Defaults[key])
280+
}
281+
} else {
282+
for (var key of Object.keys(proto2Defaults)) {
283+
setParsedOption(root, key, proto2Defaults[key])
284+
}
285+
}
286+
272287
skip(";");
273288
}
274289

@@ -283,6 +298,9 @@ function parse(source, root, options) {
283298

284299
root.setOption("edition", edition);
285300

301+
for (var key of Object.keys(editions2023Defaults)) {
302+
setParsedOption(root, key, editions2023Defaults[key])
303+
}
286304
skip(";");
287305
}
288306

@@ -355,13 +373,17 @@ function parse(source, root, options) {
355373

356374
case "required":
357375
case "repeated":
376+
if (edition)
377+
throw illegal(token)
358378
parseField(type, token);
359379
break;
360380

361381
case "optional":
362382
/* istanbul ignore if */
363383
if (isProto3) {
364384
parseField(type, "proto3_optional");
385+
} else if (edition) {
386+
throw illegal(token);
365387
} else {
366388
parseField(type, "optional");
367389
}
@@ -416,6 +438,7 @@ function parse(source, root, options) {
416438
var name = next();
417439

418440
/* istanbul ignore if */
441+
419442
if (!nameRe.test(name))
420443
throw illegal(name, "name");
421444

@@ -605,13 +628,43 @@ function parse(source, root, options) {
605628
dummy.setOption = function(name, value) {
606629
if (this.options === undefined)
607630
this.options = {};
631+
608632
this.options[name] = value;
609633
};
610-
dummy.setFeature = function(name, value) {
611-
if (this.features === undefined)
612-
this.features = {};
613-
this.features[name] = value;
614-
};
634+
dummy.setParsedOption = function(name, value, propName) {
635+
if (!this.parsedOptions) {
636+
this.parsedOptions = [];
637+
}
638+
var parsedOptions = this.parsedOptions;
639+
if (propName) {
640+
// If setting a sub property of an option then try to merge it
641+
// with an existing option
642+
var opt = parsedOptions.find(function (opt) {
643+
return Object.prototype.hasOwnProperty.call(opt, name);
644+
});
645+
if (opt) {
646+
// If we found an existing option - just merge the property value
647+
// (If it's a feature, will just write over)
648+
var newValue = opt[name];
649+
util.setProperty(newValue, propName, value);
650+
} else {
651+
// otherwise, create a new option, set its property and add it to the list
652+
opt = {};
653+
opt[name] = util.setProperty({}, propName, value);
654+
parsedOptions.push(opt);
655+
}
656+
} else {
657+
// Always create a new option when setting the value of the option itself
658+
var newOpt = {};
659+
newOpt[name] = value;
660+
parsedOptions.push(newOpt);
661+
}
662+
663+
if (/features/.test(name)) {
664+
var features = parsedOptions.find(x => {return x.hasOwnProperty("features")});
665+
this._features = features.features || {};
666+
}
667+
}
615668
ifBlock(dummy, function parseEnumValue_block(token) {
616669

617670
/* istanbul ignore else */
@@ -624,48 +677,52 @@ function parse(source, root, options) {
624677
}, function parseEnumValue_line() {
625678
parseInlineOptions(dummy); // skip
626679
});
627-
parent.add(token, value, dummy.comment, dummy.options, dummy.features);
680+
parent.add(token, value, dummy.comment, dummy.parsedOptions);
628681
}
629682

630683
function parseOption(parent, token) {
631-
// console.log(featuresRefRe.test(token = next()))
632-
if (featuresRefRe.test(peek())) {
684+
var name;
685+
var option;
686+
var optionValue;
687+
var propName;
688+
// The two logic branches below are parallel tracks, but with different regexes for the following use cases:
689+
// features expects: option features.abc.amazing_feature = A;
690+
// custom options expects: option (mo_single_msg).nested.value = "x";
691+
if (featuresTypeRefRe.test(peek())) {
633692
var token = next();
634-
var name = token.match(featuresRefRe)[1]
635-
skip("=");
636-
setFeature(parent, name, token = next())
693+
name = token;
694+
option = token.match(featuresTypeRefRe)[1];
695+
var propNameWithPeriod = token.match(featuresTypeRefRe)[2];
696+
if (fqTypeRefRe.test(propNameWithPeriod)) {
697+
propName = propNameWithPeriod.slice(1); //remove '.' before property name
698+
}
637699
} else {
638700
var isCustom = skip("(", true);
639701
if (!typeRefRe.test(token = next()))
640702
throw illegal(token, "name");
641703

642704

643-
var name = token;
644-
var option = name;
645-
var propName;
705+
name = token;
706+
option = name;
646707

647708
if (isCustom) {
648709
skip(")");
649710
name = "(" + name + ")";
650711
option = name;
651712
token = peek();
652-
console.log('in custom?'+token)
653713
if (fqTypeRefRe.test(token)) {
654714
propName = token.slice(1); //remove '.' before property name
655715
name += token;
656716
next();
657717
}
658718
}
659-
660-
console.log(token)
719+
}
661720
skip("=");
662721
var optionValue = parseOptionValue(parent, name);
663722
setParsedOption(parent, option, optionValue, propName);
664-
}
665723
}
666724

667725
function parseOptionValue(parent, name) {
668-
// { a: "foo" b { c: "bar" } }
669726
if (skip("{", true)) {
670727
var objectResult = {};
671728

@@ -683,12 +740,9 @@ function parse(source, root, options) {
683740

684741
skip(":", true);
685742

686-
if (peek() === "{")
743+
if (peek() === "{") {
687744
value = parseOptionValue(parent, name + "." + token);
688-
else if (peek() === "[") {
689-
// option (my_option) = {
690-
// repeated_value: [ "foo", "bar" ]
691-
// };
745+
} else if (peek() === "[") {
692746
value = [];
693747
var lastValue;
694748
if (skip("[", true)) {
@@ -732,12 +786,6 @@ function parse(source, root, options) {
732786
parent.setOption(name, value);
733787
}
734788

735-
function setFeature(parent, name, value) {
736-
if (parent.setFeature) {
737-
parent.setFeature(name, value);
738-
}
739-
}
740-
741789
function setParsedOption(parent, name, value, propName) {
742790
if (parent.setParsedOption)
743791
parent.setParsedOption(name, value, propName);
@@ -761,8 +809,9 @@ function parse(source, root, options) {
761809

762810
var service = new Service(token);
763811
ifBlock(service, function parseService_block(token) {
764-
if (parseCommon(service, token))
812+
if (parseCommon(service, token)) {
765813
return;
814+
}
766815

767816
/* istanbul ignore else */
768817
if (token === "rpc")
@@ -849,7 +898,7 @@ function parse(source, root, options) {
849898

850899
default:
851900
/* istanbul ignore if */
852-
if (!isProto3 || !typeRefRe.test(token))
901+
if ((!isProto3 && !edition) || !typeRefRe.test(token))
853902
throw illegal(token);
854903
push(token);
855904
parseField(parent, "optional", reference);

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.