mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
WIP Commits separated as follows: - Update lints in analysis_options files - Run `dart fix --apply` - Clean up leftover analysis issues - Run `dart format .` in the right places. Local analysis and testing passes. Checking CI now. Part of https://github.com/flutter/flutter/issues/178827 - Adoption of flutter_lints in examples/api coming in a separate change (cc @loic-sharma) ## Pre-launch Checklist - [ ] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [ ] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [ ] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [ ] I signed the [CLA]. - [ ] I listed at least one issue that this PR fixes in the description above. - [ ] I updated/added relevant documentation (doc comments with `///`). - [ ] I added new tests to check the change I am making, or this PR is [test-exempt]. - [ ] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [ ] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
193 lines
6.4 KiB
Dart
193 lines
6.4 KiB
Dart
// Copyright 2014 The Flutter Authors. 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:flutter_devicelab/framework/framework.dart';
|
|
import 'package:flutter_devicelab/framework/task_result.dart';
|
|
import 'package:flutter_devicelab/framework/utils.dart';
|
|
import 'package:path/path.dart' as path;
|
|
|
|
// the numbers below are prime, so that the totals don't seem round. :-)
|
|
const double todoCost = 1009.0; // about two average SWE days, in dollars
|
|
const double ignoreCost = 2003.0; // four average SWE days, in dollars
|
|
const double pythonCost = 3001.0; // six average SWE days, in dollars
|
|
const double skipCost =
|
|
2473.0; // 20 hours: 5 to fix the issue we're ignoring, 15 to fix the bugs we missed because the test was off
|
|
const double ignoreForFileCost = 2477.0; // similar thinking as skipCost
|
|
const double asDynamicCost = 2011.0; // a few days to refactor the code.
|
|
const double deprecationCost = 233.0; // a few hours to remove the old code.
|
|
const double legacyDeprecationCost = 9973.0; // a couple of weeks.
|
|
|
|
final RegExp todoPattern = RegExp(r'(?://|#) *TODO');
|
|
final RegExp ignorePattern = RegExp(r'// *ignore:');
|
|
final RegExp ignoreForFilePattern = RegExp(r'// *ignore_for_file:');
|
|
final RegExp asDynamicPattern = RegExp(r'\bas dynamic\b');
|
|
final RegExp deprecationPattern = RegExp(r'^ *@[dD]eprecated');
|
|
const Pattern globalsPattern = 'globals.';
|
|
const String legacyDeprecationPattern = '// flutter_ignore: deprecation_syntax, https';
|
|
|
|
Future<double> findCostsForFile(File file) async {
|
|
if (path.extension(file.path) == '.py') {
|
|
return pythonCost;
|
|
}
|
|
if (path.extension(file.path) != '.dart' &&
|
|
path.extension(file.path) != '.yaml' &&
|
|
path.extension(file.path) != '.sh') {
|
|
return 0.0;
|
|
}
|
|
final bool isTest = file.path.endsWith('_test.dart');
|
|
var total = 0.0;
|
|
for (final String line in await file.readAsLines()) {
|
|
if (line.contains(todoPattern)) {
|
|
total += todoCost;
|
|
}
|
|
if (line.contains(ignorePattern)) {
|
|
total += ignoreCost;
|
|
}
|
|
if (line.contains(ignoreForFilePattern)) {
|
|
total += ignoreForFileCost;
|
|
}
|
|
if (!isTest && line.contains(asDynamicPattern)) {
|
|
total += asDynamicCost;
|
|
}
|
|
if (line.contains(deprecationPattern)) {
|
|
total += deprecationCost;
|
|
}
|
|
if (line.contains(legacyDeprecationPattern)) {
|
|
total += legacyDeprecationCost;
|
|
}
|
|
if (isTest && line.contains('skip:') && !line.contains('[intended]')) {
|
|
total += skipCost;
|
|
}
|
|
}
|
|
return total;
|
|
}
|
|
|
|
Future<int> findGlobalsForFile(File file) async {
|
|
if (path.extension(file.path) != '.dart') {
|
|
return 0;
|
|
}
|
|
var total = 0;
|
|
for (final String line in await file.readAsLines()) {
|
|
if (line.contains(globalsPattern)) {
|
|
total += 1;
|
|
}
|
|
}
|
|
return total;
|
|
}
|
|
|
|
Future<double> findCostsForRepo() async {
|
|
final Process git = await startProcess('git', <String>[
|
|
'ls-files',
|
|
'--exclude',
|
|
'engine',
|
|
'--full-name',
|
|
flutterDirectory.path,
|
|
], workingDirectory: flutterDirectory.path);
|
|
var total = 0.0;
|
|
await for (final String entry
|
|
in git.stdout.transform<String>(utf8.decoder).transform<String>(const LineSplitter())) {
|
|
total += await findCostsForFile(File(path.join(flutterDirectory.path, entry)));
|
|
}
|
|
final int gitExitCode = await git.exitCode;
|
|
if (gitExitCode != 0) {
|
|
throw Exception('git exit with unexpected error code $gitExitCode');
|
|
}
|
|
return total;
|
|
}
|
|
|
|
Future<int> findGlobalsForTool() async {
|
|
final Process git = await startProcess('git', <String>[
|
|
'ls-files',
|
|
'--full-name',
|
|
path.join(flutterDirectory.path, 'packages', 'flutter_tools'),
|
|
], workingDirectory: flutterDirectory.path);
|
|
var total = 0;
|
|
await for (final String entry
|
|
in git.stdout.transform<String>(utf8.decoder).transform<String>(const LineSplitter())) {
|
|
total += await findGlobalsForFile(File(path.join(flutterDirectory.path, entry)));
|
|
}
|
|
final int gitExitCode = await git.exitCode;
|
|
if (gitExitCode != 0) {
|
|
throw Exception('git exit with unexpected error code $gitExitCode');
|
|
}
|
|
return total;
|
|
}
|
|
|
|
Future<int> countDependencies() async => _getCount(<String>{
|
|
...(await dependenciesAt(packageNames: const <String>['_flutter_packages'])),
|
|
...(await dependenciesAt(
|
|
packageNames: const <String>['flutter_tools'],
|
|
workingDirectory: path.join(flutterDirectory.path, 'packages', 'flutter_tools'),
|
|
)),
|
|
});
|
|
|
|
Future<Set<String>> dependenciesAt({
|
|
required List<String> packageNames,
|
|
String? workingDirectory,
|
|
}) async {
|
|
final String jsonOutput = await evalFlutter(
|
|
'pub',
|
|
options: <String>[
|
|
'deps',
|
|
'--json',
|
|
if (workingDirectory != null) ...<String>['-C', workingDirectory],
|
|
],
|
|
);
|
|
final json = jsonDecode(jsonOutput) as Map<String, dynamic>;
|
|
final packages = json['packages'] as List<dynamic>;
|
|
final Iterable<String> count = packages
|
|
.map((dynamic e) => e as Map<String, dynamic>)
|
|
.where((Map<String, dynamic> package) => packageNames.contains(package['name']))
|
|
.expand((Map<String, dynamic> element) => element['dependencies'] as List<dynamic>)
|
|
.map((dynamic e) => e as String);
|
|
return count.toSet();
|
|
}
|
|
|
|
Future<int> countConsumerDependencies() async => _getCount(
|
|
await dependenciesAt(
|
|
packageNames: <String>[
|
|
'flutter',
|
|
'flutter_test',
|
|
'flutter_driver',
|
|
'flutter_localizations',
|
|
'integration_test',
|
|
],
|
|
),
|
|
);
|
|
|
|
int _getCount(Set<String> deps) {
|
|
final int count = deps.length;
|
|
if (count < 2) {
|
|
throw Exception('"flutter pub deps --json" returned bogus output.');
|
|
}
|
|
return count;
|
|
}
|
|
|
|
const String _kCostBenchmarkKey = 'technical_debt_in_dollars';
|
|
const String _kNumberOfDependenciesKey = 'dependencies_count';
|
|
const String _kNumberOfConsumerDependenciesKey = 'consumer_dependencies_count';
|
|
const String _kNumberOfFlutterToolGlobals = 'flutter_tool_globals_count';
|
|
|
|
Future<void> main() async {
|
|
await task(() async {
|
|
return TaskResult.success(
|
|
<String, dynamic>{
|
|
_kCostBenchmarkKey: await findCostsForRepo(),
|
|
_kNumberOfDependenciesKey: await countDependencies(),
|
|
_kNumberOfConsumerDependenciesKey: await countConsumerDependencies(),
|
|
_kNumberOfFlutterToolGlobals: await findGlobalsForTool(),
|
|
},
|
|
benchmarkScoreKeys: <String>[
|
|
_kCostBenchmarkKey,
|
|
_kNumberOfDependenciesKey,
|
|
_kNumberOfConsumerDependenciesKey,
|
|
_kNumberOfFlutterToolGlobals,
|
|
],
|
|
);
|
|
});
|
|
}
|