flutter_flutter/dev/tools/mega_gallery.dart
Kate Lovett 9d96df2364
Modernize framework lints (#179089)
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
2025-11-26 01:10:39 +00:00

212 lines
6.3 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:io';
import 'package:args/args.dart';
import 'package:path/path.dart' as path;
import 'package:yaml_edit/yaml_edit.dart';
/// If no `copies` param is passed in, we scale the generated app up to 60k lines.
const int kTargetLineCount = 60 * 1024;
/// Make `n` copies of flutter_gallery.
void main(List<String> args) {
// If we're run from the `tools` dir, set the cwd to the repo root.
if (path.basename(Directory.current.path) == 'tools') {
Directory.current = Directory.current.parent.parent;
}
final argParser = ArgParser();
argParser.addOption('out', mandatory: true);
argParser.addOption('copies');
argParser.addFlag('delete', negatable: false);
argParser.addFlag('help', abbr: 'h', negatable: false);
final ArgResults results = argParser.parse(args);
if (results['help'] as bool) {
print('Generate n copies of flutter_gallery.\n');
print('usage: dart mega_gallery.dart <options>');
print(argParser.usage);
exit(0);
}
final source = Directory(_normalize('dev/integration_tests/flutter_gallery'));
final outParent = Directory(_normalize(results['out'] as String));
final out = Directory(path.join(outParent.path, 'packages'));
if (results['delete'] as bool) {
if (outParent.existsSync()) {
print('Deleting ${outParent.path}');
outParent.deleteSync(recursive: true);
}
exit(0);
}
if (!results.wasParsed('out')) {
print('The --out parameter is required.');
print(argParser.usage);
exit(1);
}
int copies;
if (!results.wasParsed('copies')) {
final SourceStats stats = getStatsFor(_dir(source, 'lib'));
copies = (kTargetLineCount / stats.lines).round();
} else {
copies = int.parse(results['copies'] as String);
}
print('Making $copies copies of flutter_gallery.');
print('');
print('Stats:');
print(' packages/flutter : ${getStatsFor(Directory("packages/flutter"))}');
print(
' dev/integration_tests/flutter_gallery : ${getStatsFor(Directory("dev/integration_tests/flutter_gallery"))}',
);
final Directory lib = _dir(out, 'lib');
if (lib.existsSync()) {
lib.deleteSync(recursive: true);
}
// Copy everything that's not a symlink, dot directory, or build/.
_copy(source, out);
// Make n - 1 copies.
for (var i = 1; i < copies; i++) {
_copyGallery(out, i);
}
// Create a new entry-point.
_createEntry(_file(out, 'lib/main.dart'), copies);
// Update the pubspec.
final String pubspec = _file(Directory(''), 'pubspec.yaml').readAsStringSync();
final yamlEditor = YamlEditor(pubspec);
yamlEditor.update(<String>['workspace'], <String>['packages']);
File(path.join(outParent.path, 'pubspec.yaml')).writeAsStringSync(yamlEditor.toString());
// Replace the (flutter_gallery specific) analysis_options.yaml file with a default one.
_file(out, 'analysis_options.yaml').writeAsStringSync('''
analyzer:
errors:
# See analysis_options.yaml in the flutter root for context.
deprecated_member_use: ignore
deprecated_member_use_from_same_package: ignore
''');
_file(out, '.dartignore').writeAsStringSync('');
// Count source lines and number of files; tell how to run it.
print(' ${path.relative(results["out"] as String)} : ${getStatsFor(out)}');
}
// TODO(devoncarew): Create an entry-point that builds a UI with all `n` copies.
void _createEntry(File mainFile, int copies) {
final imports = StringBuffer();
for (var i = 1; i < copies; i++) {
imports.writeln('// ignore: unused_import');
imports.writeln("import 'gallery_$i/main.dart' as main_$i;");
}
final contents =
'''
// 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 'package:flutter/widgets.dart';
import 'gallery/app.dart';
${imports.toString().trim()}
void main() {
runApp(const GalleryApp());
}
''';
mainFile.writeAsStringSync(contents);
}
void _copyGallery(Directory galleryDir, int index) {
final Directory lib = _dir(galleryDir, 'lib');
final Directory dest = _dir(lib, 'gallery_$index');
dest.createSync();
// Copy demo/, gallery/, and main.dart.
_copy(_dir(lib, 'demo'), _dir(dest, 'demo'));
_copy(_dir(lib, 'gallery'), _dir(dest, 'gallery'));
_file(dest, 'main.dart').writeAsBytesSync(_file(lib, 'main.dart').readAsBytesSync());
}
void _copy(Directory source, Directory target) {
if (!target.existsSync()) {
target.createSync(recursive: true);
}
for (final FileSystemEntity entity in source.listSync(followLinks: false)) {
final String name = path.basename(entity.path);
switch (entity) {
case Directory() when name != 'build' && !name.startsWith('.'):
_copy(entity, Directory(path.join(target.path, name)));
case File() when name != '.packages' && name != 'pubspec.lock':
final dest = File(path.join(target.path, name));
dest.writeAsBytesSync(entity.readAsBytesSync());
}
}
}
Directory _dir(Directory parent, String name) => Directory(path.join(parent.path, name));
File _file(Directory parent, String name) => File(path.join(parent.path, name));
String _normalize(String filePath) => path.normalize(path.absolute(filePath));
class SourceStats {
int files = 0;
int lines = 0;
@override
String toString() => '${_comma(files).padLeft(3)} files, ${_comma(lines).padLeft(6)} lines';
}
SourceStats getStatsFor(Directory dir, [SourceStats? stats]) {
stats ??= SourceStats();
for (final FileSystemEntity entity in dir.listSync(followLinks: false)) {
final String name = path.basename(entity.path);
if (entity is File && name.endsWith('.dart')) {
stats.files += 1;
stats.lines += _lineCount(entity);
} else if (entity is Directory && !name.startsWith('.')) {
getStatsFor(entity, stats);
}
}
return stats;
}
int _lineCount(File file) {
return file.readAsLinesSync().where((String line) {
line = line.trim();
if (line.isEmpty || line.startsWith('//')) {
return false;
}
return true;
}).length;
}
String _comma(int count) {
final str = count.toString();
if (str.length > 3) {
return '${str.substring(0, str.length - 3)},${str.substring(str.length - 3)}';
}
return str;
}