Files
sdk/pkg/analysis_server/tool/spec/codegen_matchers.dart
T
Danny Tuppeny b0d4ab8ef9 [analysis_server] Move test/integration to integration_test
This was mostly a rename/move of the folder (and the analysis server updated all references), but I also had to:

- add `integration_test/analysis_options.yaml` to import from `../test` to get the same lint ignores
- update paths of exclusions in `verify_sorted_test.dart`

By moving all of the tests that start the server out-of-process out of test, we can:

1. Use "dart test" to just run the faster tests ("dart test test") and get functionality of the pkg:test runner (for example running tests concurrently and JSON output)
2. Allow VS Code to spawn different debug sessions for the "test" and "integration_test" folder, which means we can use a `preLaunchTask` to trigger compilation of the analysis server from source whenever running integration tests (avoiding having to compile manually, or run from source in a way that compiles a new server for each test suite)

Change-Id: I37cc03dc32d08c5b51a2eab79f6338bb079b32ac
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/434801
Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
Reviewed-by: Samuel Rawlins <srawlins@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
2025-06-17 12:54:41 -07:00

193 lines
5.2 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 "matchers.dart".
library;
import 'package:analyzer_utilities/tools.dart';
import 'api.dart';
import 'from_html.dart';
import 'implied_types.dart';
import 'to_html.dart';
final GeneratedFile target = GeneratedFile(
'integration_test/support/protocol_matchers.dart',
(String pkgPath) async {
var visitor = CodegenMatchersVisitor(readApi(pkgPath));
return visitor.collectCode(visitor.visitApi);
},
);
class CodegenMatchersVisitor extends HierarchicalApiVisitor with CodeGenerator {
/// Visitor used to produce doc comments.
final ToHtmlVisitor toHtmlVisitor;
/// Short human-readable string describing the context of the matcher being
/// created.
late String context;
CodegenMatchersVisitor(super.api) : toHtmlVisitor = ToHtmlVisitor(api) {
codeGeneratorSettings.commentLineLength = 79;
codeGeneratorSettings.docCommentStartMarker = null;
codeGeneratorSettings.docCommentLineLeader = '/// ';
codeGeneratorSettings.docCommentEndMarker = null;
codeGeneratorSettings.languageName = 'dart';
}
/// Create a matcher for the part of the API called [name], optionally
/// clarified by [nameSuffix]. The matcher should verify that its input
/// matches the given [type].
void makeMatcher(ImpliedType impliedType) {
context = impliedType.humanReadableName;
var impliedTypeType = impliedType.type;
docComment(
toHtmlVisitor.collectHtml(() {
toHtmlVisitor.p(() {
toHtmlVisitor.write(context);
});
if (impliedTypeType != null) {
toHtmlVisitor.showType(null, impliedTypeType);
}
}),
);
write('final Matcher ${camelJoin(['is', impliedType.camelName])} = ');
if (impliedTypeType == null) {
write('isNull');
} else {
visitTypeDecl(impliedTypeType);
}
writeln(';');
writeln();
}
/// Generate a map describing the given set of fields, for use as the
/// 'requiredFields' or 'optionalFields' argument to the [MatchesJsonObject]
/// constructor.
void outputObjectFields(Iterable<TypeObjectField> fields) {
if (fields.isEmpty) {
write('null');
return;
}
writeln('{');
indent(() {
var commaNeeded = false;
for (var field in fields) {
if (commaNeeded) {
writeln(',');
}
write("'${field.name}': ");
if (field.value != null) {
write("equals('${field.value}')");
} else {
visitTypeDecl(field.type);
}
commaNeeded = true;
}
writeln();
});
write('}');
}
@override
void visitApi() {
outputHeader(year: '2017');
writeln();
writeln('/// Matchers for data types defined in the analysis server API.');
writeln('library;');
writeln();
writeln("import 'package:test/test.dart';");
writeln();
writeln("import 'integration_tests.dart';");
writeln();
writeln('// ignore_for_file: flutter_style_todos');
writeln();
var impliedTypes = computeImpliedTypes(api).values.toList();
impliedTypes.sort(
(ImpliedType first, ImpliedType second) =>
first.camelName.compareTo(second.camelName),
);
for (var impliedType in impliedTypes) {
makeMatcher(impliedType);
}
}
@override
void visitTypeEnum(TypeEnum typeEnum) {
writeln("MatchesEnum('$context', [");
indent(() {
var commaNeeded = false;
for (var value in typeEnum.values) {
if (commaNeeded) {
writeln(',');
}
write("'${value.value}'");
commaNeeded = true;
}
writeln();
});
write('])');
}
@override
void visitTypeList(TypeList typeList) {
write('isListOf(');
visitTypeDecl(typeList.itemType);
write(')');
}
@override
void visitTypeMap(TypeMap typeMap) {
write('isMapOf(');
visitTypeDecl(typeMap.keyType);
write(', ');
visitTypeDecl(typeMap.valueType);
write(')');
}
@override
void visitTypeObject(TypeObject typeObject) {
writeln('LazyMatcher(() => MatchesJsonObject(');
indent(() {
write("'$context', ");
var requiredFields = typeObject.fields.where(
(TypeObjectField field) => !field.optional,
);
outputObjectFields(requiredFields);
var optionalFields =
typeObject.fields
.where((TypeObjectField field) => field.optional)
.toList();
if (optionalFields.isNotEmpty) {
write(', optionalFields: ');
outputObjectFields(optionalFields);
}
});
write('))');
}
@override
void visitTypeReference(TypeReference typeReference) {
var typeName = typeReference.typeName;
if (typeName == 'long') {
typeName = 'int';
}
write(camelJoin(['is', typeName]));
}
@override
void visitTypeUnion(TypeUnion typeUnion) {
var commaNeeded = false;
write('isOneOf([');
for (var choice in typeUnion.choices) {
if (commaNeeded) {
write(', ');
}
visitTypeDecl(choice);
commaNeeded = true;
}
write('])');
}
}