blob: 68dae5442e4db003c5dc74aa1958a972071e7fe5 [file] [log] [blame]
Ian Hickson449f4a62019-11-27 15:04:02 -08001// Copyright 2014 The Flutter Authors. All rights reserved.
Alexander Aprelev391e91c2018-08-30 07:30:25 -07002// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
Jonah Williamsf6f59c52021-04-16 14:29:32 -07005import 'dart:async';
Alexander Aprelev391e91c2018-08-30 07:30:25 -07006import 'dart:convert';
Ian Hicksoneae05c72019-11-14 13:19:40 -08007import 'dart:core' hide print;
Ian Hicksoneae05c72019-11-14 13:19:40 -08008import 'dart:io' hide exit;
Dan Field24f39d42020-01-02 11:47:28 -08009import 'dart:typed_data';
Alexander Aprelev391e91c2018-08-30 07:30:25 -070010
Darren Austin807ca682021-08-25 14:56:03 -070011import 'package:analyzer/dart/analysis/features.dart';
12import 'package:analyzer/dart/analysis/results.dart';
13import 'package:analyzer/dart/analysis/utilities.dart';
14import 'package:analyzer/dart/ast/ast.dart';
15import 'package:analyzer/dart/ast/visitor.dart';
Dan Field24f39d42020-01-02 11:47:28 -080016import 'package:crypto/crypto.dart';
Francisco Magdaleno04ea3182020-01-02 09:25:59 -080017import 'package:meta/meta.dart';
Dan Field24f39d42020-01-02 11:47:28 -080018import 'package:path/path.dart' as path;
Alexander Aprelev391e91c2018-08-30 07:30:25 -070019
Jonah Williamsf6f59c52021-04-16 14:29:32 -070020import 'allowlist.dart';
Alexander Aprelev391e91c2018-08-30 07:30:25 -070021import 'run_command.dart';
Dan Field24f39d42020-01-02 11:47:28 -080022import 'utils.dart';
Alexander Aprelev391e91c2018-08-30 07:30:25 -070023
Alexander Aprelev391e91c2018-08-30 07:30:25 -070024final String flutterRoot = path.dirname(path.dirname(path.dirname(path.fromUri(Platform.script))));
25final String flutter = path.join(flutterRoot, 'bin', Platform.isWindows ? 'flutter.bat' : 'flutter');
Kate Lovett616f9bc2021-08-30 17:41:02 -050026final String flutterPackages = path.join(flutterRoot, 'packages');
Dan Fieldab0a3352021-12-12 13:05:03 -080027final String flutterExamples = path.join(flutterRoot, 'examples');
Alexander Aprelev391e91c2018-08-30 07:30:25 -070028final String dart = path.join(flutterRoot, 'bin', 'cache', 'dart-sdk', 'bin', Platform.isWindows ? 'dart.exe' : 'dart');
29final String pub = path.join(flutterRoot, 'bin', 'cache', 'dart-sdk', 'bin', Platform.isWindows ? 'pub.bat' : 'pub');
30final String pubCache = path.join(flutterRoot, '.pub-cache');
31
32/// When you call this, you can pass additional arguments to pass custom
33/// arguments to flutter analyze. For example, you might want to call this
34/// script with the parameter --dart-sdk to use custom dart sdk.
35///
36/// For example:
37/// bin/cache/dart-sdk/bin/dart dev/bots/analyze.dart --dart-sdk=/tmp/dart-sdk
Ian Hicksoneae05c72019-11-14 13:19:40 -080038Future<void> main(List<String> arguments) async {
Ian Hickson449f4a62019-11-27 15:04:02 -080039 print('$clock STARTING ANALYSIS');
Ian Hicksoneae05c72019-11-14 13:19:40 -080040 try {
41 await run(arguments);
42 } on ExitException catch (error) {
43 error.apply();
44 }
Dan Field24f39d42020-01-02 11:47:28 -080045 print('$clock ${bold}Analysis successful.$reset');
Ian Hicksoneae05c72019-11-14 13:19:40 -080046}
47
48Future<void> run(List<String> arguments) async {
Ian Hickson58939b72019-02-12 12:29:36 -080049 bool assertsEnabled = false;
50 assert(() { assertsEnabled = true; return true; }());
51 if (!assertsEnabled) {
Dan Field24f39d42020-01-02 11:47:28 -080052 exitWithError(<String>['The analyze.dart script must be run with --enable-asserts.']);
Ian Hickson58939b72019-02-12 12:29:36 -080053 }
Alexander Aprelev391e91c2018-08-30 07:30:25 -070054
Dan Fieldab0a3352021-12-12 13:05:03 -080055 print('$clock No sync*/async*');
56 await verifyNoSyncAsyncStar(flutterPackages);
57 await verifyNoSyncAsyncStar(flutterExamples, minimumMatches: 200);
58
Ian Hicksonbde9f112021-11-19 13:13:05 -080059 print('$clock No runtimeType in toString...');
Dan Field3e634112020-01-14 16:43:01 -080060 await verifyNoRuntimeTypeInToString(flutterRoot);
61
Ian Hicksonbde9f112021-11-19 13:13:05 -080062 print('$clock Debug mode instead of checked mode...');
Jonah Williams0d3b44e2021-08-03 13:25:05 -070063 await verifyNoCheckedMode(flutterRoot);
64
Ian Hicksonbde9f112021-11-19 13:13:05 -080065 print('$clock Links for creating GitHub issues');
66 await verifyIssueLinks(flutterRoot);
67
Dan Field24f39d42020-01-02 11:47:28 -080068 print('$clock Unexpected binaries...');
69 await verifyNoBinaries(flutterRoot);
70
71 print('$clock Trailing spaces...');
72 await verifyNoTrailingSpaces(flutterRoot); // assumes no unexpected binaries, so should be after verifyNoBinaries
73
Ian Hickson449f4a62019-11-27 15:04:02 -080074 print('$clock Deprecations...');
Ian Hickson62e4ab82019-11-15 19:21:53 -080075 await verifyDeprecations(flutterRoot);
Ian Hickson449f4a62019-11-27 15:04:02 -080076
Kate Lovett616f9bc2021-08-30 17:41:02 -050077 print('$clock Goldens...');
78 await verifyGoldenTags(flutterPackages);
79
Darren Austin41ff30c2021-08-10 23:03:48 -070080 print('$clock Skip test comments...');
81 await verifySkipTestComments(flutterRoot);
82
Ian Hickson449f4a62019-11-27 15:04:02 -080083 print('$clock Licenses...');
Ian Hicksoneae05c72019-11-14 13:19:40 -080084 await verifyNoMissingLicense(flutterRoot);
Ian Hickson449f4a62019-11-27 15:04:02 -080085
86 print('$clock Test imports...');
Ian Hicksoneae05c72019-11-14 13:19:40 -080087 await verifyNoTestImports(flutterRoot);
Ian Hickson449f4a62019-11-27 15:04:02 -080088
Ian Hickson449f4a62019-11-27 15:04:02 -080089 print('$clock Bad imports (framework)...');
Ian Hicksoneae05c72019-11-14 13:19:40 -080090 await verifyNoBadImportsInFlutter(flutterRoot);
Ian Hickson449f4a62019-11-27 15:04:02 -080091
92 print('$clock Bad imports (tools)...');
Ian Hicksoneae05c72019-11-14 13:19:40 -080093 await verifyNoBadImportsInFlutterTools(flutterRoot);
Ian Hickson449f4a62019-11-27 15:04:02 -080094
95 print('$clock Internationalization...');
Greg Spencer57224f82021-07-26 15:31:37 -070096 await verifyInternationalizations(flutterRoot, dart);
Ian Hickson449f4a62019-11-27 15:04:02 -080097
Dan Field9c0bd182021-06-23 13:08:51 -070098 print('$clock Integration test timeouts...');
99 await verifyIntegrationTestTimeouts(flutterRoot);
100
Jonah Williamsfda40942021-12-14 10:51:30 -0800101 print('$clock null initialized debug fields...');
102 await verifyNullInitializedDebugExpensiveFields(flutterRoot);
103
Alexander Aprelev391e91c2018-08-30 07:30:25 -0700104 // Ensure that all package dependencies are in sync.
Ian Hickson449f4a62019-11-27 15:04:02 -0800105 print('$clock Package dependencies...');
Alexander Aprelev391e91c2018-08-30 07:30:25 -0700106 await runCommand(flutter, <String>['update-packages', '--verify-only'],
107 workingDirectory: flutterRoot,
108 );
109
Jonah Williamsf6f59c52021-04-16 14:29:32 -0700110 /// Ensure that no new dependencies have been accidentally
111 /// added to core packages.
112 print('$clock Package Allowlist...');
113 await _checkConsumerDependencies();
114
Ian Hicksoneae05c72019-11-14 13:19:40 -0800115 // Analyze all the Dart code in the repo.
Ian Hickson449f4a62019-11-27 15:04:02 -0800116 print('$clock Dart analysis...');
Ian Hicksoneae05c72019-11-14 13:19:40 -0800117 await _runFlutterAnalyze(flutterRoot, options: <String>[
118 '--flutter-repo',
119 ...arguments,
120 ]);
121
Alexander Aprelev391e91c2018-08-30 07:30:25 -0700122 // Try with the --watch analyzer, to make sure it returns success also.
123 // The --benchmark argument exits after one run.
Ian Hickson449f4a62019-11-27 15:04:02 -0800124 print('$clock Dart analysis (with --watch)...');
Ian Hicksoneae05c72019-11-14 13:19:40 -0800125 await _runFlutterAnalyze(flutterRoot, options: <String>[
126 '--flutter-repo',
127 '--watch',
128 '--benchmark',
129 ...arguments,
130 ]);
Alexander Aprelev391e91c2018-08-30 07:30:25 -0700131
Daco Harkes67fdf932021-09-27 21:38:07 +0200132 // Analyze all the sample code in the repo.
Dan Field24f39d42020-01-02 11:47:28 -0800133 print('$clock Sample code...');
134 await runCommand(dart,
Daco Harkes67fdf932021-09-27 21:38:07 +0200135 <String>[path.join(flutterRoot, 'dev', 'bots', 'analyze_sample_code.dart'), '--verbose'],
Dan Field24f39d42020-01-02 11:47:28 -0800136 workingDirectory: flutterRoot,
137 );
138
Alexander Aprelev391e91c2018-08-30 07:30:25 -0700139 // Try analysis against a big version of the gallery; generate into a temporary directory.
Ian Hickson449f4a62019-11-27 15:04:02 -0800140 print('$clock Dart analysis (mega gallery)...');
Alexander Aprelev391e91c2018-08-30 07:30:25 -0700141 final Directory outDir = Directory.systemTemp.createTempSync('flutter_mega_gallery.');
Alexander Aprelev391e91c2018-08-30 07:30:25 -0700142 try {
143 await runCommand(dart,
144 <String>[
Alexander Aprelev391e91c2018-08-30 07:30:25 -0700145 path.join(flutterRoot, 'dev', 'tools', 'mega_gallery.dart'),
146 '--out',
147 outDir.path,
148 ],
149 workingDirectory: flutterRoot,
150 );
Ian Hickson449f4a62019-11-27 15:04:02 -0800151 await _runFlutterAnalyze(outDir.path, options: <String>[
152 '--watch',
153 '--benchmark',
154 ...arguments,
155 ]);
Alexander Aprelev391e91c2018-08-30 07:30:25 -0700156 } finally {
157 outDir.deleteSync(recursive: true);
158 }
Alexander Aprelev391e91c2018-08-30 07:30:25 -0700159}
160
Ian Hickson62e4ab82019-11-15 19:21:53 -0800161
162// TESTS
163
Dan Fieldab0a3352021-12-12 13:05:03 -0800164Future<void> verifyNoSyncAsyncStar(String workingDirectory, {int minimumMatches = 2000 }) async {
165 final RegExp syncPattern = RegExp(r'\s*?a?sync\*\s*?{');
166 const String ignorePattern = 'no_sync_async_star';
167 final RegExp commentPattern = RegExp(r'^\s*?///?');
168 final List<String> errors = <String>[];
169 await for (final File file in _allFiles(workingDirectory, 'dart', minimumMatches: minimumMatches)) {
170 if (file.path.contains('test')) {
171 continue;
172 }
173 final List<String> lines = file.readAsLinesSync();
174 for (int index = 0; index < lines.length; index += 1) {
175 final String line = lines[index];
176 if (line.startsWith(commentPattern)) {
177 continue;
178 }
179 if (line.contains(syncPattern) && !line.contains(ignorePattern) && (index == 0 || !lines[index - 1].contains(ignorePattern))) {
180 errors.add('${file.path}:$index: sync*/async* without an ignore (no_sync_async_star).');
181 }
182 }
183 }
184 if (errors.isNotEmpty) {
185 exitWithError(<String>[
186 '${bold}Do not use sync*/async* methods. See https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo#avoid-syncasync for details.$reset',
187 ...errors,
188 ]);
189 }
190}
191
Kate Lovett616f9bc2021-08-30 17:41:02 -0500192final RegExp _findGoldenTestPattern = RegExp(r'matchesGoldenFile\(');
193final RegExp _findGoldenDefinitionPattern = RegExp(r'matchesGoldenFile\(Object');
194final RegExp _leadingComment = RegExp(r'\/\/');
195final RegExp _goldenTagPattern1 = RegExp(r'@Tags\(');
196final RegExp _goldenTagPattern2 = RegExp(r"'reduced-test-set'");
197
198/// Only golden file tests in the flutter package are subject to reduced testing,
199/// for example, invocations in flutter_test to validate comparator
200/// functionality do not require tagging.
201const String _ignoreGoldenTag = '// flutter_ignore: golden_tag (see analyze.dart)';
202const String _ignoreGoldenTagForFile = '// flutter_ignore_for_file: golden_tag (see analyze.dart)';
203
204Future<void> verifyGoldenTags(String workingDirectory, { int minimumMatches = 2000 }) async {
205 final List<String> errors = <String>[];
206 await for (final File file in _allFiles(workingDirectory, 'dart', minimumMatches: minimumMatches)) {
207 bool needsTag = false;
208 bool hasTagNotation = false;
209 bool hasReducedTag = false;
210 bool ignoreForFile = false;
211 final List<String> lines = file.readAsLinesSync();
212 for (final String line in lines) {
213 if (line.contains(_goldenTagPattern1)) {
214 hasTagNotation = true;
215 }
216 if (line.contains(_goldenTagPattern2)) {
217 hasReducedTag = true;
218 }
219 if (line.contains(_findGoldenTestPattern)
220 && !line.contains(_findGoldenDefinitionPattern)
221 && !line.contains(_leadingComment)
222 && !line.contains(_ignoreGoldenTag)) {
223 needsTag = true;
224 }
225 if (line.contains(_ignoreGoldenTagForFile)) {
226 ignoreForFile = true;
227 }
228 // If the file is being ignored or a reduced test tag is already accounted
229 // for, skip parsing the rest of the lines for golden file tests.
230 if (ignoreForFile || (hasTagNotation && hasReducedTag)) {
231 break;
232 }
233 }
234 // If a reduced test tag is already accounted for, move on to the next file.
235 if (ignoreForFile || (hasTagNotation && hasReducedTag)) {
236 continue;
237 }
238 // If there are golden file tests, ensure they are tagged for all reduced
239 // test environments.
240 if (needsTag) {
241 if (!hasTagNotation) {
242 errors.add('${file.path}: Files containing golden tests must be tagged using '
243 '`@Tags(...)` at the top of the file before import statements.');
244 } else if (!hasReducedTag) {
245 errors.add('${file.path}: Files containing golden tests must be tagged with '
246 "'reduced-test-set'.");
247 }
248 }
249 }
250 if (errors.isNotEmpty) {
251 exitWithError(<String>[
252 ...errors,
253 '${bold}See: https://github.com/flutter/flutter/wiki/Writing-a-golden-file-test-for-package:flutter$reset',
254 ]);
255 }
256}
257
Ian Hickson62e4ab82019-11-15 19:21:53 -0800258final RegExp _findDeprecationPattern = RegExp(r'@[Dd]eprecated');
Michael Goderbauer197b4402021-03-19 15:33:05 -0700259final RegExp _deprecationPattern1 = RegExp(r'^( *)@Deprecated\($'); // flutter_ignore: deprecation_syntax (see analyze.dart)
Ian Hickson62e4ab82019-11-15 19:21:53 -0800260final RegExp _deprecationPattern2 = RegExp(r"^ *'(.+) '$");
Alexandre Ardhuina6832d42021-03-30 06:29:02 +0200261final RegExp _deprecationPattern3 = RegExp(r"^ *'This feature was deprecated after v([0-9]+)\.([0-9]+)\.([0-9]+)(\-[0-9]+\.[0-9]+\.pre)?\.',?$");
Ian Hickson62e4ab82019-11-15 19:21:53 -0800262final RegExp _deprecationPattern4 = RegExp(r'^ *\)$');
263
264/// Some deprecation notices are special, for example they're used to annotate members that
265/// will never go away and were never allowed but which we are trying to show messages for.
266/// (One example would be a library that intentionally conflicts with a member in another
267/// library to indicate that it is incompatible with that other library. Another would be
268/// the regexp just above...)
Michael Goderbauer197b4402021-03-19 15:33:05 -0700269const String _ignoreDeprecation = ' // flutter_ignore: deprecation_syntax (see analyze.dart)';
Ian Hickson62e4ab82019-11-15 19:21:53 -0800270
Michael Goderbauer584fd5f2020-06-16 09:15:43 -0700271/// Some deprecation notices are exempt for historical reasons. They must have an issue listed.
Michael Goderbauer197b4402021-03-19 15:33:05 -0700272final RegExp _legacyDeprecation = RegExp(r' // flutter_ignore: deprecation_syntax, https://github.com/flutter/flutter/issues/[0-9]+$');
Ian Hickson62e4ab82019-11-15 19:21:53 -0800273
Dan Field24f39d42020-01-02 11:47:28 -0800274Future<void> verifyDeprecations(String workingDirectory, { int minimumMatches = 2000 }) async {
Ian Hickson62e4ab82019-11-15 19:21:53 -0800275 final List<String> errors = <String>[];
Yuqian Lifb552ed2020-11-07 05:19:02 -0800276 await for (final File file in _allFiles(workingDirectory, 'dart', minimumMatches: minimumMatches)) {
Ian Hickson62e4ab82019-11-15 19:21:53 -0800277 int lineNumber = 0;
278 final List<String> lines = file.readAsLinesSync();
279 final List<int> linesWithDeprecations = <int>[];
Alexandre Ardhuin4f9b6cf2020-01-07 16:32:04 +0100280 for (final String line in lines) {
Ian Hickson62e4ab82019-11-15 19:21:53 -0800281 if (line.contains(_findDeprecationPattern) &&
282 !line.endsWith(_ignoreDeprecation) &&
Michael Goderbauer584fd5f2020-06-16 09:15:43 -0700283 !line.contains(_legacyDeprecation)) {
Ian Hickson62e4ab82019-11-15 19:21:53 -0800284 linesWithDeprecations.add(lineNumber);
285 }
286 lineNumber += 1;
287 }
288 for (int lineNumber in linesWithDeprecations) {
289 try {
Michael Goderbauer045ba2b2021-07-19 13:59:15 -0700290 final Match? match1 = _deprecationPattern1.firstMatch(lines[lineNumber]);
Ian Hickson62e4ab82019-11-15 19:21:53 -0800291 if (match1 == null)
292 throw 'Deprecation notice does not match required pattern.';
Michael Goderbauer045ba2b2021-07-19 13:59:15 -0700293 final String indent = match1[1]!;
Ian Hickson62e4ab82019-11-15 19:21:53 -0800294 lineNumber += 1;
295 if (lineNumber >= lines.length)
296 throw 'Incomplete deprecation notice.';
Michael Goderbauer045ba2b2021-07-19 13:59:15 -0700297 Match? match3;
298 String? message;
Ian Hickson62e4ab82019-11-15 19:21:53 -0800299 do {
Michael Goderbauer045ba2b2021-07-19 13:59:15 -0700300 final Match? match2 = _deprecationPattern2.firstMatch(lines[lineNumber]);
Tong Mu7abee112021-03-09 13:21:45 -0800301 if (match2 == null) {
302 String possibleReason = '';
303 if (lines[lineNumber].trimLeft().startsWith('"')) {
304 possibleReason = ' You might have used double quotes (") for the string instead of single quotes (\').';
305 }
306 throw 'Deprecation notice does not match required pattern.$possibleReason';
307 }
Ian Hickson62e4ab82019-11-15 19:21:53 -0800308 if (!lines[lineNumber].startsWith("$indent '"))
309 throw 'Unexpected deprecation notice indent.';
310 if (message == null) {
Michael Goderbauer045ba2b2021-07-19 13:59:15 -0700311 final String firstChar = String.fromCharCode(match2[1]!.runes.first);
Ian Hickson62e4ab82019-11-15 19:21:53 -0800312 if (firstChar.toUpperCase() != firstChar)
Christopher Fujino5cfb16b2020-06-19 12:03:38 -0700313 throw 'Deprecation notice should be a grammatically correct sentence and start with a capital letter; see style guide: https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo';
Ian Hickson62e4ab82019-11-15 19:21:53 -0800314 }
315 message = match2[1];
316 lineNumber += 1;
317 if (lineNumber >= lines.length)
318 throw 'Incomplete deprecation notice.';
319 match3 = _deprecationPattern3.firstMatch(lines[lineNumber]);
320 } while (match3 == null);
Michael Goderbauer045ba2b2021-07-19 13:59:15 -0700321 final int v1 = int.parse(match3[1]!);
322 final int v2 = int.parse(match3[2]!);
Christopher Fujino5cfb16b2020-06-19 12:03:38 -0700323 final bool hasV4 = match3[4] != null;
324 if (v1 > 1 || (v1 == 1 && v2 >= 20)) {
325 if (!hasV4)
326 throw 'Deprecation notice does not accurately indicate a dev branch version number; please see https://flutter.dev/docs/development/tools/sdk/releases to find the latest dev build version number.';
327 }
Michael Goderbauer045ba2b2021-07-19 13:59:15 -0700328 if (!message!.endsWith('.') && !message.endsWith('!') && !message.endsWith('?'))
Ian Hickson62e4ab82019-11-15 19:21:53 -0800329 throw 'Deprecation notice should be a grammatically correct sentence and end with a period.';
330 if (!lines[lineNumber].startsWith("$indent '"))
331 throw 'Unexpected deprecation notice indent.';
Ian Hickson62e4ab82019-11-15 19:21:53 -0800332 lineNumber += 1;
333 if (lineNumber >= lines.length)
334 throw 'Incomplete deprecation notice.';
335 if (!lines[lineNumber].contains(_deprecationPattern4))
336 throw 'End of deprecation notice does not match required pattern.';
337 if (!lines[lineNumber].startsWith('$indent)'))
338 throw 'Unexpected deprecation notice indent.';
339 } catch (error) {
340 errors.add('${file.path}:${lineNumber + 1}: $error');
341 }
342 }
343 }
344 // Fail if any errors
345 if (errors.isNotEmpty) {
Dan Field24f39d42020-01-02 11:47:28 -0800346 exitWithError(<String>[
347 ...errors,
348 '${bold}See: https://github.com/flutter/flutter/wiki/Tree-hygiene#handling-breaking-changes$reset',
349 ]);
Ian Hickson62e4ab82019-11-15 19:21:53 -0800350 }
351}
352
Ian Hickson449f4a62019-11-27 15:04:02 -0800353String _generateLicense(String prefix) {
354 assert(prefix != null);
355 return '${prefix}Copyright 2014 The Flutter Authors. All rights reserved.\n'
356 '${prefix}Use of this source code is governed by a BSD-style license that can be\n'
357 '${prefix}found in the LICENSE file.';
358}
359
Dan Field24f39d42020-01-02 11:47:28 -0800360Future<void> verifyNoMissingLicense(String workingDirectory, { bool checkMinimums = true }) async {
Michael Goderbauer045ba2b2021-07-19 13:59:15 -0700361 final int? overrideMinimumMatches = checkMinimums ? null : 0;
Zachary Anderson366648a2021-11-09 08:44:56 -0800362 await _verifyNoMissingLicenseForExtension(workingDirectory, 'dart', overrideMinimumMatches ?? 2000, _generateLicense('// '));
363 await _verifyNoMissingLicenseForExtension(workingDirectory, 'java', overrideMinimumMatches ?? 39, _generateLicense('// '));
364 await _verifyNoMissingLicenseForExtension(workingDirectory, 'h', overrideMinimumMatches ?? 30, _generateLicense('// '));
365 await _verifyNoMissingLicenseForExtension(workingDirectory, 'm', overrideMinimumMatches ?? 30, _generateLicense('// '));
366 await _verifyNoMissingLicenseForExtension(workingDirectory, 'swift', overrideMinimumMatches ?? 10, _generateLicense('// '));
367 await _verifyNoMissingLicenseForExtension(workingDirectory, 'gradle', overrideMinimumMatches ?? 80, _generateLicense('// '));
368 await _verifyNoMissingLicenseForExtension(workingDirectory, 'gn', overrideMinimumMatches ?? 0, _generateLicense('# '));
369 await _verifyNoMissingLicenseForExtension(workingDirectory, 'sh', overrideMinimumMatches ?? 1, '#!/usr/bin/env bash\n${_generateLicense('# ')}');
370 await _verifyNoMissingLicenseForExtension(workingDirectory, 'bat', overrideMinimumMatches ?? 1, '@ECHO off\n${_generateLicense('REM ')}');
371 await _verifyNoMissingLicenseForExtension(workingDirectory, 'ps1', overrideMinimumMatches ?? 1, _generateLicense('# '));
372 await _verifyNoMissingLicenseForExtension(workingDirectory, 'html', overrideMinimumMatches ?? 1, '<!DOCTYPE HTML>\n<!-- ${_generateLicense('')} -->', trailingBlank: false);
373 await _verifyNoMissingLicenseForExtension(workingDirectory, 'xml', overrideMinimumMatches ?? 1, '<!-- ${_generateLicense('')} -->');
Ian Hickson449f4a62019-11-27 15:04:02 -0800374}
375
Zachary Anderson366648a2021-11-09 08:44:56 -0800376Future<void> _verifyNoMissingLicenseForExtension(String workingDirectory, String extension, int minimumMatches, String license, { bool trailingBlank = true }) async {
Ian Hickson449f4a62019-11-27 15:04:02 -0800377 assert(!license.endsWith('\n'));
Zachary Anderson366648a2021-11-09 08:44:56 -0800378 final String licensePattern = '$license\n${trailingBlank ? '\n' : ''}';
Ian Hicksoneae05c72019-11-14 13:19:40 -0800379 final List<String> errors = <String>[];
Yuqian Lifb552ed2020-11-07 05:19:02 -0800380 await for (final File file in _allFiles(workingDirectory, extension, minimumMatches: minimumMatches)) {
Ian Hickson449f4a62019-11-27 15:04:02 -0800381 final String contents = file.readAsStringSync().replaceAll('\r\n', '\n');
382 if (contents.isEmpty)
383 continue; // let's not go down the /bin/true rabbit hole
Zachary Anderson366648a2021-11-09 08:44:56 -0800384 if (!contents.startsWith(licensePattern))
Ian Hicksoneae05c72019-11-14 13:19:40 -0800385 errors.add(file.path);
Alexander Aprelev391e91c2018-08-30 07:30:25 -0700386 }
Ian Hicksoneae05c72019-11-14 13:19:40 -0800387 // Fail if any errors
388 if (errors.isNotEmpty) {
Zachary Anderson366648a2021-11-09 08:44:56 -0800389 final String s = errors.length == 1 ? ' does' : 's do';
390 exitWithError(<String>[
391 '${bold}The following ${errors.length} file$s not have the right license header:$reset',
392 ...errors,
Dan Field24f39d42020-01-02 11:47:28 -0800393 'The expected license header is:',
394 license,
395 if (trailingBlank) '...followed by a blank line.',
Zachary Anderson366648a2021-11-09 08:44:56 -0800396 ]);
xsterc7a09a42019-04-24 12:40:17 -0700397 }
Alexander Aprelev391e91c2018-08-30 07:30:25 -0700398}
399
Darren Austin807ca682021-08-25 14:56:03 -0700400class _TestSkip {
401 _TestSkip(this.line, this.content);
402
403 final int line;
404 final String content;
405}
406
407Iterable<_TestSkip> _getTestSkips(File file) {
408 final ParseStringResult parseResult = parseFile(
409 featureSet: FeatureSet.latestLanguageVersion(),
410 path: file.absolute.path,
411 );
412 final _TestSkipLinesVisitor<CompilationUnit> visitor = _TestSkipLinesVisitor<CompilationUnit>(parseResult);
413 visitor.visitCompilationUnit(parseResult.unit);
414 return visitor.skips;
415}
416
417class _TestSkipLinesVisitor<T> extends RecursiveAstVisitor<T> {
418 _TestSkipLinesVisitor(this.parseResult) : skips = <_TestSkip>{};
419
420 final ParseStringResult parseResult;
421 final Set<_TestSkip> skips;
422
423 static bool isTestMethod(String name) {
424 return name.startsWith('test') || name == 'group' || name == 'expect';
425 }
426
427 @override
428 T? visitMethodInvocation(MethodInvocation node) {
429 if (isTestMethod(node.methodName.toString())) {
430 for (final Expression argument in node.argumentList.arguments) {
431 if (argument is NamedExpression && argument.name.label.name == 'skip') {
432 final int lineNumber = parseResult.lineInfo.getLocation(argument.beginToken.charOffset).lineNumber;
433 final String content = parseResult.content.substring(parseResult.lineInfo.getOffsetOfLine(lineNumber - 1),
434 parseResult.lineInfo.getOffsetOfLine(lineNumber) - 1);
435 skips.add(_TestSkip(lineNumber, content));
436 }
437 }
438 }
439 return super.visitMethodInvocation(node);
440 }
441}
442
443final RegExp _skipTestCommentPattern = RegExp(r'//(.*)$');
Darren Austin41ff30c2021-08-10 23:03:48 -0700444const Pattern _skipTestIntentionalPattern = '[intended]';
445final Pattern _skipTestTrackingBugPattern = RegExp(r'https+?://github.com/.*/issues/[0-9]+');
446
447Future<void> verifySkipTestComments(String workingDirectory) async {
448 final List<String> errors = <String>[];
Darren Austin807ca682021-08-25 14:56:03 -0700449 final Stream<File> testFiles =_allFiles(workingDirectory, 'dart', minimumMatches: 1500)
Darren Austin41ff30c2021-08-10 23:03:48 -0700450 .where((File f) => f.path.endsWith('_test.dart'));
451
452 await for (final File file in testFiles) {
Darren Austin807ca682021-08-25 14:56:03 -0700453 for (final _TestSkip skip in _getTestSkips(file)) {
454 final Match? match = _skipTestCommentPattern.firstMatch(skip.content);
Darren Austin41ff30c2021-08-10 23:03:48 -0700455 final String? skipComment = match?.group(1);
Darren Austin807ca682021-08-25 14:56:03 -0700456 if (skipComment == null ||
457 !(skipComment.contains(_skipTestIntentionalPattern) ||
458 skipComment.contains(_skipTestTrackingBugPattern))) {
459 errors.add('${file.path}:${skip.line}: skip test without a justification comment.');
Darren Austin41ff30c2021-08-10 23:03:48 -0700460 }
461 }
462 }
463
464 // Fail if any errors
465 if (errors.isNotEmpty) {
466 exitWithError(<String>[
467 ...errors,
468 '\n${bold}See: https://github.com/flutter/flutter/wiki/Tree-hygiene#skipped-tests$reset',
469 ]);
470 }
471}
472
Ian Hicksoneae05c72019-11-14 13:19:40 -0800473final RegExp _testImportPattern = RegExp(r'''import (['"])([^'"]+_test\.dart)\1''');
474const Set<String> _exemptTestImports = <String>{
475 'package:flutter_test/flutter_test.dart',
476 'hit_test.dart',
477 'package:test_api/src/backend/live_test.dart',
Dan Field76784652020-11-05 17:28:47 -0800478 'package:integration_test/integration_test.dart',
Ian Hicksoneae05c72019-11-14 13:19:40 -0800479};
Alexander Aprelev391e91c2018-08-30 07:30:25 -0700480
Ian Hicksoneae05c72019-11-14 13:19:40 -0800481Future<void> verifyNoTestImports(String workingDirectory) async {
482 final List<String> errors = <String>[];
483 assert("// foo\nimport 'binding_test.dart' as binding;\n'".contains(_testImportPattern));
Yuqian Lifb552ed2020-11-07 05:19:02 -0800484 final List<File> dartFiles = await _allFiles(path.join(workingDirectory, 'packages'), 'dart', minimumMatches: 1500).toList();
Alexandre Ardhuin4f9b6cf2020-01-07 16:32:04 +0100485 for (final File file in dartFiles) {
486 for (final String line in file.readAsLinesSync()) {
Michael Goderbauer045ba2b2021-07-19 13:59:15 -0700487 final Match? match = _testImportPattern.firstMatch(line);
Ian Hicksoneae05c72019-11-14 13:19:40 -0800488 if (match != null && !_exemptTestImports.contains(match.group(2)))
489 errors.add(file.path);
Alexander Aprelev391e91c2018-08-30 07:30:25 -0700490 }
491 }
Ian Hicksoneae05c72019-11-14 13:19:40 -0800492 // Fail if any errors
493 if (errors.isNotEmpty) {
Ian Hicksoneae05c72019-11-14 13:19:40 -0800494 final String s = errors.length == 1 ? '' : 's';
Dan Field24f39d42020-01-02 11:47:28 -0800495 exitWithError(<String>[
496 '${bold}The following file$s import a test directly. Test utilities should be in their own file.$reset',
497 ...errors,
498 ]);
Alexander Aprelev391e91c2018-08-30 07:30:25 -0700499 }
Alexander Aprelev391e91c2018-08-30 07:30:25 -0700500}
501
Ian Hicksoneae05c72019-11-14 13:19:40 -0800502Future<void> verifyNoBadImportsInFlutter(String workingDirectory) async {
Alexander Aprelev391e91c2018-08-30 07:30:25 -0700503 final List<String> errors = <String>[];
504 final String libPath = path.join(workingDirectory, 'packages', 'flutter', 'lib');
505 final String srcPath = path.join(workingDirectory, 'packages', 'flutter', 'lib', 'src');
506 // Verify there's one libPath/*.dart for each srcPath/*/.
Alexandre Ardhuind927c932018-09-12 08:29:29 +0200507 final List<String> packages = Directory(libPath).listSync()
Alexander Aprelev391e91c2018-08-30 07:30:25 -0700508 .where((FileSystemEntity entity) => entity is File && path.extension(entity.path) == '.dart')
509 .map<String>((FileSystemEntity entity) => path.basenameWithoutExtension(entity.path))
510 .toList()..sort();
Alexandre Ardhuind927c932018-09-12 08:29:29 +0200511 final List<String> directories = Directory(srcPath).listSync()
Alexander Aprelev391e91c2018-08-30 07:30:25 -0700512 .whereType<Directory>()
513 .map<String>((Directory entity) => path.basename(entity.path))
514 .toList()..sort();
Ian Hicksoneae05c72019-11-14 13:19:40 -0800515 if (!_listEquals<String>(packages, directories)) {
Alexandre Ardhuin34059ee2021-06-01 20:14:06 +0200516 errors.add(<String>[
517 'flutter/lib/*.dart does not match flutter/lib/src/*/:',
518 'These are the exported packages:',
519 ...packages.map<String>((String path) => ' lib/$path.dart'),
520 'These are the directories:',
521 ...directories.map<String>((String path) => ' lib/src/$path/')
522 ].join('\n'));
Alexander Aprelev391e91c2018-08-30 07:30:25 -0700523 }
524 // Verify that the imports are well-ordered.
525 final Map<String, Set<String>> dependencyMap = <String, Set<String>>{};
Alexandre Ardhuin4f9b6cf2020-01-07 16:32:04 +0100526 for (final String directory in directories) {
Yuqian Lifb552ed2020-11-07 05:19:02 -0800527 dependencyMap[directory] = await _findFlutterDependencies(path.join(srcPath, directory), errors, checkForMeta: directory != 'foundation');
Alexander Aprelev391e91c2018-08-30 07:30:25 -0700528 }
Michael Goderbauer045ba2b2021-07-19 13:59:15 -0700529 assert(dependencyMap['material']!.contains('widgets') &&
530 dependencyMap['widgets']!.contains('rendering') &&
531 dependencyMap['rendering']!.contains('painting')); // to make sure we're convinced _findFlutterDependencies is finding some
Alexandre Ardhuin4f9b6cf2020-01-07 16:32:04 +0100532 for (final String package in dependencyMap.keys) {
Michael Goderbauer045ba2b2021-07-19 13:59:15 -0700533 if (dependencyMap[package]!.contains(package)) {
Alexander Aprelev391e91c2018-08-30 07:30:25 -0700534 errors.add(
535 'One of the files in the $yellow$package$reset package imports that package recursively.'
536 );
537 }
538 }
LongCatIsLooongd291de02020-01-09 10:25:58 -0800539
540 for (final String key in dependencyMap.keys) {
Michael Goderbauer045ba2b2021-07-19 13:59:15 -0700541 for (final String dependency in dependencyMap[key]!) {
LongCatIsLooongd291de02020-01-09 10:25:58 -0800542 if (dependencyMap[dependency] != null)
543 continue;
544 // Sanity check before performing _deepSearch, to ensure there's no rogue
545 // dependencies.
Alexandre Ardhuin34059ee2021-06-01 20:14:06 +0200546 final String validFilenames = dependencyMap.keys.map((String name) => '$name.dart').join(', ');
LongCatIsLooongd291de02020-01-09 10:25:58 -0800547 errors.add(
548 '$key imported package:flutter/$dependency.dart '
549 'which is not one of the valid exports { $validFilenames }.\n'
550 'Consider changing $dependency.dart to one of them.'
551 );
552 }
553 }
554
Alexandre Ardhuin4f9b6cf2020-01-07 16:32:04 +0100555 for (final String package in dependencyMap.keys) {
Michael Goderbauer045ba2b2021-07-19 13:59:15 -0700556 final List<String>? loop = _deepSearch<String>(dependencyMap, package);
Alexander Aprelev391e91c2018-08-30 07:30:25 -0700557 if (loop != null) {
Alexandre Ardhuin34059ee2021-06-01 20:14:06 +0200558 errors.add('${yellow}Dependency loop:$reset ${loop.join(' depends on ')}');
Alexander Aprelev391e91c2018-08-30 07:30:25 -0700559 }
560 }
561 // Fail if any errors
562 if (errors.isNotEmpty) {
Dan Field24f39d42020-01-02 11:47:28 -0800563 exitWithError(<String>[
564 if (errors.length == 1)
565 '${bold}An error was detected when looking at import dependencies within the Flutter package:$reset'
566 else
567 '${bold}Multiple errors were detected when looking at import dependencies within the Flutter package:$reset',
568 ...errors,
569 ]);
Alexander Aprelev391e91c2018-08-30 07:30:25 -0700570 }
571}
572
Ian Hicksoneae05c72019-11-14 13:19:40 -0800573Future<void> verifyNoBadImportsInFlutterTools(String workingDirectory) async {
574 final List<String> errors = <String>[];
Yuqian Lifb552ed2020-11-07 05:19:02 -0800575 final List<File> files = await _allFiles(path.join(workingDirectory, 'packages', 'flutter_tools', 'lib'), 'dart', minimumMatches: 200).toList();
Alexandre Ardhuin4f9b6cf2020-01-07 16:32:04 +0100576 for (final File file in files) {
Ian Hicksoneae05c72019-11-14 13:19:40 -0800577 if (file.readAsStringSync().contains('package:flutter_tools/')) {
578 errors.add('$yellow${file.path}$reset imports flutter_tools.');
579 }
580 }
581 // Fail if any errors
582 if (errors.isNotEmpty) {
Dan Field24f39d42020-01-02 11:47:28 -0800583 exitWithError(<String>[
584 if (errors.length == 1)
585 '${bold}An error was detected when looking at import dependencies within the flutter_tools package:$reset'
586 else
587 '${bold}Multiple errors were detected when looking at import dependencies within the flutter_tools package:$reset',
588 ...errors.map((String paragraph) => '$paragraph\n'),
589 ]);
Ian Hicksoneae05c72019-11-14 13:19:40 -0800590 }
591}
592
Dan Field9c0bd182021-06-23 13:08:51 -0700593Future<void> verifyIntegrationTestTimeouts(String workingDirectory) async {
594 final List<String> errors = <String>[];
595 final String dev = path.join(workingDirectory, 'dev');
596 final List<File> files = await _allFiles(dev, 'dart', minimumMatches: 1)
597 .where((File file) => file.path.contains('test_driver') && (file.path.endsWith('_test.dart') || file.path.endsWith('util.dart')))
598 .toList();
599 for (final File file in files) {
600 final String contents = file.readAsStringSync();
601 final int testCount = ' test('.allMatches(contents).length;
602 final int timeoutNoneCount = 'timeout: Timeout.none'.allMatches(contents).length;
603 if (testCount != timeoutNoneCount) {
604 errors.add('$yellow${file.path}$reset has at least $testCount test(s) but only $timeoutNoneCount `Timeout.none`(s).');
605 }
606 }
607 if (errors.isNotEmpty) {
608 exitWithError(<String>[
609 if (errors.length == 1)
Ian Hickson24207182021-09-15 09:42:05 -0700610 '${bold}An error was detected when looking at integration test timeouts:$reset'
Dan Field9c0bd182021-06-23 13:08:51 -0700611 else
Ian Hickson24207182021-09-15 09:42:05 -0700612 '${bold}Multiple errors were detected when looking at integration test timeouts:$reset',
Dan Field9c0bd182021-06-23 13:08:51 -0700613 ...errors.map((String paragraph) => '$paragraph\n'),
614 ]);
615 }
616}
617
Greg Spencer57224f82021-07-26 15:31:37 -0700618Future<void> verifyInternationalizations(String workingDirectory, String dartExecutable) async {
Ian Hicksoneae05c72019-11-14 13:19:40 -0800619 final EvalResult materialGenResult = await _evalCommand(
Greg Spencer57224f82021-07-26 15:31:37 -0700620 dartExecutable,
Ian Hicksoneae05c72019-11-14 13:19:40 -0800621 <String>[
Shi-Hao Hong7874bca2019-12-16 17:30:57 -0800622 path.join('dev', 'tools', 'localization', 'bin', 'gen_localizations.dart'),
Ian Hicksoneae05c72019-11-14 13:19:40 -0800623 '--material',
624 ],
Greg Spencer57224f82021-07-26 15:31:37 -0700625 workingDirectory: workingDirectory,
Ian Hicksoneae05c72019-11-14 13:19:40 -0800626 );
627 final EvalResult cupertinoGenResult = await _evalCommand(
Greg Spencer57224f82021-07-26 15:31:37 -0700628 dartExecutable,
Ian Hicksoneae05c72019-11-14 13:19:40 -0800629 <String>[
Shi-Hao Hong7874bca2019-12-16 17:30:57 -0800630 path.join('dev', 'tools', 'localization', 'bin', 'gen_localizations.dart'),
Ian Hicksoneae05c72019-11-14 13:19:40 -0800631 '--cupertino',
632 ],
Greg Spencer57224f82021-07-26 15:31:37 -0700633 workingDirectory: workingDirectory,
Ian Hicksoneae05c72019-11-14 13:19:40 -0800634 );
635
Greg Spencer57224f82021-07-26 15:31:37 -0700636 final String materialLocalizationsFile = path.join(workingDirectory, 'packages', 'flutter_localizations', 'lib', 'src', 'l10n', 'generated_material_localizations.dart');
637 final String cupertinoLocalizationsFile = path.join(workingDirectory, 'packages', 'flutter_localizations', 'lib', 'src', 'l10n', 'generated_cupertino_localizations.dart');
Ian Hicksoneae05c72019-11-14 13:19:40 -0800638 final String expectedMaterialResult = await File(materialLocalizationsFile).readAsString();
639 final String expectedCupertinoResult = await File(cupertinoLocalizationsFile).readAsString();
640
641 if (materialGenResult.stdout.trim() != expectedMaterialResult.trim()) {
Dan Field24f39d42020-01-02 11:47:28 -0800642 exitWithError(<String>[
643 '<<<<<<< $materialLocalizationsFile',
644 expectedMaterialResult.trim(),
645 '=======',
646 materialGenResult.stdout.trim(),
647 '>>>>>>> gen_localizations',
648 'The contents of $materialLocalizationsFile are different from that produced by gen_localizations.',
649 '',
650 'Did you forget to run gen_localizations.dart after updating a .arb file?',
651 ]);
Ian Hicksoneae05c72019-11-14 13:19:40 -0800652 }
653 if (cupertinoGenResult.stdout.trim() != expectedCupertinoResult.trim()) {
Dan Field24f39d42020-01-02 11:47:28 -0800654 exitWithError(<String>[
655 '<<<<<<< $cupertinoLocalizationsFile',
656 expectedCupertinoResult.trim(),
657 '=======',
658 cupertinoGenResult.stdout.trim(),
659 '>>>>>>> gen_localizations',
660 'The contents of $cupertinoLocalizationsFile are different from that produced by gen_localizations.',
661 '',
662 'Did you forget to run gen_localizations.dart after updating a .arb file?',
663 ]);
Ian Hicksoneae05c72019-11-14 13:19:40 -0800664 }
665}
666
Jonah Williams0d3b44e2021-08-03 13:25:05 -0700667
668/// Verifies that all instances of "checked mode" have been migrated to "debug mode".
669Future<void> verifyNoCheckedMode(String workingDirectory) async {
670 final String flutterPackages = path.join(workingDirectory, 'packages');
671 final List<File> files = await _allFiles(flutterPackages, 'dart', minimumMatches: 400)
672 .where((File file) => path.extension(file.path) == '.dart')
673 .toList();
674 final List<String> problems = <String>[];
675 for (final File file in files) {
676 int lineCount = 0;
677 for (final String line in file.readAsLinesSync()) {
678 if (line.toLowerCase().contains('checked mode')) {
679 problems.add('${file.path}:$lineCount uses deprecated "checked mode" instead of "debug mode".');
680 }
681 lineCount += 1;
682 }
683 }
684 if (problems.isNotEmpty) {
685 exitWithError(problems);
686 }
687}
688
689
Dan Field3e634112020-01-14 16:43:01 -0800690Future<void> verifyNoRuntimeTypeInToString(String workingDirectory) async {
691 final String flutterLib = path.join(workingDirectory, 'packages', 'flutter', 'lib');
692 final Set<String> excludedFiles = <String>{
693 path.join(flutterLib, 'src', 'foundation', 'object.dart'), // Calls this from within an assert.
694 };
Yuqian Lifb552ed2020-11-07 05:19:02 -0800695 final List<File> files = await _allFiles(flutterLib, 'dart', minimumMatches: 400)
Dan Field3e634112020-01-14 16:43:01 -0800696 .where((File file) => !excludedFiles.contains(file.path))
697 .toList();
698 final RegExp toStringRegExp = RegExp(r'^\s+String\s+to(.+?)?String(.+?)?\(\)\s+(\{|=>)');
699 final List<String> problems = <String>[];
700 for (final File file in files) {
701 final List<String> lines = file.readAsLinesSync();
702 for (int index = 0; index < lines.length; index++) {
703 if (toStringRegExp.hasMatch(lines[index])) {
704 final int sourceLine = index + 1;
705 bool _checkForRuntimeType(String line) {
706 if (line.contains(r'$runtimeType') || line.contains('runtimeType.toString()')) {
707 problems.add('${file.path}:$sourceLine}: toString calls runtimeType.toString');
708 return true;
709 }
710 return false;
711 }
712 if (_checkForRuntimeType(lines[index])) {
713 continue;
714 }
715 if (lines[index].contains('=>')) {
716 while (!lines[index].contains(';')) {
717 index++;
718 assert(index < lines.length, 'Source file $file has unterminated toString method.');
719 if (_checkForRuntimeType(lines[index])) {
720 break;
721 }
722 }
723 } else {
724 int openBraceCount = '{'.allMatches(lines[index]).length - '}'.allMatches(lines[index]).length;
725 while (!lines[index].contains('}') && openBraceCount > 0) {
726 index++;
727 assert(index < lines.length, 'Source file $file has unbalanced braces in a toString method.');
728 if (_checkForRuntimeType(lines[index])) {
729 break;
730 }
731 openBraceCount += '{'.allMatches(lines[index]).length;
732 openBraceCount -= '}'.allMatches(lines[index]).length;
733 }
734 }
735 }
736 }
737 }
738 if (problems.isNotEmpty)
739 exitWithError(problems);
740}
741
Dan Field24f39d42020-01-02 11:47:28 -0800742Future<void> verifyNoTrailingSpaces(String workingDirectory, { int minimumMatches = 4000 }) async {
Yuqian Lifb552ed2020-11-07 05:19:02 -0800743 final List<File> files = await _allFiles(workingDirectory, null, minimumMatches: minimumMatches)
Dan Field24f39d42020-01-02 11:47:28 -0800744 .where((File file) => path.basename(file.path) != 'serviceaccount.enc')
745 .where((File file) => path.basename(file.path) != 'Ahem.ttf')
746 .where((File file) => path.extension(file.path) != '.snapshot')
747 .where((File file) => path.extension(file.path) != '.png')
748 .where((File file) => path.extension(file.path) != '.jpg')
stuartmorgan685e9d12020-03-23 10:42:26 -0700749 .where((File file) => path.extension(file.path) != '.ico')
Dan Field24f39d42020-01-02 11:47:28 -0800750 .where((File file) => path.extension(file.path) != '.jar')
Jonah Williams0b3f5cf2020-04-21 20:39:36 -0700751 .where((File file) => path.extension(file.path) != '.swp')
Dan Field24f39d42020-01-02 11:47:28 -0800752 .toList();
753 final List<String> problems = <String>[];
Alexandre Ardhuin4f9b6cf2020-01-07 16:32:04 +0100754 for (final File file in files) {
Dan Field24f39d42020-01-02 11:47:28 -0800755 final List<String> lines = file.readAsLinesSync();
756 for (int index = 0; index < lines.length; index += 1) {
757 if (lines[index].endsWith(' ')) {
758 problems.add('${file.path}:${index + 1}: trailing U+0020 space character');
759 } else if (lines[index].endsWith('\t')) {
760 problems.add('${file.path}:${index + 1}: trailing U+0009 tab character');
761 }
Ian Hicksone768c922019-12-30 17:12:19 -0800762 }
Dan Field24f39d42020-01-02 11:47:28 -0800763 if (lines.isNotEmpty && lines.last == '')
764 problems.add('${file.path}:${lines.length}: trailing blank line');
765 }
766 if (problems.isNotEmpty)
767 exitWithError(problems);
768}
769
Ian Hicksonbde9f112021-11-19 13:13:05 -0800770String _bullets(String value) => ' * $value';
771
772Future<void> verifyIssueLinks(String workingDirectory) async {
773 const String issueLinkPrefix = 'https://github.com/flutter/flutter/issues/new';
774 const Set<String> stops = <String>{ '\n', ' ', "'", '"', r'\', ')', '>' };
775 assert(!stops.contains('.')); // instead of "visit https://foo." say "visit: https://", it copy-pastes better
776 const String kGiveTemplates =
777 'Prefer to provide a link either to $issueLinkPrefix/choose (the list of issue '
778 'templates) or to a specific template directly ($issueLinkPrefix?template=...).\n';
779 final Set<String> templateNames =
780 Directory(path.join(workingDirectory, '.github', 'ISSUE_TEMPLATE'))
781 .listSync()
782 .whereType<File>()
783 .where((File file) => path.extension(file.path) == '.md')
784 .map<String>((File file) => path.basename(file.path))
785 .toSet();
786 final String kTemplates = 'The available templates are:\n${templateNames.map(_bullets).join("\n")}';
787 final List<String> problems = <String>[];
788 final Set<String> suggestions = <String>{};
789 final List<File> files = await _gitFiles(workingDirectory);
790 for (final File file in files) {
Jonah Williamsfda40942021-12-14 10:51:30 -0800791 if (path.basename(file.path).endsWith('_test.dart') || path.basename(file.path) == 'analyze.dart')
Ian Hicksonbde9f112021-11-19 13:13:05 -0800792 continue; // Skip tests, they're not public-facing.
793 final Uint8List bytes = file.readAsBytesSync();
794 // We allow invalid UTF-8 here so that binaries don't trip us up.
795 // There's a separate test in this file that verifies that all text
796 // files are actually valid UTF-8 (see verifyNoBinaries below).
797 final String contents = utf8.decode(bytes, allowMalformed: true);
798 int start = 0;
799 while ((start = contents.indexOf(issueLinkPrefix, start)) >= 0) {
800 int end = start + issueLinkPrefix.length;
801 while (end < contents.length && !stops.contains(contents[end])) {
802 end += 1;
803 }
804 final String url = contents.substring(start, end);
805 if (url == issueLinkPrefix) {
806 if (file.path != path.join(workingDirectory, 'dev', 'bots', 'analyze.dart')) {
807 problems.add('${file.path} contains a direct link to $issueLinkPrefix.');
808 suggestions.add(kGiveTemplates);
809 suggestions.add(kTemplates);
810 }
811 } else if (url.startsWith('$issueLinkPrefix?')) {
812 final Uri parsedUrl = Uri.parse(url);
813 final List<String>? templates = parsedUrl.queryParametersAll['template'];
814 if (templates == null) {
815 problems.add('${file.path} contains $url, which has no "template" argument specified.');
816 suggestions.add(kGiveTemplates);
817 suggestions.add(kTemplates);
818 } else if (templates.length != 1) {
819 problems.add('${file.path} contains $url, which has ${templates.length} templates specified.');
820 suggestions.add(kGiveTemplates);
821 suggestions.add(kTemplates);
822 } else if (!templateNames.contains(templates.single)) {
823 problems.add('${file.path} contains $url, which specifies a non-existent template ("${templates.single}").');
824 suggestions.add(kTemplates);
825 } else if (parsedUrl.queryParametersAll.keys.length > 1) {
826 problems.add('${file.path} contains $url, which the analyze.dart script is not sure how to handle.');
827 suggestions.add('Update analyze.dart to handle the URLs above, or change them to the expected pattern.');
828 }
829 } else if (url != '$issueLinkPrefix/choose') {
830 problems.add('${file.path} contains $url, which the analyze.dart script is not sure how to handle.');
831 suggestions.add('Update analyze.dart to handle the URLs above, or change them to the expected pattern.');
832 }
833 start = end;
834 }
835 }
836 assert(problems.isEmpty == suggestions.isEmpty);
837 if (problems.isNotEmpty) {
838 exitWithError(<String>[
839 ...problems,
840 ...suggestions,
841 ]);
842 }
843}
844
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +0200845@immutable
Dan Field24f39d42020-01-02 11:47:28 -0800846class Hash256 {
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +0200847 const Hash256(this.a, this.b, this.c, this.d);
Dan Field24f39d42020-01-02 11:47:28 -0800848
849 factory Hash256.fromDigest(Digest digest) {
850 assert(digest.bytes.length == 32);
851 return Hash256(
852 digest.bytes[ 0] << 56 |
853 digest.bytes[ 1] << 48 |
854 digest.bytes[ 2] << 40 |
855 digest.bytes[ 3] << 32 |
856 digest.bytes[ 4] << 24 |
857 digest.bytes[ 5] << 16 |
858 digest.bytes[ 6] << 8 |
859 digest.bytes[ 7] << 0,
860 digest.bytes[ 8] << 56 |
861 digest.bytes[ 9] << 48 |
862 digest.bytes[10] << 40 |
863 digest.bytes[11] << 32 |
864 digest.bytes[12] << 24 |
865 digest.bytes[13] << 16 |
866 digest.bytes[14] << 8 |
867 digest.bytes[15] << 0,
868 digest.bytes[16] << 56 |
869 digest.bytes[17] << 48 |
870 digest.bytes[18] << 40 |
871 digest.bytes[19] << 32 |
872 digest.bytes[20] << 24 |
873 digest.bytes[21] << 16 |
874 digest.bytes[22] << 8 |
875 digest.bytes[23] << 0,
876 digest.bytes[24] << 56 |
877 digest.bytes[25] << 48 |
878 digest.bytes[26] << 40 |
879 digest.bytes[27] << 32 |
880 digest.bytes[28] << 24 |
881 digest.bytes[29] << 16 |
882 digest.bytes[30] << 8 |
883 digest.bytes[31] << 0,
884 );
885 }
886
887 final int a;
888 final int b;
889 final int c;
890 final int d;
891
892 @override
893 bool operator ==(Object other) {
894 if (other.runtimeType != runtimeType)
895 return false;
896 return other is Hash256
897 && other.a == a
898 && other.b == b
899 && other.c == c
900 && other.d == d;
901 }
902
903 @override
Dan Fielde36e62e2021-06-30 09:46:54 -0700904 int get hashCode => Object.hash(a, b, c, d);
Dan Field24f39d42020-01-02 11:47:28 -0800905}
906
907// DO NOT ADD ANY ENTRIES TO THIS LIST.
908// We have a policy of not checking in binaries into this repository.
stuartmorgan685e9d12020-03-23 10:42:26 -0700909// If you are adding/changing template images, use the flutter_template_images
910// package and a .img.tmpl placeholder instead.
911// If you have other binaries to add, please consult Hixie for advice.
Michael Goderbauer584fd5f2020-06-16 09:15:43 -0700912final Set<Hash256> _legacyBinaries = <Hash256>{
Dan Field24f39d42020-01-02 11:47:28 -0800913 // DEFAULT ICON IMAGES
914
915 // packages/flutter_tools/templates/app/android.tmpl/app/src/main/res/mipmap-hdpi/ic_launcher.png
916 // packages/flutter_tools/templates/module/android/host_app_common/app.tmpl/src/main/res/mipmap-hdpi/ic_launcher.png
917 // (also used by many examples)
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +0200918 const Hash256(0x6A7C8F0D703E3682, 0x108F9662F8133022, 0x36240D3F8F638BB3, 0x91E32BFB96055FEF),
Dan Field24f39d42020-01-02 11:47:28 -0800919
920 // packages/flutter_tools/templates/app/android.tmpl/app/src/main/res/mipmap-mdpi/ic_launcher.png
921 // (also used by many examples)
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +0200922 const Hash256(0xC7C0C0189145E4E3, 0x2A401C61C9BDC615, 0x754B0264E7AFAE24, 0xE834BB81049EAF81),
Dan Field24f39d42020-01-02 11:47:28 -0800923
924 // packages/flutter_tools/templates/app/android.tmpl/app/src/main/res/mipmap-xhdpi/ic_launcher.png
925 // (also used by many examples)
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +0200926 const Hash256(0xE14AA40904929BF3, 0x13FDED22CF7E7FFC, 0xBF1D1AAC4263B5EF, 0x1BE8BFCE650397AA),
Dan Field24f39d42020-01-02 11:47:28 -0800927
928 // packages/flutter_tools/templates/app/android.tmpl/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
929 // (also used by many examples)
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +0200930 const Hash256(0x4D470BF22D5C17D8, 0x4EDC5F82516D1BA8, 0xA1C09559CD761CEF, 0xB792F86D9F52B540),
Dan Field24f39d42020-01-02 11:47:28 -0800931
932 // packages/flutter_tools/templates/app/android.tmpl/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
933 // (also used by many examples)
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +0200934 const Hash256(0x3C34E1F298D0C9EA, 0x3455D46DB6B7759C, 0x8211A49E9EC6E44B, 0x635FC5C87DFB4180),
Dan Field24f39d42020-01-02 11:47:28 -0800935
936 // packages/flutter_tools/templates/app/ios.tmpl/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
937 // packages/flutter_tools/templates/module/ios/host_app_ephemeral/Runner.tmpl/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
938 // (also used by a few examples)
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +0200939 const Hash256(0x7770183009E91411, 0x2DE7D8EF1D235A6A, 0x30C5834424858E0D, 0x2F8253F6B8D31926),
Dan Field24f39d42020-01-02 11:47:28 -0800940
941 // packages/flutter_tools/templates/app/ios.tmpl/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
942 // packages/flutter_tools/templates/module/ios/host_app_ephemeral/Runner.tmpl/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
943 // (also used by many examples)
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +0200944 const Hash256(0x5925DAB509451F9E, 0xCBB12CE8A625F9D4, 0xC104718EE20CAFF8, 0xB1B51032D1CD8946),
Dan Field24f39d42020-01-02 11:47:28 -0800945
946 // packages/flutter_tools/templates/app/ios.tmpl/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
947 // packages/flutter_tools/templates/app/ios.tmpl/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
948 // packages/flutter_tools/templates/module/ios/host_app_ephemeral/Runner.tmpl/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
949 // packages/flutter_tools/templates/module/ios/host_app_ephemeral/Runner.tmpl/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
950 // (also used by many examples)
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +0200951 const Hash256(0xC4D9A284C12301D0, 0xF50E248EC53ED51A, 0x19A10147B774B233, 0x08399250B0D44C55),
Dan Field24f39d42020-01-02 11:47:28 -0800952
953 // packages/flutter_tools/templates/app/ios.tmpl/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
954 // packages/flutter_tools/templates/module/ios/host_app_ephemeral/Runner.tmpl/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
955 // (also used by many examples)
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +0200956 const Hash256(0xBF97F9D3233F33E1, 0x389B09F7B8ADD537, 0x41300CB834D6C7A5, 0xCA32CBED363A4FB2),
Dan Field24f39d42020-01-02 11:47:28 -0800957
958 // packages/flutter_tools/templates/app/ios.tmpl/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
959 // packages/flutter_tools/templates/module/ios/host_app_ephemeral/Runner.tmpl/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
960 // (also used by many examples)
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +0200961 const Hash256(0x285442F69A06B45D, 0x9D79DF80321815B5, 0x46473548A37B7881, 0x9B68959C7B8ED237),
Dan Field24f39d42020-01-02 11:47:28 -0800962
963 // packages/flutter_tools/templates/app/ios.tmpl/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
964 // packages/flutter_tools/templates/module/ios/host_app_ephemeral/Runner.tmpl/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
965 // (also used by many examples)
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +0200966 const Hash256(0x2AB64AF8AC727EA9, 0x9C6AB9EAFF847F46, 0xFBF2A9A0A78A0ABC, 0xBF3180F3851645B4),
Dan Field24f39d42020-01-02 11:47:28 -0800967
968 // packages/flutter_tools/templates/app/ios.tmpl/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
969 // packages/flutter_tools/templates/module/ios/host_app_ephemeral/Runner.tmpl/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
970 // (also used by many examples)
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +0200971 const Hash256(0x9DCA09F4E5ED5684, 0xD3C4DFF41F4E8B7C, 0xB864B438172D72BE, 0x069315FA362930F9),
Dan Field24f39d42020-01-02 11:47:28 -0800972
973 // packages/flutter_tools/templates/app/ios.tmpl/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
974 // packages/flutter_tools/templates/module/ios/host_app_ephemeral/Runner.tmpl/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
975 // (also used by many examples)
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +0200976 const Hash256(0xD5AD04DE321EF37C, 0xACC5A7B960AFCCE7, 0x1BDCB96FA020C482, 0x49C1545DD1A0F497),
Dan Field24f39d42020-01-02 11:47:28 -0800977
978 // packages/flutter_tools/templates/app/ios.tmpl/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
979 // packages/flutter_tools/templates/app/ios.tmpl/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
980 // packages/flutter_tools/templates/module/ios/host_app_ephemeral/Runner.tmpl/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
981 // packages/flutter_tools/templates/module/ios/host_app_ephemeral/Runner.tmpl/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
982 // (also used by many examples)
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +0200983 const Hash256(0x809ABFE75C440770, 0xC13C4E2E46D09603, 0xC22053E9D4E0E227, 0x5DCB9C1DCFBB2C75),
Dan Field24f39d42020-01-02 11:47:28 -0800984
985 // packages/flutter_tools/templates/app/ios.tmpl/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
986 // packages/flutter_tools/templates/module/ios/host_app_ephemeral/Runner.tmpl/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
987 // (also used by many examples)
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +0200988 const Hash256(0x3DB08CB79E7B01B9, 0xE81F956E3A0AE101, 0x48D0FAFDE3EA7AA7, 0x0048DF905AA52CFD),
Dan Field24f39d42020-01-02 11:47:28 -0800989
990 // packages/flutter_tools/templates/app/ios.tmpl/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
991 // packages/flutter_tools/templates/module/ios/host_app_ephemeral/Runner.tmpl/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
992 // (also used by many examples)
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +0200993 const Hash256(0x23C13D463F5DCA5C, 0x1F14A14934003601, 0xC29F1218FD461016, 0xD8A22CEF579A665F),
Dan Field24f39d42020-01-02 11:47:28 -0800994
995 // packages/flutter_tools/templates/app/ios.tmpl/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
996 // packages/flutter_tools/templates/module/ios/host_app_ephemeral/Runner.tmpl/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
997 // (also used by many examples)
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +0200998 const Hash256(0x6DB7726530D71D3F, 0x52CB59793EB69131, 0x3BAA04796E129E1E, 0x043C0A58A1BFFD2F),
Dan Field24f39d42020-01-02 11:47:28 -0800999
1000 // packages/flutter_tools/templates/app/ios.tmpl/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
1001 // packages/flutter_tools/templates/module/ios/host_app_ephemeral/Runner.tmpl/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
1002 // (also used by many examples)
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001003 const Hash256(0xCEE565F5E6211656, 0x9B64980B209FD5CA, 0x4B3D3739011F5343, 0x250B33A1A2C6EB65),
Dan Field24f39d42020-01-02 11:47:28 -08001004
1005 // packages/flutter_tools/templates/app/ios.tmpl/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
1006 // packages/flutter_tools/templates/app/ios.tmpl/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
1007 // packages/flutter_tools/templates/app/ios.tmpl/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
1008 // packages/flutter_tools/templates/module/ios/host_app_ephemeral/Runner.tmpl/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
1009 // packages/flutter_tools/templates/module/ios/host_app_ephemeral/Runner.tmpl/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
1010 // packages/flutter_tools/templates/module/ios/host_app_ephemeral/Runner.tmpl/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
1011 // (also used by many examples)
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001012 const Hash256(0x93AE7D494FAD0FB3, 0x0CBF3AE746A39C4B, 0xC7A0F8BBF87FBB58, 0x7A3F3C01F3C5CE20),
Dan Field24f39d42020-01-02 11:47:28 -08001013
1014 // packages/flutter_tools/templates/app/macos.tmpl/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png
1015 // (also used by a few examples)
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001016 const Hash256(0xB18BEBAAD1AD6724, 0xE48BCDF699BA3927, 0xDF3F258FEBE646A3, 0xAB5C62767C6BAB40),
Dan Field24f39d42020-01-02 11:47:28 -08001017
1018 // packages/flutter_tools/templates/app/macos.tmpl/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png
1019 // (also used by a few examples)
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001020 const Hash256(0xF90D839A289ECADB, 0xF2B0B3400DA43EB8, 0x08B84908335AE4A0, 0x07457C4D5A56A57C),
Dan Field24f39d42020-01-02 11:47:28 -08001021
1022 // packages/flutter_tools/templates/app/macos.tmpl/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png
1023 // (also used by a few examples)
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001024 const Hash256(0x592C2ABF84ADB2D3, 0x91AED8B634D3233E, 0x2C65369F06018DCD, 0x8A4B27BA755EDCBE),
Dan Field24f39d42020-01-02 11:47:28 -08001025
1026 // packages/flutter_tools/templates/app/macos.tmpl/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png
1027 // (also used by a few examples)
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001028 const Hash256(0x75D9A0C034113CA8, 0xA1EC11C24B81F208, 0x6630A5A5C65C7D26, 0xA5DC03A1C0A4478C),
Dan Field24f39d42020-01-02 11:47:28 -08001029
1030 // packages/flutter_tools/templates/app/macos.tmpl/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png
1031 // (also used by a few examples)
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001032 const Hash256(0xA896E65745557732, 0xC72BD4EE3A10782F, 0xE2AA95590B5AF659, 0x869E5808DB9C01C1),
Dan Field24f39d42020-01-02 11:47:28 -08001033
1034 // packages/flutter_tools/templates/app/macos.tmpl/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png
1035 // (also used by a few examples)
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001036 const Hash256(0x3A69A8A1AAC5D9A8, 0x374492AF4B6D07A4, 0xCE637659EB24A784, 0x9C4DFB261D75C6A3),
Dan Field24f39d42020-01-02 11:47:28 -08001037
1038 // packages/flutter_tools/templates/app/macos.tmpl/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png
1039 // (also used by a few examples)
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001040 const Hash256(0xD29D4E0AF9256DC9, 0x2D0A8F8810608A5E, 0x64A132AD8B397CA2, 0xC4DDC0B1C26A68C3),
Dan Field24f39d42020-01-02 11:47:28 -08001041
Jonah Williams5d30c092020-01-10 09:37:20 -08001042 // packages/flutter_tools/templates/app/web/icons/Icon-192.png.copy.tmpl
Michael Thomsene1671812020-03-16 02:31:42 -07001043 // dev/integration_tests/flutter_gallery/web/icons/Icon-192.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001044 const Hash256(0x3DCE99077602F704, 0x21C1C6B2A240BC9B, 0x83D64D86681D45F2, 0x154143310C980BE3),
Jonah Williams5d30c092020-01-10 09:37:20 -08001045
1046 // packages/flutter_tools/templates/app/web/icons/Icon-512.png.copy.tmpl
Michael Thomsene1671812020-03-16 02:31:42 -07001047 // dev/integration_tests/flutter_gallery/web/icons/Icon-512.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001048 const Hash256(0xBACCB205AE45f0B4, 0x21BE1657259B4943, 0xAC40C95094AB877F, 0x3BCBE12CD544DCBE),
Dan Field24f39d42020-01-02 11:47:28 -08001049
Jonah Williamsab426852020-01-28 13:09:18 -08001050 // packages/flutter_tools/templates/app/web/favicon.png.copy.tmpl
Michael Thomsene1671812020-03-16 02:31:42 -07001051 // dev/integration_tests/flutter_gallery/web/favicon.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001052 const Hash256(0x7AB2525F4B86B65D, 0x3E4C70358A17E5A1, 0xAAF6F437f99CBCC0, 0x46DAD73d59BB9015),
Jonah Williamsab426852020-01-28 13:09:18 -08001053
Dan Field24f39d42020-01-02 11:47:28 -08001054 // GALLERY ICONS
1055
Michael Thomsene1671812020-03-16 02:31:42 -07001056 // dev/integration_tests/flutter_gallery/android/app/src/main/res/mipmap-hdpi/ic_background.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001057 const Hash256(0x03CFDE53C249475C, 0x277E8B8E90AC8A13, 0xE5FC13C358A94CCB, 0x67CA866C9862A0DD),
Dan Field24f39d42020-01-02 11:47:28 -08001058
Michael Thomsene1671812020-03-16 02:31:42 -07001059 // dev/integration_tests/flutter_gallery/android/app/src/main/res/mipmap-hdpi/ic_foreground.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001060 const Hash256(0x86A83E23A505EFCC, 0x39C358B699EDE12F, 0xC088EE516A1D0C73, 0xF3B5D74DDAD164B1),
Dan Field24f39d42020-01-02 11:47:28 -08001061
Michael Thomsene1671812020-03-16 02:31:42 -07001062 // dev/integration_tests/flutter_gallery/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001063 const Hash256(0xD813B1A77320355E, 0xB68C485CD47D0F0F, 0x3C7E1910DCD46F08, 0x60A6401B8DC13647),
Dan Field24f39d42020-01-02 11:47:28 -08001064
Michael Thomsene1671812020-03-16 02:31:42 -07001065 // dev/integration_tests/flutter_gallery/android/app/src/main/res/mipmap-xhdpi/ic_background.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001066 const Hash256(0x35AFA76BD5D6053F, 0xEE927436C78A8794, 0xA8BA5F5D9FC9653B, 0xE5B96567BB7215ED),
Dan Field24f39d42020-01-02 11:47:28 -08001067
Michael Thomsene1671812020-03-16 02:31:42 -07001068 // dev/integration_tests/flutter_gallery/android/app/src/main/res/mipmap-xhdpi/ic_foreground.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001069 const Hash256(0x263CE9B4F1F69B43, 0xEBB08AE9FE8F80E7, 0x95647A59EF2C040B, 0xA8AEB246861A7DFF),
Dan Field24f39d42020-01-02 11:47:28 -08001070
Michael Thomsene1671812020-03-16 02:31:42 -07001071 // dev/integration_tests/flutter_gallery/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001072 const Hash256(0x5E1A93C3653BAAFF, 0x1AAC6BCEB8DCBC2F, 0x2AE7D68ECB07E507, 0xCB1FA8354B28313A),
Dan Field24f39d42020-01-02 11:47:28 -08001073
Michael Thomsene1671812020-03-16 02:31:42 -07001074 // dev/integration_tests/flutter_gallery/android/app/src/main/res/mipmap-xxhdpi/ic_background.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001075 const Hash256(0xA5C77499151DDEC6, 0xDB40D0AC7321FD74, 0x0646C0C0F786743F, 0x8F3C3C408CAC5E8C),
Dan Field24f39d42020-01-02 11:47:28 -08001076
Michael Thomsene1671812020-03-16 02:31:42 -07001077 // dev/integration_tests/flutter_gallery/android/app/src/main/res/mipmap-xxhdpi/ic_foreground.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001078 const Hash256(0x33DE450980A2A16B, 0x1982AC7CDC1E7B01, 0x919E07E0289C2139, 0x65F85BCED8895FEF),
Dan Field24f39d42020-01-02 11:47:28 -08001079
Michael Thomsene1671812020-03-16 02:31:42 -07001080 // dev/integration_tests/flutter_gallery/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001081 const Hash256(0xC3B8577F4A89BA03, 0x830944FB06C3566B, 0x4C99140A2CA52958, 0x089BFDC3079C59B7),
Dan Field24f39d42020-01-02 11:47:28 -08001082
Michael Thomsene1671812020-03-16 02:31:42 -07001083 // dev/integration_tests/flutter_gallery/android/app/src/main/res/mipmap-xxxhdpi/ic_background.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001084 const Hash256(0xDEBC241D6F9C5767, 0x8980FDD46FA7ED0C, 0x5B8ACD26BCC5E1BC, 0x473C89B432D467AD),
Dan Field24f39d42020-01-02 11:47:28 -08001085
Michael Thomsene1671812020-03-16 02:31:42 -07001086 // dev/integration_tests/flutter_gallery/android/app/src/main/res/mipmap-xxxhdpi/ic_foreground.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001087 const Hash256(0xBEFE5F7E82BF8B64, 0x148D869E3742004B, 0xF821A9F5A1BCDC00, 0x357D246DCC659DC2),
Dan Field24f39d42020-01-02 11:47:28 -08001088
Michael Thomsene1671812020-03-16 02:31:42 -07001089 // dev/integration_tests/flutter_gallery/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001090 const Hash256(0xC385404341FF9EDD, 0x30FBE76F0EC99155, 0x8EA4F4AFE8CC0C60, 0x1CA3EDEF177E1DA8),
Dan Field24f39d42020-01-02 11:47:28 -08001091
Michael Thomsene1671812020-03-16 02:31:42 -07001092 // dev/integration_tests/flutter_gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-1024.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001093 const Hash256(0x6BE5751A29F57A80, 0x36A4B31CC542C749, 0x984E49B22BD65CAA, 0x75AE8B2440848719),
Dan Field24f39d42020-01-02 11:47:28 -08001094
Michael Thomsene1671812020-03-16 02:31:42 -07001095 // dev/integration_tests/flutter_gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-120.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001096 const Hash256(0x9972A2264BFA8F8D, 0x964AFE799EADC1FA, 0x2247FB31097F994A, 0x1495DC32DF071793),
Dan Field24f39d42020-01-02 11:47:28 -08001097
Michael Thomsene1671812020-03-16 02:31:42 -07001098 // dev/integration_tests/flutter_gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-152.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001099 const Hash256(0x4C7CC9B09BEEDA24, 0x45F57D6967753910, 0x57D68E1A6B883D2C, 0x8C52701A74F1400F),
Dan Field24f39d42020-01-02 11:47:28 -08001100
Michael Thomsene1671812020-03-16 02:31:42 -07001101 // dev/integration_tests/flutter_gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-167.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001102 const Hash256(0x66DACAC1CFE4D349, 0xDBE994CB9125FFD7, 0x2D795CFC9CF9F739, 0xEDBB06CE25082E9C),
Dan Field24f39d42020-01-02 11:47:28 -08001103
Michael Thomsene1671812020-03-16 02:31:42 -07001104 // dev/integration_tests/flutter_gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-180.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001105 const Hash256(0x5188621015EBC327, 0xC9EF63AD76E60ECE, 0xE82BDC3E4ABF09E2, 0xEE0139FA7C0A2BE5),
Dan Field24f39d42020-01-02 11:47:28 -08001106
Michael Thomsene1671812020-03-16 02:31:42 -07001107 // dev/integration_tests/flutter_gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-20.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001108 const Hash256(0x27D2752D04EE9A6B, 0x78410E208F74A6CD, 0xC90D9E03B73B8C60, 0xD05F7D623E790487),
Dan Field24f39d42020-01-02 11:47:28 -08001109
Michael Thomsene1671812020-03-16 02:31:42 -07001110 // dev/integration_tests/flutter_gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-29.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001111 const Hash256(0xBB20556B2826CF85, 0xD5BAC73AA69C2AC3, 0x8E71DAD64F15B855, 0xB30CB73E0AF89307),
Dan Field24f39d42020-01-02 11:47:28 -08001112
Michael Thomsene1671812020-03-16 02:31:42 -07001113 // dev/integration_tests/flutter_gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-40.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001114 const Hash256(0x623820FA45CDB0AC, 0x808403E34AD6A53E, 0xA3E9FDAE83EE0931, 0xB020A3A4EF2CDDE7),
Dan Field24f39d42020-01-02 11:47:28 -08001115
Michael Thomsene1671812020-03-16 02:31:42 -07001116 // dev/integration_tests/flutter_gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-58.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001117 const Hash256(0xC6D631D1E107215E, 0xD4A58FEC5F3AA4B5, 0x0AE9724E07114C0C, 0x453E5D87C2CAD3B3),
Dan Field24f39d42020-01-02 11:47:28 -08001118
Michael Thomsene1671812020-03-16 02:31:42 -07001119 // dev/integration_tests/flutter_gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-60.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001120 const Hash256(0x4B6F58D1EB8723C6, 0xE717A0D09FEC8806, 0x90C6D1EF4F71836E, 0x618672827979B1A2),
Dan Field24f39d42020-01-02 11:47:28 -08001121
Michael Thomsene1671812020-03-16 02:31:42 -07001122 // dev/integration_tests/flutter_gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-76.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001123 const Hash256(0x0A1744CC7634D508, 0xE85DD793331F0C8A, 0x0B7C6DDFE0975D8F, 0x29E91C905BBB1BED),
Dan Field24f39d42020-01-02 11:47:28 -08001124
Michael Thomsene1671812020-03-16 02:31:42 -07001125 // dev/integration_tests/flutter_gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-80.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001126 const Hash256(0x24032FBD1E6519D6, 0x0BA93C0D5C189554, 0xF50EAE23756518A2, 0x3FABACF4BD5DAF08),
Dan Field24f39d42020-01-02 11:47:28 -08001127
Michael Thomsene1671812020-03-16 02:31:42 -07001128 // dev/integration_tests/flutter_gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-87.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001129 const Hash256(0xC17BAE6DF6BB234A, 0xE0AF4BEB0B805F12, 0x14E74EB7AA9A30F1, 0x5763689165DA7DDF),
Dan Field24f39d42020-01-02 11:47:28 -08001130
1131
1132 // STOCKS ICONS
1133
Greg Spencer4b4cff92020-01-30 09:31:07 -08001134 // dev/benchmarks/test_apps/stocks/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001135 const Hash256(0x74052AB5241D4418, 0x7085180608BC3114, 0xD12493C50CD8BBC7, 0x56DED186C37ACE84),
Dan Field24f39d42020-01-02 11:47:28 -08001136
Greg Spencer4b4cff92020-01-30 09:31:07 -08001137 // dev/benchmarks/test_apps/stocks/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001138 const Hash256(0xE37947332E3491CB, 0x82920EE86A086FEA, 0xE1E0A70B3700A7DA, 0xDCAFBDD8F40E2E19),
Dan Field24f39d42020-01-02 11:47:28 -08001139
Greg Spencer4b4cff92020-01-30 09:31:07 -08001140 // dev/benchmarks/test_apps/stocks/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001141 const Hash256(0xE608CDFC0C8579FB, 0xE38873BAAF7BC944, 0x9C9D2EE3685A4FAE, 0x671EF0C8BC41D17C),
Dan Field24f39d42020-01-02 11:47:28 -08001142
Greg Spencer4b4cff92020-01-30 09:31:07 -08001143 // dev/benchmarks/test_apps/stocks/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001144 const Hash256(0xBD53D86977DF9C54, 0xF605743C5ABA114C, 0x9D51D1A8BB917E1A, 0x14CAA26C335CAEBD),
Dan Field24f39d42020-01-02 11:47:28 -08001145
Greg Spencer4b4cff92020-01-30 09:31:07 -08001146 // dev/benchmarks/test_apps/stocks/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001147 const Hash256(0x64E4D02262C4F3D0, 0xBB4FDC21CD0A816C, 0x4CD2A0194E00FB0F, 0x1C3AE4142FAC0D15),
Dan Field24f39d42020-01-02 11:47:28 -08001148
Greg Spencer4b4cff92020-01-30 09:31:07 -08001149 // dev/benchmarks/test_apps/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png
1150 // dev/benchmarks/test_apps/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-Small-40@3x.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001151 const Hash256(0x5BA3283A76918FC0, 0xEE127D0F22D7A0B6, 0xDF03DAED61669427, 0x93D89DDD87A08117),
Dan Field24f39d42020-01-02 11:47:28 -08001152
Greg Spencer4b4cff92020-01-30 09:31:07 -08001153 // dev/benchmarks/test_apps/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001154 const Hash256(0xCD7F26ED31DEA42A, 0x535D155EC6261499, 0x34E6738255FDB2C4, 0xBD8D4BDDE9A99B05),
Dan Field24f39d42020-01-02 11:47:28 -08001155
Greg Spencer4b4cff92020-01-30 09:31:07 -08001156 // dev/benchmarks/test_apps/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-76.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001157 const Hash256(0x3FA1225FC9A96A7E, 0xCD071BC42881AB0E, 0x7747EB72FFB72459, 0xA37971BBAD27EE24),
Dan Field24f39d42020-01-02 11:47:28 -08001158
Greg Spencer4b4cff92020-01-30 09:31:07 -08001159 // dev/benchmarks/test_apps/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-76@2x.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001160 const Hash256(0xCD867001ACD7BBDB, 0x25CDFD452AE89FA2, 0x8C2DC980CAF55F48, 0x0B16C246CFB389BC),
Dan Field24f39d42020-01-02 11:47:28 -08001161
Greg Spencer4b4cff92020-01-30 09:31:07 -08001162 // dev/benchmarks/test_apps/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-83.5@2x.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001163 const Hash256(0x848E9736E5C4915A, 0x7945BCF6B32FD56B, 0x1F1E7CDDD914352E, 0xC9681D38EF2A70DA),
Dan Field24f39d42020-01-02 11:47:28 -08001164
Greg Spencer4b4cff92020-01-30 09:31:07 -08001165 // dev/benchmarks/test_apps/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-Notification.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001166 const Hash256(0x654BA7D6C4E05CA0, 0x7799878884EF8F11, 0xA383E1F24CEF5568, 0x3C47604A966983C8),
Dan Field24f39d42020-01-02 11:47:28 -08001167
Greg Spencer4b4cff92020-01-30 09:31:07 -08001168 // dev/benchmarks/test_apps/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-Notification@2x.png
1169 // dev/benchmarks/test_apps/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-Small-40.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001170 const Hash256(0x743056FE7D83FE42, 0xA2990825B6AD0415, 0x1AF73D0D43B227AA, 0x07EBEA9B767381D9),
Dan Field24f39d42020-01-02 11:47:28 -08001171
Greg Spencer4b4cff92020-01-30 09:31:07 -08001172 // dev/benchmarks/test_apps/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-Notification@3x.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001173 const Hash256(0xA7E1570812D119CF, 0xEF4B602EF28DD0A4, 0x100D066E66F5B9B9, 0x881765DC9303343B),
Dan Field24f39d42020-01-02 11:47:28 -08001174
Greg Spencer4b4cff92020-01-30 09:31:07 -08001175 // dev/benchmarks/test_apps/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-Small-40@2x.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001176 const Hash256(0xB4102839A1E41671, 0x62DACBDEFA471953, 0xB1EE89A0AB7594BE, 0x1D9AC1E67DC2B2CE),
Dan Field24f39d42020-01-02 11:47:28 -08001177
Greg Spencer4b4cff92020-01-30 09:31:07 -08001178 // dev/benchmarks/test_apps/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-Small.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001179 const Hash256(0x70AC6571B593A967, 0xF1CBAEC9BC02D02D, 0x93AD766D8290ADE6, 0x840139BF9F219019),
Dan Field24f39d42020-01-02 11:47:28 -08001180
Greg Spencer4b4cff92020-01-30 09:31:07 -08001181 // dev/benchmarks/test_apps/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-Small@2x.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001182 const Hash256(0x5D87A78386DA2C43, 0xDDA8FEF2CA51438C, 0xE5A276FE28C6CF0A, 0xEBE89085B56665B6),
Dan Field24f39d42020-01-02 11:47:28 -08001183
Greg Spencer4b4cff92020-01-30 09:31:07 -08001184 // dev/benchmarks/test_apps/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-Small@3x.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001185 const Hash256(0x4D9F5E81F668DA44, 0xB20A77F8BF7BA2E1, 0xF384533B5AD58F07, 0xB3A2F93F8635CD96),
Dan Field24f39d42020-01-02 11:47:28 -08001186
1187
1188 // LEGACY ICONS
1189
1190 // dev/benchmarks/complex_layout/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@3x.png
1191 // dev/benchmarks/microbenchmarks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@3x.png
1192 // examples/flutter_view/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@3x.png
1193 // (not really sure where this came from, or why neither the template nor most examples use them)
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001194 const Hash256(0x6E645DC9ED913AAD, 0xB50ED29EEB16830D, 0xB32CA12F39121DB9, 0xB7BC1449DDDBF8B8),
Dan Field24f39d42020-01-02 11:47:28 -08001195
1196 // dev/benchmarks/macrobenchmarks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
1197 // dev/integration_tests/codegen/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
1198 // dev/integration_tests/ios_add2app/ios_add2app/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
1199 // dev/integration_tests/release_smoke_test/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001200 const Hash256(0xDEFAC77E08EC71EC, 0xA04CCA3C95D1FC33, 0xB9F26E1CB15CB051, 0x47DEFC79CDD7C158),
Dan Field24f39d42020-01-02 11:47:28 -08001201
1202 // examples/flutter_view/ios/Runner/ic_add.png
1203 // examples/platform_view/ios/Runner/ic_add.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001204 const Hash256(0x3CCE7450334675E2, 0xE3AABCA20B028993, 0x127BE82FE0EB3DFF, 0x8B027B3BAF052F2F),
Dan Field24f39d42020-01-02 11:47:28 -08001205
1206 // examples/image_list/images/coast.jpg
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001207 const Hash256(0xDA957FD30C51B8D2, 0x7D74C2C918692DC4, 0xD3C5C99BB00F0D6B, 0x5EBB30395A6EDE82),
Dan Field24f39d42020-01-02 11:47:28 -08001208
1209 // examples/image_list/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001210 const Hash256(0xB5792CA06F48A431, 0xD4379ABA2160BD5D, 0xE92339FC64C6A0D3, 0x417AA359634CD905),
Dan Field24f39d42020-01-02 11:47:28 -08001211
1212
1213 // TEST ASSETS
1214
1215 // dev/benchmarks/macrobenchmarks/assets/999x1000.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001216 const Hash256(0x553E9C36DFF3E610, 0x6A608BDE822A0019, 0xDE4F1769B6FBDB97, 0xBC3C20E26B839F59),
Dan Field24f39d42020-01-02 11:47:28 -08001217
1218 // dev/bots/test/analyze-test-input/root/packages/foo/serviceaccount.enc
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001219 const Hash256(0xA8100AE6AA1940D0, 0xB663BB31CD466142, 0xEBBDBD5187131B92, 0xD93818987832EB89),
Dan Field24f39d42020-01-02 11:47:28 -08001220
1221 // dev/automated_tests/icon/test.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001222 const Hash256(0xE214B4A0FEEEC6FA, 0x8E7AA8CC9BFBEC40, 0xBCDAC2F2DEBC950F, 0x75AF8EBF02BCE459),
Dan Field24f39d42020-01-02 11:47:28 -08001223
1224 // dev/integration_tests/android_splash_screens/splash_screen_kitchen_sink/android/app/src/main/res/drawable-land-xxhdpi/flutter_splash_screen.png
1225 // dev/integration_tests/android_splash_screens/splash_screen_kitchen_sink/android/app/src/main/res/mipmap-land-xxhdpi/flutter_splash_screen.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001226 const Hash256(0x2D4F8D7A3DFEF9D3, 0xA0C66938E169AB58, 0x8C6BBBBD1973E34E, 0x03C428416D010182),
Dan Field24f39d42020-01-02 11:47:28 -08001227
1228 // dev/integration_tests/android_splash_screens/splash_screen_kitchen_sink/android/app/src/main/res/drawable-xxhdpi/flutter_splash_screen.png
1229 // dev/integration_tests/android_splash_screens/splash_screen_kitchen_sink/android/app/src/main/res/mipmap-xxhdpi/flutter_splash_screen.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001230 const Hash256(0xCD46C01BAFA3B243, 0xA6AA1645EEDDE481, 0x143AC8ABAB1A0996, 0x22CAA9D41F74649A),
Dan Field24f39d42020-01-02 11:47:28 -08001231
1232 // dev/integration_tests/flutter_driver_screenshot_test/assets/red_square.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001233 const Hash256(0x40054377E1E084F4, 0x4F4410CE8F44C210, 0xABA945DFC55ED0EF, 0x23BDF9469E32F8D3),
Dan Field24f39d42020-01-02 11:47:28 -08001234
1235 // dev/integration_tests/flutter_driver_screenshot_test/test_driver/goldens/red_square_image/iPhone7,2.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001236 const Hash256(0x7F9D27C7BC418284, 0x01214E21CA886B2F, 0x40D9DA2B31AE7754, 0x71D68375F9C8A824),
Dan Field24f39d42020-01-02 11:47:28 -08001237
1238 // examples/flutter_view/assets/flutter-mark-square-64.png
1239 // examples/platform_view/assets/flutter-mark-square-64.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001240 const Hash256(0xF416B0D8AC552EC8, 0x819D1F492D1AB5E6, 0xD4F20CF45DB47C22, 0x7BB431FEFB5B67B2),
Dan Field24f39d42020-01-02 11:47:28 -08001241
1242 // packages/flutter_tools/test/data/intellij/plugins/Dart/lib/Dart.jar
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001243 const Hash256(0x576E489D788A13DB, 0xBF40E4A39A3DAB37, 0x15CCF0002032E79C, 0xD260C69B29E06646),
Dan Field24f39d42020-01-02 11:47:28 -08001244
1245 // packages/flutter_tools/test/data/intellij/plugins/flutter-intellij.jar
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001246 const Hash256(0x4C67221E25626CB2, 0x3F94E1F49D34E4CF, 0x3A9787A514924FC5, 0x9EF1E143E5BC5690),
Dan Field24f39d42020-01-02 11:47:28 -08001247
1248
Dan Field24f39d42020-01-02 11:47:28 -08001249 // MISCELLANEOUS
1250
1251 // dev/bots/serviceaccount.enc
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001252 const Hash256(0x1F19ADB4D80AFE8C, 0xE61899BA776B1A8D, 0xCA398C75F5F7050D, 0xFB0E72D7FBBBA69B),
Dan Field24f39d42020-01-02 11:47:28 -08001253
1254 // dev/docs/favicon.ico
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001255 const Hash256(0x67368CA1733E933A, 0xCA3BC56EF0695012, 0xE862C371AD4412F0, 0x3EC396039C609965),
Dan Field24f39d42020-01-02 11:47:28 -08001256
1257 // dev/snippets/assets/code_sample.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001258 const Hash256(0xAB2211A47BDA001D, 0x173A52FD9C75EBC7, 0xE158942FFA8243AD, 0x2A148871990D4297),
Dan Field24f39d42020-01-02 11:47:28 -08001259
1260 // dev/snippets/assets/code_snippet.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001261 const Hash256(0xDEC70574DA46DFBB, 0xFA657A771F3E1FBD, 0xB265CFC6B2AA5FE3, 0x93BA4F325D1520BA),
Dan Field24f39d42020-01-02 11:47:28 -08001262
1263 // packages/flutter_tools/static/Ahem.ttf
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001264 const Hash256(0x63D2ABD0041C3E3B, 0x4B52AD8D382353B5, 0x3C51C6785E76CE56, 0xED9DACAD2D2E31C4),
Dan Field24f39d42020-01-02 11:47:28 -08001265};
1266
Michael Goderbauer045ba2b2021-07-19 13:59:15 -07001267Future<void> verifyNoBinaries(String workingDirectory, { Set<Hash256>? legacyBinaries }) async {
Michael Goderbauer584fd5f2020-06-16 09:15:43 -07001268 // Please do not add anything to the _legacyBinaries set above.
Dan Field24f39d42020-01-02 11:47:28 -08001269 // We have a policy of not checking in binaries into this repository.
stuartmorgan685e9d12020-03-23 10:42:26 -07001270 // If you are adding/changing template images, use the flutter_template_images
1271 // package and a .img.tmpl placeholder instead.
1272 // If you have other binaries to add, please consult Hixie for advice.
Dan Field24f39d42020-01-02 11:47:28 -08001273 assert(
Michael Goderbauer584fd5f2020-06-16 09:15:43 -07001274 _legacyBinaries
Dan Field24f39d42020-01-02 11:47:28 -08001275 .expand<int>((Hash256 hash) => <int>[hash.a, hash.b, hash.c, hash.d])
Casey Hillers781cd4d2020-02-11 18:25:17 -08001276 .reduce((int value, int element) => value ^ element) == 0x606B51C908B40BFA // Please do not modify this line.
Dan Field24f39d42020-01-02 11:47:28 -08001277 );
Michael Goderbauer584fd5f2020-06-16 09:15:43 -07001278 legacyBinaries ??= _legacyBinaries;
Dan Field24f39d42020-01-02 11:47:28 -08001279 if (!Platform.isWindows) { // TODO(ianh): Port this to Windows
Ian Hicksonbde9f112021-11-19 13:13:05 -08001280 final List<File> files = await _gitFiles(workingDirectory);
Dan Field24f39d42020-01-02 11:47:28 -08001281 final List<String> problems = <String>[];
Alexandre Ardhuin4f9b6cf2020-01-07 16:32:04 +01001282 for (final File file in files) {
Dan Field24f39d42020-01-02 11:47:28 -08001283 final Uint8List bytes = file.readAsBytesSync();
1284 try {
1285 utf8.decode(bytes);
1286 } on FormatException catch (error) {
Paul Berryc32d1382020-05-04 09:10:20 -07001287 final Digest digest = sha256.convert(bytes);
Michael Goderbauer584fd5f2020-06-16 09:15:43 -07001288 if (!legacyBinaries.contains(Hash256.fromDigest(digest)))
Paul Berryc32d1382020-05-04 09:10:20 -07001289 problems.add('${file.path}:${error.offset}: file is not valid UTF-8');
Dan Field24f39d42020-01-02 11:47:28 -08001290 }
1291 }
1292 if (problems.isNotEmpty) {
1293 exitWithError(<String>[
1294 ...problems,
1295 'All files in this repository must be UTF-8. In particular, images and other binaries',
1296 'must not be checked into this repository. This is because we are very sensitive to the',
1297 'size of the repository as it is distributed to all our developers. If you have a binary',
1298 'to which you need access, you should consider how to fetch it from another repository;',
1299 'for example, the "assets-for-api-docs" repository is used for images in API docs.',
1300 ]);
Ian Hicksoneae05c72019-11-14 13:19:40 -08001301 }
1302 }
1303}
1304
1305
1306// UTILITY FUNCTIONS
1307
1308bool _listEquals<T>(List<T> a, List<T> b) {
Alexander Aprelev391e91c2018-08-30 07:30:25 -07001309 assert(a != null);
1310 assert(b != null);
1311 if (a.length != b.length)
1312 return false;
1313 for (int index = 0; index < a.length; index += 1) {
1314 if (a[index] != b[index])
1315 return false;
1316 }
1317 return true;
1318}
1319
Yuqian Lifb552ed2020-11-07 05:19:02 -08001320Future<List<File>> _gitFiles(String workingDirectory, {bool runSilently = true}) async {
1321 final EvalResult evalResult = await _evalCommand(
1322 'git', <String>['ls-files', '-z'],
1323 workingDirectory: workingDirectory,
1324 runSilently: runSilently,
1325 );
1326 if (evalResult.exitCode != 0) {
1327 exitWithError(<String>[
Dan Field9c0bd182021-06-23 13:08:51 -07001328 'git ls-files failed with exit code ${evalResult.exitCode}',
Yuqian Lifb552ed2020-11-07 05:19:02 -08001329 '${bold}stdout:$reset',
1330 evalResult.stdout,
1331 '${bold}stderr:$reset',
1332 evalResult.stderr,
1333 ]);
1334 }
1335 final List<String> filenames = evalResult
1336 .stdout
1337 .split('\x00');
1338 assert(filenames.last.isEmpty); // git ls-files gives a trailing blank 0x00
1339 filenames.removeLast();
1340 return filenames
1341 .map<File>((String filename) => File(path.join(workingDirectory, filename)))
1342 .toList();
1343}
1344
Michael Goderbauer045ba2b2021-07-19 13:59:15 -07001345Stream<File> _allFiles(String workingDirectory, String? extension, { required int minimumMatches }) async* {
Yuqian Lifb552ed2020-11-07 05:19:02 -08001346 final Set<String> gitFileNamesSet = <String>{};
1347 gitFileNamesSet.addAll((await _gitFiles(workingDirectory)).map((File f) => path.canonicalize(f.absolute.path)));
1348
Dan Field24f39d42020-01-02 11:47:28 -08001349 assert(extension == null || !extension.startsWith('.'), 'Extension argument should not start with a period.');
Ian Hickson62e4ab82019-11-15 19:21:53 -08001350 final Set<FileSystemEntity> pending = <FileSystemEntity>{ Directory(workingDirectory) };
Dan Field24f39d42020-01-02 11:47:28 -08001351 int matches = 0;
Ian Hickson62e4ab82019-11-15 19:21:53 -08001352 while (pending.isNotEmpty) {
1353 final FileSystemEntity entity = pending.first;
1354 pending.remove(entity);
Ian Hickson449f4a62019-11-27 15:04:02 -08001355 if (path.extension(entity.path) == '.tmpl')
1356 continue;
Ian Hickson62e4ab82019-11-15 19:21:53 -08001357 if (entity is File) {
Yuqian Lifb552ed2020-11-07 05:19:02 -08001358 if (!gitFileNamesSet.contains(path.canonicalize(entity.absolute.path)))
1359 continue;
Ian Hickson449f4a62019-11-27 15:04:02 -08001360 if (_isGeneratedPluginRegistrant(entity))
1361 continue;
1362 if (path.basename(entity.path) == 'flutter_export_environment.sh')
1363 continue;
1364 if (path.basename(entity.path) == 'gradlew.bat')
1365 continue;
Darren Austin4253e422020-09-18 14:37:04 -07001366 if (path.basename(entity.path) == '.DS_Store')
1367 continue;
Dan Field24f39d42020-01-02 11:47:28 -08001368 if (extension == null || path.extension(entity.path) == '.$extension') {
1369 matches += 1;
Ian Hickson62e4ab82019-11-15 19:21:53 -08001370 yield entity;
Dan Field24f39d42020-01-02 11:47:28 -08001371 }
Ian Hickson62e4ab82019-11-15 19:21:53 -08001372 } else if (entity is Directory) {
1373 if (File(path.join(entity.path, '.dartignore')).existsSync())
1374 continue;
1375 if (path.basename(entity.path) == '.git')
1376 continue;
Jim Graham210f7682020-07-29 17:16:26 -07001377 if (path.basename(entity.path) == '.idea')
1378 continue;
Dan Field24f39d42020-01-02 11:47:28 -08001379 if (path.basename(entity.path) == '.gradle')
1380 continue;
Ian Hickson62e4ab82019-11-15 19:21:53 -08001381 if (path.basename(entity.path) == '.dart_tool')
1382 continue;
Darren Austin753b8482020-07-20 18:51:04 -07001383 if (path.basename(entity.path) == '.idea')
1384 continue;
Ian Hickson449f4a62019-11-27 15:04:02 -08001385 if (path.basename(entity.path) == 'build')
1386 continue;
Ian Hickson62e4ab82019-11-15 19:21:53 -08001387 pending.addAll(entity.listSync());
1388 }
1389 }
Dan Field24f39d42020-01-02 11:47:28 -08001390 assert(matches >= minimumMatches, 'Expected to find at least $minimumMatches files with extension ".$extension" in "$workingDirectory", but only found $matches.');
Ian Hicksoneae05c72019-11-14 13:19:40 -08001391}
1392
1393class EvalResult {
1394 EvalResult({
Michael Goderbauer045ba2b2021-07-19 13:59:15 -07001395 required this.stdout,
1396 required this.stderr,
Ian Hicksoneae05c72019-11-14 13:19:40 -08001397 this.exitCode = 0,
1398 });
1399
1400 final String stdout;
1401 final String stderr;
1402 final int exitCode;
1403}
1404
Dan Field24f39d42020-01-02 11:47:28 -08001405// TODO(ianh): Refactor this to reuse the code in run_command.dart
Ian Hicksoneae05c72019-11-14 13:19:40 -08001406Future<EvalResult> _evalCommand(String executable, List<String> arguments, {
Michael Goderbauer045ba2b2021-07-19 13:59:15 -07001407 required String workingDirectory,
1408 Map<String, String>? environment,
Ian Hicksoneae05c72019-11-14 13:19:40 -08001409 bool allowNonZeroExit = false,
Yuqian Lifb552ed2020-11-07 05:19:02 -08001410 bool runSilently = false,
Ian Hicksoneae05c72019-11-14 13:19:40 -08001411}) async {
1412 final String commandDescription = '${path.relative(executable, from: workingDirectory)} ${arguments.join(' ')}';
1413 final String relativeWorkingDir = path.relative(workingDirectory);
Yuqian Lifb552ed2020-11-07 05:19:02 -08001414
1415 if (!runSilently) {
1416 printProgress('RUNNING', relativeWorkingDir, commandDescription);
1417 }
Ian Hicksoneae05c72019-11-14 13:19:40 -08001418
1419 final Stopwatch time = Stopwatch()..start();
1420 final Process process = await Process.start(executable, arguments,
1421 workingDirectory: workingDirectory,
1422 environment: environment,
1423 );
1424
1425 final Future<List<List<int>>> savedStdout = process.stdout.toList();
1426 final Future<List<List<int>>> savedStderr = process.stderr.toList();
1427 final int exitCode = await process.exitCode;
1428 final EvalResult result = EvalResult(
1429 stdout: utf8.decode((await savedStdout).expand<int>((List<int> ints) => ints).toList()),
1430 stderr: utf8.decode((await savedStderr).expand<int>((List<int> ints) => ints).toList()),
1431 exitCode: exitCode,
1432 );
1433
Yuqian Lifb552ed2020-11-07 05:19:02 -08001434 if (!runSilently) {
1435 print('$clock ELAPSED TIME: $bold${prettyPrintDuration(time.elapsed)}$reset for $commandDescription in $relativeWorkingDir');
1436 }
Ian Hicksoneae05c72019-11-14 13:19:40 -08001437
1438 if (exitCode != 0 && !allowNonZeroExit) {
1439 stderr.write(result.stderr);
Dan Field24f39d42020-01-02 11:47:28 -08001440 exitWithError(<String>[
1441 '${bold}ERROR:$red Last command exited with $exitCode.$reset',
1442 '${bold}Command:$red $commandDescription$reset',
1443 '${bold}Relative working directory:$red $relativeWorkingDir$reset',
1444 ]);
Ian Hicksoneae05c72019-11-14 13:19:40 -08001445 }
1446
1447 return result;
1448}
1449
Jonah Williamsf6f59c52021-04-16 14:29:32 -07001450Future<void> _checkConsumerDependencies() async {
1451 final ProcessResult result = await Process.run(flutter, <String>[
1452 'update-packages',
1453 '--transitive-closure',
1454 '--consumer-only',
1455 ]);
1456 if (result.exitCode != 0) {
Michael Goderbauer045ba2b2021-07-19 13:59:15 -07001457 print(result.stdout as Object);
1458 print(result.stderr as Object);
Jonah Williamsf6f59c52021-04-16 14:29:32 -07001459 exit(result.exitCode);
1460 }
Ian Hickson126cd732021-10-04 10:28:03 -07001461 final Set<String> dependencies = <String>{};
Jonah Williamsf6f59c52021-04-16 14:29:32 -07001462 for (final String line in result.stdout.toString().split('\n')) {
1463 if (!line.contains('->')) {
1464 continue;
1465 }
1466 final List<String> parts = line.split('->');
1467 final String name = parts[0].trim();
Ian Hickson126cd732021-10-04 10:28:03 -07001468 dependencies.add(name);
Jonah Williamsf6f59c52021-04-16 14:29:32 -07001469 }
Jonah Williamsf6f59c52021-04-16 14:29:32 -07001470
Ian Hickson126cd732021-10-04 10:28:03 -07001471 final Set<String> removed = kCorePackageAllowList.difference(dependencies);
1472 final Set<String> added = dependencies.difference(kCorePackageAllowList);
Jonah Williamsf6f59c52021-04-16 14:29:32 -07001473
Ian Hickson126cd732021-10-04 10:28:03 -07001474 String plural(int n, String s, String p) => n == 1 ? s : p;
1475
1476 if (added.isNotEmpty) {
Jonah Williamsf6f59c52021-04-16 14:29:32 -07001477 exitWithError(<String>[
Ian Hickson126cd732021-10-04 10:28:03 -07001478 'The transitive closure of package dependencies contains ${plural(added.length, "a non-allowlisted package", "non-allowlisted packages")}:',
1479 ' ${added.join(', ')}',
1480 'We strongly desire to keep the number of dependencies to a minimum and',
1481 'therefore would much prefer not to add new dependencies.',
1482 'See dev/bots/allowlist.dart for instructions on how to update the package',
1483 'allowlist if you nonetheless believe this is a necessary addition.',
Jonah Williamsf6f59c52021-04-16 14:29:32 -07001484 ]);
1485 }
1486
Ian Hickson126cd732021-10-04 10:28:03 -07001487 if (removed.isNotEmpty) {
Jonah Williamsf6f59c52021-04-16 14:29:32 -07001488 exitWithError(<String>[
Ian Hickson126cd732021-10-04 10:28:03 -07001489 'Excellent news! ${plural(removed.length, "A package dependency has been removed!", "Multiple package dependencies have been removed!")}',
1490 ' ${removed.join(', ')}',
1491 'To make sure we do not accidentally add ${plural(removed.length, "this dependency", "these dependencies")} back in the future,',
1492 'please remove ${plural(removed.length, "this", "these")} packages from the allow-list in dev/bots/allowlist.dart.',
1493 'Thanks!',
Jonah Williamsf6f59c52021-04-16 14:29:32 -07001494 ]);
1495 }
1496}
1497
Jonah Williamsfda40942021-12-14 10:51:30 -08001498const String _kDebugOnlyAnnotation = '@_debugOnly';
1499final RegExp _nullInitializedField = RegExp(r'kDebugMode \? [\w\<\> ,{}()]+ : null;');
1500
1501Future<void> verifyNullInitializedDebugExpensiveFields(String workingDirectory, {int minimumMatches = 400}) async {
1502 final String flutterLib = path.join(workingDirectory, 'packages', 'flutter', 'lib');
1503 final List<File> files = await _allFiles(flutterLib, 'dart', minimumMatches: minimumMatches)
1504 .toList();
1505 final List<String> errors = <String>[];
1506 for (final File file in files) {
1507 final List<String> lines = file.readAsLinesSync();
1508 for (int i = 0; i < lines.length; i += 1) {
1509 final String line = lines[i];
1510 if (!line.contains(_kDebugOnlyAnnotation)) {
1511 continue;
1512 }
1513 final String nextLine = lines[i + 1];
1514 if (_nullInitializedField.firstMatch(nextLine) == null) {
1515 errors.add('${file.path} L$i');
1516 }
1517 }
1518 }
1519
1520 if (errors.isNotEmpty) {
1521 exitWithError(<String>[
1522 '${bold}ERROR: ${red}fields annotated with @_debugOnly must null initialize.$reset',
1523 'to ensure both the field and initializer are removed from profile/release mode.',
1524 'These fields should be written as:\n',
1525 'field = kDebugMode ? <DebugValue> : null;\n',
1526 'Errors were found in the following files:',
1527 ...errors,
1528 ]);
1529 }
1530}
1531
Michael Goderbauer045ba2b2021-07-19 13:59:15 -07001532Future<CommandResult> _runFlutterAnalyze(String workingDirectory, {
Ian Hicksoneae05c72019-11-14 13:19:40 -08001533 List<String> options = const <String>[],
Dan Field24f39d42020-01-02 11:47:28 -08001534}) async {
Michael Goderbauercb867bb2021-03-05 18:38:15 -08001535 return runCommand(
Ian Hicksoneae05c72019-11-14 13:19:40 -08001536 flutter,
Jenn Magder95d7d672021-04-18 09:29:02 -07001537 <String>['analyze', ...options],
Ian Hicksoneae05c72019-11-14 13:19:40 -08001538 workingDirectory: workingDirectory,
1539 );
1540}
1541
Greg Spencer35fcd902019-01-14 13:49:50 -08001542final RegExp _importPattern = RegExp(r'''^\s*import (['"])package:flutter/([^.]+)\.dart\1''');
Ian Hickson58939b72019-02-12 12:29:36 -08001543final RegExp _importMetaPattern = RegExp(r'''^\s*import (['"])package:meta/meta\.dart\1''');
Alexander Aprelev391e91c2018-08-30 07:30:25 -07001544
Yuqian Lifb552ed2020-11-07 05:19:02 -08001545Future<Set<String>> _findFlutterDependencies(String srcPath, List<String> errors, { bool checkForMeta = false }) async {
Michael Goderbauercb867bb2021-03-05 18:38:15 -08001546 return _allFiles(srcPath, 'dart', minimumMatches: 1)
Alexandre Ardhuinec1a0152019-12-05 22:34:06 +01001547 .map<Set<String>>((File file) {
1548 final Set<String> result = <String>{};
Alexandre Ardhuin4f9b6cf2020-01-07 16:32:04 +01001549 for (final String line in file.readAsLinesSync()) {
Michael Goderbauer045ba2b2021-07-19 13:59:15 -07001550 Match? match = _importPattern.firstMatch(line);
Alexandre Ardhuinec1a0152019-12-05 22:34:06 +01001551 if (match != null)
Michael Goderbauer045ba2b2021-07-19 13:59:15 -07001552 result.add(match.group(2)!);
Alexandre Ardhuinec1a0152019-12-05 22:34:06 +01001553 if (checkForMeta) {
1554 match = _importMetaPattern.firstMatch(line);
1555 if (match != null) {
1556 errors.add(
1557 '${file.path}\nThis package imports the ${yellow}meta$reset package.\n'
1558 'You should instead import the "foundation.dart" library.'
1559 );
1560 }
Alexander Aprelev391e91c2018-08-30 07:30:25 -07001561 }
1562 }
Alexandre Ardhuinec1a0152019-12-05 22:34:06 +01001563 return result;
1564 })
Michael Goderbauer045ba2b2021-07-19 13:59:15 -07001565 .reduce((Set<String>? value, Set<String> element) {
Alexandre Ardhuinec1a0152019-12-05 22:34:06 +01001566 value ??= <String>{};
1567 value.addAll(element);
1568 return value;
1569 });
Alexander Aprelev391e91c2018-08-30 07:30:25 -07001570}
1571
Michael Goderbauer045ba2b2021-07-19 13:59:15 -07001572List<T>? _deepSearch<T>(Map<T, Set<T>> map, T start, [ Set<T>? seen ]) {
LongCatIsLooongd291de02020-01-09 10:25:58 -08001573 if (map[start] == null)
1574 return null; // We catch these separately.
1575
Michael Goderbauer045ba2b2021-07-19 13:59:15 -07001576 for (final T key in map[start]!) {
Alexander Aprelev391e91c2018-08-30 07:30:25 -07001577 if (key == start)
1578 continue; // we catch these separately
1579 if (seen != null && seen.contains(key))
1580 return <T>[start, key];
Michael Goderbauer045ba2b2021-07-19 13:59:15 -07001581 final List<T>? result = _deepSearch<T>(
Alexander Aprelev391e91c2018-08-30 07:30:25 -07001582 map,
1583 key,
Alexandre Ardhuin919dcf52019-06-27 21:23:16 +02001584 <T>{
1585 if (seen == null) start else ...seen,
1586 key,
1587 },
Alexander Aprelev391e91c2018-08-30 07:30:25 -07001588 );
1589 if (result != null) {
1590 result.insert(0, start);
1591 // Only report the shortest chains.
1592 // For example a->b->a, rather than c->a->b->a.
1593 // Since we visit every node, we know the shortest chains are those
1594 // that start and end on the loop.
1595 if (result.first == result.last)
1596 return result;
1597 }
1598 }
1599 return null;
1600}
1601
Alexander Aprelev391e91c2018-08-30 07:30:25 -07001602bool _isGeneratedPluginRegistrant(File file) {
1603 final String filename = path.basename(file.path);
1604 return !file.path.contains('.pub-cache')
1605 && (filename == 'GeneratedPluginRegistrant.java' ||
1606 filename == 'GeneratedPluginRegistrant.h' ||
Jonah Williams0b3f5cf2020-04-21 20:39:36 -07001607 filename == 'GeneratedPluginRegistrant.m' ||
Jonah Williams14722d32020-09-28 10:07:35 -07001608 filename == 'generated_plugin_registrant.dart' ||
1609 filename == 'generated_plugin_registrant.h');
Alexander Aprelev391e91c2018-08-30 07:30:25 -07001610}