mirror of
https://github.com/dart-lang/sdk
synced 2024-09-05 00:13:50 +00:00
[dds] Remove copy of dap tool
Cq-Include-Trybots: luci.dart.try:flutter-linux-try,flutter-web-try Bug: b/286184681 Change-Id: I408d36a10dde43e3e182d57685f7d8423124461b Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/344021 Reviewed-by: Ben Konyi <bkonyi@google.com> Commit-Queue: Alexander Thomas <athom@google.com>
This commit is contained in:
parent
bc260f4d73
commit
e609c8232b
|
@ -37,7 +37,6 @@ dependencies:
|
|||
# best practice for packages is to specify their compatible version ranges.
|
||||
# See also https://dart.dev/tools/pub/dependencies.
|
||||
dev_dependencies:
|
||||
http: any
|
||||
lints: any
|
||||
test: any
|
||||
webdriver: any
|
||||
|
|
|
@ -1,199 +0,0 @@
|
|||
# Debug Adapter Protocol
|
||||
|
||||
Dart includes support for debugging using [the Debug Adapter Protocol](https://microsoft.github.io/debug-adapter-protocol/) as an alternative to using the [VM Service](https://github.com/dart-lang/sdk/blob/master/runtime/vm/service/service.md) directly, simplifying the integration for new editors.
|
||||
|
||||
The debug adapters are started with the `dart debug_adapter` command and are intended to be consumed by DAP-compliant tools such as Dart-specific extensions for editors, or configured by users whose editors include generic configurable DAP clients.
|
||||
|
||||
Two adapters are available:
|
||||
|
||||
- `dart debug_adapter`
|
||||
- `dart debug_adapter --test`
|
||||
|
||||
The standard adapter will run scripts using `dart` while the `--test` adapter will cause scripts to be run using `dart test` and will emit custom `dart.testNotification` events (described below).
|
||||
|
||||
Because in the DAP protocol the client speaks first, running this command from the terminal will result in no output (nor will the process terminate). This is expected behaviour.
|
||||
|
||||
For details on the standard DAP functionality, see [the Debug Adapter Protocol Overview](https://microsoft.github.io/debug-adapter-protocol/) and [the Debug Adapter Protocol Specification](https://microsoft.github.io/debug-adapter-protocol/specification). Custom extensions are detailed below.
|
||||
|
||||
**Flutter**: Flutter apps should be run using the debug adapter in the `flutter` tool - [see this document](https://github.com/flutter/flutter/blob/master/packages/flutter_tools/lib/src/debug_adapters/README.md).
|
||||
|
||||
## Launch/Attach Arguments
|
||||
|
||||
Arguments common to both `launchRequest` and `attachRequest` are:
|
||||
|
||||
- `bool? debugExternalPackageLibraries` - whether to enable debugging for packages that are not inside the current workspace
|
||||
- `bool? debugSdkLibraries` - whether to enable debugging for SDK libraries
|
||||
- `bool? showGettersInDebugViews` - whether to include getters in Variables and Evaluation responses (inc. hovers/watch windows)
|
||||
- `bool? evaluateGettersInDebugViews` - whether to eagerly evaluate getters in Variables and Evaluation responses (inc. hovers/watch windows) without needing user action. Implies `showGettersInDebugViews=true`.
|
||||
- `bool? evaluateToStringInDebugViews` - whether to invoke `toString()` in expression evaluation requests (inc. hovers/watch windows)
|
||||
- `bool? sendLogsToClient` - used to proxy all VM Service traffic back to the client in custom `dart.log` events (has performance implications, intended for troubleshooting)
|
||||
- `int? vmServicePort` - the port to bind the VM Service too
|
||||
- `List<String>? additionalProjectPaths` - paths of any projects (outside of `cwd`) that are open in the users workspace
|
||||
- `String? cwd` - the working directory for the Dart process to be spawned in
|
||||
- `Map<String, String>? env` - environment variables to be passed to any spawned process
|
||||
- `bool? sendCustomProgressEvents` - whether to send custom `dart.progressStart`, `dart.progressUpdate`, `dart.progressEnd` progress events in place of standard DAP `progressStart`, `progressUpdate`, `progressEnd` events. When this is set, only standard events will not be sent regardless of the `supportsProgressReporting` capability.
|
||||
- `bool? allowAnsiColorOutput` - whether to allow ansi color codes in Output events
|
||||
|
||||
Arguments specific to `launchRequest` are:
|
||||
|
||||
- `bool? noDebug` - whether to run in debug or noDebug mode (if not supplied, defaults to debug)
|
||||
- `String program` - the path of the Dart program to run
|
||||
- `List<String>? args` - arguments to be passed to the Dart program (after the `program` on the command line)
|
||||
- `List<String>? toolArgs` - arguments passed after the tool that will run `program` (after `dart` for CLI scripts and after `dart run test:test` for test scripts)
|
||||
- `List<String>? vmAdditionalArgs` - arguments passed directly to the Dart VM (after `dart` for both CLI scripts and test scripts)
|
||||
- `String? console` - if set to `"terminal"` or `"externalTerminal"` will be run using the `runInTerminal` reverse-request; otherwise the debug adapter spawns the Dart process
|
||||
- `String? customTool` - an optional tool to run instead of `dart` - the custom tool must be completely compatible with the tool/command it is replacing
|
||||
- `int? customToolReplacesArgs` - the number of arguments to delete from the beginning of the argument list when invoking `customTool` - e.g. setting `customTool` to `dart_test` and
|
||||
`customToolReplacesArgs` to `2` for a test run would invoke `dart_test foo_test.dart` instead of `dart run test:test foo_test.dart` (if larger than the number of computed arguments all arguments will be removed, if not supplied will default to `0`)
|
||||
|
||||
Arguments specific to `attachRequest` are:
|
||||
|
||||
- `String? vmServiceInfoFile` - the file to read the VM Service info from \*
|
||||
- `String? vmServiceUri` - the VM Service URI to attach to \*
|
||||
|
||||
\* Exactly one of `vmServiceInfoFile` or `vmServiceUri` should be supplied.
|
||||
|
||||
## Expression Evaluation Format Specifiers
|
||||
|
||||
Special suffixes can be added to evaluation expressions (such as a Watch window or Debug Console) that will affect the formatting of variables:
|
||||
|
||||
- `,nq` - don't add quotes around strings
|
||||
- `,h` - format integers as hex
|
||||
- `,d` - format integers as decimal (base 10)
|
||||
|
||||
A format specifier overrides any other formatting (such as the `format` argument that can be supplied to `variablesRequest` and `evaluateRequest`). Format specifiers also carry down the variables tree, so adding `,h` to an expression that is a `List<int>` will cause the values inside the list (once expanded) to be rendered as hex. Multiple format specifiers can be comma-separated (`myVariable,nq,h` on a class will cause `String`s in child fields to be unquoted and `int`s to be formatted as hex).
|
||||
|
||||
## Custom Requests
|
||||
|
||||
Some custom requests are available for clients to call.
|
||||
|
||||
### `updateDebugOptions`
|
||||
|
||||
`updateDebugOptions` allows updating some debug options usually provided at launch/attach while the session is running. Any keys included in the request will overwrite the previously set values. To update only some values, include only those in the parameters.
|
||||
|
||||
```
|
||||
{
|
||||
"debugSdkLibraries": true
|
||||
"debugExternalPackageLibraries": false
|
||||
}
|
||||
```
|
||||
|
||||
### `callService`
|
||||
|
||||
`callService` allows calling arbitrary services (for example service extensions that have been registered). The service RPC/method should be sent in the `method` field and `params` will depend on the service being called.
|
||||
|
||||
```
|
||||
{
|
||||
"method": "myFooService",
|
||||
"params": {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `hotReload`
|
||||
|
||||
`hotReload` calls the VM's `reloadSources` service for each active isolate, reloading all modified source files.
|
||||
|
||||
```
|
||||
{
|
||||
"method": "hotReload",
|
||||
"params": null
|
||||
}
|
||||
```
|
||||
|
||||
## Custom Events
|
||||
|
||||
The debug adapter may emit several custom events that are useful to clients.
|
||||
|
||||
### `dart.debuggerUris`
|
||||
|
||||
When running in debug mode, a `dart.debuggerUris` event will be emitted containing the URI of the VM Service.
|
||||
|
||||
```
|
||||
{
|
||||
"type": "event",
|
||||
"event": "dart.debuggerUris",
|
||||
"body": {
|
||||
"vmServiceUri": "ws://127.0.0.1:123/abdef123="
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `dart.log`
|
||||
|
||||
When `sendLogsToClient` in the launch/attach arguments is `true`, debug logging and all traffic to the VM Service will be proxied back to the client in `dart.log` events to aid troubleshooting.
|
||||
|
||||
```
|
||||
{
|
||||
"type": "event",
|
||||
"event": "dart.log",
|
||||
"body": {
|
||||
"message": "<log message or json string>"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `dart.serviceRegistered`
|
||||
|
||||
Emitted when a VM Service is registered.
|
||||
|
||||
```
|
||||
{
|
||||
"type": "event",
|
||||
"event": "dart.serviceRegistered",
|
||||
"body": {
|
||||
"service": "ServiceName",
|
||||
"method": "methodName"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `dart.serviceUnregistered`
|
||||
|
||||
Emitted when a VM Service is unregistered.
|
||||
|
||||
```
|
||||
{
|
||||
"type": "event",
|
||||
"event": "dart.serviceUnregistered",
|
||||
"body": {
|
||||
"service": "ServiceName",
|
||||
"method": "methodName"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `dart.serviceExtensionAdded`
|
||||
|
||||
Emitted when a VM Service Extension is added.
|
||||
|
||||
```
|
||||
{
|
||||
"type": "event",
|
||||
"event": "dart.serviceExtensionAdded",
|
||||
"body": {
|
||||
"extensionRPC": "<extensionRPC to call>",
|
||||
"isolateId": "<isolateId>"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `dart.testNotification`
|
||||
|
||||
When running the `--test` debug adapter, `package:test` JSON messages will be passed back to the client in a `dart.testNotification` event. For details on this protocol, see the [package:test documentation](https://github.com/dart-lang/test/blob/master/pkgs/test/doc/json_reporter.md).
|
||||
|
||||
```
|
||||
{
|
||||
"type": "event",
|
||||
"event": "dart.testNotification",
|
||||
"body": {
|
||||
"type": "testStart",
|
||||
"test": {
|
||||
"id": 1,
|
||||
"name": "my test name",
|
||||
// ...
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
|
@ -1,699 +0,0 @@
|
|||
// Copyright (c) 2021, 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:math';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
|
||||
import 'json_schema.dart';
|
||||
import 'json_schema_extensions.dart';
|
||||
|
||||
/// Generates Dart classes from the Debug Adapter Protocol's JSON Schema.
|
||||
class CodeGenerator {
|
||||
/// Writes all required Dart classes for the supplied DAP [schema].
|
||||
void writeAll(IndentableStringBuffer buffer, JsonSchema schema) {
|
||||
_writeDefinitionClasses(buffer, schema);
|
||||
buffer.writeln();
|
||||
_writeBodyClasses(buffer, schema);
|
||||
buffer.writeln();
|
||||
_writeEventTypeLookup(buffer, schema);
|
||||
buffer.writeln();
|
||||
_writeCommandArgumentTypeLookup(buffer, schema);
|
||||
}
|
||||
|
||||
/// Maps a name used in the DAP spec to a valid name for use in Dart.
|
||||
///
|
||||
/// Reserved words like `default` will be mapped to a suitable alternative.
|
||||
/// Prefixed underscores are removed to avoid making things private.
|
||||
///
|
||||
/// Underscores between words are swapped for camelCase.
|
||||
String _dartSafeName(String name) {
|
||||
const improvedName = {
|
||||
'default': 'defaultValue',
|
||||
};
|
||||
return improvedName[name] ??
|
||||
// Some types are prefixed with _ in the spec but that will make them
|
||||
// private in Dart and inaccessible to the adapter so we strip it off.
|
||||
name
|
||||
.replaceAll(RegExp(r'^_+'), '')
|
||||
// Also replace any other underscores to make camelCase
|
||||
.replaceAllMapped(
|
||||
RegExp(r'_(.)'), (m) => m.group(1)!.toUpperCase());
|
||||
}
|
||||
|
||||
/// Re-wraps [lines] at [maxLength] to help keep comments for indented code
|
||||
/// within 80 characters.
|
||||
Iterable<String> _wrapLines(List<String> lines, int maxLength) sync* {
|
||||
lines = lines.map((l) => l.trimRight()).toList();
|
||||
for (var line in lines) {
|
||||
while (true) {
|
||||
if (line.length <= maxLength || line.startsWith('-')) {
|
||||
yield line;
|
||||
break;
|
||||
} else {
|
||||
var lastSpace = line.lastIndexOf(' ', max(maxLength, 0));
|
||||
// If there was no valid place to wrap, yield the whole string.
|
||||
if (lastSpace == -1) {
|
||||
yield line;
|
||||
break;
|
||||
} else {
|
||||
yield line.substring(0, lastSpace);
|
||||
line = line.substring(lastSpace + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// For each Response/Event class in the spec, generate a specific class to
|
||||
/// represent its body.
|
||||
///
|
||||
/// These classes are used to simplify sending responses/events from the
|
||||
/// Debug Adapters by avoiding the need to construct the entire response/event
|
||||
/// which requires additional fields (for example the corresponding requests
|
||||
/// id/command and sequences):
|
||||
///
|
||||
/// this.sendResponse(FooBody(x: 1))
|
||||
///
|
||||
/// instead of
|
||||
///
|
||||
/// this.sendResponse(Response(
|
||||
/// seq: seq++,
|
||||
/// request_seq: request.seq,
|
||||
/// command: request.command,
|
||||
/// body: {
|
||||
/// x: 1
|
||||
/// ...
|
||||
/// }
|
||||
/// ))
|
||||
void _writeBodyClasses(IndentableStringBuffer buffer, JsonSchema schema) {
|
||||
for (final entry in schema.definitions.entries.sortedBy((e) => e.key)) {
|
||||
final name = entry.key;
|
||||
final type = entry.value;
|
||||
final baseType = type.baseType;
|
||||
|
||||
if (baseType?.refName == 'Response' || baseType?.refName == 'Event') {
|
||||
final baseClass = baseType?.refName == 'Event'
|
||||
? JsonType.named(schema, 'EventBody')
|
||||
: null;
|
||||
final classProperties = schema.propertiesFor(type);
|
||||
final bodyProperty = classProperties['body'];
|
||||
var bodyPropertyProperties = bodyProperty?.properties;
|
||||
|
||||
_writeClass(
|
||||
buffer,
|
||||
bodyProperty ?? JsonType.empty(schema),
|
||||
'${name}Body',
|
||||
bodyPropertyProperties ?? {},
|
||||
{},
|
||||
baseClass,
|
||||
null,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Writes a `canParse` function for a DAP spec class.
|
||||
///
|
||||
/// The function checks whether an Object? is a valid map that contains all
|
||||
/// required fields and matches the types of the spec class.
|
||||
///
|
||||
/// This is used where the spec contains union classes and we need to decide
|
||||
/// which of the allowed types a given value is.
|
||||
void _writeCanParseMethod(
|
||||
IndentableStringBuffer buffer,
|
||||
JsonType type,
|
||||
Map<String, JsonType> properties, {
|
||||
required String? baseTypeRefName,
|
||||
}) {
|
||||
buffer
|
||||
..writeIndentedln('static bool canParse(Object? obj) {')
|
||||
..indent()
|
||||
..writeIndentedln('if (obj is! Map<String, dynamic>) {')
|
||||
..indent()
|
||||
..writeIndentedln('return false;')
|
||||
..outdent()
|
||||
..writeIndentedln('}');
|
||||
// In order to consider this valid for parsing, all fields that must not be
|
||||
// undefined must be present and also type check for the correct type.
|
||||
// Any fields that are optional but present, must still type check.
|
||||
for (final entry in properties.entries.sortedBy((e) => e.key)) {
|
||||
final propertyName = entry.key;
|
||||
final propertyType = entry.value;
|
||||
final isOptional = !type.requiresField(propertyName);
|
||||
|
||||
if (propertyType.isAny && isOptional) {
|
||||
continue;
|
||||
}
|
||||
|
||||
buffer.writeIndented('if (');
|
||||
_writeTypeCheckCondition(buffer, propertyType, "obj['$propertyName']",
|
||||
isOptional: isOptional, invert: true);
|
||||
buffer
|
||||
..writeln(') {')
|
||||
..indent()
|
||||
..writeIndentedln('return false;')
|
||||
..outdent()
|
||||
..writeIndentedln('}');
|
||||
}
|
||||
buffer
|
||||
..writeIndentedln(
|
||||
baseTypeRefName != null
|
||||
? 'return $baseTypeRefName.canParse(obj);'
|
||||
: 'return true;',
|
||||
)
|
||||
..outdent()
|
||||
..writeIndentedln('}');
|
||||
}
|
||||
|
||||
/// Writes the Dart class for [type].
|
||||
void _writeClass(
|
||||
IndentableStringBuffer buffer,
|
||||
JsonType type,
|
||||
String name,
|
||||
Map<String, JsonType> classProperties,
|
||||
Map<String, JsonType> baseProperties,
|
||||
JsonType? baseType,
|
||||
JsonType? resolvedBaseType, {
|
||||
Map<String, String> additionalValues = const {},
|
||||
}) {
|
||||
_writeTypeDescription(buffer, type);
|
||||
|
||||
// Types that are just aliases to simple value types should be written as
|
||||
// typedefs.
|
||||
if (type.isSimpleValue) {
|
||||
buffer.writeln('typedef $name = ${type.asDartType()};');
|
||||
return;
|
||||
}
|
||||
|
||||
// Some properties are defined in both the base and the class, because the
|
||||
// type may be narrowed, but sometimes we only want those that are defined
|
||||
// only in this class.
|
||||
final classOnlyProperties = {
|
||||
for (final property in classProperties.entries)
|
||||
if (!baseProperties.containsKey(property.key))
|
||||
property.key: property.value,
|
||||
};
|
||||
buffer.write('class $name ');
|
||||
if (baseType != null) {
|
||||
buffer.write('extends ${baseType.refName} ');
|
||||
}
|
||||
buffer
|
||||
..writeln('{')
|
||||
..indent();
|
||||
for (final val in additionalValues.entries) {
|
||||
buffer
|
||||
..writeIndentedln('@override')
|
||||
..writeIndentedln("final ${val.key} = '${val.value}';");
|
||||
}
|
||||
_writeFields(buffer, type, classOnlyProperties);
|
||||
buffer.writeln();
|
||||
_writeFromJsonStaticMethod(buffer, name);
|
||||
buffer.writeln();
|
||||
_writeConstructor(buffer, name, type, classProperties, baseProperties,
|
||||
classOnlyProperties,
|
||||
baseType: resolvedBaseType);
|
||||
buffer.writeln();
|
||||
_writeFromMapConstructor(buffer, name, type, classOnlyProperties,
|
||||
callSuper: resolvedBaseType != null);
|
||||
buffer.writeln();
|
||||
_writeCanParseMethod(buffer, type, classProperties,
|
||||
baseTypeRefName: baseType?.refName);
|
||||
buffer.writeln();
|
||||
_writeToJsonMethod(buffer, name, type, classOnlyProperties,
|
||||
callSuper: resolvedBaseType != null);
|
||||
buffer
|
||||
..outdent()
|
||||
..writeln('}')
|
||||
..writeln();
|
||||
}
|
||||
|
||||
/// Write a map to look up the `command` for a given `RequestArguments` type
|
||||
/// to simplify sending requests back to the client:
|
||||
///
|
||||
/// this.sendRequest(FooArguments(x: 1))
|
||||
///
|
||||
/// instead of
|
||||
///
|
||||
/// this.sendRequest(Request(
|
||||
/// seq: seq++,
|
||||
/// command: request.command,
|
||||
/// arguments: {
|
||||
/// x: 1
|
||||
/// ...
|
||||
/// }
|
||||
/// ))
|
||||
void _writeCommandArgumentTypeLookup(
|
||||
IndentableStringBuffer buffer, JsonSchema schema) {
|
||||
buffer
|
||||
..writeln('const commandTypes = {')
|
||||
..indent();
|
||||
for (final entry in schema.definitions.entries.sortedBy((e) => e.key)) {
|
||||
final type = entry.value;
|
||||
final baseType = type.baseType;
|
||||
|
||||
if (baseType?.refName == 'Request') {
|
||||
final classProperties = schema.propertiesFor(type);
|
||||
final argumentsProperty = classProperties['arguments'];
|
||||
final commandType = classProperties['command']?.literalValue;
|
||||
if (argumentsProperty?.dollarRef != null && commandType != null) {
|
||||
buffer.writeIndentedln(
|
||||
"${argumentsProperty!.refName}: '$commandType',");
|
||||
}
|
||||
}
|
||||
}
|
||||
buffer
|
||||
..writeln('};')
|
||||
..outdent();
|
||||
}
|
||||
|
||||
/// Writes a constructor for [type].
|
||||
///
|
||||
/// The constructor will have named arguments for all fields, with those that
|
||||
/// are mandatory marked with `required`.
|
||||
void _writeConstructor(
|
||||
IndentableStringBuffer buffer,
|
||||
String name,
|
||||
JsonType type,
|
||||
Map<String, JsonType> classProperties,
|
||||
Map<String, JsonType> baseProperties,
|
||||
Map<String, JsonType> classOnlyProperties, {
|
||||
required JsonType? baseType,
|
||||
}) {
|
||||
buffer.writeIndented('$name(');
|
||||
if (classProperties.isNotEmpty || baseProperties.isNotEmpty) {
|
||||
buffer
|
||||
..writeln('{')
|
||||
..indent();
|
||||
|
||||
// Properties for this class are written as 'this.foo'.
|
||||
for (final entry in classOnlyProperties.entries.sortedBy((e) => e.key)) {
|
||||
final propertyName = entry.key;
|
||||
final fieldName = _dartSafeName(propertyName);
|
||||
final isOptional = !type.requiresField(propertyName);
|
||||
buffer.writeIndented('');
|
||||
if (!isOptional) {
|
||||
buffer.write('required ');
|
||||
}
|
||||
buffer.writeln('this.$fieldName, ');
|
||||
}
|
||||
|
||||
// Properties from the base class are standard arguments that will be
|
||||
// passed to a super() call.
|
||||
for (final entry in baseProperties.entries.sortedBy((e) => e.key)) {
|
||||
final propertyName = entry.key;
|
||||
// If this field is defined by the class and the base, prefer the
|
||||
// class one as it may contain things like the literal values.
|
||||
final propertyType = classProperties[propertyName] ?? entry.value;
|
||||
|
||||
final fieldName = _dartSafeName(propertyName);
|
||||
if (propertyType.literalValue != null) {
|
||||
continue;
|
||||
}
|
||||
final isOptional = !type.requiresField(propertyName);
|
||||
final dartType = propertyType.asDartType(isOptional: isOptional);
|
||||
buffer.writeIndented('');
|
||||
if (!isOptional) {
|
||||
buffer.write('required ');
|
||||
}
|
||||
buffer.writeln('$dartType $fieldName, ');
|
||||
}
|
||||
buffer
|
||||
..outdent()
|
||||
..writeIndented('}');
|
||||
}
|
||||
buffer.write(')');
|
||||
|
||||
if (baseType != null) {
|
||||
buffer.write(': super(');
|
||||
if (baseProperties.isNotEmpty) {
|
||||
buffer
|
||||
..writeln()
|
||||
..indent();
|
||||
for (final entry in baseProperties.entries) {
|
||||
final propertyName = entry.key;
|
||||
// Skip any properties that have literal values defined by the base
|
||||
// as we won't need to supply them.
|
||||
if (entry.value.literalValue != null) {
|
||||
continue;
|
||||
}
|
||||
// If this field is defined by the class and the base, prefer the
|
||||
// class one as it may contain things like the literal values.
|
||||
final propertyType = classProperties[propertyName] ?? entry.value;
|
||||
final fieldName = _dartSafeName(propertyName);
|
||||
final literalValue = propertyType.literalValue;
|
||||
final value = literalValue != null ? "'$literalValue'" : fieldName;
|
||||
buffer.writeIndentedln('$fieldName: $value, ');
|
||||
}
|
||||
buffer
|
||||
..outdent()
|
||||
..writeIndented('');
|
||||
}
|
||||
buffer.write(')');
|
||||
}
|
||||
buffer.writeln(';');
|
||||
}
|
||||
|
||||
/// Write a class for each item in the DAP spec.
|
||||
///
|
||||
/// Skips over the Request and Event sub-classes, as they are handled by the
|
||||
/// simplified body classes written by [_writeBodyClasses]. Uses
|
||||
/// [RequestArguments] as the base class for all argument classes.
|
||||
void _writeDefinitionClasses(
|
||||
IndentableStringBuffer buffer, JsonSchema schema) {
|
||||
for (final entry in schema.definitions.entries.sortedBy((e) => e.key)) {
|
||||
final name = entry.key;
|
||||
final type = entry.value;
|
||||
|
||||
var baseType = type.baseType;
|
||||
final resolvedBaseType =
|
||||
baseType != null ? schema.typeFor(baseType) : null;
|
||||
final classProperties = schema.propertiesFor(type, includeBase: false);
|
||||
final baseProperties = resolvedBaseType != null
|
||||
? schema.propertiesFor(resolvedBaseType)
|
||||
: <String, JsonType>{};
|
||||
|
||||
// Skip creation of Request sub-classes, as we don't use these we just
|
||||
// pass the arguments into the method directly.
|
||||
if (name != 'Request' && name.endsWith('Request')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip creation of Event sub-classes, as we don't use these we just
|
||||
// pass the body into sendEvent directly.
|
||||
if (name != 'Event' && name.endsWith('Event')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Create a synthetic base class for arguments to provide type safety
|
||||
// for sending requests.
|
||||
if (baseType == null && name.endsWith('Arguments')) {
|
||||
baseType = JsonType.named(schema, 'RequestArguments');
|
||||
}
|
||||
|
||||
_writeClass(
|
||||
buffer,
|
||||
type,
|
||||
name,
|
||||
classProperties,
|
||||
baseProperties,
|
||||
baseType,
|
||||
resolvedBaseType,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Writes a DartDoc comment, wrapped at 80 characters taking into account
|
||||
/// the indentation.
|
||||
void _writeDescription(IndentableStringBuffer buffer, String? description) {
|
||||
final maxLength = 80 - buffer.totalIndent - 4;
|
||||
if (description != null) {
|
||||
for (final line in _wrapLines(description.split('\n'), maxLength)) {
|
||||
buffer.writeIndentedln('/// $line');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Write a map to look up the `event` for a given `EventBody` type
|
||||
/// to simplify sending events back to the client:
|
||||
///
|
||||
/// this.sendEvent(FooEvent(x: 1))
|
||||
///
|
||||
/// instead of
|
||||
///
|
||||
/// this.sendEvent(Event(
|
||||
/// seq: seq++,
|
||||
/// event: 'FooEvent',
|
||||
/// arguments: {
|
||||
/// x: 1
|
||||
/// ...
|
||||
/// }
|
||||
/// ))
|
||||
void _writeEventTypeLookup(IndentableStringBuffer buffer, JsonSchema schema) {
|
||||
buffer
|
||||
..writeln('const eventTypes = {')
|
||||
..indent();
|
||||
for (final entry in schema.definitions.entries.sortedBy((e) => e.key)) {
|
||||
final name = entry.key;
|
||||
final type = entry.value;
|
||||
final baseType = type.baseType;
|
||||
|
||||
if (baseType?.refName == 'Event') {
|
||||
final classProperties = schema.propertiesFor(type);
|
||||
final eventType = classProperties['event']!.literalValue;
|
||||
buffer.writeIndentedln("${name}Body: '$eventType',");
|
||||
}
|
||||
}
|
||||
buffer
|
||||
..writeln('};')
|
||||
..outdent();
|
||||
}
|
||||
|
||||
/// Writes Dart fields for [properties], taking into account whether they are
|
||||
/// required for [type].
|
||||
void _writeFields(IndentableStringBuffer buffer, JsonType type,
|
||||
Map<String, JsonType> properties) {
|
||||
for (final entry in properties.entries.sortedBy((e) => e.key)) {
|
||||
final propertyName = entry.key;
|
||||
final fieldName = _dartSafeName(propertyName);
|
||||
final propertyType = entry.value;
|
||||
final isOptional = !type.requiresField(propertyName);
|
||||
final dartType = propertyType.asDartType(isOptional: isOptional);
|
||||
_writeDescription(buffer, propertyType.description);
|
||||
buffer.writeIndentedln('final $dartType $fieldName;');
|
||||
}
|
||||
}
|
||||
|
||||
/// Writes an expression to deserialize a [valueCode].
|
||||
///
|
||||
/// If [type] represents a spec type, it's `fromJson` function will be called.
|
||||
/// If [type] is a [List], it will be mapped over this function again.
|
||||
/// If [type] is an union, the appropriate `canParse` functions will be used to
|
||||
/// determine which `fromJson` function to call.
|
||||
void _writeFromJsonExpression(
|
||||
IndentableStringBuffer buffer, JsonType type, String valueCode,
|
||||
{bool isOptional = false}) {
|
||||
final baseType = type.aliasFor ?? type;
|
||||
final dartType = type.asDartType(isOptional: isOptional);
|
||||
final dartTypeNotNullable = type.asDartType();
|
||||
final nullOp = isOptional ? '?' : '';
|
||||
|
||||
if (baseType.isAny || baseType.isSimple) {
|
||||
buffer.write(valueCode);
|
||||
if (dartType != 'Object?') {
|
||||
buffer.write(' as $dartType');
|
||||
}
|
||||
} else if (type.isList) {
|
||||
buffer.write('($valueCode as List$nullOp)$nullOp.map((item) => ');
|
||||
_writeFromJsonExpression(buffer, type.items!, 'item');
|
||||
buffer.write(').toList()');
|
||||
} else if (type.isUnion) {
|
||||
final types = type.unionTypes;
|
||||
|
||||
// Write a check against each type, e.g.:
|
||||
// x is y ? new Either.tx(x) : (...)
|
||||
for (var i = 0; i < types.length; i++) {
|
||||
final isLast = i == types.length - 1;
|
||||
|
||||
// For the last item, if we're optional we won't wrap if in a check, as
|
||||
// the constructor will only be called if canParse() returned true, so
|
||||
// it'll the only remaining option.
|
||||
if (!isLast || isOptional) {
|
||||
_writeTypeCheckCondition(buffer, types[i], valueCode,
|
||||
isOptional: false);
|
||||
buffer.write(' ? ');
|
||||
}
|
||||
buffer.write('$dartTypeNotNullable.t${i + 1}(');
|
||||
_writeFromJsonExpression(buffer, types[i], valueCode);
|
||||
buffer.write(')');
|
||||
|
||||
if (!isLast) {
|
||||
buffer.write(' : ');
|
||||
} else if (isLast && isOptional) {
|
||||
buffer.write(' : null');
|
||||
}
|
||||
}
|
||||
} else if (type.isSpecType) {
|
||||
if (isOptional) {
|
||||
buffer.write('$valueCode == null ? null : ');
|
||||
}
|
||||
buffer.write(
|
||||
'$dartTypeNotNullable.fromJson($valueCode as Map<String, Object?>)');
|
||||
} else {
|
||||
throw 'Unable to type check $valueCode against $type';
|
||||
}
|
||||
}
|
||||
|
||||
/// Writes a static `fromJson` method that converts an object into a spec type
|
||||
/// by calling its fromMap constructor.
|
||||
///
|
||||
/// This is a helper method used as a tear-off since the constructor cannot be.
|
||||
void _writeFromJsonStaticMethod(
|
||||
IndentableStringBuffer buffer,
|
||||
String name,
|
||||
) =>
|
||||
buffer.writeIndentedln(
|
||||
'static $name fromJson(Map<String, Object?> obj) => $name.fromMap(obj);');
|
||||
|
||||
/// Writes a fromMap constructor to construct an object from a JSON map.
|
||||
void _writeFromMapConstructor(
|
||||
IndentableStringBuffer buffer,
|
||||
String name,
|
||||
JsonType type,
|
||||
Map<String, JsonType> properties, {
|
||||
bool callSuper = false,
|
||||
}) {
|
||||
buffer.writeIndented('$name.fromMap(Map<String, Object?> obj)');
|
||||
if (properties.isNotEmpty || callSuper) {
|
||||
buffer
|
||||
..writeln(':')
|
||||
..indent();
|
||||
var isFirst = true;
|
||||
for (final entry in properties.entries.sortedBy((e) => e.key)) {
|
||||
if (isFirst) {
|
||||
isFirst = false;
|
||||
} else {
|
||||
buffer.writeln(',');
|
||||
}
|
||||
|
||||
final propertyName = entry.key;
|
||||
final fieldName = _dartSafeName(propertyName);
|
||||
final propertyType = entry.value;
|
||||
final isOptional = !type.requiresField(propertyName);
|
||||
|
||||
buffer.writeIndented('$fieldName = ');
|
||||
_writeFromJsonExpression(buffer, propertyType, "obj['$propertyName']",
|
||||
isOptional: isOptional);
|
||||
}
|
||||
if (callSuper) {
|
||||
if (!isFirst) {
|
||||
buffer.writeln(',');
|
||||
}
|
||||
buffer.writeIndented('super.fromMap(obj)');
|
||||
}
|
||||
buffer.outdent();
|
||||
}
|
||||
buffer.writeln(';');
|
||||
}
|
||||
|
||||
/// Writes a toJson method to construct a JSON map for this class, recursively
|
||||
/// calling through base classes.
|
||||
void _writeToJsonMethod(
|
||||
IndentableStringBuffer buffer,
|
||||
String name,
|
||||
JsonType type,
|
||||
Map<String, JsonType> properties, {
|
||||
bool callSuper = false,
|
||||
}) {
|
||||
if (callSuper) {
|
||||
buffer.writeIndentedln('@override');
|
||||
}
|
||||
buffer
|
||||
..writeIndentedln('Map<String, Object?> toJson() => {')
|
||||
..indent();
|
||||
if (callSuper) {
|
||||
buffer.writeIndentedln('...super.toJson(),');
|
||||
}
|
||||
for (final entry in properties.entries.sortedBy((e) => e.key)) {
|
||||
final propertyName = entry.key;
|
||||
final fieldName = _dartSafeName(propertyName);
|
||||
final isOptional = !type.requiresField(propertyName);
|
||||
buffer.writeIndented('');
|
||||
if (isOptional) {
|
||||
buffer.write('if ($fieldName != null) ');
|
||||
}
|
||||
buffer.writeln("'$propertyName': $fieldName, ");
|
||||
}
|
||||
buffer
|
||||
..outdent()
|
||||
..writeIndentedln('};');
|
||||
}
|
||||
|
||||
/// Writes an expression that checks whether [valueCode] represents a [type].
|
||||
void _writeTypeCheckCondition(
|
||||
IndentableStringBuffer buffer, JsonType type, String valueCode,
|
||||
{required bool isOptional, bool invert = false}) {
|
||||
final baseType = type.aliasFor ?? type;
|
||||
final dartType = type.asDartType(isOptional: isOptional);
|
||||
|
||||
// When the expression is inverted, invert the operators so the generated
|
||||
// code is easier to read.
|
||||
final opBang = invert ? '!' : '';
|
||||
final opTrue = invert ? 'false' : 'true';
|
||||
final opIs = invert ? 'is!' : 'is';
|
||||
final opEquals = invert ? '!=' : '==';
|
||||
final opAnd = invert ? '||' : '&&';
|
||||
final opOr = invert ? '&&' : '||';
|
||||
final opEvery = invert ? 'any' : 'every';
|
||||
|
||||
if (baseType.isAny) {
|
||||
buffer.write(opTrue);
|
||||
} else if (dartType == 'Null') {
|
||||
buffer.write('$valueCode $opEquals null');
|
||||
} else if (baseType.isSimple) {
|
||||
buffer.write('$valueCode $opIs $dartType');
|
||||
} else if (type.isList) {
|
||||
buffer.write('($valueCode $opIs List');
|
||||
buffer.write(' $opAnd ($valueCode.$opEvery((item) => ');
|
||||
_writeTypeCheckCondition(buffer, type.items!, 'item',
|
||||
isOptional: false, invert: invert);
|
||||
buffer.write('))');
|
||||
buffer.write(')');
|
||||
} else if (type.isUnion) {
|
||||
final types = type.unionTypes;
|
||||
// To type check a union, we recursively check against each of its types.
|
||||
buffer.write('(');
|
||||
for (var i = 0; i < types.length; i++) {
|
||||
if (i != 0) {
|
||||
buffer.write(' $opOr ');
|
||||
}
|
||||
_writeTypeCheckCondition(buffer, types[i], valueCode,
|
||||
isOptional: false, invert: invert);
|
||||
}
|
||||
if (isOptional) {
|
||||
buffer.write(' $opOr $valueCode $opEquals null');
|
||||
}
|
||||
buffer.write(')');
|
||||
} else if (type.isSpecType) {
|
||||
buffer.write('$opBang${type.asDartType()}.canParse($valueCode)');
|
||||
} else {
|
||||
throw 'Unable to type check $valueCode against $type';
|
||||
}
|
||||
}
|
||||
|
||||
/// Writes the description for [type], looking at the base type from the
|
||||
/// DAP spec if necessary.
|
||||
void _writeTypeDescription(IndentableStringBuffer buffer, JsonType type) {
|
||||
// In the DAP spec, many of the descriptions are on one of the allOf types
|
||||
// rather than the type itself.
|
||||
final description = type.description ??
|
||||
type.allOf
|
||||
?.firstWhereOrNull((element) => element.description != null)
|
||||
?.description;
|
||||
|
||||
_writeDescription(buffer, description);
|
||||
}
|
||||
}
|
||||
|
||||
/// A [StringBuffer] with support for indenting.
|
||||
class IndentableStringBuffer extends StringBuffer {
|
||||
int _indentLevel = 0;
|
||||
final int _indentSpaces = 2;
|
||||
|
||||
int get totalIndent => _indentLevel * _indentSpaces;
|
||||
String get _indentString => ' ' * totalIndent;
|
||||
|
||||
void indent() => _indentLevel++;
|
||||
void outdent() => _indentLevel--;
|
||||
|
||||
void writeIndented(Object obj) {
|
||||
write(_indentString);
|
||||
write(obj);
|
||||
}
|
||||
|
||||
void writeIndentedln(Object obj) {
|
||||
write(_indentString);
|
||||
writeln(obj);
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -1,23 +0,0 @@
|
|||
debugAdapterProtocol.json is an unmodified copy of the DAP Specification,
|
||||
downloaded from:
|
||||
|
||||
https://raw.githubusercontent.com/microsoft/debug-adapter-protocol/gh-pages/debugAdapterProtocol.json
|
||||
|
||||
The licence for this file is included below. This accompanying file is the
|
||||
version of the specification that was used to generate a portion of the Dart
|
||||
code used to support the protocol.
|
||||
|
||||
To regenerate the generated code, run the script in "tool/dap/generate_all.dart"
|
||||
with no arguments. To download the latest version of the specification before
|
||||
regenerating the code, run the same script with the "--download" argument.
|
||||
|
||||
---
|
||||
|
||||
Copyright (c) Microsoft Corporation.
|
||||
|
||||
All rights reserved.
|
||||
|
||||
Distributed under the following terms:
|
||||
|
||||
1. Documentation is licensed under the Creative Commons Attribution 3.0 United States License. Code is licensed under the MIT License.
|
||||
2. This license does not grant you rights to use any trademarks or logos of Microsoft. For Microsoft’s general trademark guidelines, go to http://go.microsoft.com/fwlink/?LinkID=254653
|
|
@ -1,97 +0,0 @@
|
|||
// Copyright (c) 2021, 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:args/args.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:path/path.dart' as path;
|
||||
|
||||
import 'codegen.dart';
|
||||
import 'json_schema.dart';
|
||||
|
||||
Future<void> main(List<String> arguments) async {
|
||||
final args = argParser.parse(arguments);
|
||||
if (args[argHelp]) {
|
||||
print(argParser.usage);
|
||||
return;
|
||||
}
|
||||
|
||||
if (args[argDownload]) {
|
||||
await downloadSpec();
|
||||
}
|
||||
|
||||
final schemaContent = await File(specFile).readAsString();
|
||||
final schemaJson = jsonDecode(schemaContent);
|
||||
final schema = JsonSchema.fromJson(schemaJson);
|
||||
|
||||
final buffer = IndentableStringBuffer();
|
||||
CodeGenerator().writeAll(buffer, schema);
|
||||
final generatedCode = buffer.toString();
|
||||
await File(generatedCodeFile)
|
||||
.writeAsString('$codeFileHeader\n$generatedCode');
|
||||
|
||||
// Format the generated code.
|
||||
await Process.run(Platform.resolvedExecutable, ['format', generatedCodeFile]);
|
||||
}
|
||||
|
||||
const argDownload = 'download';
|
||||
const argHelp = 'help';
|
||||
const codeFileHeader = '''
|
||||
// Copyright (c) 2021, 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 code was auto-generated by tool/dap/generate_all.dart; do not hand-edit!
|
||||
|
||||
// ignore_for_file: prefer_void_to_null
|
||||
|
||||
import 'protocol_common.dart';
|
||||
import 'protocol_special.dart';
|
||||
''';
|
||||
final argParser = ArgParser()
|
||||
..addFlag(argHelp, hide: true)
|
||||
..addFlag(argDownload,
|
||||
negatable: false,
|
||||
abbr: 'd',
|
||||
help: 'Download latest version of the DAP spec before generating types');
|
||||
final generatedCodeFile =
|
||||
path.join(toolFolder, '../../lib/src/dap/protocol_generated.dart');
|
||||
final licenseFile = path.join(specFolder, 'debugAdapterProtocol.license.txt');
|
||||
final specFile = path.join(specFolder, 'debugAdapterProtocol.json');
|
||||
final specFolder = path.join(toolFolder, 'external_dap_spec');
|
||||
final specLicenseUri = Uri.parse(
|
||||
'https://raw.githubusercontent.com/microsoft/debug-adapter-protocol/main/License.txt');
|
||||
final specUri = Uri.parse(
|
||||
'https://raw.githubusercontent.com/microsoft/debug-adapter-protocol/gh-pages/debugAdapterProtocol.json');
|
||||
final toolFolder = path.dirname(Platform.script.toFilePath());
|
||||
|
||||
Future<void> downloadSpec() async {
|
||||
final specResp = await http.get(specUri);
|
||||
final licenseResp = await http.get(specLicenseUri);
|
||||
|
||||
assert(specResp.statusCode == 200);
|
||||
assert(licenseResp.statusCode == 200);
|
||||
|
||||
final licenseHeader = '''
|
||||
debugAdapterProtocol.json is an unmodified copy of the DAP Specification,
|
||||
downloaded from:
|
||||
|
||||
$specUri
|
||||
|
||||
The licence for this file is included below. This accompanying file is the
|
||||
version of the specification that was used to generate a portion of the Dart
|
||||
code used to support the protocol.
|
||||
|
||||
To regenerate the generated code, run the script in "tool/dap/generate_all.dart"
|
||||
with no arguments. To download the latest version of the specification before
|
||||
regenerating the code, run the same script with the "--download" argument.
|
||||
|
||||
---
|
||||
''';
|
||||
|
||||
await File(specFile).writeAsString(specResp.body);
|
||||
await File(licenseFile).writeAsString('$licenseHeader\n${licenseResp.body}');
|
||||
}
|
|
@ -1,90 +0,0 @@
|
|||
// Copyright (c) 2021, 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 'package:dap/dap.dart';
|
||||
|
||||
class JsonSchema {
|
||||
late final Uri dollarSchema;
|
||||
late final Map<String, JsonType> definitions;
|
||||
|
||||
JsonSchema.fromJson(Map<String, Object?> json) {
|
||||
dollarSchema = Uri.parse(json[r'$schema'] as String);
|
||||
definitions = (json['definitions'] as Map<String, Object?>).map((key,
|
||||
value) =>
|
||||
MapEntry(key, JsonType.fromJson(this, value as Map<String, Object?>)));
|
||||
}
|
||||
}
|
||||
|
||||
class JsonType {
|
||||
final JsonSchema root;
|
||||
final List<JsonType>? allOf;
|
||||
final List<JsonType>? oneOf;
|
||||
final String? description;
|
||||
final String? dollarRef;
|
||||
final List<String>? enumValues;
|
||||
final JsonType? items;
|
||||
final Map<String, JsonType>? properties;
|
||||
final List<String>? required;
|
||||
final String? title;
|
||||
final Either2<String, List<String>>? type;
|
||||
|
||||
JsonType.empty(this.root)
|
||||
: allOf = null,
|
||||
oneOf = null,
|
||||
description = null,
|
||||
dollarRef = null,
|
||||
enumValues = null,
|
||||
items = null,
|
||||
properties = null,
|
||||
required = null,
|
||||
title = null,
|
||||
type = null;
|
||||
|
||||
JsonType.fromJson(this.root, Map<String, Object?> json)
|
||||
: allOf = json['allOf'] == null
|
||||
? null
|
||||
: (json['allOf'] as List<Object?>)
|
||||
.cast<Map<String, Object?>>()
|
||||
.map((item) => JsonType.fromJson(root, item))
|
||||
.toList(),
|
||||
description = json['description'] as String?,
|
||||
dollarRef = json[r'$ref'] as String?,
|
||||
enumValues = (json['enum'] as List<Object?>?)?.cast<String>(),
|
||||
items = json['items'] == null
|
||||
? null
|
||||
: JsonType.fromJson(root, json['items'] as Map<String, Object?>),
|
||||
oneOf = json['oneOf'] == null
|
||||
? null
|
||||
: (json['oneOf'] as List<Object?>)
|
||||
.cast<Map<String, Object?>>()
|
||||
.map((item) => JsonType.fromJson(root, item))
|
||||
.toList(),
|
||||
properties = json['properties'] == null
|
||||
? null
|
||||
: (json['properties'] as Map<String, Object?>).map((key, value) =>
|
||||
MapEntry(key,
|
||||
JsonType.fromJson(root, value as Map<String, Object?>))),
|
||||
required = (json['required'] as List<Object?>?)?.cast<String>(),
|
||||
title = json['title'] as String?,
|
||||
type = json['type'] == null
|
||||
? null
|
||||
: json['type'] is String
|
||||
? Either2<String, List<String>>.t1(json['type'] as String)
|
||||
: Either2<String, List<String>>.t2(
|
||||
(json['type'] as List<Object?>).cast<String>());
|
||||
|
||||
/// Creates a dummy type to represent a type that exists outside of the
|
||||
/// generated code (in 'lib/src/dap/protocol_common.dart').
|
||||
JsonType.named(this.root, String name)
|
||||
: allOf = null,
|
||||
oneOf = null,
|
||||
description = null,
|
||||
dollarRef = '#/definitions/$name',
|
||||
enumValues = null,
|
||||
items = null,
|
||||
properties = null,
|
||||
required = null,
|
||||
title = null,
|
||||
type = null;
|
||||
}
|
|
@ -1,172 +0,0 @@
|
|||
// Copyright (c) 2021, 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 'package:collection/collection.dart';
|
||||
|
||||
import 'json_schema.dart';
|
||||
|
||||
String _toDartType(String type) {
|
||||
if (type.startsWith('#/definitions/')) {
|
||||
return type.replaceAll('#/definitions/', '');
|
||||
}
|
||||
switch (type) {
|
||||
case 'object':
|
||||
return 'Map<String, Object?>';
|
||||
case 'integer':
|
||||
return 'int';
|
||||
case 'number':
|
||||
return 'num';
|
||||
case 'string':
|
||||
return 'String';
|
||||
case 'boolean':
|
||||
return 'bool';
|
||||
case 'null':
|
||||
return 'Null';
|
||||
default:
|
||||
return type;
|
||||
}
|
||||
}
|
||||
|
||||
String _toDartUnionType(List<String> types) {
|
||||
const allLiteralTypes = {
|
||||
'array',
|
||||
'boolean',
|
||||
'integer',
|
||||
'null',
|
||||
'number',
|
||||
'object',
|
||||
'string'
|
||||
};
|
||||
if (types.length == 7 && allLiteralTypes.containsAll(types)) {
|
||||
return 'Object';
|
||||
}
|
||||
return 'Either${types.length}<${types.map(_toDartType).join(', ')}>';
|
||||
}
|
||||
|
||||
extension JsonSchemaExtensions on JsonSchema {
|
||||
JsonType typeFor(JsonType type) => type.dollarRef != null
|
||||
// TODO(dantup): Do we need to support more than just refs to definitions?
|
||||
? definitions[type.refName]!
|
||||
: type;
|
||||
|
||||
Map<String, JsonType> propertiesFor(JsonType type,
|
||||
{bool includeBase = true}) {
|
||||
// Merge this types direct properties with anything from the included
|
||||
// (allOf) types, but excluding those that come from the base class.
|
||||
final baseType = type.baseType;
|
||||
final includedBaseTypes =
|
||||
(type.allOf ?? []).where((t) => includeBase || t != baseType);
|
||||
final properties = {
|
||||
for (final other in includedBaseTypes) ...propertiesFor(typeFor(other)),
|
||||
...?type.properties,
|
||||
};
|
||||
|
||||
return properties;
|
||||
}
|
||||
}
|
||||
|
||||
extension JsonTypeExtensions on JsonType {
|
||||
String asDartType({bool isOptional = false}) {
|
||||
final dartType = dollarRef != null
|
||||
? _toDartType(dollarRef!)
|
||||
: oneOf != null
|
||||
? _toDartUnionType(oneOf!.map((item) => item.asDartType()).toList())
|
||||
: type == null
|
||||
? refName
|
||||
: type!.valueEquals('array')
|
||||
? 'List<${items!.asDartType()}>'
|
||||
: type!.map(_toDartType, _toDartUnionType);
|
||||
|
||||
return isOptional ? '$dartType?' : dartType;
|
||||
}
|
||||
|
||||
/// Whether this type can have any type of value (Object/dynamic/any).
|
||||
bool get isAny => asDartType() == 'Object';
|
||||
|
||||
/// Whether this type represents a List.
|
||||
bool get isList => type?.valueEquals('array') ?? false;
|
||||
|
||||
/// Whether this type is a simple value like `String`, `bool`, `int`.
|
||||
bool get isSimpleValue => isSimple && asDartType() != 'Map<String, Object?>';
|
||||
|
||||
/// If this type is an alias to a simple value type, returns that type.
|
||||
/// Otherwise, returns `null`.
|
||||
JsonType? get aliasFor {
|
||||
final targetType = dollarRef != null ? root.typeFor(this) : null;
|
||||
if (targetType == null) {
|
||||
return null;
|
||||
}
|
||||
return targetType.isSimpleValue ? targetType : null;
|
||||
}
|
||||
|
||||
/// Whether this type is a simple type that needs no special handling for
|
||||
/// deserialization (such as `String`, `bool`, `int`, `Map<String, Object?>`).
|
||||
bool get isSimple {
|
||||
const dartSimpleTypes = {
|
||||
'bool',
|
||||
'int',
|
||||
'num',
|
||||
'String',
|
||||
'Map<String, Object?>',
|
||||
'Null',
|
||||
};
|
||||
return type != null &&
|
||||
dartSimpleTypes.contains(type!.map(_toDartType, _toDartUnionType));
|
||||
}
|
||||
|
||||
/// Whether this type is a Union type using JSON schema's "oneOf" of where its
|
||||
/// [type] is a list of types.
|
||||
bool get isUnion =>
|
||||
oneOf != null || type != null && type!.map((_) => false, (_) => true);
|
||||
|
||||
/// Whether this type is a reference to another spec type (using `dollarRef`).
|
||||
bool get isSpecType => dollarRef != null;
|
||||
|
||||
/// Whether [propertyName] is a required for this type or its base types.
|
||||
bool requiresField(String propertyName) {
|
||||
if (required?.contains(propertyName) ?? false) {
|
||||
return true;
|
||||
}
|
||||
if (allOf?.any((type) => root.typeFor(type).requiresField(propertyName)) ??
|
||||
false) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// The name of the type that this one references.
|
||||
String get refName => dollarRef!.replaceAll('#/definitions/', '');
|
||||
|
||||
/// The literal value of this type, if it can have only one.
|
||||
///
|
||||
/// These are represented in the spec using an enum with only a single value.
|
||||
String? get literalValue => enumValues?.singleOrNull;
|
||||
|
||||
/// The base type for this type. Base types are inferred by a type using
|
||||
/// allOf and the first listed type being a reference (dollarRef) to another
|
||||
/// spec type.
|
||||
JsonType? get baseType {
|
||||
final all = allOf;
|
||||
if (all != null && all.length > 1 && all.first.dollarRef != null) {
|
||||
return all.first;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// The list of possible types allowed by this union.
|
||||
///
|
||||
/// May be represented using `oneOf` or a list of types in `type`.
|
||||
List<JsonType> get unionTypes {
|
||||
final types = oneOf ??
|
||||
// Fabricate a union for types where "type" is an array of literal types:
|
||||
// ['a', 'b']
|
||||
type!.map(
|
||||
(_) => throw 'unexpected non-union in isUnion condition',
|
||||
(types) =>
|
||||
types.map((t) => JsonType.fromJson(root, {'type': t})).toList(),
|
||||
)!;
|
||||
return types;
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue