use LazyMap in extension constructors
diff --git a/pkgs/dart_model/lib/dart_model.dart b/pkgs/dart_model/lib/dart_model.dart
index 1358b0b..be4c7bf 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 6e1e7c7..a21fd71 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<String, Object?> 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 MetadataAnnotation.fromJson(Map<String, Object?> 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 @@
List<MetadataAnnotation>? metadataAnnotations,
Map<String, Member>? 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<MetadataAnnotation> get metadataAnnotations =>
@@ -53,9 +71,14 @@
extension type Library.fromJson(Map<String, Object?> node) {
Library({
Map<String, Interface>? 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<String, Interface> get scopes => (node['scopes'] as Map).cast();
@@ -65,9 +88,14 @@
extension type Member.fromJson(Map<String, Object?> 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 Model.fromJson(Map<String, Object?> node) {
Model({
Map<String, Library>? 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<String, Library> get uris => (node['uris'] as Map).cast();
@@ -94,9 +127,14 @@
extension type NullableType.fromJson(Map<String, Object?> 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 @@
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 Query.fromJson(Map<String, Object?> 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 16a6b3c..85e1350 100644
--- a/pkgs/dart_model/lib/src/json_buffer.dart
+++ b/pkgs/dart_model/lib/src/json_buffer.dart
@@ -16,6 +16,13 @@
LazyMap(Iterable<String> 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<String> 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 9e72112..c182f30 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 @@
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 AugmentResponse.fromJson(Map<String, Object?> node) {
AugmentResponse({
List<Augmentation>? augmentations,
- }) : this.fromJson({
- if (augmentations != null) 'augmentations': augmentations,
- });
+ }) : this.fromJson(LazyMap(
+ [
+ if (augmentations != null) 'augmentations',
+ ],
+ (key) => switch (key) {
+ 'augmentations' => augmentations,
+ _ => null,
+ }));
/// The augmentations.
List<Augmentation> get augmentations =>
@@ -37,9 +48,14 @@
extension type ErrorResponse.fromJson(Map<String, Object?> 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 HostEndpoint.fromJson(Map<String, Object?> 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 @@
extension type HostRequest.fromJson(Map<String, Object?> 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 MacroDescription.fromJson(Map<String, Object?> node) {
MacroDescription({
List<int>? 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<int> get runsInPhases => (node['runsInPhases'] as List).cast();
@@ -111,16 +137,21 @@
extension type MacroStartedRequest.fromJson(Map<String, Object?> 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<String, Object?> node) {
- MacroStartedResponse() : this.fromJson({});
+ MacroStartedResponse() : this.fromJson(const LazyMap.empty());
}
enum MacroRequestType {
@@ -135,21 +166,21 @@
extension type MacroRequest.fromJson(Map<String, Object?> 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 QueryRequest.fromJson(Map<String, Object?> 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 QueryResponse.fromJson(Map<String, Object?> 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 @@
extension type Response.fromJson(Map<String, Object?> 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 2089b13..6aad0c6 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 @@
/// 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<String> directives = const [], String? dartModelJson}) {
final result = <String>[
'// 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 @@
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 @@
if (extraPropertyMetadatas.isNotEmpty) {
result.writeln(', {');
for (final property in extraPropertyMetadatas) {
- result.writeParameter(property, definition);
+ result.writeParameter(property);
}
result.write('}');
}
@@ -186,7 +188,7 @@
..writeln("'type': '$type',")
..writeln("'value': $lowerType,");
for (final property in extraPropertyMetadatas) {
- result.writeMapElement(property, definition);
+ result.writeMapElement(property);
}
result.writeln('});');
}
@@ -241,7 +243,8 @@
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 @@
// 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 @@
String name;
PropertyType type;
String? elementTypeName;
+ bool isRequired;
- PropertyMetadata(
- {this.description,
- required this.name,
- required this.type,
- this.elementTypeName});
+ PropertyMetadata({
+ this.description,
+ required this.name,
+ required this.type,
+ this.elementTypeName,
+ required this.isRequired,
+ });
}
/// Loads referenced schemas.
@@ -355,13 +370,26 @@
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 @@
///
/// 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 @@
}
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 cb989e3..277b8ba 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 @@
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')