mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00

* add trailing commas on list/map/parameters * add trailing commas on Invocation with nb of arg>1 * add commas for widget containing widgets * add trailing commas if instantiation contains trailing comma * revert bad change
223 lines
9.0 KiB
Dart
223 lines
9.0 KiB
Dart
// Copyright 2015 The Chromium 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:async';
|
|
|
|
import 'package:args/args.dart';
|
|
import 'package:yaml/yaml.dart' as yaml;
|
|
|
|
import '../base/common.dart';
|
|
import '../base/file_system.dart';
|
|
import '../base/utils.dart';
|
|
import '../cache.dart';
|
|
import '../globals.dart';
|
|
|
|
/// Common behavior for `flutter analyze` and `flutter analyze --watch`
|
|
abstract class AnalyzeBase {
|
|
AnalyzeBase(this.argResults);
|
|
|
|
/// The parsed argument results for execution.
|
|
final ArgResults argResults;
|
|
|
|
/// Called by [AnalyzeCommand] to start the analysis process.
|
|
Future<void> analyze();
|
|
|
|
void dumpErrors(Iterable<String> errors) {
|
|
if (argResults['write'] != null) {
|
|
try {
|
|
final RandomAccessFile resultsFile = fs.file(argResults['write']).openSync(mode: FileMode.write);
|
|
try {
|
|
resultsFile.lockSync();
|
|
resultsFile.writeStringSync(errors.join('\n'));
|
|
} finally {
|
|
resultsFile.close();
|
|
}
|
|
} catch (e) {
|
|
printError('Failed to save output to "${argResults['write']}": $e');
|
|
}
|
|
}
|
|
}
|
|
|
|
void writeBenchmark(Stopwatch stopwatch, int errorCount, int membersMissingDocumentation) {
|
|
const String benchmarkOut = 'analysis_benchmark.json';
|
|
final Map<String, dynamic> data = <String, dynamic>{
|
|
'time': stopwatch.elapsedMilliseconds / 1000.0,
|
|
'issues': errorCount,
|
|
'missingDartDocs': membersMissingDocumentation,
|
|
};
|
|
fs.file(benchmarkOut).writeAsStringSync(toPrettyJson(data));
|
|
printStatus('Analysis benchmark written to $benchmarkOut ($data).');
|
|
}
|
|
|
|
bool get isBenchmarking => argResults['benchmark'];
|
|
}
|
|
|
|
/// Return true if [fileList] contains a path that resides inside the Flutter repository.
|
|
/// If [fileList] is empty, then return true if the current directory resides inside the Flutter repository.
|
|
bool inRepo(List<String> fileList) {
|
|
if (fileList == null || fileList.isEmpty)
|
|
fileList = <String>[fs.path.current];
|
|
final String root = fs.path.normalize(fs.path.absolute(Cache.flutterRoot));
|
|
final String prefix = root + fs.path.separator;
|
|
for (String file in fileList) {
|
|
file = fs.path.normalize(fs.path.absolute(file));
|
|
if (file == root || file.startsWith(prefix))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
class PackageDependency {
|
|
// This is a map from dependency targets (lib directories) to a list
|
|
// of places that ask for that target (.packages or pubspec.yaml files)
|
|
Map<String, List<String>> values = <String, List<String>>{};
|
|
String canonicalSource;
|
|
void addCanonicalCase(String packagePath, String pubSpecYamlPath) {
|
|
assert(canonicalSource == null);
|
|
add(packagePath, pubSpecYamlPath);
|
|
canonicalSource = pubSpecYamlPath;
|
|
}
|
|
void add(String packagePath, String sourcePath) {
|
|
values.putIfAbsent(packagePath, () => <String>[]).add(sourcePath);
|
|
}
|
|
bool get hasConflict => values.length > 1;
|
|
bool get hasConflictAffectingFlutterRepo {
|
|
assert(fs.path.isAbsolute(Cache.flutterRoot));
|
|
for (List<String> targetSources in values.values) {
|
|
for (String source in targetSources) {
|
|
assert(fs.path.isAbsolute(source));
|
|
if (fs.path.isWithin(Cache.flutterRoot, source))
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
void describeConflict(StringBuffer result) {
|
|
assert(hasConflict);
|
|
final List<String> targets = values.keys.toList();
|
|
targets.sort((String a, String b) => values[b].length.compareTo(values[a].length));
|
|
for (String target in targets) {
|
|
final int count = values[target].length;
|
|
result.writeln(' $count ${count == 1 ? 'source wants' : 'sources want'} "$target":');
|
|
bool canonical = false;
|
|
for (String source in values[target]) {
|
|
result.writeln(' $source');
|
|
if (source == canonicalSource)
|
|
canonical = true;
|
|
}
|
|
if (canonical) {
|
|
result.writeln(' (This is the actual package definition, so it is considered the canonical "right answer".)');
|
|
}
|
|
}
|
|
}
|
|
String get target => values.keys.single;
|
|
}
|
|
|
|
class PackageDependencyTracker {
|
|
/// Packages whose source is defined in the vended SDK.
|
|
static const List<String> _vendedSdkPackages = <String>['analyzer', 'front_end', 'kernel'];
|
|
|
|
// This is a map from package names to objects that track the paths
|
|
// involved (sources and targets).
|
|
Map<String, PackageDependency> packages = <String, PackageDependency>{};
|
|
|
|
PackageDependency getPackageDependency(String packageName) {
|
|
return packages.putIfAbsent(packageName, () => PackageDependency());
|
|
}
|
|
|
|
/// Read the .packages file in [directory] and add referenced packages to [dependencies].
|
|
void addDependenciesFromPackagesFileIn(Directory directory) {
|
|
final String dotPackagesPath = fs.path.join(directory.path, '.packages');
|
|
final File dotPackages = fs.file(dotPackagesPath);
|
|
if (dotPackages.existsSync()) {
|
|
// this directory has opinions about what we should be using
|
|
final Iterable<String> lines = dotPackages
|
|
.readAsStringSync()
|
|
.split('\n')
|
|
.where((String line) => !line.startsWith(RegExp(r'^ *#')));
|
|
for (String line in lines) {
|
|
final int colon = line.indexOf(':');
|
|
if (colon > 0) {
|
|
final String packageName = line.substring(0, colon);
|
|
final String packagePath = fs.path.fromUri(line.substring(colon+1));
|
|
// Ensure that we only add `analyzer` and dependent packages defined in the vended SDK (and referred to with a local
|
|
// fs.path. directive). Analyzer package versions reached via transitive dependencies (e.g., via `test`) are ignored
|
|
// since they would produce spurious conflicts.
|
|
if (!_vendedSdkPackages.contains(packageName) || packagePath.startsWith('..'))
|
|
add(packageName, fs.path.normalize(fs.path.absolute(directory.path, packagePath)), dotPackagesPath);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void addCanonicalCase(String packageName, String packagePath, String pubSpecYamlPath) {
|
|
getPackageDependency(packageName).addCanonicalCase(packagePath, pubSpecYamlPath);
|
|
}
|
|
|
|
void add(String packageName, String packagePath, String dotPackagesPath) {
|
|
getPackageDependency(packageName).add(packagePath, dotPackagesPath);
|
|
}
|
|
|
|
void checkForConflictingDependencies(Iterable<Directory> pubSpecDirectories, PackageDependencyTracker dependencies) {
|
|
for (Directory directory in pubSpecDirectories) {
|
|
final String pubSpecYamlPath = fs.path.join(directory.path, 'pubspec.yaml');
|
|
final File pubSpecYamlFile = fs.file(pubSpecYamlPath);
|
|
if (pubSpecYamlFile.existsSync()) {
|
|
// we are analyzing the actual canonical source for this package;
|
|
// make sure we remember that, in case all the packages are actually
|
|
// pointing elsewhere somehow.
|
|
final yaml.YamlMap pubSpecYaml = yaml.loadYaml(fs.file(pubSpecYamlPath).readAsStringSync());
|
|
final String packageName = pubSpecYaml['name'];
|
|
final String packagePath = fs.path.normalize(fs.path.absolute(fs.path.join(directory.path, 'lib')));
|
|
dependencies.addCanonicalCase(packageName, packagePath, pubSpecYamlPath);
|
|
}
|
|
dependencies.addDependenciesFromPackagesFileIn(directory);
|
|
}
|
|
|
|
// prepare a union of all the .packages files
|
|
if (dependencies.hasConflicts) {
|
|
final StringBuffer message = StringBuffer();
|
|
message.writeln(dependencies.generateConflictReport());
|
|
message.writeln('Make sure you have run "pub upgrade" in all the directories mentioned above.');
|
|
if (dependencies.hasConflictsAffectingFlutterRepo) {
|
|
message.writeln(
|
|
'For packages in the flutter repository, try using "flutter update-packages" to do all of them at once.\n'
|
|
'If you need to actually upgrade them, consider "flutter update-packages --force-upgrade". '
|
|
'(This will update your pubspec.yaml files as well, so you may wish to do this on a separate branch.)'
|
|
);
|
|
}
|
|
message.write(
|
|
'If this does not help, to track down the conflict you can use '
|
|
'"pub deps --style=list" and "pub upgrade --verbosity=solver" in the affected directories.'
|
|
);
|
|
throwToolExit(message.toString());
|
|
}
|
|
}
|
|
|
|
bool get hasConflicts {
|
|
return packages.values.any((PackageDependency dependency) => dependency.hasConflict);
|
|
}
|
|
|
|
bool get hasConflictsAffectingFlutterRepo {
|
|
return packages.values.any((PackageDependency dependency) => dependency.hasConflictAffectingFlutterRepo);
|
|
}
|
|
|
|
String generateConflictReport() {
|
|
assert(hasConflicts);
|
|
final StringBuffer result = StringBuffer();
|
|
for (String package in packages.keys.where((String package) => packages[package].hasConflict)) {
|
|
result.writeln('Package "$package" has conflicts:');
|
|
packages[package].describeConflict(result);
|
|
}
|
|
return result.toString();
|
|
}
|
|
|
|
Map<String, String> asPackageMap() {
|
|
final Map<String, String> result = <String, String>{};
|
|
for (String package in packages.keys)
|
|
result[package] = packages[package].target;
|
|
return result;
|
|
}
|
|
}
|