[record_use] Generate syntax
diff --git a/pkgs/json_syntax_generator/lib/src/generator/property_generator.dart b/pkgs/json_syntax_generator/lib/src/generator/property_generator.dart
index b5094fc..c3fc90e 100644
--- a/pkgs/json_syntax_generator/lib/src/generator/property_generator.dart
+++ b/pkgs/json_syntax_generator/lib/src/generator/property_generator.dart
@@ -381,7 +381,8 @@
List<String> $validateName() => _reader.$jsonValidate('$jsonKey');
''');
default:
- throw UnimplementedError(itemType.toString());
+ // TODO(dcharkes): Implement this.
+ // throw UnimplementedError(itemType.toString());
}
}
diff --git a/pkgs/json_syntax_generator/lib/src/parser/schema_analyzer.dart b/pkgs/json_syntax_generator/lib/src/parser/schema_analyzer.dart
index 54e11e3..b034131 100644
--- a/pkgs/json_syntax_generator/lib/src/parser/schema_analyzer.dart
+++ b/pkgs/json_syntax_generator/lib/src/parser/schema_analyzer.dart
@@ -372,13 +372,28 @@
);
}
case SchemaType.object:
- final typeName = items.className!;
- _analyzeClass(items);
- final classInfo = _classes[typeName]!;
- dartType = ListDartType(
- itemType: ClassDartType(classInfo: classInfo, isNullable: false),
- isNullable: !required,
- );
+ final typeName = items.className;
+ if (typeName != null) {
+ _analyzeClass(items);
+ final classInfo = _classes[typeName]!;
+ dartType = ListDartType(
+ itemType: ClassDartType(
+ classInfo: classInfo,
+ isNullable: false,
+ ),
+ isNullable: !required,
+ );
+ } else if (items.generateMapOf) {
+ dartType = const ListDartType(
+ itemType: MapDartType(
+ valueType: ObjectDartType(isNullable: true),
+ isNullable: true,
+ ),
+ isNullable: true,
+ );
+ } else {
+ throw UnimplementedError(itemType.toString());
+ }
default:
throw UnimplementedError(itemType.toString());
}
diff --git a/pkgs/record_use/doc/schema/record_use.schema.json b/pkgs/record_use/doc/schema/record_use.schema.json
new file mode 100644
index 0000000..27ea9cc
--- /dev/null
+++ b/pkgs/record_use/doc/schema/record_use.schema.json
@@ -0,0 +1,220 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "$ref": "#/definitions/RecordedUses",
+ "definitions": {
+ "RecordedUses": {
+ "type": "object",
+ "properties": {
+ "metadata": {
+ "type": "object",
+ "properties": {
+ "version": {
+ "type": "string"
+ },
+ "comment": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "version",
+ "comment"
+ ]
+ },
+ "ids": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/Identifier"
+ }
+ },
+ "constants": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/Constant"
+ }
+ },
+ "usages": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/Usage"
+ }
+ }
+ },
+ "required": [
+ "metadata"
+ ]
+ },
+ "Identifier": {
+ "type": "object",
+ "properties": {
+ "uri": {
+ "type": "string"
+ },
+ "name": {
+ "type": "string"
+ },
+ "parent": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "uri",
+ "name"
+ ]
+ },
+ "Constant": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "String",
+ "bool",
+ "int",
+ "list",
+ "map",
+ "Null",
+ "Instance"
+ ]
+ },
+ "value": {
+ "type": [
+ "string",
+ "boolean",
+ "integer",
+ "array",
+ "object",
+ "null"
+ ]
+ }
+ },
+ "required": [
+ "type"
+ ]
+ },
+ "Instance": {
+ "type": "object",
+ "properties": {
+ "instanceConstant": {
+ "type": "integer"
+ },
+ "loadingUnit": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "instanceConstant",
+ "loadingUnit"
+ ]
+ },
+ "Arguments": {
+ "type": "object",
+ "properties": {
+ "constant": {
+ "type": "object",
+ "properties": {
+ "positional": {
+ "type": "object",
+ "additionalProperties": true
+ },
+ "named": {
+ "type": "object",
+ "additionalProperties": true
+ }
+ }
+ },
+ "nonConstant": {
+ "type": "object",
+ "properties": {
+ "positional": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "additionalProperties": true
+ }
+ },
+ "named": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "additionalProperties": true
+ }
+ }
+ }
+ }
+ }
+ },
+ "Reference": {
+ "type": "object",
+ "properties": {
+ "arguments": {
+ "$ref": "#/definitions/Arguments"
+ },
+ "loadingUnit": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "arguments",
+ "loadingUnit"
+ ]
+ },
+ "Usage": {
+ "type": "object",
+ "properties": {
+ "identifier": {
+ "type": "integer"
+ },
+ "instances": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/Instance"
+ }
+ },
+ "calls": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/Call"
+ }
+ },
+ "loadingUnit": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "identifier"
+ ]
+ },
+ "Call": {
+ "type": "object",
+ "properties": {
+ "arguments": {
+ "$ref": "#/definitions/Arguments"
+ },
+ "loadingUnit": {
+ "type": "string"
+ },
+ "definition": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "loadingUnit": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "id",
+ "loadingUnit"
+ ]
+ },
+ "references": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/Reference"
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/pkgs/record_use/lib/src/syntax.g.dart b/pkgs/record_use/lib/src/syntax.g.dart
new file mode 100644
index 0000000..7ebc52f
--- /dev/null
+++ b/pkgs/record_use/lib/src/syntax.g.dart
@@ -0,0 +1,991 @@
+// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// This file is generated, do not edit.
+// File generated by pkg/record_use/tool/generate_syntax.dart.
+
+// ignore_for_file: unused_element
+
+import 'dart:io';
+
+class Arguments {
+ final Map<String, Object?> json;
+
+ final List<Object> path;
+
+ JsonReader get _reader => JsonReader(json, path);
+
+ Arguments.fromJson(this.json, {this.path = const []});
+
+ Arguments({required Constant? constant, required NonConstant? nonConstant})
+ : json = {},
+ path = const [] {
+ _constant = constant;
+ _nonConstant = nonConstant;
+ json.sortOnKey();
+ }
+
+ Constant? get constant {
+ final jsonValue = _reader.optionalMap('constant');
+ if (jsonValue == null) return null;
+ return Constant.fromJson(jsonValue, path: [...path, 'constant']);
+ }
+
+ set _constant(Constant? value) {
+ json.setOrRemove('constant', value?.json);
+ }
+
+ List<String> _validateConstant() {
+ final mapErrors = _reader.validate<Map<String, Object?>?>('constant');
+ if (mapErrors.isNotEmpty) {
+ return mapErrors;
+ }
+ return constant?.validate() ?? [];
+ }
+
+ NonConstant? get nonConstant {
+ final jsonValue = _reader.optionalMap('nonConstant');
+ if (jsonValue == null) return null;
+ return NonConstant.fromJson(jsonValue, path: [...path, 'nonConstant']);
+ }
+
+ set _nonConstant(NonConstant? value) {
+ json.setOrRemove('nonConstant', value?.json);
+ }
+
+ List<String> _validateNonConstant() {
+ final mapErrors = _reader.validate<Map<String, Object?>?>('nonConstant');
+ if (mapErrors.isNotEmpty) {
+ return mapErrors;
+ }
+ return nonConstant?.validate() ?? [];
+ }
+
+ List<String> validate() => [
+ ..._validateConstant(),
+ ..._validateNonConstant(),
+ ];
+
+ @override
+ String toString() => 'Arguments($json)';
+}
+
+class Call {
+ final Map<String, Object?> json;
+
+ final List<Object> path;
+
+ JsonReader get _reader => JsonReader(json, path);
+
+ Call.fromJson(this.json, {this.path = const []});
+
+ Call({
+ required Arguments? arguments,
+ required Definition? definition,
+ required String? loadingUnit,
+ required List<Reference>? references,
+ }) : json = {},
+ path = const [] {
+ _arguments = arguments;
+ _definition = definition;
+ _loadingUnit = loadingUnit;
+ _references = references;
+ json.sortOnKey();
+ }
+
+ Arguments? get arguments {
+ final jsonValue = _reader.optionalMap('arguments');
+ if (jsonValue == null) return null;
+ return Arguments.fromJson(jsonValue, path: [...path, 'arguments']);
+ }
+
+ set _arguments(Arguments? value) {
+ json.setOrRemove('arguments', value?.json);
+ }
+
+ List<String> _validateArguments() {
+ final mapErrors = _reader.validate<Map<String, Object?>?>('arguments');
+ if (mapErrors.isNotEmpty) {
+ return mapErrors;
+ }
+ return arguments?.validate() ?? [];
+ }
+
+ Definition? get definition {
+ final jsonValue = _reader.optionalMap('definition');
+ if (jsonValue == null) return null;
+ return Definition.fromJson(jsonValue, path: [...path, 'definition']);
+ }
+
+ set _definition(Definition? value) {
+ json.setOrRemove('definition', value?.json);
+ }
+
+ List<String> _validateDefinition() {
+ final mapErrors = _reader.validate<Map<String, Object?>?>('definition');
+ if (mapErrors.isNotEmpty) {
+ return mapErrors;
+ }
+ return definition?.validate() ?? [];
+ }
+
+ String? get loadingUnit => _reader.get<String?>('loadingUnit');
+
+ set _loadingUnit(String? value) {
+ json.setOrRemove('loadingUnit', value);
+ }
+
+ List<String> _validateLoadingUnit() =>
+ _reader.validate<String?>('loadingUnit');
+
+ List<Reference>? get references {
+ final jsonValue = _reader.optionalList('references');
+ if (jsonValue == null) return null;
+ return [
+ for (final (index, element) in jsonValue.indexed)
+ Reference.fromJson(
+ element as Map<String, Object?>,
+ path: [...path, 'references', index],
+ ),
+ ];
+ }
+
+ set _references(List<Reference>? value) {
+ if (value == null) {
+ json.remove('references');
+ } else {
+ json['references'] = [for (final item in value) item.json];
+ }
+ }
+
+ List<String> _validateReferences() {
+ final listErrors = _reader.validateOptionalList<Map<String, Object?>>(
+ 'references',
+ );
+ if (listErrors.isNotEmpty) {
+ return listErrors;
+ }
+ final elements = references;
+ if (elements == null) {
+ return [];
+ }
+ return [for (final element in elements) ...element.validate()];
+ }
+
+ List<String> validate() => [
+ ..._validateArguments(),
+ ..._validateDefinition(),
+ ..._validateLoadingUnit(),
+ ..._validateReferences(),
+ ];
+
+ @override
+ String toString() => 'Call($json)';
+}
+
+class Constant {
+ final Map<String, Object?> json;
+
+ final List<Object> path;
+
+ JsonReader get _reader => JsonReader(json, path);
+
+ Constant.fromJson(this.json, {this.path = const []});
+
+ Constant({
+ required Map<String, Object?>? named,
+ required Map<String, Object?>? positional,
+ }) : json = {},
+ path = const [] {
+ _named = named;
+ _positional = positional;
+ json.sortOnKey();
+ }
+
+ Map<String, Object?>? get named => _reader.optionalMap('named');
+
+ set _named(Map<String, Object?>? value) {
+ json.setOrRemove('named', value);
+ }
+
+ List<String> _validateNamed() => _reader.validateOptionalMap('named');
+
+ Map<String, Object?>? get positional => _reader.optionalMap('positional');
+
+ set _positional(Map<String, Object?>? value) {
+ json.setOrRemove('positional', value);
+ }
+
+ List<String> _validatePositional() =>
+ _reader.validateOptionalMap('positional');
+
+ List<String> validate() => [..._validateNamed(), ..._validatePositional()];
+
+ @override
+ String toString() => 'Constant($json)';
+}
+
+class Definition {
+ final Map<String, Object?> json;
+
+ final List<Object> path;
+
+ JsonReader get _reader => JsonReader(json, path);
+
+ Definition.fromJson(this.json, {this.path = const []});
+
+ Definition({required int id, required String loadingUnit})
+ : json = {},
+ path = const [] {
+ _id = id;
+ _loadingUnit = loadingUnit;
+ json.sortOnKey();
+ }
+
+ int get id => _reader.get<int>('id');
+
+ set _id(int value) {
+ json.setOrRemove('id', value);
+ }
+
+ List<String> _validateId() => _reader.validate<int>('id');
+
+ String get loadingUnit => _reader.get<String>('loadingUnit');
+
+ set _loadingUnit(String value) {
+ json.setOrRemove('loadingUnit', value);
+ }
+
+ List<String> _validateLoadingUnit() =>
+ _reader.validate<String>('loadingUnit');
+
+ List<String> validate() => [..._validateId(), ..._validateLoadingUnit()];
+
+ @override
+ String toString() => 'Definition($json)';
+}
+
+class Identifier {
+ final Map<String, Object?> json;
+
+ final List<Object> path;
+
+ JsonReader get _reader => JsonReader(json, path);
+
+ Identifier.fromJson(this.json, {this.path = const []});
+
+ Identifier({
+ required String name,
+ required String? parent,
+ required String uri,
+ }) : json = {},
+ path = const [] {
+ _name = name;
+ _parent = parent;
+ _uri = uri;
+ json.sortOnKey();
+ }
+
+ String get name => _reader.get<String>('name');
+
+ set _name(String value) {
+ json.setOrRemove('name', value);
+ }
+
+ List<String> _validateName() => _reader.validate<String>('name');
+
+ String? get parent => _reader.get<String?>('parent');
+
+ set _parent(String? value) {
+ json.setOrRemove('parent', value);
+ }
+
+ List<String> _validateParent() => _reader.validate<String?>('parent');
+
+ String get uri => _reader.get<String>('uri');
+
+ set _uri(String value) {
+ json.setOrRemove('uri', value);
+ }
+
+ List<String> _validateUri() => _reader.validate<String>('uri');
+
+ List<String> validate() => [
+ ..._validateName(),
+ ..._validateParent(),
+ ..._validateUri(),
+ ];
+
+ @override
+ String toString() => 'Identifier($json)';
+}
+
+class Instance {
+ final Map<String, Object?> json;
+
+ final List<Object> path;
+
+ JsonReader get _reader => JsonReader(json, path);
+
+ Instance.fromJson(this.json, {this.path = const []});
+
+ Instance({required int instanceConstant, required String loadingUnit})
+ : json = {},
+ path = const [] {
+ _instanceConstant = instanceConstant;
+ _loadingUnit = loadingUnit;
+ json.sortOnKey();
+ }
+
+ int get instanceConstant => _reader.get<int>('instanceConstant');
+
+ set _instanceConstant(int value) {
+ json.setOrRemove('instanceConstant', value);
+ }
+
+ List<String> _validateInstanceConstant() =>
+ _reader.validate<int>('instanceConstant');
+
+ String get loadingUnit => _reader.get<String>('loadingUnit');
+
+ set _loadingUnit(String value) {
+ json.setOrRemove('loadingUnit', value);
+ }
+
+ List<String> _validateLoadingUnit() =>
+ _reader.validate<String>('loadingUnit');
+
+ List<String> validate() => [
+ ..._validateInstanceConstant(),
+ ..._validateLoadingUnit(),
+ ];
+
+ @override
+ String toString() => 'Instance($json)';
+}
+
+class Metadata {
+ final Map<String, Object?> json;
+
+ final List<Object> path;
+
+ JsonReader get _reader => JsonReader(json, path);
+
+ Metadata.fromJson(this.json, {this.path = const []});
+
+ Metadata({required String comment, required String version})
+ : json = {},
+ path = const [] {
+ _comment = comment;
+ _version = version;
+ json.sortOnKey();
+ }
+
+ String get comment => _reader.get<String>('comment');
+
+ set _comment(String value) {
+ json.setOrRemove('comment', value);
+ }
+
+ List<String> _validateComment() => _reader.validate<String>('comment');
+
+ String get version => _reader.get<String>('version');
+
+ set _version(String value) {
+ json.setOrRemove('version', value);
+ }
+
+ List<String> _validateVersion() => _reader.validate<String>('version');
+
+ List<String> validate() => [..._validateComment(), ..._validateVersion()];
+
+ @override
+ String toString() => 'Metadata($json)';
+}
+
+class NonConstant {
+ final Map<String, Object?> json;
+
+ final List<Object> path;
+
+ JsonReader get _reader => JsonReader(json, path);
+
+ NonConstant.fromJson(this.json, {this.path = const []});
+
+ NonConstant({
+ required List<Map<String, Object?>?>? named,
+ required List<Map<String, Object?>?>? positional,
+ }) : json = {},
+ path = const [] {
+ _named = named;
+ _positional = positional;
+ json.sortOnKey();
+ }
+
+ List<String> validate() => [..._validateNamed(), ..._validatePositional()];
+
+ @override
+ String toString() => 'NonConstant($json)';
+}
+
+class RecordedUses {
+ final Map<String, Object?> json;
+
+ final List<Object> path;
+
+ JsonReader get _reader => JsonReader(json, path);
+
+ RecordedUses.fromJson(this.json, {this.path = const []});
+
+ RecordedUses({
+ required List<Constant>? constants,
+ required List<Identifier>? ids,
+ required Metadata metadata,
+ required List<Usage>? usages,
+ }) : json = {},
+ path = const [] {
+ _constants = constants;
+ _ids = ids;
+ _metadata = metadata;
+ _usages = usages;
+ json.sortOnKey();
+ }
+
+ List<Constant>? get constants {
+ final jsonValue = _reader.optionalList('constants');
+ if (jsonValue == null) return null;
+ return [
+ for (final (index, element) in jsonValue.indexed)
+ Constant.fromJson(
+ element as Map<String, Object?>,
+ path: [...path, 'constants', index],
+ ),
+ ];
+ }
+
+ set _constants(List<Constant>? value) {
+ if (value == null) {
+ json.remove('constants');
+ } else {
+ json['constants'] = [for (final item in value) item.json];
+ }
+ }
+
+ List<String> _validateConstants() {
+ final listErrors = _reader.validateOptionalList<Map<String, Object?>>(
+ 'constants',
+ );
+ if (listErrors.isNotEmpty) {
+ return listErrors;
+ }
+ final elements = constants;
+ if (elements == null) {
+ return [];
+ }
+ return [for (final element in elements) ...element.validate()];
+ }
+
+ List<Identifier>? get ids {
+ final jsonValue = _reader.optionalList('ids');
+ if (jsonValue == null) return null;
+ return [
+ for (final (index, element) in jsonValue.indexed)
+ Identifier.fromJson(
+ element as Map<String, Object?>,
+ path: [...path, 'ids', index],
+ ),
+ ];
+ }
+
+ set _ids(List<Identifier>? value) {
+ if (value == null) {
+ json.remove('ids');
+ } else {
+ json['ids'] = [for (final item in value) item.json];
+ }
+ }
+
+ List<String> _validateIds() {
+ final listErrors = _reader.validateOptionalList<Map<String, Object?>>(
+ 'ids',
+ );
+ if (listErrors.isNotEmpty) {
+ return listErrors;
+ }
+ final elements = ids;
+ if (elements == null) {
+ return [];
+ }
+ return [for (final element in elements) ...element.validate()];
+ }
+
+ Metadata get metadata {
+ final jsonValue = _reader.map$('metadata');
+ return Metadata.fromJson(jsonValue, path: [...path, 'metadata']);
+ }
+
+ set _metadata(Metadata value) {
+ json['metadata'] = value.json;
+ }
+
+ List<String> _validateMetadata() {
+ final mapErrors = _reader.validate<Map<String, Object?>>('metadata');
+ if (mapErrors.isNotEmpty) {
+ return mapErrors;
+ }
+ return metadata.validate();
+ }
+
+ List<Usage>? get usages {
+ final jsonValue = _reader.optionalList('usages');
+ if (jsonValue == null) return null;
+ return [
+ for (final (index, element) in jsonValue.indexed)
+ Usage.fromJson(
+ element as Map<String, Object?>,
+ path: [...path, 'usages', index],
+ ),
+ ];
+ }
+
+ set _usages(List<Usage>? value) {
+ if (value == null) {
+ json.remove('usages');
+ } else {
+ json['usages'] = [for (final item in value) item.json];
+ }
+ }
+
+ List<String> _validateUsages() {
+ final listErrors = _reader.validateOptionalList<Map<String, Object?>>(
+ 'usages',
+ );
+ if (listErrors.isNotEmpty) {
+ return listErrors;
+ }
+ final elements = usages;
+ if (elements == null) {
+ return [];
+ }
+ return [for (final element in elements) ...element.validate()];
+ }
+
+ List<String> validate() => [
+ ..._validateConstants(),
+ ..._validateIds(),
+ ..._validateMetadata(),
+ ..._validateUsages(),
+ ];
+
+ @override
+ String toString() => 'RecordedUses($json)';
+}
+
+class Reference {
+ final Map<String, Object?> json;
+
+ final List<Object> path;
+
+ JsonReader get _reader => JsonReader(json, path);
+
+ Reference.fromJson(this.json, {this.path = const []});
+
+ Reference({required Arguments arguments, required String loadingUnit})
+ : json = {},
+ path = const [] {
+ _arguments = arguments;
+ _loadingUnit = loadingUnit;
+ json.sortOnKey();
+ }
+
+ Arguments get arguments {
+ final jsonValue = _reader.map$('arguments');
+ return Arguments.fromJson(jsonValue, path: [...path, 'arguments']);
+ }
+
+ set _arguments(Arguments value) {
+ json['arguments'] = value.json;
+ }
+
+ List<String> _validateArguments() {
+ final mapErrors = _reader.validate<Map<String, Object?>>('arguments');
+ if (mapErrors.isNotEmpty) {
+ return mapErrors;
+ }
+ return arguments.validate();
+ }
+
+ String get loadingUnit => _reader.get<String>('loadingUnit');
+
+ set _loadingUnit(String value) {
+ json.setOrRemove('loadingUnit', value);
+ }
+
+ List<String> _validateLoadingUnit() =>
+ _reader.validate<String>('loadingUnit');
+
+ List<String> validate() => [
+ ..._validateArguments(),
+ ..._validateLoadingUnit(),
+ ];
+
+ @override
+ String toString() => 'Reference($json)';
+}
+
+class Usage {
+ final Map<String, Object?> json;
+
+ final List<Object> path;
+
+ JsonReader get _reader => JsonReader(json, path);
+
+ Usage.fromJson(this.json, {this.path = const []});
+
+ Usage({
+ required List<Call>? calls,
+ required int identifier,
+ required List<Instance>? instances,
+ required String? loadingUnit,
+ }) : json = {},
+ path = const [] {
+ _calls = calls;
+ _identifier = identifier;
+ _instances = instances;
+ _loadingUnit = loadingUnit;
+ json.sortOnKey();
+ }
+
+ List<Call>? get calls {
+ final jsonValue = _reader.optionalList('calls');
+ if (jsonValue == null) return null;
+ return [
+ for (final (index, element) in jsonValue.indexed)
+ Call.fromJson(
+ element as Map<String, Object?>,
+ path: [...path, 'calls', index],
+ ),
+ ];
+ }
+
+ set _calls(List<Call>? value) {
+ if (value == null) {
+ json.remove('calls');
+ } else {
+ json['calls'] = [for (final item in value) item.json];
+ }
+ }
+
+ List<String> _validateCalls() {
+ final listErrors = _reader.validateOptionalList<Map<String, Object?>>(
+ 'calls',
+ );
+ if (listErrors.isNotEmpty) {
+ return listErrors;
+ }
+ final elements = calls;
+ if (elements == null) {
+ return [];
+ }
+ return [for (final element in elements) ...element.validate()];
+ }
+
+ int get identifier => _reader.get<int>('identifier');
+
+ set _identifier(int value) {
+ json.setOrRemove('identifier', value);
+ }
+
+ List<String> _validateIdentifier() => _reader.validate<int>('identifier');
+
+ List<Instance>? get instances {
+ final jsonValue = _reader.optionalList('instances');
+ if (jsonValue == null) return null;
+ return [
+ for (final (index, element) in jsonValue.indexed)
+ Instance.fromJson(
+ element as Map<String, Object?>,
+ path: [...path, 'instances', index],
+ ),
+ ];
+ }
+
+ set _instances(List<Instance>? value) {
+ if (value == null) {
+ json.remove('instances');
+ } else {
+ json['instances'] = [for (final item in value) item.json];
+ }
+ }
+
+ List<String> _validateInstances() {
+ final listErrors = _reader.validateOptionalList<Map<String, Object?>>(
+ 'instances',
+ );
+ if (listErrors.isNotEmpty) {
+ return listErrors;
+ }
+ final elements = instances;
+ if (elements == null) {
+ return [];
+ }
+ return [for (final element in elements) ...element.validate()];
+ }
+
+ String? get loadingUnit => _reader.get<String?>('loadingUnit');
+
+ set _loadingUnit(String? value) {
+ json.setOrRemove('loadingUnit', value);
+ }
+
+ List<String> _validateLoadingUnit() =>
+ _reader.validate<String?>('loadingUnit');
+
+ List<String> validate() => [
+ ..._validateCalls(),
+ ..._validateIdentifier(),
+ ..._validateInstances(),
+ ..._validateLoadingUnit(),
+ ];
+
+ @override
+ String toString() => 'Usage($json)';
+}
+
+class JsonReader {
+ /// The JSON Object this reader is reading.
+ final Map<String, Object?> json;
+
+ /// The path traversed by readers of the surrounding JSON.
+ ///
+ /// Contains [String] property keys and [int] indices.
+ ///
+ /// This is used to give more precise error messages.
+ final List<Object> path;
+
+ JsonReader(this.json, this.path);
+
+ T get<T extends Object?>(String key) {
+ final value = json[key];
+ if (value is T) return value;
+ throwFormatException(value, T, [key]);
+ }
+
+ List<String> validate<T extends Object?>(String key) {
+ final value = json[key];
+ if (value is T) return [];
+ return [
+ errorString(value, T, [key]),
+ ];
+ }
+
+ List<T> list<T extends Object?>(String key) =>
+ _castList<T>(get<List<Object?>>(key), key);
+
+ List<String> validateList<T extends Object?>(String key) {
+ final listErrors = validate<List<Object?>>(key);
+ if (listErrors.isNotEmpty) {
+ return listErrors;
+ }
+ return _validateListElements(get<List<Object?>>(key), key);
+ }
+
+ List<T>? optionalList<T extends Object?>(String key) =>
+ switch (get<List<Object?>?>(key)?.cast<T>()) {
+ null => null,
+ final l => _castList<T>(l, key),
+ };
+
+ List<String> validateOptionalList<T extends Object?>(String key) {
+ final listErrors = validate<List<Object?>?>(key);
+ if (listErrors.isNotEmpty) {
+ return listErrors;
+ }
+ final list = get<List<Object?>?>(key);
+ if (list == null) {
+ return [];
+ }
+ return _validateListElements(list, key);
+ }
+
+ /// [List.cast] but with [FormatException]s.
+ List<T> _castList<T extends Object?>(List<Object?> list, String key) {
+ for (final (index, value) in list.indexed) {
+ if (value is! T) {
+ throwFormatException(value, T, [key, index]);
+ }
+ }
+ return list.cast();
+ }
+
+ List<String> _validateListElements<T extends Object?>(
+ List<Object?> list,
+ String key,
+ ) {
+ final result = <String>[];
+ for (final (index, value) in list.indexed) {
+ if (value is! T) {
+ result.add(errorString(value, T, [key, index]));
+ }
+ }
+ return result;
+ }
+
+ Map<String, T> map$<T extends Object?>(String key) =>
+ _castMap<T>(get<Map<String, Object?>>(key), key);
+
+ List<String> validateMap<T extends Object?>(String key) {
+ final mapErrors = validate<Map<String, Object?>>(key);
+ if (mapErrors.isNotEmpty) {
+ return mapErrors;
+ }
+ return _validateMapElements<T>(get<Map<String, Object?>>(key), key);
+ }
+
+ Map<String, T>? optionalMap<T extends Object?>(String key) =>
+ switch (get<Map<String, Object?>?>(key)) {
+ null => null,
+ final m => _castMap<T>(m, key),
+ };
+
+ List<String> validateOptionalMap<T extends Object?>(String key) {
+ final mapErrors = validate<Map<String, Object?>?>(key);
+ if (mapErrors.isNotEmpty) {
+ return mapErrors;
+ }
+ final map = get<Map<String, Object?>?>(key);
+ if (map == null) {
+ return [];
+ }
+ return _validateMapElements<T>(map, key);
+ }
+
+ /// [Map.cast] but with [FormatException]s.
+ Map<String, T> _castMap<T extends Object?>(
+ Map<String, Object?> map_,
+ String parentKey,
+ ) {
+ for (final MapEntry(:key, :value) in map_.entries) {
+ if (value is! T) {
+ throwFormatException(value, T, [parentKey, key]);
+ }
+ }
+ return map_.cast();
+ }
+
+ List<String> _validateMapElements<T extends Object?>(
+ Map<String, Object?> map_,
+ String parentKey,
+ ) {
+ final result = <String>[];
+ for (final MapEntry(:key, :value) in map_.entries) {
+ if (value is! T) {
+ result.add(errorString(value, T, [parentKey, key]));
+ }
+ }
+ return result;
+ }
+
+ List<String>? optionalStringList(String key) => optionalList<String>(key);
+
+ List<String> validateOptionalStringList(String key) =>
+ validateOptionalList<String>(key);
+
+ List<String> stringList(String key) => list<String>(key);
+
+ List<String> validateStringList(String key) => validateList<String>(key);
+
+ Uri path$(String key) => _fileSystemPathToUri(get<String>(key));
+
+ List<String> validatePath(String key) => validate<String>(key);
+
+ Uri? optionalPath(String key) {
+ final value = get<String?>(key);
+ if (value == null) return null;
+ return _fileSystemPathToUri(value);
+ }
+
+ List<String> validateOptionalPath(String key) => validate<String?>(key);
+
+ List<Uri>? optionalPathList(String key) {
+ final strings = optionalStringList(key);
+ if (strings == null) {
+ return null;
+ }
+ return [for (final string in strings) _fileSystemPathToUri(string)];
+ }
+
+ List<String> validateOptionalPathList(String key) =>
+ validateOptionalStringList(key);
+
+ static Uri _fileSystemPathToUri(String path) {
+ if (path.endsWith(Platform.pathSeparator)) {
+ return Uri.directory(path);
+ }
+ return Uri.file(path);
+ }
+
+ String _jsonPathToString(List<Object> pathEnding) =>
+ [...path, ...pathEnding].join('.');
+
+ Never throwFormatException(
+ Object? value,
+ Type expectedType,
+ List<Object> pathExtension,
+ ) {
+ throw FormatException(errorString(value, expectedType, pathExtension));
+ }
+
+ String errorString(
+ Object? value,
+ Type expectedType,
+ List<Object> pathExtension,
+ ) {
+ final pathString = _jsonPathToString(pathExtension);
+ if (value == null) {
+ return "No value was provided for '$pathString'."
+ ' Expected a $expectedType.';
+ }
+ return "Unexpected value '$value' (${value.runtimeType}) for '$pathString'."
+ ' Expected a $expectedType.';
+ }
+
+ /// Traverses a JSON path, returns `null` if the path cannot be traversed.
+ Object? tryTraverse(List<String> path) {
+ Object? json = this.json;
+ for (final key in path) {
+ if (json is! Map<String, Object?>) {
+ return null;
+ }
+ json = json[key];
+ }
+ return json;
+ }
+}
+
+extension on Map<String, Object?> {
+ void setOrRemove(String key, Object? value) {
+ if (value == null) {
+ remove(key);
+ } else {
+ this[key] = value;
+ }
+ }
+}
+
+extension on List<Uri> {
+ List<String> toJson() => [for (final uri in this) uri.toFilePath()];
+}
+
+extension<K extends Comparable<K>, V extends Object?> on Map<K, V> {
+ void sortOnKey() {
+ final result = <K, V>{};
+ final keysSorted = keys.toList()..sort();
+ for (final key in keysSorted) {
+ result[key] = this[key] as V;
+ }
+ clear();
+ addAll(result);
+ }
+}
diff --git a/pkgs/record_use/lib/src/syntax.g.txt b/pkgs/record_use/lib/src/syntax.g.txt
new file mode 100644
index 0000000..f43a89c
--- /dev/null
+++ b/pkgs/record_use/lib/src/syntax.g.txt
@@ -0,0 +1,339 @@
+SchemaInfo(
+ classes: [
+ NormalClassInfo(
+ name: Arguments,
+ superclassName: null,
+ subclassNames: [ ],
+ properties: [
+ PropertyInfo(
+ name: constant,
+ jsonKey: constant,
+ type: Constant?,
+ isOverride: false,
+ setterPrivate: true,
+ ),
+ PropertyInfo(
+ name: nonConstant,
+ jsonKey: nonConstant,
+ type: NonConstant?,
+ isOverride: false,
+ setterPrivate: true,
+ )
+ ],
+ taggedUnionProperty: null,
+ taggedUnionValue: null,
+ extraValidation: [
+
+ ],
+ ),
+ NormalClassInfo(
+ name: Call,
+ superclassName: null,
+ subclassNames: [ ],
+ properties: [
+ PropertyInfo(
+ name: arguments,
+ jsonKey: arguments,
+ type: Arguments?,
+ isOverride: false,
+ setterPrivate: true,
+ ),
+ PropertyInfo(
+ name: definition,
+ jsonKey: definition,
+ type: Definition?,
+ isOverride: false,
+ setterPrivate: true,
+ ),
+ PropertyInfo(
+ name: loadingUnit,
+ jsonKey: loadingUnit,
+ type: String?,
+ isOverride: false,
+ setterPrivate: true,
+ ),
+ PropertyInfo(
+ name: references,
+ jsonKey: references,
+ type: List<Reference>?,
+ isOverride: false,
+ setterPrivate: true,
+ )
+ ],
+ taggedUnionProperty: null,
+ taggedUnionValue: null,
+ extraValidation: [
+
+ ],
+ ),
+ NormalClassInfo(
+ name: Constant,
+ superclassName: null,
+ subclassNames: [ ],
+ properties: [
+ PropertyInfo(
+ name: named,
+ jsonKey: named,
+ type: Map<String, Object?>?,
+ isOverride: false,
+ setterPrivate: true,
+ ),
+ PropertyInfo(
+ name: positional,
+ jsonKey: positional,
+ type: Map<String, Object?>?,
+ isOverride: false,
+ setterPrivate: true,
+ )
+ ],
+ taggedUnionProperty: null,
+ taggedUnionValue: null,
+ extraValidation: [
+
+ ],
+ ),
+ NormalClassInfo(
+ name: Definition,
+ superclassName: null,
+ subclassNames: [ ],
+ properties: [
+ PropertyInfo(
+ name: id,
+ jsonKey: id,
+ type: int,
+ isOverride: false,
+ setterPrivate: true,
+ ),
+ PropertyInfo(
+ name: loadingUnit,
+ jsonKey: loadingUnit,
+ type: String,
+ isOverride: false,
+ setterPrivate: true,
+ )
+ ],
+ taggedUnionProperty: null,
+ taggedUnionValue: null,
+ extraValidation: [
+
+ ],
+ ),
+ NormalClassInfo(
+ name: Identifier,
+ superclassName: null,
+ subclassNames: [ ],
+ properties: [
+ PropertyInfo(
+ name: name,
+ jsonKey: name,
+ type: String,
+ isOverride: false,
+ setterPrivate: true,
+ ),
+ PropertyInfo(
+ name: parent,
+ jsonKey: parent,
+ type: String?,
+ isOverride: false,
+ setterPrivate: true,
+ ),
+ PropertyInfo(
+ name: uri,
+ jsonKey: uri,
+ type: String,
+ isOverride: false,
+ setterPrivate: true,
+ )
+ ],
+ taggedUnionProperty: null,
+ taggedUnionValue: null,
+ extraValidation: [
+
+ ],
+ ),
+ NormalClassInfo(
+ name: Instance,
+ superclassName: null,
+ subclassNames: [ ],
+ properties: [
+ PropertyInfo(
+ name: instanceConstant,
+ jsonKey: instanceConstant,
+ type: int,
+ isOverride: false,
+ setterPrivate: true,
+ ),
+ PropertyInfo(
+ name: loadingUnit,
+ jsonKey: loadingUnit,
+ type: String,
+ isOverride: false,
+ setterPrivate: true,
+ )
+ ],
+ taggedUnionProperty: null,
+ taggedUnionValue: null,
+ extraValidation: [
+
+ ],
+ ),
+ NormalClassInfo(
+ name: Metadata,
+ superclassName: null,
+ subclassNames: [ ],
+ properties: [
+ PropertyInfo(
+ name: comment,
+ jsonKey: comment,
+ type: String,
+ isOverride: false,
+ setterPrivate: true,
+ ),
+ PropertyInfo(
+ name: version,
+ jsonKey: version,
+ type: String,
+ isOverride: false,
+ setterPrivate: true,
+ )
+ ],
+ taggedUnionProperty: null,
+ taggedUnionValue: null,
+ extraValidation: [
+
+ ],
+ ),
+ NormalClassInfo(
+ name: NonConstant,
+ superclassName: null,
+ subclassNames: [ ],
+ properties: [
+ PropertyInfo(
+ name: named,
+ jsonKey: named,
+ type: List<Map<String, Object?>?>?,
+ isOverride: false,
+ setterPrivate: true,
+ ),
+ PropertyInfo(
+ name: positional,
+ jsonKey: positional,
+ type: List<Map<String, Object?>?>?,
+ isOverride: false,
+ setterPrivate: true,
+ )
+ ],
+ taggedUnionProperty: null,
+ taggedUnionValue: null,
+ extraValidation: [
+
+ ],
+ ),
+ NormalClassInfo(
+ name: RecordedUses,
+ superclassName: null,
+ subclassNames: [ ],
+ properties: [
+ PropertyInfo(
+ name: constants,
+ jsonKey: constants,
+ type: List<Constant>?,
+ isOverride: false,
+ setterPrivate: true,
+ ),
+ PropertyInfo(
+ name: ids,
+ jsonKey: ids,
+ type: List<Identifier>?,
+ isOverride: false,
+ setterPrivate: true,
+ ),
+ PropertyInfo(
+ name: metadata,
+ jsonKey: metadata,
+ type: Metadata,
+ isOverride: false,
+ setterPrivate: true,
+ ),
+ PropertyInfo(
+ name: usages,
+ jsonKey: usages,
+ type: List<Usage>?,
+ isOverride: false,
+ setterPrivate: true,
+ )
+ ],
+ taggedUnionProperty: null,
+ taggedUnionValue: null,
+ extraValidation: [
+
+ ],
+ ),
+ NormalClassInfo(
+ name: Reference,
+ superclassName: null,
+ subclassNames: [ ],
+ properties: [
+ PropertyInfo(
+ name: arguments,
+ jsonKey: arguments,
+ type: Arguments,
+ isOverride: false,
+ setterPrivate: true,
+ ),
+ PropertyInfo(
+ name: loadingUnit,
+ jsonKey: loadingUnit,
+ type: String,
+ isOverride: false,
+ setterPrivate: true,
+ )
+ ],
+ taggedUnionProperty: null,
+ taggedUnionValue: null,
+ extraValidation: [
+
+ ],
+ ),
+ NormalClassInfo(
+ name: Usage,
+ superclassName: null,
+ subclassNames: [ ],
+ properties: [
+ PropertyInfo(
+ name: calls,
+ jsonKey: calls,
+ type: List<Call>?,
+ isOverride: false,
+ setterPrivate: true,
+ ),
+ PropertyInfo(
+ name: identifier,
+ jsonKey: identifier,
+ type: int,
+ isOverride: false,
+ setterPrivate: true,
+ ),
+ PropertyInfo(
+ name: instances,
+ jsonKey: instances,
+ type: List<Instance>?,
+ isOverride: false,
+ setterPrivate: true,
+ ),
+ PropertyInfo(
+ name: loadingUnit,
+ jsonKey: loadingUnit,
+ type: String?,
+ isOverride: false,
+ setterPrivate: true,
+ )
+ ],
+ taggedUnionProperty: null,
+ taggedUnionValue: null,
+ extraValidation: [
+
+ ],
+ )
+ ]
+)
\ No newline at end of file
diff --git a/pkgs/record_use/pubspec.yaml b/pkgs/record_use/pubspec.yaml
new file mode 100644
index 0000000..97ec2f2
--- /dev/null
+++ b/pkgs/record_use/pubspec.yaml
@@ -0,0 +1,20 @@
+name: record_use
+description: >
+ The serialization logic and API for the usage recording SDK feature.
+version: 0.4.0
+repository: https://github.com/dart-lang/sdk/tree/main/pkg/record_use
+
+environment:
+ sdk: ^3.7.0
+
+dependencies:
+ collection: ^1.18.0
+ pub_semver: ^2.1.4
+
+dev_dependencies:
+ dart_flutter_team_lints: any
+ json_schema: any
+ json_syntax_generator:
+ path: ../json_syntax_generator
+ lints: any
+ test: any
diff --git a/pkgs/record_use/test_data/json/recorded_uses.json b/pkgs/record_use/test_data/json/recorded_uses.json
new file mode 100644
index 0000000..e732030
--- /dev/null
+++ b/pkgs/record_use/test_data/json/recorded_uses.json
@@ -0,0 +1,153 @@
+{
+ "$schema": "../../doc/schema/record_use.schema.json",
+ "metadata": {
+ "version": "1.6.2-wip+5.-.2.z",
+ "comment": "Recorded references at compile time and their argument values, as far as known, to definitions annotated with @RecordUse"
+ },
+ "ids": [
+ {
+ "uri": "file://lib/_internal/js_runtime/lib/js_helper.dart",
+ "name": "MyAnnotation"
+ },
+ {
+ "uri": "file://lib/_internal/js_runtime/lib/js_helper.dart",
+ "parent": "MyClass",
+ "name": "get:loadDeferredLibrary"
+ }
+ ],
+ "constants": [
+ {
+ "type": "String",
+ "value": "jenkins"
+ },
+ {
+ "type": "String",
+ "value": "mercury"
+ },
+ {
+ "type": "String",
+ "value": "lib_SHA1"
+ },
+ {
+ "type": "bool",
+ "value": false
+ },
+ {
+ "type": "int",
+ "value": 1
+ },
+ {
+ "type": "String",
+ "value": "camus"
+ },
+ {
+ "type": "String",
+ "value": "einstein"
+ },
+ {
+ "type": "String",
+ "value": "insert"
+ },
+ {
+ "type": "list",
+ "value": [
+ 6,
+ 7,
+ 3
+ ]
+ },
+ {
+ "type": "list",
+ "value": [
+ 5,
+ 8,
+ 6
+ ]
+ },
+ {
+ "type": "int",
+ "value": 0
+ },
+ {
+ "type": "int",
+ "value": 99
+ },
+ {
+ "type": "map",
+ "value": {
+ "key": 11
+ }
+ },
+ {
+ "type": "int",
+ "value": 42
+ },
+ {
+ "type": "Null"
+ },
+ {
+ "type": "Instance",
+ "value": {
+ "a": 13,
+ "b": 14
+ }
+ }
+ ],
+ "usages": [
+ {
+ "identifier": 0,
+ "instances": [
+ {
+ "instanceConstant": 15,
+ "loadingUnit": "3"
+ }
+ ]
+ },
+ {
+ "identifier": 1,
+ "calls": [
+ {
+ "arguments": {
+ "constant": {
+ "positional": {
+ "0": 2,
+ "1": 3,
+ "2": 4
+ },
+ "named": {
+ "leroy": 0,
+ "freddy": 1
+ }
+ }
+ },
+ "loadingUnit": "o.js"
+ },
+ {
+ "arguments": {
+ "constant": {
+ "positional": {
+ "0": 2,
+ "2": 10,
+ "4": 12
+ },
+ "named": {
+ "leroy": 0,
+ "albert": 9
+ }
+ },
+ "nonConstant": {
+ "positional": [
+ 1
+ ],
+ "named": [
+ "freddy"
+ ]
+ }
+ },
+ "loadingUnit": "o.js"
+ }
+ ],
+ "loadingUnit": "part_15.js"
+ }
+ ]
+}
diff --git a/pkgs/record_use/test_data/json/recorded_uses_2.json b/pkgs/record_use/test_data/json/recorded_uses_2.json
new file mode 100644
index 0000000..f110080
--- /dev/null
+++ b/pkgs/record_use/test_data/json/recorded_uses_2.json
@@ -0,0 +1,44 @@
+{
+ "$schema": "../../doc/schema/record_use.schema.json",
+ "metadata": {
+ "comment": "Recorded usages of objects tagged with a `RecordUse` annotation",
+ "version": "0.1.0"
+ },
+ "ids": [
+ {
+ "uri": "package:drop_dylib_recording/src/drop_dylib_recording.dart",
+ "name": "getMathMethod"
+ }
+ ],
+ "constants": [
+ {
+ "type": "String",
+ "value": "add"
+ }
+ ],
+ "usages": [
+ {
+ "identifier": 0,
+ "calls": [
+ {
+ "definition": {
+ "id": 0,
+ "loadingUnit": "1"
+ },
+ "references": [
+ {
+ "arguments": {
+ "constant": {
+ "positional": {
+ "0": 0
+ }
+ }
+ },
+ "loadingUnit": "1"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/pkgs/record_use/tool/generate_syntax.dart b/pkgs/record_use/tool/generate_syntax.dart
new file mode 100644
index 0000000..47a6833
--- /dev/null
+++ b/pkgs/record_use/tool/generate_syntax.dart
@@ -0,0 +1,40 @@
+// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:convert';
+import 'dart:io';
+
+import 'package:json_schema/json_schema.dart';
+import 'package:json_syntax_generator/json_syntax_generator.dart';
+
+void main(List<String> args) {
+ final schemaFile = File.fromUri(
+ Platform.script.resolve('../doc/schema/record_use.schema.json'),
+ );
+ final schemaJson = jsonDecode(schemaFile.readAsStringSync()) as Map;
+ final schema = JsonSchema.create(schemaJson);
+
+ final analyzedSchema = SchemaAnalyzer(schema).analyze();
+ final textDumpFile = File.fromUri(
+ Platform.script.resolve('../lib/src/syntax.g.txt'),
+ );
+ textDumpFile.parent.createSync(recursive: true);
+ if (args.contains('-d')) {
+ textDumpFile.writeAsStringSync(analyzedSchema.toString());
+ } else if (textDumpFile.existsSync()) {
+ textDumpFile.deleteSync();
+ }
+ final output =
+ SyntaxGenerator(
+ analyzedSchema,
+ header: '''
+// This file is generated, do not edit.
+// File generated by pkg/record_use/tool/generate_syntax.dart.
+''',
+ ).generate();
+ final outputUri = Platform.script.resolve('../lib/src/syntax.g.dart');
+ File.fromUri(outputUri).writeAsStringSync(output);
+ Process.runSync(Platform.executable, ['format', outputUri.toFilePath()]);
+ print('Generated $outputUri');
+}