diff --git a/bin/internal/update_dart_sdk.sh b/bin/internal/update_dart_sdk.sh index 67d9cdc6c9..148f46c106 100755 --- a/bin/internal/update_dart_sdk.sh +++ b/bin/internal/update_dart_sdk.sh @@ -128,7 +128,7 @@ if [ ! -f "$ENGINE_STAMP" ] || [ "$ENGINE_VERSION" != `cat "$ENGINE_STAMP"` ]; t >&2 echo >&2 echo "It appears that the downloaded file is corrupt; please try again." >&2 echo "If this problem persists, please report the problem at:" - >&2 echo " https://github.com/flutter/flutter/issues/new?template=ACTIVATION.md" + >&2 echo " https://github.com/flutter/flutter/issues/new?template=1_activation.md" >&2 echo rm -f -- "$DART_SDK_ZIP" exit 1 diff --git a/dev/bots/analyze.dart b/dev/bots/analyze.dart index a642f49c15..794d5b10b5 100644 --- a/dev/bots/analyze.dart +++ b/dev/bots/analyze.dart @@ -51,12 +51,15 @@ Future run(List arguments) async { exitWithError(['The analyze.dart script must be run with --enable-asserts.']); } - print('$clock runtimeType in toString...'); + print('$clock No runtimeType in toString...'); await verifyNoRuntimeTypeInToString(flutterRoot); - print('$clock debug mode instead of checked mode...'); + print('$clock Debug mode instead of checked mode...'); await verifyNoCheckedMode(flutterRoot); + print('$clock Links for creating GitHub issues'); + await verifyIssueLinks(flutterRoot); + print('$clock Unexpected binaries...'); await verifyNoBinaries(flutterRoot); @@ -728,6 +731,81 @@ Future verifyNoTrailingSpaces(String workingDirectory, { int minimumMatche exitWithError(problems); } +String _bullets(String value) => ' * $value'; + +Future verifyIssueLinks(String workingDirectory) async { + const String issueLinkPrefix = 'https://github.com/flutter/flutter/issues/new'; + const Set stops = { '\n', ' ', "'", '"', r'\', ')', '>' }; + assert(!stops.contains('.')); // instead of "visit https://foo." say "visit: https://", it copy-pastes better + const String kGiveTemplates = + 'Prefer to provide a link either to $issueLinkPrefix/choose (the list of issue ' + 'templates) or to a specific template directly ($issueLinkPrefix?template=...).\n'; + final Set templateNames = + Directory(path.join(workingDirectory, '.github', 'ISSUE_TEMPLATE')) + .listSync() + .whereType() + .where((File file) => path.extension(file.path) == '.md') + .map((File file) => path.basename(file.path)) + .toSet(); + final String kTemplates = 'The available templates are:\n${templateNames.map(_bullets).join("\n")}'; + final List problems = []; + final Set suggestions = {}; + final List files = await _gitFiles(workingDirectory); + for (final File file in files) { + if (path.basename(file.path).endsWith('_test.dart')) + continue; // Skip tests, they're not public-facing. + final Uint8List bytes = file.readAsBytesSync(); + // We allow invalid UTF-8 here so that binaries don't trip us up. + // There's a separate test in this file that verifies that all text + // files are actually valid UTF-8 (see verifyNoBinaries below). + final String contents = utf8.decode(bytes, allowMalformed: true); + int start = 0; + while ((start = contents.indexOf(issueLinkPrefix, start)) >= 0) { + int end = start + issueLinkPrefix.length; + while (end < contents.length && !stops.contains(contents[end])) { + end += 1; + } + final String url = contents.substring(start, end); + if (url == issueLinkPrefix) { + if (file.path != path.join(workingDirectory, 'dev', 'bots', 'analyze.dart')) { + problems.add('${file.path} contains a direct link to $issueLinkPrefix.'); + suggestions.add(kGiveTemplates); + suggestions.add(kTemplates); + } + } else if (url.startsWith('$issueLinkPrefix?')) { + final Uri parsedUrl = Uri.parse(url); + final List? templates = parsedUrl.queryParametersAll['template']; + if (templates == null) { + problems.add('${file.path} contains $url, which has no "template" argument specified.'); + suggestions.add(kGiveTemplates); + suggestions.add(kTemplates); + } else if (templates.length != 1) { + problems.add('${file.path} contains $url, which has ${templates.length} templates specified.'); + suggestions.add(kGiveTemplates); + suggestions.add(kTemplates); + } else if (!templateNames.contains(templates.single)) { + problems.add('${file.path} contains $url, which specifies a non-existent template ("${templates.single}").'); + suggestions.add(kTemplates); + } else if (parsedUrl.queryParametersAll.keys.length > 1) { + problems.add('${file.path} contains $url, which the analyze.dart script is not sure how to handle.'); + suggestions.add('Update analyze.dart to handle the URLs above, or change them to the expected pattern.'); + } + } else if (url != '$issueLinkPrefix/choose') { + problems.add('${file.path} contains $url, which the analyze.dart script is not sure how to handle.'); + suggestions.add('Update analyze.dart to handle the URLs above, or change them to the expected pattern.'); + } + start = end; + } + } + assert(problems.isEmpty == suggestions.isEmpty); + if (problems.isNotEmpty) { + exitWithError([ + ...problems, + ...suggestions, + ]); + } +} + @immutable class Hash256 { const Hash256(this.a, this.b, this.c, this.d); @@ -1163,7 +1241,7 @@ Future verifyNoBinaries(String workingDirectory, { Set? legacyBin ); legacyBinaries ??= _legacyBinaries; if (!Platform.isWindows) { // TODO(ianh): Port this to Windows - final List files = await _gitFiles(workingDirectory, runSilently: false); + final List files = await _gitFiles(workingDirectory); final List problems = []; for (final File file in files) { final Uint8List bytes = file.readAsBytesSync(); diff --git a/dev/bots/test/analyze_test.dart b/dev/bots/test/analyze_test.dart index 04e6a4b61e..5ba5ff4b0f 100644 --- a/dev/bots/test/analyze_test.dart +++ b/dev/bots/test/analyze_test.dart @@ -156,9 +156,7 @@ void main() { legacyBinaries: {const Hash256(0x39A050CD69434936, 0, 0, 0)}, ), exitCode: Platform.isWindows ? 0 : 1); if (!Platform.isWindows) { - // The output starts with the call to git ls-files, the details of which - // change from run to run, so we only check the trailing end of the output. - expect(result, endsWith('\n' + expect(result, '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n' 'test/analyze-test-input/root/packages/foo/serviceaccount.enc:0: file is not valid UTF-8\n' 'All files in this repository must be UTF-8. In particular, images and other binaries\n' @@ -167,7 +165,7 @@ void main() { 'to which you need access, you should consider how to fetch it from another repository;\n' 'for example, the "assets-for-api-docs" repository is used for images in API docs.\n' '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n' - )); + ); } }); diff --git a/packages/flutter_tools/lib/src/reporting/github_template.dart b/packages/flutter_tools/lib/src/reporting/github_template.dart index f98d1b038e..ed13ad1661 100644 --- a/packages/flutter_tools/lib/src/reporting/github_template.dart +++ b/packages/flutter_tools/lib/src/reporting/github_template.dart @@ -110,8 +110,9 @@ $doctorText ${_projectMetadataInformation()} '''; - final String fullURL = 'https://github.com/flutter/flutter/issues/new?' - 'title=${Uri.encodeQueryComponent(title)}' + final String fullURL = 'https://github.com/flutter/flutter/issues' + '/new' // We split this here to appease our lint that looks for bad "new bug" links. + '?title=${Uri.encodeQueryComponent(title)}' '&body=${Uri.encodeQueryComponent(body)}' '&labels=${Uri.encodeQueryComponent('tool,severe: crash')}';