Index: lib/file_generator.dart |
diff --git a/lib/file_generator.dart b/lib/file_generator.dart |
index 65cba47953dcc0339f235c9dafccf6333952c871..7fca1d9b825076f86ba806af280849fd553a5bf0 100644 |
--- a/lib/file_generator.dart |
+++ b/lib/file_generator.dart |
@@ -4,23 +4,86 @@ |
part of protoc; |
+final _dartIdentifier = new RegExp(r'^\w+$'); |
+ |
/// Generates the Dart output files for one .proto input file. |
/// |
/// Outputs include .pb.dart, pbenum.dart, and .pbjson.dart. |
class FileGenerator extends ProtobufContainer { |
- /// Returns the the mixin to use by default in this file, |
- /// or null for no mixin by default. |
- static PbMixin _getDefaultMixin(FileDescriptorProto desc) { |
- if (!desc.hasOptions()) return null; |
- if (!desc.options.hasExtension(Dart_options.defaultMixin)) { |
- return null; |
- } |
- var name = desc.options.getExtension(Dart_options.defaultMixin); |
- PbMixin mixin = findMixin(name); |
- if (mixin == null) { |
- throw ("unknown mixin class: ${name}"); |
- } |
- return mixin; |
+ /// Reads and the declared mixins in the file, keyed by name. |
+ /// |
+ /// Performs some basic validation on declared mixins, e.g. whether names |
+ /// are valid dart identifiers and whether there are cycles in the `parent` |
+ /// hierarchy. |
+ /// Does not check for existence of import files or classes. |
+ static Map<String, PbMixin> _getDeclaredMixins(FileDescriptorProto desc) { |
+ String mixinError(String error) => |
+ 'Option "mixins" in file ${desc.name}: $error'; |
+ |
+ final dartMixins = <String, DartMixin>{}; |
skybrian
2016/06/23 19:01:42
can move down
frederikmutzel
2016/06/27 08:44:31
Done.
|
+ if (!desc.hasOptions() || !desc.options.hasExtension(Dart_options.mixins)) { |
+ return <String, PbMixin>{}; |
+ } |
+ for (DartMixin mixin in desc.options.getExtension(Dart_options.mixins)) { |
+ if (dartMixins.containsKey(mixin.name)) { |
+ throw mixinError('Duplicate mixin name: "${mixin.name}"'); |
+ } |
+ if (!mixin.name.startsWith(_dartIdentifier)) { |
+ throw mixinError( |
+ '"${mixin.name}" is not a valid dart class identifier'); |
+ } |
+ if (mixin.hasParent() && !mixin.parent.startsWith(_dartIdentifier)) { |
+ throw mixinError('Mixin parent "${mixin.parent}" of "${mixin.name}" is ' |
+ 'not a valid dart class identifier'); |
+ } |
+ dartMixins[mixin.name] = mixin; |
+ } |
+ |
+ // Detect cycles and unknown parents. |
+ for (var mixin in dartMixins.values) { |
+ if (!mixin.hasParent()) continue; |
+ var currentMixin = mixin; |
+ var parentChain = <String>[]; |
+ while (currentMixin.hasParent()) { |
+ var parentName = currentMixin.parent; |
+ |
+ bool declaredMixin = dartMixins.containsKey(parentName); |
+ bool internalMixin = !declaredMixin && findMixin(parentName) != null; |
skybrian
2016/06/23 19:01:42
Should we allow inheritance between declared and i
frederikmutzel
2016/06/27 08:44:31
I think it could make migration easier if you don'
|
+ |
+ if (internalMixin) break; // No further validation of parent chain. |
+ |
+ if (!declaredMixin) { |
+ throw mixinError('Unknown mixin parent "${mixin.parent}" of ' |
+ '"${currentMixin.name}"'); |
+ } |
+ |
+ if (parentChain.contains(parentName)) { |
+ var cycle = parentChain.join('->') + '->$parentName'; |
+ throw mixinError('Cycle in parent chain: $cycle'); |
+ } |
+ parentChain.add(parentName); |
+ currentMixin = dartMixins[parentName]; |
+ } |
+ } |
+ |
+ // Turn DartMixins into PbMixins. |
+ final pbMixins = <String, PbMixin>{}; |
+ PbMixin resolveMixin(String name) { |
+ if (pbMixins.containsKey(name)) return pbMixins[name]; |
+ if (dartMixins.containsKey(name)) { |
+ var dartMixin = dartMixins[name]; |
+ var pbMixin = new PbMixin(dartMixin.name, |
+ importFrom: dartMixin.importFrom, |
+ parent: resolveMixin(dartMixin.parent)); |
+ pbMixins[name] = pbMixin; |
+ return pbMixin; |
+ } |
+ return findMixin(name); |
+ } |
+ for (var mixin in dartMixins.values) { |
+ resolveMixin(mixin.name); |
+ } |
+ return pbMixins; |
} |
final FileDescriptorProto _fileDescriptor; |
@@ -45,15 +108,23 @@ class FileGenerator extends ProtobufContainer { |
throw "FAILURE: Import with absolute path is not supported"; |
} |
- var defaultMixin = _getDefaultMixin(_fileDescriptor); |
+ var declaredMixins = _getDeclaredMixins(descriptor); |
+ var defaultMixin = |
+ descriptor.options?.getExtension(Dart_options.defaultMixin) ?? ''; |
+ if (defaultMixin.isNotEmpty && |
+ !declaredMixins.containsKey(defaultMixin) && |
+ findMixin(defaultMixin) == null) { |
+ throw ('Option default_mixin on file ${descriptor.name}: Unknown mixin ' |
+ '$defaultMixin'); |
+ } |
// Load and register all enum and message types. |
for (EnumDescriptorProto enumType in _fileDescriptor.enumType) { |
enumGenerators.add(new EnumGenerator(enumType, this)); |
} |
for (DescriptorProto messageType in _fileDescriptor.messageType) { |
- messageGenerators |
- .add(new MessageGenerator(messageType, this, defaultMixin)); |
+ messageGenerators.add(new MessageGenerator( |
+ messageType, this, declaredMixins, defaultMixin)); |
} |
for (FieldDescriptorProto extension in _fileDescriptor.extension) { |
extensionGenerators.add(new ExtensionGenerator(extension, this)); |