blob: 9544cc1b2a1e917648b0da57ea4e96a4df9b505e [file] [log] [blame]
// Copyright 2023 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 'package:file/file.dart';
import 'package:path/path.dart' as p;
import 'package:yaml/yaml.dart';
import 'common/package_command.dart';
// TODO(devoncarew): figure out how to test this
/// Update the main analysis_options.yaml file using the latest set of lints
/// from package:dart_flutter_team_lints/analysis_options.yaml.
class UpdateAnalysisOptionsCommand extends PackageCommand {
/// Creates an instance of the update-analysis-options command.
UpdateAnalysisOptionsCommand(super.packagesDir);
@override
final String name = 'update-analysis-options';
@override
final String description =
'Update the main analysis_options.yaml file using the latest set of '
'lints from package:dart_flutter_team_lints/analysis_options.yaml.';
@override
Future<void> run() async {
final FileSystem fileSystem = packagesDir.fileSystem;
final Directory rootDir = packagesDir.parent;
final File optionsFile = rootDir.childFile('analysis_options.yaml');
print('Updating ${optionsFile.basename}:');
final Directory toolDir =
rootDir.childDirectory('script').childDirectory('tool');
final Map<String, Directory> packages = _findPackageConfig(toolDir)!;
final _Lints lints = _Lints.readFrom(
'package:dart_flutter_team_lints/analysis_options.yaml',
packages,
fileSystem: fileSystem,
);
void printLints(_Lints lints) {
if (lints.parent != null) {
printLints(lints.parent!);
}
print(' ${lints.include} [${lints.lints.length} lints]');
}
printLints(lints);
final StringBuffer out = StringBuffer();
final List<String> optionsAsList = optionsFile.readAsLinesSync();
// copy over the preamble, up to ' rules:'
int index = 0;
while (optionsAsList[index] != ' rules:') {
out.writeln(optionsAsList[index]);
index++;
}
out.writeln(optionsAsList[index]);
// copy the included lints
void copyLints(_Lints lints) {
if (lints.parent != null) {
copyLints(lints.parent!);
}
out.writeln(' # ${lints.include} [${lints.lints.length} lints]');
for (final String lint in lints.lints) {
optionsAsList.remove(' - $lint');
final int location = optionsAsList
.indexWhere((String line) => line.startsWith(' # - $lint'));
final String line =
location == -1 ? ' - $lint' : optionsAsList.removeAt(location);
out.writeln(line);
}
out.writeln();
}
copyLints(lints);
// copy the additional customization, from ' # Additional customizations'
int location = optionsAsList.indexWhere(
(String line) => line.contains('# Additional customizations'));
while (location < optionsAsList.length) {
out.writeln(optionsAsList[location]);
location++;
}
// update the original file
optionsFile.writeAsStringSync(out.toString());
print('Wrote update lints to ${optionsFile.path}');
}
}
Map<String, Directory>? _findPackageConfig(Directory dir) {
final File configFile =
dir.childDirectory('.dart_tool').childFile('package_config.json');
if (configFile.existsSync()) {
return _parseConfigFile(configFile);
} else {
return null;
}
}
Map<String, Directory>? _parseConfigFile(File configFile) {
final Map<String, dynamic> json =
jsonDecode(configFile.readAsStringSync()) as Map<String, dynamic>;
final List<Map<String, dynamic>> packages =
(json['packages'] as List<dynamic>).cast<Map<String, dynamic>>();
return Map<String, Directory>.fromIterable(
packages,
key: (dynamic package) =>
(package as Map<String, dynamic>)['name'] as String,
value: (dynamic package) {
final String rootUri =
(package as Map<String, dynamic>)['rootUri'] as String;
final String filePath = Uri.parse(rootUri).toFilePath();
if (p.isRelative(filePath)) {
return configFile.fileSystem
.directory(p.normalize(p.join(configFile.parent.path, filePath)));
} else {
return configFile.fileSystem.directory(filePath);
}
},
);
}
class _Lints {
_Lints._({
this.parent,
required this.include,
required this.lints,
});
static _Lints readFrom(
String include,
Map<String, Directory> packages, {
required FileSystem fileSystem,
}) {
// "package:lints/recommended.yaml"
final Uri uri = Uri.parse(include);
final String package = uri.pathSegments[0];
final String filePath = uri.pathSegments[1];
final Directory dir = packages[package]!;
final File configFile = fileSystem.file(p.join(dir.path, 'lib', filePath));
final YamlMap yaml = loadYaml(configFile.readAsStringSync()) as YamlMap;
final String? localInclude = yaml['include'] as String?;
final YamlList lints = (yaml['linter'] as YamlMap?)?['rules'] as YamlList;
return _Lints._(
parent: localInclude == null
? null
: _Lints.readFrom(localInclude, packages, fileSystem: fileSystem),
include: include,
lints: lints.cast<String>().toList()..sort(),
);
}
final _Lints? parent;
final String include;
final List<String> lints;
}