Skip to content

Commit 4ee0bf2

Browse files
davidmorganlrhn
authored andcommitted
[macros] Add schema validation and schema. (#3886)
Changes to model to fit a more standard/reasonable schema.
1 parent 2bfae7f commit 4ee0bf2

File tree

13 files changed

+429
-192
lines changed

13 files changed

+429
-192
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
lib/schema.strict.json
2+
lib/schemas.dart

working/macros/dart_model/dart_model/lib/model.dart

Lines changed: 47 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import 'dart:convert';
66

77
import 'package:collection/collection.dart';
88

9-
Map<Object, Model> _roots = Map.identity();
9+
Map<Object, Uris> _uris = Map.identity();
1010
Map<Object, String> _names = Map.identity();
1111

1212
final class QualifiedName {
@@ -35,26 +35,9 @@ final class QualifiedName {
3535
}
3636

3737
extension type Model.fromJson(Map<String, Object?> node) {
38-
Model() : this.fromJson({});
38+
Model() : this.fromJson({'uris': <String, Object?>{}});
3939

40-
Iterable<String> get uris => node.keys;
41-
Iterable<Library> get libraries => node.values.cast();
42-
43-
Library? library(String uri) => node[uri] as Library?;
44-
Scope? scope(QualifiedName qualifiedName) =>
45-
library(qualifiedName.uri)?.scope(qualifiedName.name);
46-
47-
void ensure(String name) {
48-
if (node.containsKey(name)) return;
49-
add(name, Library());
50-
}
51-
52-
void add(String name, Library library) {
53-
if (node.containsKey(name)) throw ArgumentError('Already present: $name');
54-
_names[library] = name;
55-
_roots[library] = this;
56-
node[name] = library;
57-
}
40+
Uris get uris => node['uris'] as Uris;
5841

5942
bool hasPath(Path path) {
6043
if (path.path.length == 1) {
@@ -132,6 +115,27 @@ extension type Model.fromJson(Map<String, Object?> node) {
132115
String prettyPrint() => const JsonEncoder.withIndent(' ').convert(node);
133116
}
134117

118+
extension type Uris.fromJson(Map<String, Object?> node) {
119+
Iterable<String> get uris => node.keys;
120+
Iterable<Library> get libraries => node.values.cast();
121+
122+
Library? library(String uri) => node[uri] as Library?;
123+
Scope? scope(QualifiedName qualifiedName) =>
124+
library(qualifiedName.uri)?.scope(qualifiedName.name);
125+
126+
void ensure(String name) {
127+
if (node.containsKey(name)) return;
128+
add(name, Library());
129+
}
130+
131+
void add(String name, Library library) {
132+
if (node.containsKey(name)) throw ArgumentError('Already present: $name');
133+
_names[library] = name;
134+
_uris[library] = this;
135+
node[name] = library;
136+
}
137+
}
138+
135139
extension type Library.fromJson(Map<String, Object?> node) implements Object {
136140
Library() : this.fromJson({});
137141

@@ -142,7 +146,7 @@ extension type Library.fromJson(Map<String, Object?> node) implements Object {
142146

143147
void add(String name, Scope scope) {
144148
_names[scope] = name;
145-
_roots[scope] = _roots[this]!;
149+
_uris[scope] = _uris[this]!;
146150
node[name] = scope;
147151
}
148152
}
@@ -163,7 +167,10 @@ extension type Class.fromJson(Map<String, Object?> node) implements Scope {
163167
Iterable<QualifiedName>? interfaces,
164168
Map<String, Member>? members})
165169
: this.fromJson({
166-
'properties': ['class', if (abstract == true) 'abstract'],
170+
'properties': {
171+
'class': true,
172+
if (abstract != null) 'abstract': abstract
173+
},
167174
if (annotations != null) 'annotations': annotations.toList(),
168175
if (supertype != null) 'supertype': supertype.toString(),
169176
if (interfaces != null)
@@ -177,14 +184,14 @@ extension type Class.fromJson(Map<String, Object?> node) implements Scope {
177184
extension type Interface.fromJson(Map<String, Object?> node) implements Scope {
178185
String get name => _names[this]!;
179186

180-
bool get isClass => (node['properties'] as List).contains('class');
181-
bool get isAbstract => (node['properties'] as List).contains('abstract');
187+
bool get isClass => (node['properties'] as Map)['class'] as bool;
188+
bool get isAbstract => (node['properties'] as Map)['abstract'] as bool;
182189

183190
Interface? get supertype {
184191
if (!node.containsKey('supertype')) return null;
185192
final name = QualifiedName.tryParse(node['supertype'] as String);
186193
if (name == null) return null;
187-
return _roots[this]!.scope(name)?.asInterface;
194+
return _uris[this]!.scope(name)?.asInterface;
188195
}
189196

190197
List<Annotation> get annotations => (node['annotations'] as List).cast();
@@ -195,7 +202,7 @@ extension type Interface.fromJson(Map<String, Object?> node) implements Scope {
195202
final result = <Interface>[];
196203
for (final interface in (node['interfaces'] as List).cast<String>()) {
197204
final name = QualifiedName.tryParse(interface)!;
198-
result.add(_roots[this]!.scope(name)!.asInterface!);
205+
result.add(_uris[this]!.scope(name)!.asInterface!);
199206
}
200207
return result;
201208
}
@@ -218,24 +225,24 @@ extension type Member.fromJson(Map<String, Object?> node) {
218225
required bool static,
219226
required bool synthetic})
220227
: this.fromJson({
221-
'properties': [
222-
if (abstract) 'abstract',
223-
if (getter) 'getter',
224-
if (method) 'method',
225-
if (field) 'field',
226-
if (static) 'static',
227-
if (synthetic) 'synthetic'
228-
]
228+
'properties': {
229+
'abstract': abstract,
230+
'getter': getter,
231+
'method': method,
232+
'field': field,
233+
'static': static,
234+
'synthetic': synthetic,
235+
}
229236
});
230237

231238
String get name => _names[this]!;
232239

233-
bool get isAbstract => (node['properties'] as List).contains('abstract');
234-
bool get isField => (node['properties'] as List).contains('field');
235-
bool get isGetter => (node['properties'] as List).contains('getter');
236-
bool get isMethod => (node['properties'] as List).contains('method');
237-
bool get isStatic => (node['properties'] as List).contains('static');
238-
bool get isSynthetic => (node['properties'] as List).contains('synthetic');
240+
bool get isAbstract => (node['properties'] as Map)['abstract'] as bool;
241+
bool get isField => (node['properties'] as Map)['field'] as bool;
242+
bool get isGetter => (node['properties'] as Map)['getter'] as bool;
243+
bool get isMethod => (node['properties'] as Map)['method'] as bool;
244+
bool get isStatic => (node['properties'] as Map)['static'] as bool;
245+
bool get isSynthetic => (node['properties'] as Map)['synthetic'] as bool;
239246
}
240247

241248
extension type Annotation.fromJson(Map<String, Object?> node) {

working/macros/dart_model/dart_model/lib/query.dart

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,14 @@ extension type Query.fromJson(Map<String, Object?> node) {
1111
Query.uri(String uri)
1212
: this(operations: [
1313
Operation.include([
14-
Path([uri])
14+
Path(['uris', uri])
1515
])
1616
]);
1717

1818
Query.qualifiedName({required String uri, required String name})
1919
: this(operations: [
2020
Operation.include([
21-
Path([uri, name])
21+
Path(['uris', uri, name])
2222
])
2323
]);
2424

@@ -52,12 +52,12 @@ extension type Query.fromJson(Map<String, Object?> node) {
5252

5353
// TODO(davidmorgan): implement properly.
5454
String get firstUri =>
55-
operations.firstWhere((o) => o.isInclude).paths[0].path.first;
55+
operations.firstWhere((o) => o.isInclude).paths[0].path[1];
5656

5757
String? get firstName {
5858
final operation = operations.firstWhere((o) => o.isInclude);
5959
final path = operation.paths[0];
60-
return path.path.length > 1 ? path.path[1] : null;
60+
return path.path.length > 2 ? path.path[2] : null;
6161
}
6262
}
6363

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
{
2+
"$id": "https://github.com/dart-lang/language/blob/main/working/macros/dart_model/schema/dart_model.schema.json",
3+
"$schema": "https://json-schema.org/draft/2020-12/schema",
4+
"title": "Model",
5+
"type": "object",
6+
"properties": {
7+
"uris": {
8+
"type": "object",
9+
"description": "Libraries by URI.",
10+
"additionalProperties": {
11+
"type": "object",
12+
"description": "Scopes by name.",
13+
"additionalProperties": {"$ref": "#/$defs/Interface"}
14+
}
15+
}
16+
},
17+
"$defs": {
18+
"Annotation": {
19+
"type": "object",
20+
"description": "An annotation.",
21+
"properties": {
22+
"type": {"$ref": "#/$defs/Type"},
23+
"value": {
24+
"type": "object",
25+
"additionalProperties": true
26+
}
27+
}
28+
},
29+
"Interface": {
30+
"type": "object",
31+
"description": "An interface.",
32+
"properties": {
33+
"annotations": {
34+
"type": "array",
35+
"items": {"$ref": "#/$defs/Annotation"}
36+
},
37+
"interfaces": {
38+
"type": "array",
39+
"description": "Array of interfaces implemented.",
40+
"items": {"$ref": "#/$defs/Type"}
41+
},
42+
"members": {
43+
"type": "object",
44+
"description": "Map of members by name.",
45+
"additionalProperties": {"$ref": "#/$defs/Member"}
46+
},
47+
"properties": {"$ref": "#/$defs/Properties"},
48+
"supertype": {"$ref": "#/$defs/Type"}
49+
}
50+
},
51+
"Member": {
52+
"type": "object",
53+
"description": "Member of a scope.",
54+
"properties": {
55+
"properties": {"$ref": "#/$defs/Properties"}
56+
}
57+
},
58+
"Properties": {
59+
"type": "object",
60+
"description": "Set of boolean properties.",
61+
"properties": {
62+
"abstract": {"type": "boolean"},
63+
"class": {"type": "boolean"},
64+
"getter": {"type": "boolean"},
65+
"field": {"type": "boolean"},
66+
"method": {"type": "boolean"},
67+
"static": {"type": "boolean"},
68+
"synthetic": {"type": "boolean"}
69+
}
70+
},
71+
"Type": {
72+
"oneOf": [
73+
{"$ref": "#/$defs/QualifiedName"}
74+
]
75+
},
76+
"QualifiedName": {
77+
"type": "string"
78+
}
79+
}
80+
}

working/macros/dart_model/dart_model/test/model_test.dart

Lines changed: 32 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -8,45 +8,48 @@ import 'package:test/test.dart';
88
void main() {
99
group(Model, () {
1010
final model = Model.fromJson({
11-
'package:end_to_end_test/values.dart': {
12-
'SimpleValue': {
13-
'properties': ['abstract', 'class ', 'final'],
14-
'implements': [
15-
{
16-
'name': 'Built',
17-
'parameters': [
18-
'package:end_to_end_test/values.dart#SimpleValue',
19-
'package:end_to_end_test/values.dart#SimpleValueBuilder',
20-
]
21-
},
22-
],
23-
'members': {
24-
'serializer': {
25-
'properties': ['static', 'getter'],
26-
'returnType': {
27-
'name': 'Serializer',
11+
'uris': {
12+
'package:end_to_end_test/values.dart': {
13+
'SimpleValue': {
14+
'properties': ['abstract', 'class ', 'final'],
15+
'implements': [
16+
{
17+
'name': 'Built',
2818
'parameters': [
2919
'package:end_to_end_test/values.dart#SimpleValue',
20+
'package:end_to_end_test/values.dart#SimpleValueBuilder',
3021
]
31-
}
32-
},
33-
'anInt': {
34-
'properties': ['abstract', 'getter'],
35-
'returnType': 'dart:core#int',
36-
},
37-
'aString': {
38-
'properties': ['abstract', 'getter'],
39-
'returnType': 'dart:core#String?',
22+
},
23+
],
24+
'members': {
25+
'serializer': {
26+
'properties': ['static', 'getter'],
27+
'returnType': {
28+
'name': 'Serializer',
29+
'parameters': [
30+
'package:end_to_end_test/values.dart#SimpleValue',
31+
]
32+
}
33+
},
34+
'anInt': {
35+
'properties': ['abstract', 'getter'],
36+
'returnType': 'dart:core#int',
37+
},
38+
'aString': {
39+
'properties': ['abstract', 'getter'],
40+
'returnType': 'dart:core#String?',
41+
},
4042
},
41-
},
43+
}
4244
}
4345
}
4446
});
4547

4648
test('works as described', () {
47-
expect(model.uris, ['package:end_to_end_test/values.dart']);
49+
expect(model.uris.uris, ['package:end_to_end_test/values.dart']);
4850

49-
final library = model.library('package:end_to_end_test/values.dart')!;
51+
final library =
52+
model.uris.library('package:end_to_end_test/values.dart')!;
5053
expect(library.names, ['SimpleValue']);
5154
});
5255
});

0 commit comments

Comments
 (0)