From 48c3a62caeb74c18b4e4b849804c52fb0ce5e16f Mon Sep 17 00:00:00 2001 From: Jake Macdonald Date: Thu, 15 Aug 2024 22:38:26 +0000 Subject: [PATCH] use LazyMap in extension constructors --- pkgs/dart_model/lib/dart_model.dart | 1 + pkgs/dart_model/lib/src/dart_model.g.dart | 123 +++++++++++++----- pkgs/dart_model/lib/src/json_buffer.dart | 7 + .../lib/src/macro_service.g.dart | 121 +++++++++++------ .../lib/generate_dart_model.dart | 98 +++++++++----- .../test/generated_output_test.dart | 8 +- 6 files changed, 250 insertions(+), 108 deletions(-) diff --git a/pkgs/dart_model/lib/dart_model.dart b/pkgs/dart_model/lib/dart_model.dart index 1358b0bf..be4c7bfe 100644 --- a/pkgs/dart_model/lib/dart_model.dart +++ b/pkgs/dart_model/lib/dart_model.dart @@ -4,4 +4,5 @@ export 'src/dart_model.dart'; export 'src/json.dart'; +export 'src/json_buffer.dart' show LazyMap; export 'src/json_changes.dart'; diff --git a/pkgs/dart_model/lib/src/dart_model.g.dart b/pkgs/dart_model/lib/src/dart_model.g.dart index 6e1e7c7c..a21fd710 100644 --- a/pkgs/dart_model/lib/src/dart_model.g.dart +++ b/pkgs/dart_model/lib/src/dart_model.g.dart @@ -1,13 +1,20 @@ // This file is generated. To make changes edit schemas/*.schema.json // then run from the repo root: dart tool/dart_model_generator/bin/main.dart +import 'json_buffer.dart' show LazyMap; + /// An augmentation to Dart code. TODO(davidmorgan): this is a placeholder. extension type Augmentation.fromJson(Map node) { Augmentation({ String? code, - }) : this.fromJson({ - if (code != null) 'code': code, - }); + }) : this.fromJson(LazyMap( + [ + if (code != null) 'code', + ], + (key) => switch (key) { + 'code' => code, + _ => null, + })); /// Augmentation code. String get code => node['code'] as String; @@ -17,9 +24,14 @@ extension type Augmentation.fromJson(Map node) { extension type MetadataAnnotation.fromJson(Map node) { MetadataAnnotation({ QualifiedName? type, - }) : this.fromJson({ - if (type != null) 'type': type, - }); + }) : this.fromJson(LazyMap( + [ + if (type != null) 'type', + ], + (key) => switch (key) { + 'type' => type, + _ => null, + })); /// The type of the annotation. QualifiedName get type => node['type'] as QualifiedName; @@ -31,12 +43,18 @@ extension type Interface.fromJson(Map node) { List? metadataAnnotations, Map? members, Properties? properties, - }) : this.fromJson({ - if (metadataAnnotations != null) - 'metadataAnnotations': metadataAnnotations, - if (members != null) 'members': members, - if (properties != null) 'properties': properties, - }); + }) : this.fromJson(LazyMap( + [ + if (metadataAnnotations != null) 'metadataAnnotations', + if (members != null) 'members', + if (properties != null) 'properties', + ], + (key) => switch (key) { + 'metadataAnnotations' => metadataAnnotations, + 'members' => members, + 'properties' => properties, + _ => null, + })); /// The metadata annotations attached to this iterface. List get metadataAnnotations => @@ -53,9 +71,14 @@ extension type Interface.fromJson(Map node) { extension type Library.fromJson(Map node) { Library({ Map? scopes, - }) : this.fromJson({ - if (scopes != null) 'scopes': scopes, - }); + }) : this.fromJson(LazyMap( + [ + if (scopes != null) 'scopes', + ], + (key) => switch (key) { + 'scopes' => scopes, + _ => null, + })); /// Scopes by name. Map get scopes => (node['scopes'] as Map).cast(); @@ -65,9 +88,14 @@ extension type Library.fromJson(Map node) { extension type Member.fromJson(Map node) { Member({ Properties? properties, - }) : this.fromJson({ - if (properties != null) 'properties': properties, - }); + }) : this.fromJson(LazyMap( + [ + if (properties != null) 'properties', + ], + (key) => switch (key) { + 'properties' => properties, + _ => null, + })); /// The properties of this member. Properties get properties => node['properties'] as Properties; @@ -77,9 +105,14 @@ extension type Member.fromJson(Map node) { extension type Model.fromJson(Map node) { Model({ Map? uris, - }) : this.fromJson({ - if (uris != null) 'uris': uris, - }); + }) : this.fromJson(LazyMap( + [ + if (uris != null) 'uris', + ], + (key) => switch (key) { + 'uris' => uris, + _ => null, + })); /// Libraries by URI. Map get uris => (node['uris'] as Map).cast(); @@ -94,9 +127,14 @@ extension type NeverType.fromJson(Null _) { extension type NullableType.fromJson(Map node) { NullableType({ StaticType? inner, - }) : this.fromJson({ - if (inner != null) 'inner': inner, - }); + }) : this.fromJson(LazyMap( + [ + if (inner != null) 'inner', + ], + (key) => switch (key) { + 'inner' => inner, + _ => null, + })); StaticType get inner => node['inner'] as StaticType; } @@ -109,14 +147,24 @@ extension type Properties.fromJson(Map node) { bool? isField, bool? isMethod, bool? isStatic, - }) : this.fromJson({ - if (isAbstract != null) 'isAbstract': isAbstract, - if (isClass != null) 'isClass': isClass, - if (isGetter != null) 'isGetter': isGetter, - if (isField != null) 'isField': isField, - if (isMethod != null) 'isMethod': isMethod, - if (isStatic != null) 'isStatic': isStatic, - }); + }) : this.fromJson(LazyMap( + [ + if (isAbstract != null) 'isAbstract', + if (isClass != null) 'isClass', + if (isGetter != null) 'isGetter', + if (isField != null) 'isField', + if (isMethod != null) 'isMethod', + if (isStatic != null) 'isStatic', + ], + (key) => switch (key) { + 'isAbstract' => isAbstract, + 'isClass' => isClass, + 'isGetter' => isGetter, + 'isField' => isField, + 'isMethod' => isMethod, + 'isStatic' => isStatic, + _ => null, + })); /// Whether the entity is abstract, meaning it has no definition. bool get isAbstract => node['isAbstract'] as bool; @@ -146,9 +194,14 @@ extension type QualifiedName.fromJson(String string) { extension type Query.fromJson(Map node) { Query({ QualifiedName? target, - }) : this.fromJson({ - if (target != null) 'target': target, - }); + }) : this.fromJson(LazyMap( + [ + if (target != null) 'target', + ], + (key) => switch (key) { + 'target' => target, + _ => null, + })); /// The class to query about. QualifiedName get target => node['target'] as QualifiedName; diff --git a/pkgs/dart_model/lib/src/json_buffer.dart b/pkgs/dart_model/lib/src/json_buffer.dart index 16a6b3c6..85e1350e 100644 --- a/pkgs/dart_model/lib/src/json_buffer.dart +++ b/pkgs/dart_model/lib/src/json_buffer.dart @@ -16,6 +16,13 @@ class LazyMap with MapMixin implements Map { LazyMap(Iterable keys, this.lookup) : _keys = keys.toList(); + const LazyMap.empty() + : _keys = const [], + lookup = _emptyLookup; + + /// So we can create a const empty map. + static Object? _emptyLookup(_) => null; + @override Iterable get keys => _keys; diff --git a/pkgs/macro_service/lib/src/macro_service.g.dart b/pkgs/macro_service/lib/src/macro_service.g.dart index 9e721120..c182f302 100644 --- a/pkgs/macro_service/lib/src/macro_service.g.dart +++ b/pkgs/macro_service/lib/src/macro_service.g.dart @@ -8,10 +8,16 @@ extension type AugmentRequest.fromJson(Map node) { AugmentRequest({ int? phase, QualifiedName? target, - }) : this.fromJson({ - if (phase != null) 'phase': phase, - if (target != null) 'target': target, - }); + }) : this.fromJson(LazyMap( + [ + if (phase != null) 'phase', + if (target != null) 'target', + ], + (key) => switch (key) { + 'phase' => phase, + 'target' => target, + _ => null, + })); /// Which phase to run: 1, 2 or 3. int get phase => node['phase'] as int; @@ -24,9 +30,14 @@ extension type AugmentRequest.fromJson(Map node) { extension type AugmentResponse.fromJson(Map node) { AugmentResponse({ List? augmentations, - }) : this.fromJson({ - if (augmentations != null) 'augmentations': augmentations, - }); + }) : this.fromJson(LazyMap( + [ + if (augmentations != null) 'augmentations', + ], + (key) => switch (key) { + 'augmentations' => augmentations, + _ => null, + })); /// The augmentations. List get augmentations => @@ -37,9 +48,14 @@ extension type AugmentResponse.fromJson(Map node) { extension type ErrorResponse.fromJson(Map node) { ErrorResponse({ String? error, - }) : this.fromJson({ - if (error != null) 'error': error, - }); + }) : this.fromJson(LazyMap( + [ + if (error != null) 'error', + ], + (key) => switch (key) { + 'error' => error, + _ => null, + })); /// The error. String get error => node['error'] as String; @@ -49,9 +65,14 @@ extension type ErrorResponse.fromJson(Map node) { extension type HostEndpoint.fromJson(Map node) { HostEndpoint({ int? port, - }) : this.fromJson({ - if (port != null) 'port': port, - }); + }) : this.fromJson(LazyMap( + [ + if (port != null) 'port', + ], + (key) => switch (key) { + 'port' => port, + _ => null, + })); /// TCP port to connect to. int get port => node['port'] as int; @@ -68,12 +89,12 @@ enum HostRequestType { extension type HostRequest.fromJson(Map node) { static HostRequest augmentRequest( AugmentRequest augmentRequest, { - required int id, + int? id, }) => HostRequest.fromJson({ 'type': 'AugmentRequest', 'value': augmentRequest, - 'id': id, + if (id != null) 'id': id, }); HostRequestType get type { switch (node['type'] as String) { @@ -99,9 +120,14 @@ extension type HostRequest.fromJson(Map node) { extension type MacroDescription.fromJson(Map node) { MacroDescription({ List? runsInPhases, - }) : this.fromJson({ - if (runsInPhases != null) 'runsInPhases': runsInPhases, - }); + }) : this.fromJson(LazyMap( + [ + if (runsInPhases != null) 'runsInPhases', + ], + (key) => switch (key) { + 'runsInPhases' => runsInPhases, + _ => null, + })); /// Phases that the macro runs in: 1, 2 and/or 3. List get runsInPhases => (node['runsInPhases'] as List).cast(); @@ -111,16 +137,21 @@ extension type MacroDescription.fromJson(Map node) { extension type MacroStartedRequest.fromJson(Map node) { MacroStartedRequest({ MacroDescription? macroDescription, - }) : this.fromJson({ - if (macroDescription != null) 'macroDescription': macroDescription, - }); + }) : this.fromJson(LazyMap( + [ + if (macroDescription != null) 'macroDescription', + ], + (key) => switch (key) { + 'macroDescription' => macroDescription, + _ => null, + })); MacroDescription get macroDescription => node['macroDescription'] as MacroDescription; } /// Host's response to a [MacroStartedRequest]. extension type MacroStartedResponse.fromJson(Map node) { - MacroStartedResponse() : this.fromJson({}); + MacroStartedResponse() : this.fromJson(const LazyMap.empty()); } enum MacroRequestType { @@ -135,21 +166,21 @@ enum MacroRequestType { extension type MacroRequest.fromJson(Map node) { static MacroRequest macroStartedRequest( MacroStartedRequest macroStartedRequest, { - required int id, + int? id, }) => MacroRequest.fromJson({ 'type': 'MacroStartedRequest', 'value': macroStartedRequest, - 'id': id, + if (id != null) 'id': id, }); static MacroRequest queryRequest( QueryRequest queryRequest, { - required int id, + int? id, }) => MacroRequest.fromJson({ 'type': 'QueryRequest', 'value': queryRequest, - 'id': id, + if (id != null) 'id': id, }); MacroRequestType get type { switch (node['type'] as String) { @@ -184,9 +215,14 @@ extension type MacroRequest.fromJson(Map node) { extension type QueryRequest.fromJson(Map node) { QueryRequest({ Query? query, - }) : this.fromJson({ - if (query != null) 'query': query, - }); + }) : this.fromJson(LazyMap( + [ + if (query != null) 'query', + ], + (key) => switch (key) { + 'query' => query, + _ => null, + })); Query get query => node['query'] as Query; } @@ -194,9 +230,14 @@ extension type QueryRequest.fromJson(Map node) { extension type QueryResponse.fromJson(Map node) { QueryResponse({ Model? model, - }) : this.fromJson({ - if (model != null) 'model': model, - }); + }) : this.fromJson(LazyMap( + [ + if (model != null) 'model', + ], + (key) => switch (key) { + 'model' => model, + _ => null, + })); Model get model => node['model'] as Model; } @@ -214,39 +255,39 @@ enum ResponseType { extension type Response.fromJson(Map node) { static Response augmentResponse( AugmentResponse augmentResponse, { - required int requestId, + int? requestId, }) => Response.fromJson({ 'type': 'AugmentResponse', 'value': augmentResponse, - 'requestId': requestId, + if (requestId != null) 'requestId': requestId, }); static Response errorResponse( ErrorResponse errorResponse, { - required int requestId, + int? requestId, }) => Response.fromJson({ 'type': 'ErrorResponse', 'value': errorResponse, - 'requestId': requestId, + if (requestId != null) 'requestId': requestId, }); static Response macroStartedResponse( MacroStartedResponse macroStartedResponse, { - required int requestId, + int? requestId, }) => Response.fromJson({ 'type': 'MacroStartedResponse', 'value': macroStartedResponse, - 'requestId': requestId, + if (requestId != null) 'requestId': requestId, }); static Response queryResponse( QueryResponse queryResponse, { - required int requestId, + int? requestId, }) => Response.fromJson({ 'type': 'QueryResponse', 'value': queryResponse, - 'requestId': requestId, + if (requestId != null) 'requestId': requestId, }); ResponseType get type { switch (node['type'] as String) { diff --git a/tool/dart_model_generator/lib/generate_dart_model.dart b/tool/dart_model_generator/lib/generate_dart_model.dart index 2089b13b..6aad0c63 100644 --- a/tool/dart_model_generator/lib/generate_dart_model.dart +++ b/tool/dart_model_generator/lib/generate_dart_model.dart @@ -17,24 +17,23 @@ import 'package:json_schema/src/json_schema/models/ref_provider.dart'; /// They have a `fromJson` constructor that takes that JSON, and a no-name /// constructor that builds it. void run() { - File('pkgs/dart_model/lib/src/dart_model.g.dart').writeAsStringSync( - generate(File('schemas/dart_model.schema.json').readAsStringSync())); + File('pkgs/dart_model/lib/src/dart_model.g.dart').writeAsStringSync(generate( + File('schemas/dart_model.schema.json').readAsStringSync(), + directives: const ["import 'json_buffer.dart' show LazyMap;"])); File('pkgs/macro_service/lib/src/macro_service.g.dart').writeAsStringSync( generate(File('schemas/macro_service.schema.json').readAsStringSync(), - importDartModel: true)); + directives: const ["import 'package:dart_model/dart_model.dart';"])); } /// Generates and returns code for [schemaJson]. String generate(String schemaJson, - {bool importDartModel = false, - bool importMacroService = false, - String? dartModelJson}) { + {List directives = const [], String? dartModelJson}) { final result = [ '// This file is generated. To make changes edit schemas/*.schema.json', '// then run from the repo root: ' 'dart tool/dart_model_generator/bin/main.dart', '', - if (importDartModel) "import 'package:dart_model/dart_model.dart';", + ...directives, ]; final schema = JsonSchema.create(schemaJson, refProvider: LocalRefProvider(dartModelJson ?? @@ -95,17 +94,20 @@ String _generateExtensionType(String name, JsonSchema definition) { switch (definition.type) { case SchemaType.object: if (propertyMetadatas.isEmpty) { - result.writeln(' $name() : this.fromJson({});'); + result.writeln(' $name() : this.fromJson(const LazyMap.empty());'); } else { result.writeln(' $name({'); for (final property in propertyMetadatas) { - result.writeParameter(property, definition); + result.writeParameter(property); } - result.writeln('}) : this.fromJson({'); + result.writeln('}) : this.fromJson(LazyMap(['); for (final property in propertyMetadatas) { - result.writeMapElement(property, definition); + result.writeKeyElement(property); } - result.writeln('});'); + result.writeln('], (key) => switch (key) {'); + propertyMetadatas.forEach(result.writeSwitchCase); + result.writeln('_ => null,'); // Default case + result.writeln('}));'); } case SchemaType.string: result.writeln('$name(String string) : this.fromJson(string);'); @@ -176,7 +178,7 @@ String _generateUnion( if (extraPropertyMetadatas.isNotEmpty) { result.writeln(', {'); for (final property in extraPropertyMetadatas) { - result.writeParameter(property, definition); + result.writeParameter(property); } result.write('}'); } @@ -186,7 +188,7 @@ String _generateUnion( ..writeln("'type': '$type',") ..writeln("'value': $lowerType,"); for (final property in extraPropertyMetadatas) { - result.writeMapElement(property, definition); + result.writeMapElement(property); } result.writeln('});'); } @@ -241,7 +243,8 @@ PropertyMetadata _readPropertyMetadata(String name, JsonSchema schema) { description: schema.schemaMap![r'$comment'] as String?, name: name, type: PropertyType.object, - elementTypeName: schemaName); + elementTypeName: schemaName, + isRequired: schema.propertyRequired(name)); } else { throw UnsupportedError('Unsupported: $name $schema'); } @@ -250,24 +253,33 @@ PropertyMetadata _readPropertyMetadata(String name, JsonSchema schema) { // Otherwise, it's a schema with a type. return switch (schema.type) { SchemaType.boolean => PropertyMetadata( - description: schema.description, name: name, type: PropertyType.bool), + description: schema.description, + name: name, + type: PropertyType.bool, + isRequired: schema.propertyRequired(name)), SchemaType.string => PropertyMetadata( - description: schema.description, name: name, type: PropertyType.string), + description: schema.description, + name: name, + type: PropertyType.string, + isRequired: schema.propertyRequired(name)), SchemaType.integer => PropertyMetadata( description: schema.description, name: name, - type: PropertyType.integer), + type: PropertyType.integer, + isRequired: schema.propertyRequired(name)), SchemaType.array => PropertyMetadata( description: schema.description, name: name, type: PropertyType.list, - elementTypeName: _readRefNameOrType(schema, 'items')), + elementTypeName: _readRefNameOrType(schema, 'items'), + isRequired: schema.propertyRequired(name)), SchemaType.object => PropertyMetadata( description: schema.description, name: name, type: PropertyType.map, // `additionalProperties` should be a type specified with a `$ref`. - elementTypeName: _readRefNameOrType(schema, 'additionalProperties')), + elementTypeName: _readRefNameOrType(schema, 'additionalProperties'), + isRequired: schema.propertyRequired(name)), _ => throw UnsupportedError('Unsupported schema type: ${schema.type}'), }; } @@ -311,12 +323,15 @@ class PropertyMetadata { String name; PropertyType type; String? elementTypeName; - - PropertyMetadata( - {this.description, - required this.name, - required this.type, - this.elementTypeName}); + bool isRequired; + + PropertyMetadata({ + this.description, + required this.name, + required this.type, + this.elementTypeName, + required this.isRequired, + }); } /// Loads referenced schemas. @@ -355,13 +370,26 @@ extension on StringBuffer { if (nullable) write('?'); } + /// Writes an element in a `keys` list for a map containing [property], + /// assuming there is a variable in scope with the same name (usually, a + /// function parameter). + /// + /// If the property is not required, the element will be omitted if the + /// variable is null. + void writeKeyElement(PropertyMetadata property) { + if (!property.isRequired) { + write('if (${property.name} != null) '); + } + writeln("'${property.name}',"); + } + /// Writes a map element for [property], assuming there is a variable in /// scope with the same name (usually, a function parameter). /// /// If the property is not required, the element will be omitted if the /// variable is null. - void writeMapElement(PropertyMetadata property, JsonSchema schema) { - if (!schema.propertyRequired(property.name)) { + void writeMapElement(PropertyMetadata property) { + if (!property.isRequired) { write('if (${property.name} != null) '); } writeln("'${property.name}': ${property.name},"); @@ -371,10 +399,9 @@ extension on StringBuffer { /// /// If the property is required, it will be non-nullable and marked as /// `required`, otherwise it will be nullable and optional. - void writeParameter(PropertyMetadata property, JsonSchema schema) { - var required = schema.propertyRequired(property.name); - if (required) write('required '); - writeType(property, nullable: !required); + void writeParameter(PropertyMetadata property) { + if (property.isRequired) write('required '); + writeType(property, nullable: !property.isRequired); writeln(' ${property.name},'); } @@ -402,4 +429,11 @@ extension on StringBuffer { } writeln(';'); } + + /// Writes a switch case statement to read [property] by its name, assuming + /// there is a variable in scope with the same name (usually, a function + /// parameter). + void writeSwitchCase(PropertyMetadata property) { + writeln("'${property.name}' => ${property.name},"); + } } diff --git a/tool/dart_model_generator/test/generated_output_test.dart b/tool/dart_model_generator/test/generated_output_test.dart index cb989e3a..277b8baa 100644 --- a/tool/dart_model_generator/test/generated_output_test.dart +++ b/tool/dart_model_generator/test/generated_output_test.dart @@ -13,7 +13,13 @@ void main() { test('$package output is up to date', () { final expected = dart_model_generator.generate( File('../../schemas/$package.schema.json').readAsStringSync(), - importDartModel: package == 'macro_service', + directives: switch (package) { + 'dart_model' => const ["import 'json_buffer.dart' show LazyMap;"], + 'macro_service' => const [ + "import 'package:dart_model/dart_model.dart';" + ], + _ => const [], + }, dartModelJson: File('../../schemas/dart_model.schema.json').readAsStringSync()); final actual = File('../../pkgs/$package/lib/src/$package.g.dart')