Skip to content

Commit c23a9df

Browse files
authored
[json_syntax_generator] Support string regex in map keys (#2443)
Follow up of #2441. This time adding support for regexes on the strings that are the keys of maps. In order to avoid duplicating: https://github.com/dart-lang/pub/blob/c3e50919d11896f014cb971e1776d00a0e2d18b3/lib/src/pubspec_parse.dart#L219-L226 Again, tested on the pubspec lock schema.
1 parent ee47c28 commit c23a9df

File tree

11 files changed

+522
-70
lines changed

11 files changed

+522
-70
lines changed

pkgs/code_assets/lib/src/code_assets/syntax.g.dart

Lines changed: 77 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1052,24 +1052,47 @@ class JsonReader {
10521052
return result;
10531053
}
10541054

1055-
Map<String, T> map$<T extends Object?>(String key) =>
1056-
_castMap<T>(get<Map<String, Object?>>(key), key);
1055+
Map<String, T> map$<T extends Object?>(String key, {RegExp? keyPattern}) {
1056+
final map = get<Map<String, Object?>>(key);
1057+
final keyErrors = _validateMapKeys(map, key, keyPattern: keyPattern);
1058+
if (keyErrors.isNotEmpty) {
1059+
throw FormatException(keyErrors.join('\n'));
1060+
}
1061+
return _castMap<T>(map, key);
1062+
}
10571063

1058-
List<String> validateMap<T extends Object?>(String key) {
1064+
List<String> validateMap<T extends Object?>(
1065+
String key, {
1066+
RegExp? keyPattern,
1067+
}) {
10591068
final mapErrors = validate<Map<String, Object?>>(key);
10601069
if (mapErrors.isNotEmpty) {
10611070
return mapErrors;
10621071
}
1063-
return _validateMapElements<T>(get<Map<String, Object?>>(key), key);
1072+
final map = get<Map<String, Object?>>(key);
1073+
return [
1074+
..._validateMapKeys(map, key, keyPattern: keyPattern),
1075+
..._validateMapElements<T>(map, key),
1076+
];
10641077
}
10651078

1066-
Map<String, T>? optionalMap<T extends Object?>(String key) =>
1067-
switch (get<Map<String, Object?>?>(key)) {
1068-
null => null,
1069-
final m => _castMap<T>(m, key),
1070-
};
1079+
Map<String, T>? optionalMap<T extends Object?>(
1080+
String key, {
1081+
RegExp? keyPattern,
1082+
}) {
1083+
final map = get<Map<String, Object?>?>(key);
1084+
if (map == null) return null;
1085+
final keyErrors = _validateMapKeys(map, key, keyPattern: keyPattern);
1086+
if (keyErrors.isNotEmpty) {
1087+
throw FormatException(keyErrors.join('\n'));
1088+
}
1089+
return _castMap<T>(map, key);
1090+
}
10711091

1072-
List<String> validateOptionalMap<T extends Object?>(String key) {
1092+
List<String> validateOptionalMap<T extends Object?>(
1093+
String key, {
1094+
RegExp? keyPattern,
1095+
}) {
10731096
final mapErrors = validate<Map<String, Object?>?>(key);
10741097
if (mapErrors.isNotEmpty) {
10751098
return mapErrors;
@@ -1078,7 +1101,10 @@ class JsonReader {
10781101
if (map == null) {
10791102
return [];
10801103
}
1081-
return _validateMapElements<T>(map, key);
1104+
return [
1105+
..._validateMapKeys(map, key, keyPattern: keyPattern),
1106+
..._validateMapElements<T>(map, key),
1107+
];
10821108
}
10831109

10841110
/// [Map.cast] but with [FormatException]s.
@@ -1094,6 +1120,23 @@ class JsonReader {
10941120
return map_.cast();
10951121
}
10961122

1123+
List<String> _validateMapKeys(
1124+
Map<String, Object?> map_,
1125+
String parentKey, {
1126+
required RegExp? keyPattern,
1127+
}) {
1128+
if (keyPattern == null) return [];
1129+
final result = <String>[];
1130+
for (final key in map_.keys) {
1131+
if (!keyPattern.hasMatch(key)) {
1132+
result.add(
1133+
keyErrorString(key, pattern: keyPattern, pathExtension: [parentKey]),
1134+
);
1135+
}
1136+
}
1137+
return result;
1138+
}
1139+
10971140
List<String> _validateMapElements<T extends Object?>(
10981141
Map<String, Object?> map_,
10991142
String parentKey,
@@ -1198,6 +1241,16 @@ class JsonReader {
11981241
' Expected a $expectedType$satisfying.';
11991242
}
12001243

1244+
String keyErrorString(
1245+
String key, {
1246+
required RegExp pattern,
1247+
List<Object> pathExtension = const [],
1248+
}) {
1249+
final pathString = _jsonPathToString(pathExtension);
1250+
return "Unexpected key '$key' in '$pathString'."
1251+
' Expected a key satisfying ${pattern.pattern}.';
1252+
}
1253+
12011254
/// Traverses a JSON path, returns `null` if the path cannot be traversed.
12021255
Object? tryTraverse(List<String> path) {
12031256
Object? json = this.json;
@@ -1236,3 +1289,16 @@ extension<K extends Comparable<K>, V extends Object?> on Map<K, V> {
12361289
addAll(result);
12371290
}
12381291
}
1292+
1293+
void _checkArgumentMapKeys(Map<String, Object?>? map, {RegExp? keyPattern}) {
1294+
if (map == null) return;
1295+
for (final key in map.keys) {
1296+
if (keyPattern != null && !keyPattern.hasMatch(key)) {
1297+
throw ArgumentError.value(
1298+
map,
1299+
"Unexpected key '$key'."
1300+
' Expected a key satisfying ${keyPattern.pattern}.',
1301+
);
1302+
}
1303+
}
1304+
}

pkgs/data_assets/lib/src/data_assets/syntax.g.dart

Lines changed: 77 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -241,24 +241,47 @@ class JsonReader {
241241
return result;
242242
}
243243

244-
Map<String, T> map$<T extends Object?>(String key) =>
245-
_castMap<T>(get<Map<String, Object?>>(key), key);
244+
Map<String, T> map$<T extends Object?>(String key, {RegExp? keyPattern}) {
245+
final map = get<Map<String, Object?>>(key);
246+
final keyErrors = _validateMapKeys(map, key, keyPattern: keyPattern);
247+
if (keyErrors.isNotEmpty) {
248+
throw FormatException(keyErrors.join('\n'));
249+
}
250+
return _castMap<T>(map, key);
251+
}
246252

247-
List<String> validateMap<T extends Object?>(String key) {
253+
List<String> validateMap<T extends Object?>(
254+
String key, {
255+
RegExp? keyPattern,
256+
}) {
248257
final mapErrors = validate<Map<String, Object?>>(key);
249258
if (mapErrors.isNotEmpty) {
250259
return mapErrors;
251260
}
252-
return _validateMapElements<T>(get<Map<String, Object?>>(key), key);
261+
final map = get<Map<String, Object?>>(key);
262+
return [
263+
..._validateMapKeys(map, key, keyPattern: keyPattern),
264+
..._validateMapElements<T>(map, key),
265+
];
253266
}
254267

255-
Map<String, T>? optionalMap<T extends Object?>(String key) =>
256-
switch (get<Map<String, Object?>?>(key)) {
257-
null => null,
258-
final m => _castMap<T>(m, key),
259-
};
268+
Map<String, T>? optionalMap<T extends Object?>(
269+
String key, {
270+
RegExp? keyPattern,
271+
}) {
272+
final map = get<Map<String, Object?>?>(key);
273+
if (map == null) return null;
274+
final keyErrors = _validateMapKeys(map, key, keyPattern: keyPattern);
275+
if (keyErrors.isNotEmpty) {
276+
throw FormatException(keyErrors.join('\n'));
277+
}
278+
return _castMap<T>(map, key);
279+
}
260280

261-
List<String> validateOptionalMap<T extends Object?>(String key) {
281+
List<String> validateOptionalMap<T extends Object?>(
282+
String key, {
283+
RegExp? keyPattern,
284+
}) {
262285
final mapErrors = validate<Map<String, Object?>?>(key);
263286
if (mapErrors.isNotEmpty) {
264287
return mapErrors;
@@ -267,7 +290,10 @@ class JsonReader {
267290
if (map == null) {
268291
return [];
269292
}
270-
return _validateMapElements<T>(map, key);
293+
return [
294+
..._validateMapKeys(map, key, keyPattern: keyPattern),
295+
..._validateMapElements<T>(map, key),
296+
];
271297
}
272298

273299
/// [Map.cast] but with [FormatException]s.
@@ -283,6 +309,23 @@ class JsonReader {
283309
return map_.cast();
284310
}
285311

312+
List<String> _validateMapKeys(
313+
Map<String, Object?> map_,
314+
String parentKey, {
315+
required RegExp? keyPattern,
316+
}) {
317+
if (keyPattern == null) return [];
318+
final result = <String>[];
319+
for (final key in map_.keys) {
320+
if (!keyPattern.hasMatch(key)) {
321+
result.add(
322+
keyErrorString(key, pattern: keyPattern, pathExtension: [parentKey]),
323+
);
324+
}
325+
}
326+
return result;
327+
}
328+
286329
List<String> _validateMapElements<T extends Object?>(
287330
Map<String, Object?> map_,
288331
String parentKey,
@@ -387,6 +430,16 @@ class JsonReader {
387430
' Expected a $expectedType$satisfying.';
388431
}
389432

433+
String keyErrorString(
434+
String key, {
435+
required RegExp pattern,
436+
List<Object> pathExtension = const [],
437+
}) {
438+
final pathString = _jsonPathToString(pathExtension);
439+
return "Unexpected key '$key' in '$pathString'."
440+
' Expected a key satisfying ${pattern.pattern}.';
441+
}
442+
390443
/// Traverses a JSON path, returns `null` if the path cannot be traversed.
391444
Object? tryTraverse(List<String> path) {
392445
Object? json = this.json;
@@ -425,3 +478,16 @@ extension<K extends Comparable<K>, V extends Object?> on Map<K, V> {
425478
addAll(result);
426479
}
427480
}
481+
482+
void _checkArgumentMapKeys(Map<String, Object?>? map, {RegExp? keyPattern}) {
483+
if (map == null) return;
484+
for (final key in map.keys) {
485+
if (keyPattern != null && !keyPattern.hasMatch(key)) {
486+
throw ArgumentError.value(
487+
map,
488+
"Unexpected key '$key'."
489+
' Expected a key satisfying ${keyPattern.pattern}.',
490+
);
491+
}
492+
}
493+
}

pkgs/hooks/lib/src/hooks/syntax.g.dart

Lines changed: 79 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,7 @@ class BuildInputSyntax extends HookInputSyntax {
165165
}
166166

167167
set _assets(Map<String, List<AssetSyntax>>? value) {
168+
_checkArgumentMapKeys(value);
168169
if (value == null) {
169170
json.remove('assets');
170171
} else {
@@ -292,6 +293,7 @@ class BuildOutputSyntax extends HookOutputSyntax {
292293
}
293294

294295
set assetsForLinking(Map<String, List<AssetSyntax>>? value) {
296+
_checkArgumentMapKeys(value);
295297
if (value == null) {
296298
json.remove('assets_for_linking');
297299
} else {
@@ -1102,24 +1104,47 @@ class JsonReader {
11021104
return result;
11031105
}
11041106

1105-
Map<String, T> map$<T extends Object?>(String key) =>
1106-
_castMap<T>(get<Map<String, Object?>>(key), key);
1107+
Map<String, T> map$<T extends Object?>(String key, {RegExp? keyPattern}) {
1108+
final map = get<Map<String, Object?>>(key);
1109+
final keyErrors = _validateMapKeys(map, key, keyPattern: keyPattern);
1110+
if (keyErrors.isNotEmpty) {
1111+
throw FormatException(keyErrors.join('\n'));
1112+
}
1113+
return _castMap<T>(map, key);
1114+
}
11071115

1108-
List<String> validateMap<T extends Object?>(String key) {
1116+
List<String> validateMap<T extends Object?>(
1117+
String key, {
1118+
RegExp? keyPattern,
1119+
}) {
11091120
final mapErrors = validate<Map<String, Object?>>(key);
11101121
if (mapErrors.isNotEmpty) {
11111122
return mapErrors;
11121123
}
1113-
return _validateMapElements<T>(get<Map<String, Object?>>(key), key);
1124+
final map = get<Map<String, Object?>>(key);
1125+
return [
1126+
..._validateMapKeys(map, key, keyPattern: keyPattern),
1127+
..._validateMapElements<T>(map, key),
1128+
];
11141129
}
11151130

1116-
Map<String, T>? optionalMap<T extends Object?>(String key) =>
1117-
switch (get<Map<String, Object?>?>(key)) {
1118-
null => null,
1119-
final m => _castMap<T>(m, key),
1120-
};
1131+
Map<String, T>? optionalMap<T extends Object?>(
1132+
String key, {
1133+
RegExp? keyPattern,
1134+
}) {
1135+
final map = get<Map<String, Object?>?>(key);
1136+
if (map == null) return null;
1137+
final keyErrors = _validateMapKeys(map, key, keyPattern: keyPattern);
1138+
if (keyErrors.isNotEmpty) {
1139+
throw FormatException(keyErrors.join('\n'));
1140+
}
1141+
return _castMap<T>(map, key);
1142+
}
11211143

1122-
List<String> validateOptionalMap<T extends Object?>(String key) {
1144+
List<String> validateOptionalMap<T extends Object?>(
1145+
String key, {
1146+
RegExp? keyPattern,
1147+
}) {
11231148
final mapErrors = validate<Map<String, Object?>?>(key);
11241149
if (mapErrors.isNotEmpty) {
11251150
return mapErrors;
@@ -1128,7 +1153,10 @@ class JsonReader {
11281153
if (map == null) {
11291154
return [];
11301155
}
1131-
return _validateMapElements<T>(map, key);
1156+
return [
1157+
..._validateMapKeys(map, key, keyPattern: keyPattern),
1158+
..._validateMapElements<T>(map, key),
1159+
];
11321160
}
11331161

11341162
/// [Map.cast] but with [FormatException]s.
@@ -1144,6 +1172,23 @@ class JsonReader {
11441172
return map_.cast();
11451173
}
11461174

1175+
List<String> _validateMapKeys(
1176+
Map<String, Object?> map_,
1177+
String parentKey, {
1178+
required RegExp? keyPattern,
1179+
}) {
1180+
if (keyPattern == null) return [];
1181+
final result = <String>[];
1182+
for (final key in map_.keys) {
1183+
if (!keyPattern.hasMatch(key)) {
1184+
result.add(
1185+
keyErrorString(key, pattern: keyPattern, pathExtension: [parentKey]),
1186+
);
1187+
}
1188+
}
1189+
return result;
1190+
}
1191+
11471192
List<String> _validateMapElements<T extends Object?>(
11481193
Map<String, Object?> map_,
11491194
String parentKey,
@@ -1248,6 +1293,16 @@ class JsonReader {
12481293
' Expected a $expectedType$satisfying.';
12491294
}
12501295

1296+
String keyErrorString(
1297+
String key, {
1298+
required RegExp pattern,
1299+
List<Object> pathExtension = const [],
1300+
}) {
1301+
final pathString = _jsonPathToString(pathExtension);
1302+
return "Unexpected key '$key' in '$pathString'."
1303+
' Expected a key satisfying ${pattern.pattern}.';
1304+
}
1305+
12511306
/// Traverses a JSON path, returns `null` if the path cannot be traversed.
12521307
Object? tryTraverse(List<String> path) {
12531308
Object? json = this.json;
@@ -1286,3 +1341,16 @@ extension<K extends Comparable<K>, V extends Object?> on Map<K, V> {
12861341
addAll(result);
12871342
}
12881343
}
1344+
1345+
void _checkArgumentMapKeys(Map<String, Object?>? map, {RegExp? keyPattern}) {
1346+
if (map == null) return;
1347+
for (final key in map.keys) {
1348+
if (keyPattern != null && !keyPattern.hasMatch(key)) {
1349+
throw ArgumentError.value(
1350+
map,
1351+
"Unexpected key '$key'."
1352+
' Expected a key satisfying ${keyPattern.pattern}.',
1353+
);
1354+
}
1355+
}
1356+
}

0 commit comments

Comments
 (0)