fb2d1171e5
Modifies `GeneratedDirectory.outputDirPath` and `GeneratedFile.outputPath` to be relative to the SDK's `pkg` directory rather than relative to the containing package. Accordingly, modifies the `GeneratedContent` methods `check`, `checkAll`, `generate`, `generateAll`, `output`, as well as the `DirectoryContentsComputer` and `FileContentsComputer` callbacks, so that their first parameter is the path to the `pkg` directory rather than the path to the containing package. Also modifies the `readApi` functions in `pkg/analysis_server` and `pkg/analyzer_plugin` to accept a path to the `pkg` directory rather than a path to the containing package, since these functions are called by code generation callbacks. These changes should make code generation logic easier to reason about. They also will make it easier to move the outputs of code generation from one package to another, which will pave the way for some follow-up work in which I intend to start sharing error message representations belonging to `pkg/analyzer`, `pkg/front_end`, and `pkg/_fe_analyzer_shared`. Change-Id: Ia9b369b16f2df931c8a472f91400f2c5a0b8be9d Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/438480 Reviewed-by: Samuel Rawlins <srawlins@google.com> Commit-Queue: Paul Berry <paulberry@google.com>
259 lines
8.7 KiB
Dart
259 lines
8.7 KiB
Dart
// Copyright (c) 2014, 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.
|
|
|
|
/// Code generation for the file "integration_test_methods.dart".
|
|
library;
|
|
|
|
import 'package:analyzer_utilities/tools.dart';
|
|
|
|
import 'api.dart';
|
|
import 'codegen_dart.dart';
|
|
import 'from_html.dart';
|
|
import 'to_html.dart';
|
|
|
|
final GeneratedFile target = GeneratedFile(
|
|
'analyzer_plugin/test/integration/support/integration_test_methods.dart',
|
|
(pkgRoot) async {
|
|
var visitor =
|
|
CodegenInttestMethodsVisitor('analyzer_plugin', readApi(pkgRoot));
|
|
return visitor.collectCode(visitor.visitApi);
|
|
});
|
|
|
|
/// Visitor that generates the code for integration_test_methods.dart
|
|
class CodegenInttestMethodsVisitor extends DartCodegenVisitor
|
|
with CodeGenerator {
|
|
/// The name of the package into which code is being generated.
|
|
final String packageName;
|
|
|
|
/// Visitor used to produce doc comments.
|
|
final ToHtmlVisitor toHtmlVisitor;
|
|
|
|
/// Code snippets concatenated to initialize all of the class fields.
|
|
List<String> fieldInitializationCode = <String>[];
|
|
|
|
/// Code snippets concatenated to produce the contents of the switch statement
|
|
/// for dispatching notifications.
|
|
List<String> notificationSwitchContents = <String>[];
|
|
|
|
CodegenInttestMethodsVisitor(this.packageName, Api api)
|
|
: toHtmlVisitor = ToHtmlVisitor(api),
|
|
super(api) {
|
|
codeGeneratorSettings.commentLineLength = 79;
|
|
codeGeneratorSettings.docCommentStartMarker = null;
|
|
codeGeneratorSettings.docCommentLineLeader = '/// ';
|
|
codeGeneratorSettings.docCommentEndMarker = null;
|
|
codeGeneratorSettings.languageName = 'dart';
|
|
}
|
|
|
|
/// Generate a function argument for the given parameter field.
|
|
String formatArgument(TypeObjectField field) =>
|
|
'${fieldDartType(field)} ${field.name}';
|
|
|
|
/// Figure out the appropriate Dart type for data having the given API
|
|
/// protocol [type].
|
|
String jsonType(TypeDecl? type) {
|
|
type = resolveTypeReferenceChain(type);
|
|
if (type is TypeEnum) {
|
|
return 'String';
|
|
} else if (type is TypeList) {
|
|
return 'List<${jsonType(type.itemType)}>';
|
|
} else if (type is TypeMap) {
|
|
return 'Map<String, ${jsonType(type.valueType)}>';
|
|
} else if (type is TypeObject) {
|
|
return 'Map<String, dynamic>';
|
|
} else if (type is TypeReference) {
|
|
switch (type.typeName) {
|
|
case 'String':
|
|
case 'int':
|
|
case 'bool':
|
|
// These types correspond exactly to Dart types
|
|
return type.typeName;
|
|
case 'object':
|
|
return 'Map<String, dynamic>';
|
|
default:
|
|
throw Exception(type.typeName);
|
|
}
|
|
} else if (type is TypeUnion) {
|
|
return 'Object';
|
|
} else {
|
|
throw Exception('Unexpected kind of TypeDecl');
|
|
}
|
|
}
|
|
|
|
@override
|
|
void visitApi() {
|
|
outputHeader(year: '2017');
|
|
writeln();
|
|
writeln('/// Convenience methods for running integration tests.');
|
|
writeln('library;');
|
|
writeln();
|
|
writeln("import 'dart:async';");
|
|
writeln();
|
|
for (var uri in api.types.importUris) {
|
|
write("import '");
|
|
write(uri);
|
|
writeln("';");
|
|
}
|
|
writeln("import 'package:$packageName/protocol/protocol_generated.dart';");
|
|
writeln(
|
|
"import 'package:$packageName/src/protocol/protocol_internal.dart';");
|
|
writeln("import 'package:test/test.dart';");
|
|
writeln();
|
|
writeln("import 'integration_tests.dart';");
|
|
writeln("import 'protocol_matchers.dart';");
|
|
writeln();
|
|
writeln('/// Convenience methods for running integration tests.');
|
|
writeln('abstract class IntegrationTestMixin {');
|
|
indent(() {
|
|
writeln('Server get server;');
|
|
super.visitApi();
|
|
writeln();
|
|
docComment(toHtmlVisitor.collectHtml(() {
|
|
toHtmlVisitor.writeln('Initialize the fields in InttestMixin, and');
|
|
toHtmlVisitor.writeln('ensure that notifications will be handled.');
|
|
}));
|
|
writeln('void initializeInttestMixin() {');
|
|
indent(() {
|
|
write(fieldInitializationCode.join());
|
|
});
|
|
writeln('}');
|
|
writeln();
|
|
docComment(toHtmlVisitor.collectHtml(() {
|
|
toHtmlVisitor.writeln('Dispatch the notification named [event], and');
|
|
toHtmlVisitor.writeln('containing parameters [params], to the');
|
|
toHtmlVisitor.writeln('appropriate stream.');
|
|
}));
|
|
writeln('void dispatchNotification(String event, params) {');
|
|
indent(() {
|
|
writeln('var decoder = ResponseDecoder(null);');
|
|
writeln('switch (event) {');
|
|
indent(() {
|
|
write(notificationSwitchContents.join());
|
|
writeln('default:');
|
|
indent(() {
|
|
writeln("fail('Unexpected notification: \$event');");
|
|
});
|
|
});
|
|
writeln('}');
|
|
});
|
|
writeln('}');
|
|
});
|
|
writeln('}');
|
|
}
|
|
|
|
@override
|
|
void visitNotification(Notification notification) {
|
|
var streamName =
|
|
camelJoin(['on', notification.domainName, notification.event]);
|
|
var className = camelJoin(
|
|
[notification.domainName, notification.event, 'params'],
|
|
doCapitalize: true);
|
|
writeln();
|
|
docComment(toHtmlVisitor.collectHtml(() {
|
|
toHtmlVisitor.translateHtml(notification.html);
|
|
toHtmlVisitor.describePayload(notification.params, 'Parameters');
|
|
}));
|
|
writeln('late Stream<$className> $streamName;');
|
|
writeln();
|
|
docComment(toHtmlVisitor.collectHtml(() {
|
|
toHtmlVisitor.write('Stream controller for [$streamName].');
|
|
}));
|
|
writeln('late StreamController<$className> _$streamName;');
|
|
fieldInitializationCode.add(collectCode(() {
|
|
writeln('_$streamName = StreamController<$className>(sync: true);');
|
|
writeln('$streamName = _$streamName.stream.asBroadcastStream();');
|
|
}));
|
|
notificationSwitchContents.add(collectCode(() {
|
|
writeln("case '${notification.longEvent}':");
|
|
indent(() {
|
|
var paramsValidator = camelJoin(
|
|
['is', notification.domainName, notification.event, 'params']);
|
|
writeln('outOfTestExpect(params, $paramsValidator);');
|
|
String constructorCall;
|
|
if (notification.params == null) {
|
|
constructorCall = '$className()';
|
|
} else {
|
|
constructorCall = "$className.fromJson(decoder, 'params', params)";
|
|
}
|
|
writeln('_$streamName.add($constructorCall);');
|
|
writeln('break;');
|
|
});
|
|
}));
|
|
}
|
|
|
|
@override
|
|
void visitRequest(Request request) {
|
|
var methodName = camelJoin(['send', request.domainName, request.method]);
|
|
var args = <String>[];
|
|
var optionalArgs = <String>[];
|
|
var params = request.params;
|
|
if (params != null) {
|
|
for (var field in params.fields) {
|
|
if (field.optional) {
|
|
optionalArgs.add(formatArgument(field));
|
|
} else {
|
|
args.add(formatArgument(field));
|
|
}
|
|
}
|
|
}
|
|
if (optionalArgs.isNotEmpty) {
|
|
args.add('{${optionalArgs.join(', ')}}');
|
|
}
|
|
writeln();
|
|
docComment(toHtmlVisitor.collectHtml(() {
|
|
toHtmlVisitor.translateHtml(request.html);
|
|
toHtmlVisitor.describePayload(request.params, 'Parameters');
|
|
toHtmlVisitor.describePayload(request.result, 'Returns');
|
|
}));
|
|
if (request.deprecated) {
|
|
writeln('@deprecated');
|
|
}
|
|
String? resultClass;
|
|
String? futureClass;
|
|
if (request.result == null) {
|
|
futureClass = 'Future';
|
|
} else {
|
|
resultClass = camelJoin([request.domainName, request.method, 'result'],
|
|
doCapitalize: true);
|
|
futureClass = 'Future<$resultClass>';
|
|
}
|
|
writeln('$futureClass $methodName(${args.join(', ')}) async {');
|
|
indent(() {
|
|
var requestClass = camelJoin(
|
|
[request.domainName, request.method, 'params'],
|
|
doCapitalize: true);
|
|
var paramsVar = 'null';
|
|
var params = request.params;
|
|
if (params != null) {
|
|
paramsVar = 'params';
|
|
var args = <String>[];
|
|
var optionalArgs = <String>[];
|
|
for (var field in params.fields) {
|
|
if (field.optional) {
|
|
optionalArgs.add('${field.name}: ${field.name}');
|
|
} else {
|
|
args.add(field.name);
|
|
}
|
|
}
|
|
args.addAll(optionalArgs);
|
|
writeln('var params = $requestClass(${args.join(', ')}).toJson();');
|
|
}
|
|
var methodJson = "'${request.longMethod}'";
|
|
writeln('var result = await server.send($methodJson, $paramsVar);');
|
|
if (request.result != null) {
|
|
var kind = 'null';
|
|
if (requestClass == 'EditGetRefactoringParams') {
|
|
kind = 'kind';
|
|
}
|
|
writeln('var decoder = ResponseDecoder($kind);');
|
|
writeln("return $resultClass.fromJson(decoder, 'result', result);");
|
|
} else {
|
|
writeln('outOfTestExpect(result, isNull);');
|
|
writeln('return null;');
|
|
}
|
|
});
|
|
writeln('}');
|
|
}
|
|
}
|