Files
Paul Berry afcfbbeba8 Migrate developer experience packages to new constructor decl syntax.
(Part of https://github.com/dart-lang/sdk/issues/63288)

This change migrates the packages owned by the developer experience
team to use the new constructor declaration syntax, described in
https://github.com/dart-lang/language/blob/main/accepted/future-releases/primary-constructors/feature-specification.md#abbreviations-of-in-body-constructor-declarations.

This change was performed in an automated fashion, by (a) bumping the
packages' SDK constraints to `3.13.0-0`, (b) enabling the lints
`unnecessary_type_name_in_constructor` and
`unnecessary_const_in_enum_constructor`, (c) fixing the resulting lint
failures using `dart fix`, and then (d) reformatting the affected
files.

To ease code review, I've reverted unrelated formatting changes.

Since this change requires bumping SDK constaints to `3.13.0-0`, it
was only performed on packages that are *not* published on
pub. (Packages that *are* published on pub should remain on lower
language versions until at least after the stable version of 3.13 is
released, so that we don't block users on the stable channel from
receiving updates to those packages.)

Change-Id: Ibb4daebafd239da58251e838ea6a3f336a6a6964
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/505046
Commit-Queue: Paul Berry <paulberry@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
SLSA-Policy-Verified: SLSA Policy Verification Service <devtools-gerritcodereview-exitgate@google.com>
2026-05-27 14:52:58 -07:00

153 lines
5.3 KiB
Dart

// Copyright (c) 2023, 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 'package:analyzer/dart/analysis/analysis_context_collection.dart';
import 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer/dart/analysis/session.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/visitor.dart';
import 'package:analyzer/file_system/file_system.dart';
import 'package:analyzer/file_system/physical_file_system.dart';
import 'package:analyzer_testing/package_root.dart';
import 'package:http/http.dart' as http;
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
Future<void> main() async {
var tests = <AnnotatedTest>[];
// Note: Unauthenticated GitHub API calls are limited to 60 per hour
// so this script cannot be run often and will fail if there are over
// 60 unique issues listed.
await findFailingTestAnnotations(tests, packagePath: 'analysis_server');
await findFailingTestAnnotations(tests, packagePath: 'analyzer');
await findFailingTestAnnotations(tests, packagePath: 'analyzer_cli');
await findFailingTestAnnotations(tests, packagePath: 'analyzer_plugin');
var issueUris = tests.map((test) => test.issueUri).toSet();
print('Found ${tests.length} with ${issueUris.length} unique issues.');
print('Fetching test statuses from GitHub...');
var closedIssues = <Uri>{};
for (var issueUri in issueUris) {
var response = await http.get(issueUri);
if (response.statusCode != 200) {
throw 'Failed to call GitHub API: ${response.statusCode} - ${response.reasonPhrase}\n${response.body}';
}
var issueData = jsonDecode(response.body) as Map<String, Object?>;
if (issueData['state'] != 'open') {
closedIssues.add(issueUri);
}
}
print('Found ${closedIssues.length} closed issues:');
for (var tests in tests.where(
(test) => closedIssues.contains(test.issueUri),
)) {
var relativePath = pathContext.relative(tests.file.path, from: packageRoot);
print('$relativePath ${tests.testName} ${_formatUri(tests.issueUri)}');
}
}
final pathContext = provider.pathContext;
final provider = PhysicalResourceProvider.INSTANCE;
Future<void> findFailingTestAnnotations(
List<AnnotatedTest> tests, {
required String packagePath,
}) async {
var pkgRootPath = pathContext.normalize(packageRoot);
var testsPath = pathContext.join(pkgRootPath, packagePath, 'test');
var collection = AnalysisContextCollection(
includedPaths: <String>[testsPath],
resourceProvider: provider,
);
var contexts = collection.contexts;
if (contexts.length != 1) {
fail('The directory $testsPath contains multiple analysis contexts.');
}
var session = contexts[0].currentSession;
var directory = provider.getFolder(testsPath);
print('Searching for FailedTest/SkippedTest annotations in $packagePath...');
await _findFailingTestAnnotationsIn(session, testsPath, directory, tests);
}
Future<void> _findFailingTestAnnotationsIn(
AnalysisSession session,
String testDirPath,
Folder directory,
List<AnnotatedTest> tests,
) async {
var children = directory.getChildren();
children.sort((first, second) => first.shortName.compareTo(second.shortName));
for (var child in children) {
if (child is Folder) {
await _findFailingTestAnnotationsIn(session, testDirPath, child, tests);
} else if (child is File && child.shortName.endsWith('_test.dart')) {
var path = child.path;
var result = session.getParsedUnit(path);
if (result is! ParsedUnitResult) {
fail('Could not parse $path');
}
var unit = result.unit;
var diagnostics = result.diagnostics;
if (diagnostics.isNotEmpty) {
fail('Errors found when parsing $path');
}
var tracker = FailingTestAnnotationTracker(child);
unit.accept(tracker);
tests.addAll(tracker.annotatedTests);
}
}
}
String _formatUri(Uri uri) =>
uri.path.replaceAll('/repos/', '').replaceAll('/issues/', '#');
class AnnotatedTest {
final File file;
final String testName;
final Uri issueUri;
new(this.file, this.testName, this.issueUri);
}
/// A [RecursiveAstVisitor] that tracks nodes annotated with [FailingTest] or
/// [SkippedTest].
class FailingTestAnnotationTracker extends RecursiveAstVisitor<void> {
final annotatedTests = <AnnotatedTest>[];
final File file;
new(this.file);
@override
void visitAnnotation(Annotation node) {
if (node.name.name == 'FailingTest' || node.name.name == 'SkippedTest') {
var issue = node.arguments?.arguments
.whereType<NamedArgument>()
.where((arg) => arg.name.lexeme == 'issue')
.firstOrNull;
var issueUrl = (issue?.argumentExpression as SimpleStringLiteral?)?.value;
if (issueUrl != null && issueUrl.startsWith('https://github.com/')) {
var issueUri = Uri.parse(issueUrl);
var apiUri = issueUri.replace(
host: 'api.github.com',
pathSegments: ['repos', ...issueUri.pathSegments],
);
var method = node.parent as MethodDeclaration;
annotatedTests.add(AnnotatedTest(file, method.name.lexeme, apiUri));
}
}
super.visitAnnotation(node);
}
}