diff --git a/.cirrus.yml b/.cirrus.yml index 2433c6df473..9499f293b76 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -138,7 +138,7 @@ task: - name: docs-linux # linux-only environment: CPU: 4 - MEMORY: 8G + MEMORY: 12G only_if: "$CIRRUS_PR != ''" script: - ./dev/bots/docs.sh diff --git a/dartdoc_options.yaml b/dartdoc_options.yaml index cbef352566d..d82f23c1efb 100644 --- a/dartdoc_options.yaml +++ b/dartdoc_options.yaml @@ -1,16 +1,17 @@ # This file is used by dartdoc when generating API documentation for Flutter. dartdoc: - # Before you can run dartdoc, the snippets tool needs to have a snapshot built. - # The dev/tools/dartdoc.dart script does this automatically. + # Before you can run dartdoc, the snippets tool needs to be + # activated with "pub global activate snippets " + # The dev/bots/docs.sh script does this automatically. tools: snippet: - command: ["dev/snippets/lib/main.dart", "--type=snippet"] + command: ["bin/cache/dart-sdk/bin/pub", "global", "run", "snippets", "--output-directory=doc/snippets", "--type=snippet"] description: "Creates sample code documentation output from embedded documentation samples." sample: - command: ["dev/snippets/lib/main.dart", "--type=sample"] + command: ["bin/cache/dart-sdk/bin/pub", "global", "run", "snippets", "--output-directory=doc/snippets", "--type=sample"] description: "Creates full application sample code documentation output from embedded documentation samples." dartpad: - command: ["dev/snippets/lib/main.dart", "--type=sample", "--dartpad"] + command: ["bin/cache/dart-sdk/bin/pub", "global", "run", "snippets", "--output-directory=doc/snippets", "--type=dartpad"] description: "Creates full application sample code documentation output from embedded documentation samples and displays it in an embedded DartPad." errors: # Default errors of dartdoc: diff --git a/dev/bots/analyze_sample_code.dart b/dev/bots/analyze_sample_code.dart index e82abedfb0a..beacde99b46 100644 --- a/dev/bots/analyze_sample_code.dart +++ b/dev/bots/analyze_sample_code.dart @@ -7,8 +7,10 @@ // To run this, from the root of the Flutter repository: // bin/cache/dart-sdk/bin/dart dev/bots/analyze_sample_code.dart -// @dart= 2.12 +// @dart= 2.14 +import 'dart:async'; +import 'dart:collection'; import 'dart:convert'; import 'dart:io'; @@ -16,12 +18,22 @@ import 'package:args/args.dart'; import 'package:path/path.dart' as path; import 'package:watcher/watcher.dart'; +// If you update this version, also update it in dev/bots/docs.sh +const String _snippetsActivateVersion = '0.2.2'; + final String _flutterRoot = path.dirname(path.dirname(path.dirname(path.fromUri(Platform.script)))); final String _defaultFlutterPackage = path.join(_flutterRoot, 'packages', 'flutter', 'lib'); final String _defaultDartUiLocation = path.join(_flutterRoot, 'bin', 'cache', 'pkg', 'sky_engine', 'lib', 'ui'); final String _flutter = path.join(_flutterRoot, 'bin', Platform.isWindows ? 'flutter.bat' : 'flutter'); -void main(List arguments) { +/// Finds the location of the pub executable, with the assumption that it is +/// in the same location as the Dart executable used to run this script. +String get _pubExecutable { + final File dartExecutable = File(Platform.resolvedExecutable); + return path.join(path.dirname(dartExecutable.absolute.path), Platform.isWindows ? 'pub.exe' : 'pub'); +} + +Future main(List arguments) async { final ArgParser argParser = ArgParser(); argParser.addOption( 'temp', @@ -61,6 +73,13 @@ void main(List arguments) { abbr: 'i', help: 'Analyzes the sample code in the specified file interactively.', ); + argParser.addFlag( + 'global-activate-snippets', + defaultsTo: true, + negatable: true, + help: 'Whether or not to "pub global activate" the snippets package. If set, will ' + 'activate version $_snippetsActivateVersion', + ); final ArgResults parsedArguments = argParser.parse(arguments); @@ -107,8 +126,25 @@ void main(List arguments) { tempDirectory.createSync(); } + if (parsedArguments['global-activate-snippets']! as bool) { + try { + Process.runSync( + _pubExecutable, + [ + 'global', + 'activate', + 'snippets', + _snippetsActivateVersion, + ], + workingDirectory: _flutterRoot, + ); + } on ProcessException catch (e) { + stderr.writeln('Unable to global activate snippets package at version $_snippetsActivateVersion: $e'); + exit(1); + } + } if (parsedArguments['interactive'] != null) { - _runInteractive( + await _runInteractive( tempDir: tempDirectory, flutterPackage: flutterPackage, filePath: parsedArguments['interactive'] as String, @@ -116,7 +152,7 @@ void main(List arguments) { ); } else { try { - exitCode = SampleChecker( + exitCode = await SampleChecker( flutterPackage, tempDirectory: tempDirectory, verbose: parsedArguments['verbose'] as bool, @@ -129,6 +165,95 @@ void main(List arguments) { } } +typedef TaskQueueClosure = Future Function(); + +class _TaskQueueItem { + _TaskQueueItem(this._closure, this._completer, {this.onComplete}); + + final TaskQueueClosure _closure; + final Completer _completer; + void Function()? onComplete; + + Future run() async { + try { + _completer.complete(await _closure()); + } catch (e) { + _completer.completeError(e); + } finally { + onComplete?.call(); + } + } +} + +/// A task queue of Futures to be completed in parallel, throttling +/// the number of simultaneous tasks. +/// +/// The tasks return results of type T. +class TaskQueue { + /// Creates a task queue with a maximum number of simultaneous jobs. + /// The [maxJobs] parameter defaults to the number of CPU cores on the + /// system. + TaskQueue({int? maxJobs}) + : maxJobs = maxJobs ?? Platform.numberOfProcessors; + + /// The maximum number of jobs that this queue will run simultaneously. + final int maxJobs; + + final Queue<_TaskQueueItem> _pendingTasks = Queue<_TaskQueueItem>(); + final Set<_TaskQueueItem> _activeTasks = <_TaskQueueItem>{}; + final Set> _completeListeners = >{}; + + /// Returns a future that completes when all tasks in the [TaskQueue] are + /// complete. + Future get tasksComplete { + // In case this is called when there are no tasks, we want it to + // signal complete immediately. + if (_activeTasks.isEmpty && _pendingTasks.isEmpty) { + return Future.value(); + } + final Completer completer = Completer(); + _completeListeners.add(completer); + return completer.future; + } + + /// Adds a single closure to the task queue, returning a future that + /// completes when the task completes. + Future add(TaskQueueClosure task) { + final Completer completer = Completer(); + _pendingTasks.add(_TaskQueueItem(task, completer)); + if (_activeTasks.length < maxJobs) { + _processTask(); + } + return completer.future; + } + + // Process a single task. + void _processTask() { + if (_pendingTasks.isNotEmpty && _activeTasks.length <= maxJobs) { + final _TaskQueueItem item = _pendingTasks.removeFirst(); + _activeTasks.add(item); + item.onComplete = () { + _activeTasks.remove(item); + _processTask(); + }; + item.run(); + } else { + _checkForCompletion(); + } + } + + void _checkForCompletion() { + if (_activeTasks.isEmpty && _pendingTasks.isEmpty) { + for (final Completer completer in _completeListeners) { + if (!completer.isCompleted) { + completer.complete(); + } + } + _completeListeners.clear(); + } + } +} + class SampleCheckerException implements Exception { SampleCheckerException(this.message, {this.file, this.line}); final String message; @@ -147,6 +272,12 @@ class SampleCheckerException implements Exception { } } +class AnalysisResult { + const AnalysisResult(this.exitCode, this.errors); + final int exitCode; + final Map> errors; +} + /// Checks samples and code snippets for analysis errors. /// /// Extracts dartdoc content from flutter package source code, identifies code @@ -237,25 +368,6 @@ class SampleChecker { /// generate them. int _expressionId = 0; - /// The exit code from the analysis process. - int _exitCode = 0; - - // Once the snippets tool has been precompiled by Dart, this contains the AOT - // snapshot. - String? _snippetsSnapshotPath; - - /// Finds the location of the snippets script. - String get _snippetsExecutable { - final String platformScriptPath = path.dirname(path.fromUri(Platform.script)); - return path.canonicalize(path.join(platformScriptPath, '..', 'snippets', 'lib', 'main.dart')); - } - - /// Finds the location of the Dart executable. - String get _dartExecutable { - final File dartExecutable = File(Platform.resolvedExecutable); - return dartExecutable.absolute.path; - } - static List _listDartFiles(Directory directory, {bool recursive = false}) { return directory.listSync(recursive: recursive, followLinks: false).whereType().where((File file) => path.extension(file.path) == '.dart').toList(); } @@ -282,9 +394,8 @@ class SampleChecker { List? _headers; /// Checks all the samples in the Dart files in [_flutterPackage] for errors. - int checkSamples() { - _exitCode = 0; - Map> errors = >{}; + Future checkSamples() async { + AnalysisResult? analysisResult; try { final Map sections = {}; final Map snippets = {}; @@ -295,14 +406,14 @@ class SampleChecker { ..._listDartFiles(_flutterPackage, recursive: true), if (_dartUiLocation != null && _dartUiLocation!.existsSync()) ... _listDartFiles(_dartUiLocation!, recursive: true), ]; - _extractSamples(filesToAnalyze, sectionMap: sections, sampleMap: snippets); - errors = _analyze(_tempDirectory, sections, snippets); + await _extractSamples(filesToAnalyze, sectionMap: sections, sampleMap: snippets); + analysisResult = _analyze(_tempDirectory, sections, snippets); } finally { - if (errors.isNotEmpty) { - for (final String filePath in errors.keys) { - errors[filePath]!.forEach(stderr.writeln); + if (analysisResult != null && analysisResult.errors.isNotEmpty) { + for (final String filePath in analysisResult.errors.keys) { + analysisResult.errors[filePath]!.forEach(stderr.writeln); } - stderr.writeln('\nFound ${errors.length} sample code errors.'); + stderr.writeln('\nFound ${analysisResult.errors.length} sample code errors.'); } if (_keepTmp) { print('Leaving temporary directory ${_tempDirectory.path} around for your perusal.'); @@ -313,15 +424,8 @@ class SampleChecker { stderr.writeln('Failed to delete ${_tempDirectory.path}: $e'); } } - // If we made a snapshot, remove it (so as not to clutter up the tree). - if (_snippetsSnapshotPath != null) { - final File snapshot = File(_snippetsSnapshotPath!); - if (snapshot.existsSync()) { - snapshot.deleteSync(); - } - } } - return _exitCode; + return analysisResult.exitCode; } /// Creates a name for the snippets tool to use for the snippet ID from a @@ -333,37 +437,37 @@ class SampleChecker { return sampleId; } - // Precompiles the snippets tool if _snippetsSnapshotPath isn't set yet, and - // runs the precompiled version if it is set. - ProcessResult _runSnippetsScript(List args) { + // The cached JSON Flutter version information from 'flutter --version --machine'. + String? _flutterVersion; + + Future _runSnippetsScript(List args) async { final String workingDirectory = path.join(_flutterRoot, 'dev', 'docs'); - if (_snippetsSnapshotPath == null) { - _snippetsSnapshotPath = '$_snippetsExecutable.snapshot'; - return Process.runSync( - _dartExecutable, - [ - '--snapshot=$_snippetsSnapshotPath', - '--snapshot-kind=app-jit', - path.canonicalize(_snippetsExecutable), - ...args, - ], - workingDirectory: workingDirectory, - ); - } else { - return Process.runSync( - _dartExecutable, - [ - path.canonicalize(_snippetsSnapshotPath!), - ...args, - ], - workingDirectory: workingDirectory, - ); + if (_flutterVersion == null) { + // Capture the flutter version information once so that the snippets tool doesn't + // have to run it for every snippet. + final ProcessResult versionResult = Process.runSync(_flutter, ['--version', '--machine']); + _flutterVersion = versionResult.stdout as String? ?? ''; } + return Process.run( + _pubExecutable, + [ + 'global', + 'run', + 'snippets', + ...args, + ], + workingDirectory: workingDirectory, + environment: { + if (!Platform.environment.containsKey('FLUTTER_ROOT')) 'FLUTTER_ROOT': _flutterRoot, + if (_flutterVersion!.isNotEmpty) 'FLUTTER_VERSION': _flutterVersion!, + }, + includeParentEnvironment: true, + ); } /// Writes out the given sample to an output file in the [_tempDirectory] and /// returns the output file. - File _writeSample(Sample sample) { + Future _writeSample(Sample sample) async { // Generate the snippet. final String sampleId = _createNameFromSource('sample', sample.start.filename, sample.start.line); final String inputName = '$sampleId.input'; @@ -374,11 +478,14 @@ class SampleChecker { final List args = [ '--output=${outputFile.absolute.path}', '--input=${inputFile.absolute.path}', + // Formatting the output will fail on analysis errors, and we want it to fail + // here, not there. + '--no-format-output', ...sample.args, ]; if (verbose) print('Generating sample for ${sample.start.filename}:${sample.start.line}'); - final ProcessResult process = _runSnippetsScript(args); + final ProcessResult process = await _runSnippetsScript(args); if (verbose) stderr.write('${process.stderr}'); if (process.exitCode != 0) { @@ -394,12 +501,12 @@ class SampleChecker { /// Extracts the samples from the Dart files in [files], writes them /// to disk, and adds them to the appropriate [sectionMap] or [sampleMap]. - void _extractSamples( + Future _extractSamples( List files, { required Map sectionMap, required Map sampleMap, bool silent = false, - }) { + }) async { final List
sections =
[]; final List samples = []; int dartpadCount = 0; @@ -545,13 +652,19 @@ class SampleChecker { if (sectionMap != null) sectionMap[path] = section; } + final TaskQueue sampleQueue = TaskQueue(); for (final Sample sample in samples) { - final File snippetFile = _writeSample(sample); + final Future futureFile = sampleQueue.add(() => _writeSample(sample)); if (sampleMap != null) { - sample.contents = snippetFile.readAsLinesSync(); - sampleMap[snippetFile.absolute.path] = sample; + sampleQueue.add(() async { + final File snippetFile = await futureFile; + sample.contents = await snippetFile.readAsLines(); + sampleMap[snippetFile.absolute.path] = sample; + return futureFile; + }); } } + await sampleQueue.tasksComplete; } /// Helper to process arguments given as a (possibly quoted) string. @@ -596,9 +709,11 @@ class SampleChecker { /// Creates the configuration files necessary for the analyzer to consider /// the temporary directory a package, and sets which lint rules to enforce. void _createConfigurationFiles(Directory directory) { - final File pubSpec = File(path.join(directory.path, 'pubspec.yaml'))..createSync(recursive: true); + final File pubSpec = File(path.join(directory.path, 'pubspec.yaml')); + if (!pubSpec.existsSync()) { + pubSpec.createSync(recursive: true); - pubSpec.writeAsStringSync(''' + pubSpec.writeAsStringSync(''' name: analyze_sample_code environment: sdk: ">=2.12.0-0 <3.0.0" @@ -611,11 +726,13 @@ dependencies: dev_dependencies: flutter_lints: ^1.0.3 '''); - + } // Import the analysis options from the Flutter root. final File analysisOptions = File(path.join(directory.path, 'analysis_options.yaml')); - analysisOptions.writeAsStringSync(''' + if (!analysisOptions.existsSync()) { + analysisOptions.createSync(recursive: true); + analysisOptions.writeAsStringSync(''' include: package:flutter_lints/flutter.yaml linter: @@ -623,6 +740,7 @@ linter: # Samples want to print things pretty often. avoid_print: false '''); + } } /// Writes out a sample section to the disk and returns the file. @@ -641,7 +759,7 @@ linter: } /// Invokes the analyzer on the given [directory] and returns the stdout. - List _runAnalyzer(Directory directory, {bool silent = true}) { + int _runAnalyzer(Directory directory, {bool silent = true, required List output}) { if (!silent) print('Starting analysis of code samples.'); _createConfigurationFiles(directory); @@ -654,7 +772,8 @@ linter: final List stdout = result.stdout.toString().trim().split('\n'); // Remove output from building the flutter tool. stderr.removeWhere((String line) { - return line.startsWith('Building flutter tool...'); + return line.startsWith('Building flutter tool...') + || line.startsWith('Waiting for another flutter command to release the startup lock...'); }); // Check out the stderr to see if the analyzer had it's own issues. if (stderr.isNotEmpty && stderr.first.contains(RegExp(r' issues? found\. \(ran in '))) { @@ -672,19 +791,21 @@ linter: if (stdout.isNotEmpty && stdout.first.startsWith('Running "flutter pub get" in ')) { stdout.removeAt(0); } - _exitCode = result.exitCode; - return stdout; + output.addAll(stdout); + return result.exitCode; } /// Starts the analysis phase of checking the samples by invoking the analyzer /// and parsing its output to create a map of filename to [AnalysisError]s. - Map> _analyze( + AnalysisResult _analyze( Directory directory, Map sections, Map samples, { bool silent = false, }) { - final List errors = _runAnalyzer(directory, silent: silent); + final List errors = []; + int exitCode = _runAnalyzer(directory, silent: silent, output: errors); + final Map> analysisErrors = >{}; void addAnalysisError(File file, AnalysisError error) { if (analysisErrors.containsKey(file.path)) { @@ -825,15 +946,15 @@ linter: ); } } - if (_exitCode == 1 && analysisErrors.isEmpty && !unknownAnalyzerErrors) { - _exitCode = 0; + if (exitCode == 1 && analysisErrors.isEmpty && !unknownAnalyzerErrors) { + exitCode = 0; } - if (_exitCode == 0) { + if (exitCode == 0) { if (!silent) print('No analysis errors in samples!'); assert(analysisErrors.isEmpty); } - return analysisErrors; + return AnalysisResult(exitCode, analysisErrors); } /// Process one block of sample code (the part inside of "```" markers). @@ -1070,17 +1191,17 @@ Future _runInteractive({ } print('Starting up in interactive mode on ${path.relative(filePath, from: _flutterRoot)} ...'); - void analyze(SampleChecker checker, File file) { + Future analyze(SampleChecker checker, File file) async { final Map sections = {}; final Map snippets = {}; - checker._extractSamples([file], silent: true, sectionMap: sections, sampleMap: snippets); - final Map> errors = checker._analyze(checker._tempDirectory, sections, snippets, silent: true); + await checker._extractSamples([file], silent: true, sectionMap: sections, sampleMap: snippets); + final AnalysisResult analysisResult = checker._analyze(checker._tempDirectory, sections, snippets, silent: true); stderr.writeln('\u001B[2J\u001B[H'); // Clears the old results from the terminal. - if (errors.isNotEmpty) { - for (final String filePath in errors.keys) { - errors[filePath]!.forEach(stderr.writeln); + if (analysisResult.errors.isNotEmpty) { + for (final String filePath in analysisResult.errors.keys) { + analysisResult.errors[filePath]!.forEach(stderr.writeln); } - stderr.writeln('\nFound ${errors.length} errors.'); + stderr.writeln('\nFound ${analysisResult.errors.length} errors.'); } else { stderr.writeln('\nNo issues found.'); } @@ -1088,7 +1209,7 @@ Future _runInteractive({ final SampleChecker checker = SampleChecker(flutterPackage, tempDirectory: tempDir) .._createConfigurationFiles(tempDir); - analyze(checker, file); + await analyze(checker, file); print('Type "q" to quit, or "r" to delete temp dir and manually reload.'); diff --git a/dev/bots/docs.sh b/dev/bots/docs.sh index 6ef62ccf72f..ad182366500 100755 --- a/dev/bots/docs.sh +++ b/dev/bots/docs.sh @@ -13,14 +13,26 @@ function script_location() { script_location="$(readlink "$script_location")" [[ "$script_location" != /* ]] && script_location="$DIR/$script_location" done - echo "$(cd -P "$(dirname "$script_location")" >/dev/null && pwd)" + cd -P "$(dirname "$script_location")" >/dev/null && pwd } function generate_docs() { # Install and activate dartdoc. # NOTE: When updating to a new dartdoc version, please also update # `dartdoc_options.yaml` to include newly introduced error and warning types. - "$PUB" global activate dartdoc 1.0.0 + "$PUB" global activate dartdoc 1.0.2 + + # Install and activate the snippets tool, which resides in the + # assets-for-api-docs repo: + # https://github.com/flutter/assets-for-api-docs/tree/master/packages/snippets + # >>> If you update this version, also update it in dev/bots/analyze_sample_code.dart <<< + "$PUB" global activate snippets 0.2.2 + + # Install and activate the snippets tool, which resides in the + # assets-for-api-docs repo: + # https://github.com/flutter/assets-for-api-docs/tree/master/packages/snippets + # >>> If you update this version, also update it in dev/bots/analyze_sample_code.dart <<< + "$PUB" global activate snippets 0.2.1 # This script generates a unified doc set, and creates # a custom index.html, placing everything into dev/docs/doc. @@ -96,9 +108,9 @@ function move_offline_into_place() { mv flutter.docs.zip doc/offline/flutter.docs.zip du -sh doc/offline/flutter.docs.zip if [[ "$LUCI_BRANCH" == "stable" ]]; then - echo -e "\n ${FLUTTER_VERSION}\n https://api.flutter.dev/offline/flutter.docset.tar.gz\n" > doc/offline/flutter.xml + echo -e "\n ${FLUTTER_VERSION_STRING}\n https://api.flutter.dev/offline/flutter.docset.tar.gz\n" > doc/offline/flutter.xml else - echo -e "\n ${FLUTTER_VERSION}\n https://master-api.flutter.dev/offline/flutter.docset.tar.gz\n" > doc/offline/flutter.xml + echo -e "\n ${FLUTTER_VERSION_STRING}\n https://master-api.flutter.dev/offline/flutter.docset.tar.gz\n" > doc/offline/flutter.xml fi mv flutter.docset.tar.gz doc/offline/flutter.docset.tar.gz du -sh doc/offline/flutter.docset.tar.gz @@ -125,9 +137,11 @@ DART="$DART_BIN/dart" PUB="$DART_BIN/pub" export PATH="$FLUTTER_BIN:$DART_BIN:$PATH" -# Make sure dart is installed by invoking flutter to download it. -"$FLUTTER" --version -FLUTTER_VERSION=$(cat "$FLUTTER_ROOT/version") +# Make sure dart is installed by invoking Flutter to download it. +# This also creates the 'version' file. +FLUTTER_VERSION=$("$FLUTTER" --version --machine) +export FLUTTER_VERSION +FLUTTER_VERSION_STRING=$(cat "$FLUTTER_ROOT/version") # If the pub cache directory exists in the root, then use that. FLUTTER_PUB_CACHE="$FLUTTER_ROOT/.pub-cache" diff --git a/dev/bots/test.dart b/dev/bots/test.dart index 2c8348d277d..554c4340c5c 100644 --- a/dev/bots/test.dart +++ b/dev/bots/test.dart @@ -746,7 +746,6 @@ Future _runFrameworkTests() async { print('${green}Running package tests$reset for directories other than packages/flutter'); await _pubRunTest(path.join(flutterRoot, 'dev', 'bots')); await _pubRunTest(path.join(flutterRoot, 'dev', 'devicelab'), ensurePrecompiledTool: false); // See https://github.com/flutter/flutter/issues/86209 - await _pubRunTest(path.join(flutterRoot, 'dev', 'snippets')); // TODO(fujino): Move this to its own test shard await _pubRunTest(path.join(flutterRoot, 'dev', 'conductor'), forceSingleCore: true); await _runFlutterTest(path.join(flutterRoot, 'dev', 'integration_tests', 'android_semantics_testing')); diff --git a/dev/bots/test/analyze-sample-code-test-dart-ui/ui.dart b/dev/bots/test/analyze-sample-code-test-dart-ui/ui.dart index ba5a55590d4..5b0239d16df 100644 --- a/dev/bots/test/analyze-sample-code-test-dart-ui/ui.dart +++ b/dev/bots/test/analyze-sample-code-test-dart-ui/ui.dart @@ -11,11 +11,13 @@ library dart.ui; /// Annotation used by Flutter's Dart compiler to indicate that an /// [Object.toString] override should not be replaced with a supercall. /// -/// {@tool sample} +/// {@tool sample --template=stateless_widget_material} /// A sample if using keepToString to prevent replacement by a supercall. /// /// ```dart /// class MyStringBuffer { +/// error; +/// /// StringBuffer _buffer = StringBuffer(); /// /// @keepToString diff --git a/dev/bots/test/analyze_sample_code_test.dart b/dev/bots/test/analyze_sample_code_test.dart index 98d90441c01..4f7b32a33ac 100644 --- a/dev/bots/test/analyze_sample_code_test.dart +++ b/dev/bots/test/analyze_sample_code_test.dart @@ -20,11 +20,10 @@ void main() { ['analyze_sample_code.dart', '--no-include-dart-ui', 'test/analyze-sample-code-test-input'], ); final List stdoutLines = process.stdout.toString().split('\n'); - final List stderrLines = process.stderr.toString().split('\n') - ..removeWhere((String line) => line.startsWith('Analyzer output:') || line.startsWith('Building flutter tool...')); + final List stderrLines = process.stderr.toString().split('\n'); expect(process.exitCode, isNot(equals(0))); - expect(stderrLines, [ - 'In sample starting at dev/bots/test/analyze-sample-code-test-input/known_broken_documentation.dart:125: child: Text(title),', + expect(stderrLines, containsAll([ + 'In sample starting at dev/bots/test/analyze-sample-code-test-input/known_broken_documentation.dart:125: child: Text(title),', ">>> error: The final variable 'title' can't be read because it is potentially unassigned at this point (read_potentially_unassigned_final)", 'dev/bots/test/analyze-sample-code-test-input/known_broken_documentation.dart:30:9: new Opacity(', '>>> info: Unnecessary new keyword (unnecessary_new)', @@ -38,32 +37,35 @@ void main() { ">>> error: A value of type 'Null' can't be assigned to a variable of type 'int' (invalid_assignment)", 'dev/bots/test/analyze-sample-code-test-input/known_broken_documentation.dart:120:24: const SizedBox(),', '>>> error: Unexpected comma at end of sample code. (missing_identifier)', - '', 'Found 2 sample code errors.', - '' - ]); - expect(stdoutLines, [ + ])); + expect(stdoutLines, containsAll([ 'Found 9 snippet code blocks, 0 sample code sections, and 2 dartpad sections.', 'Starting analysis of code samples.', - '', - ]); + ])); }); test('Analyzes dart:ui code', () { final ProcessResult process = Process.runSync( '../../bin/cache/dart-sdk/bin/dart', [ 'analyze_sample_code.dart', - '--dart-ui-location', - 'test/analyze-sample-code-test-dart-ui', + '--dart-ui-location=test/analyze-sample-code-test-dart-ui', 'test/analyze-sample-code-test-input', ], ); final List stdoutLines = process.stdout.toString().split('\n'); + final List stderrLines = process.stderr.toString().split('\n'); expect(process.exitCode, isNot(equals(0))); - expect(stdoutLines, equals([ + expect(stderrLines, containsAll([ + 'In sample starting at dev/bots/test/analyze-sample-code-test-dart-ui/ui.dart:15:class MyStatelessWidget extends StatelessWidget {', + ">>> error: Missing concrete implementation of 'StatelessWidget.build' (non_abstract_class_inherits_abstract_member)", + 'In sample starting at dev/bots/test/analyze-sample-code-test-dart-ui/ui.dart:15:class MyStringBuffer {', + ">>> error: Classes can't be declared inside other classes (class_in_class)", + ])); + expect(stdoutLines, containsAll([ // There is one sample code section in the test's dummy dart:ui code. 'Found 9 snippet code blocks, 1 sample code sections, and 2 dartpad sections.', - '', + 'Starting analysis of code samples.', ])); }); } diff --git a/dev/snippets/config/skeletons/dartpad-sample.html b/dev/snippets/config/skeletons/dartpad-sample.html index ad850a084a8..8cbd443846d 100644 --- a/dev/snippets/config/skeletons/dartpad-sample.html +++ b/dev/snippets/config/skeletons/dartpad-sample.html @@ -8,18 +8,6 @@ link -
- - - -
{{description}} @@ -28,15 +16,5 @@ flutter create --sample={{id}} mysample
- {@end-inject-html} diff --git a/dev/snippets/config/skeletons/snippet.html b/dev/snippets/config/skeletons/snippet.html index 75f87824e4e..99574b028b8 100644 --- a/dev/snippets/config/skeletons/snippet.html +++ b/dev/snippets/config/skeletons/snippet.html @@ -8,9 +8,6 @@ link -
- -
{{description}}
diff --git a/dev/snippets/config/templates/freeform.tmpl b/dev/snippets/config/templates/freeform.tmpl index 1337bd4b585..ea4f7f08180 100644 --- a/dev/snippets/config/templates/freeform.tmpl +++ b/dev/snippets/config/templates/freeform.tmpl @@ -1,5 +1,5 @@ -/// Flutter code sample for {{element}} - +// Flutter code sample for {{element}} +// {{description}} {{code-dartImports}} diff --git a/dev/snippets/config/templates/stateful_widget.tmpl b/dev/snippets/config/templates/stateful_widget.tmpl index db4472249f8..014f618e6d1 100644 --- a/dev/snippets/config/templates/stateful_widget.tmpl +++ b/dev/snippets/config/templates/stateful_widget.tmpl @@ -1,5 +1,5 @@ -/// Flutter code sample for {{element}} - +// Flutter code sample for {{element}} +// {{description}} {{code-dartImports}} @@ -35,5 +35,5 @@ class MyStatefulWidget extends StatefulWidget { /// This is the private State class that goes with MyStatefulWidget. class _MyStatefulWidgetState extends State { - {{code}} +{{code}} } diff --git a/dev/snippets/config/templates/stateful_widget_cupertino.tmpl b/dev/snippets/config/templates/stateful_widget_cupertino.tmpl index a07430d9eb3..e424cddcb3c 100644 --- a/dev/snippets/config/templates/stateful_widget_cupertino.tmpl +++ b/dev/snippets/config/templates/stateful_widget_cupertino.tmpl @@ -1,5 +1,5 @@ -/// Flutter code sample for {{element}} - +// Flutter code sample for {{element}} +// {{description}} {{code-dartImports}} @@ -36,5 +36,5 @@ class MyStatefulWidget extends StatefulWidget { /// This is the private State class that goes with MyStatefulWidget. class _MyStatefulWidgetState extends State { - {{code}} +{{code}} } diff --git a/dev/snippets/config/templates/stateful_widget_cupertino_page_scaffold.tmpl b/dev/snippets/config/templates/stateful_widget_cupertino_page_scaffold.tmpl index 509b2558b0b..905f4da4d5c 100644 --- a/dev/snippets/config/templates/stateful_widget_cupertino_page_scaffold.tmpl +++ b/dev/snippets/config/templates/stateful_widget_cupertino_page_scaffold.tmpl @@ -1,5 +1,5 @@ -/// Flutter code sample for {{element}} - +// Flutter code sample for {{element}} +// {{description}} {{code-dartImports}} @@ -39,5 +39,5 @@ class MyStatefulWidget extends StatefulWidget { /// This is the private State class that goes with MyStatefulWidget. class _MyStatefulWidgetState extends State { - {{code}} +{{code}} } diff --git a/dev/snippets/config/templates/stateful_widget_cupertino_ticker.tmpl b/dev/snippets/config/templates/stateful_widget_cupertino_ticker.tmpl index 32262d67fe8..38c75479831 100644 --- a/dev/snippets/config/templates/stateful_widget_cupertino_ticker.tmpl +++ b/dev/snippets/config/templates/stateful_widget_cupertino_ticker.tmpl @@ -1,5 +1,5 @@ -/// Flutter code sample for {{element}} - +// Flutter code sample for {{element}} +// {{description}} {{code-dartImports}} @@ -37,5 +37,5 @@ class MyStatefulWidget extends StatefulWidget { /// This is the private State class that goes with MyStatefulWidget. /// AnimationControllers can be created with `vsync: this` because of TickerProviderStateMixin. class _MyStatefulWidgetState extends State with TickerProviderStateMixin { - {{code}} +{{code}} } diff --git a/dev/snippets/config/templates/stateful_widget_material.tmpl b/dev/snippets/config/templates/stateful_widget_material.tmpl index 233bbc9769c..fe916062875 100644 --- a/dev/snippets/config/templates/stateful_widget_material.tmpl +++ b/dev/snippets/config/templates/stateful_widget_material.tmpl @@ -1,5 +1,5 @@ -/// Flutter code sample for {{element}} - +// Flutter code sample for {{element}} +// {{description}} {{code-dartImports}} @@ -36,5 +36,5 @@ class MyStatefulWidget extends StatefulWidget { /// This is the private State class that goes with MyStatefulWidget. class _MyStatefulWidgetState extends State { - {{code}} +{{code}} } diff --git a/dev/snippets/config/templates/stateful_widget_material_ticker.tmpl b/dev/snippets/config/templates/stateful_widget_material_ticker.tmpl index 02f2e2047e3..0357ff41a8b 100644 --- a/dev/snippets/config/templates/stateful_widget_material_ticker.tmpl +++ b/dev/snippets/config/templates/stateful_widget_material_ticker.tmpl @@ -1,5 +1,5 @@ -/// Flutter code sample for {{element}} - +// Flutter code sample for {{element}} +// {{description}} {{code-dartImports}} @@ -37,5 +37,5 @@ class MyStatefulWidget extends StatefulWidget { /// This is the private State class that goes with MyStatefulWidget. /// AnimationControllers can be created with `vsync: this` because of TickerProviderStateMixin. class _MyStatefulWidgetState extends State with TickerProviderStateMixin { - {{code}} +{{code}} } diff --git a/dev/snippets/config/templates/stateful_widget_restoration.tmpl b/dev/snippets/config/templates/stateful_widget_restoration.tmpl index 05c7c1ba354..405a9b6629c 100644 --- a/dev/snippets/config/templates/stateful_widget_restoration.tmpl +++ b/dev/snippets/config/templates/stateful_widget_restoration.tmpl @@ -1,5 +1,5 @@ -/// Flutter code sample for {{element}} - +// Flutter code sample for {{element}} +// {{description}} {{code-dartImports}} diff --git a/dev/snippets/config/templates/stateful_widget_restoration_cupertino.tmpl b/dev/snippets/config/templates/stateful_widget_restoration_cupertino.tmpl index d7267f3d2d2..3435e8c2a00 100644 --- a/dev/snippets/config/templates/stateful_widget_restoration_cupertino.tmpl +++ b/dev/snippets/config/templates/stateful_widget_restoration_cupertino.tmpl @@ -1,5 +1,5 @@ -/// Flutter code sample for {{element}} - +// Flutter code sample for {{element}} +// {{description}} {{code-dartImports}} diff --git a/dev/snippets/config/templates/stateful_widget_restoration_material.tmpl b/dev/snippets/config/templates/stateful_widget_restoration_material.tmpl index 5d3fc3c95bd..11045ab5926 100644 --- a/dev/snippets/config/templates/stateful_widget_restoration_material.tmpl +++ b/dev/snippets/config/templates/stateful_widget_restoration_material.tmpl @@ -1,5 +1,5 @@ -/// Flutter code sample for {{element}} - +// Flutter code sample for {{element}} +// {{description}} {{code-dartImports}} diff --git a/dev/snippets/config/templates/stateful_widget_scaffold.tmpl b/dev/snippets/config/templates/stateful_widget_scaffold.tmpl index b7b6ccb542d..bfc77b006e7 100644 --- a/dev/snippets/config/templates/stateful_widget_scaffold.tmpl +++ b/dev/snippets/config/templates/stateful_widget_scaffold.tmpl @@ -1,5 +1,5 @@ -/// Flutter code sample for {{element}} - +// Flutter code sample for {{element}} +// {{description}} {{code-dartImports}} @@ -39,5 +39,5 @@ class MyStatefulWidget extends StatefulWidget { /// This is the private State class that goes with MyStatefulWidget. class _MyStatefulWidgetState extends State { - {{code}} +{{code}} } diff --git a/dev/snippets/config/templates/stateful_widget_scaffold_center.tmpl b/dev/snippets/config/templates/stateful_widget_scaffold_center.tmpl index 16191373dfb..bd6bc66b512 100644 --- a/dev/snippets/config/templates/stateful_widget_scaffold_center.tmpl +++ b/dev/snippets/config/templates/stateful_widget_scaffold_center.tmpl @@ -1,5 +1,5 @@ -/// Flutter code sample for {{element}} - +// Flutter code sample for {{element}} +// {{description}} {{code-dartImports}} @@ -41,5 +41,5 @@ class MyStatefulWidget extends StatefulWidget { /// This is the private State class that goes with MyStatefulWidget. class _MyStatefulWidgetState extends State { - {{code}} +{{code}} } diff --git a/dev/snippets/config/templates/stateful_widget_scaffold_center_freeform_state.tmpl b/dev/snippets/config/templates/stateful_widget_scaffold_center_freeform_state.tmpl index 4f05456dea1..1659319c4f3 100644 --- a/dev/snippets/config/templates/stateful_widget_scaffold_center_freeform_state.tmpl +++ b/dev/snippets/config/templates/stateful_widget_scaffold_center_freeform_state.tmpl @@ -1,5 +1,5 @@ -/// Flutter code sample for {{element}} - +// Flutter code sample for {{element}} +// {{description}} {{code-dartImports}} diff --git a/dev/snippets/config/templates/stateful_widget_ticker.tmpl b/dev/snippets/config/templates/stateful_widget_ticker.tmpl index de5daeacf09..ced2116ed56 100644 --- a/dev/snippets/config/templates/stateful_widget_ticker.tmpl +++ b/dev/snippets/config/templates/stateful_widget_ticker.tmpl @@ -1,5 +1,5 @@ -/// Flutter code sample for {{element}} - +// Flutter code sample for {{element}} +// {{description}} {{code-dartImports}} @@ -36,5 +36,5 @@ class MyStatefulWidget extends StatefulWidget { /// This is the private State class that goes with MyStatefulWidget. /// AnimationControllers can be created with `vsync: this` because of TickerProviderStateMixin. class _MyStatefulWidgetState extends State with TickerProviderStateMixin { - {{code}} +{{code}} } diff --git a/dev/snippets/config/templates/stateless_widget.tmpl b/dev/snippets/config/templates/stateless_widget.tmpl index fa5c4a848d1..c04e577423c 100644 --- a/dev/snippets/config/templates/stateless_widget.tmpl +++ b/dev/snippets/config/templates/stateless_widget.tmpl @@ -1,5 +1,5 @@ -/// Flutter code sample for {{element}} - +// Flutter code sample for {{element}} +// {{description}} {{code-dartImports}} @@ -30,5 +30,5 @@ class MyStatelessWidget extends StatelessWidget { const MyStatelessWidget({Key? key}) : super(key: key); @override - {{code}} +{{code}} } diff --git a/dev/snippets/config/templates/stateless_widget_cupertino.tmpl b/dev/snippets/config/templates/stateless_widget_cupertino.tmpl index 7d116d09584..9eec7767482 100644 --- a/dev/snippets/config/templates/stateless_widget_cupertino.tmpl +++ b/dev/snippets/config/templates/stateless_widget_cupertino.tmpl @@ -1,5 +1,5 @@ -/// Flutter code sample for {{element}} - +// Flutter code sample for {{element}} +// {{description}} {{code-dartImports}} @@ -32,5 +32,5 @@ class MyStatelessWidget extends StatelessWidget { const MyStatelessWidget({Key? key}) : super(key: key); @override - {{code}} +{{code}} } diff --git a/dev/snippets/config/templates/stateless_widget_cupertino_page_scaffold.tmpl b/dev/snippets/config/templates/stateless_widget_cupertino_page_scaffold.tmpl index ffa71d0784a..dd4552a82a7 100644 --- a/dev/snippets/config/templates/stateless_widget_cupertino_page_scaffold.tmpl +++ b/dev/snippets/config/templates/stateless_widget_cupertino_page_scaffold.tmpl @@ -1,5 +1,5 @@ -/// Flutter code sample for {{element}} - +// Flutter code sample for {{element}} +// {{description}} {{code-dartImports}} @@ -35,5 +35,5 @@ class MyStatelessWidget extends StatelessWidget { const MyStatelessWidget({Key? key}) : super(key: key); @override - {{code}} +{{code}} } diff --git a/dev/snippets/config/templates/stateless_widget_material.tmpl b/dev/snippets/config/templates/stateless_widget_material.tmpl index ef408dfd209..dc5e7a3c6dd 100644 --- a/dev/snippets/config/templates/stateless_widget_material.tmpl +++ b/dev/snippets/config/templates/stateless_widget_material.tmpl @@ -1,5 +1,5 @@ -/// Flutter code sample for {{element}} - +// Flutter code sample for {{element}} +// {{description}} {{code-dartImports}} @@ -32,5 +32,5 @@ class MyStatelessWidget extends StatelessWidget { const MyStatelessWidget({Key? key}) : super(key: key); @override - {{code}} +{{code}} } diff --git a/dev/snippets/config/templates/stateless_widget_restoration_cupertino.tmpl b/dev/snippets/config/templates/stateless_widget_restoration_cupertino.tmpl index 5e2a6e13c1b..975654ac96d 100644 --- a/dev/snippets/config/templates/stateless_widget_restoration_cupertino.tmpl +++ b/dev/snippets/config/templates/stateless_widget_restoration_cupertino.tmpl @@ -1,5 +1,5 @@ -/// Flutter code sample for {{element}} - +// Flutter code sample for {{element}} +// {{description}} {{code-dartImports}} @@ -32,5 +32,5 @@ class MyStatelessWidget extends StatelessWidget { const MyStatelessWidget({Key? key}) : super(key: key); @override - {{code}} +{{code}} } diff --git a/dev/snippets/config/templates/stateless_widget_restoration_material.tmpl b/dev/snippets/config/templates/stateless_widget_restoration_material.tmpl index 2d936ef3478..5e34280dbd1 100644 --- a/dev/snippets/config/templates/stateless_widget_restoration_material.tmpl +++ b/dev/snippets/config/templates/stateless_widget_restoration_material.tmpl @@ -1,5 +1,5 @@ -/// Flutter code sample for {{element}} - +// Flutter code sample for {{element}} +// {{description}} {{code-dartImports}} @@ -32,5 +32,5 @@ class MyStatelessWidget extends StatelessWidget { const MyStatelessWidget({Key? key}) : super(key: key); @override - {{code}} +{{code}} } diff --git a/dev/snippets/config/templates/stateless_widget_scaffold.tmpl b/dev/snippets/config/templates/stateless_widget_scaffold.tmpl index a79807efe05..834656e3487 100644 --- a/dev/snippets/config/templates/stateless_widget_scaffold.tmpl +++ b/dev/snippets/config/templates/stateless_widget_scaffold.tmpl @@ -1,5 +1,5 @@ -/// Flutter code sample for {{element}} - +// Flutter code sample for {{element}} +// {{description}} {{code-dartImports}} @@ -35,5 +35,5 @@ class MyStatelessWidget extends StatelessWidget { const MyStatelessWidget({Key? key}) : super(key: key); @override - {{code}} +{{code}} } diff --git a/dev/snippets/config/templates/stateless_widget_scaffold_center.tmpl b/dev/snippets/config/templates/stateless_widget_scaffold_center.tmpl index f3d24395894..85eb7a56ed1 100644 --- a/dev/snippets/config/templates/stateless_widget_scaffold_center.tmpl +++ b/dev/snippets/config/templates/stateless_widget_scaffold_center.tmpl @@ -1,5 +1,5 @@ -/// Flutter code sample for {{element}} - +// Flutter code sample for {{element}} +// {{description}} {{code-dartImports}} @@ -37,5 +37,5 @@ class MyStatelessWidget extends StatelessWidget { const MyStatelessWidget({Key? key}) : super(key: key); @override - {{code}} +{{code}} } diff --git a/dev/snippets/lib/configuration.dart b/dev/snippets/lib/configuration.dart deleted file mode 100644 index d3729ba4c22..00000000000 --- a/dev/snippets/lib/configuration.dart +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright 2014 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'dart:io' hide Platform; - -import 'package:meta/meta.dart'; -import 'package:path/path.dart' as path; - -/// What type of snippet to produce. -enum SnippetType { - /// Produces a snippet that includes the code interpolated into an application - /// template. - sample, - - /// Produces a nicely formatted sample code, but no application. - snippet, -} - -/// Return the name of an enum item. -String getEnumName(dynamic enumItem) { - final String name = '$enumItem'; - final int index = name.indexOf('.'); - return index == -1 ? name : name.substring(index + 1); -} - -/// A class to compute the configuration of the snippets input and output -/// locations based in the current location of the snippets main.dart. -class Configuration { - Configuration({required this.flutterRoot}) : assert(flutterRoot != null); - - final Directory flutterRoot; - - /// This is the configuration directory for the snippets system, containing - /// the skeletons and templates. - @visibleForTesting - Directory get configDirectory { - _configPath ??= Directory( - path.canonicalize(path.join(flutterRoot.absolute.path, 'dev', 'snippets', 'config'))); - return _configPath!; - } - - // Nullable so that we can use it as a lazy cache. - Directory? _configPath; - - /// This is where the snippets themselves will be written, in order to be - /// uploaded to the docs site. - Directory get outputDirectory { - _docsDirectory ??= Directory( - path.canonicalize(path.join(flutterRoot.absolute.path, 'dev', 'docs', 'doc', 'snippets'))); - return _docsDirectory!; - } - - // Nullable so that we can use it as a lazy cache. - Directory? _docsDirectory; - - /// This makes sure that the output directory exists. - void createOutputDirectory() { - if (!outputDirectory.existsSync()) { - outputDirectory.createSync(recursive: true); - } - } - - /// The directory containing the HTML skeletons to be filled out with metadata - /// and returned to dartdoc for insertion in the output. - Directory get skeletonsDirectory => Directory(path.join(configDirectory.path,'skeletons')); - - /// The directory containing the code templates that can be referenced by the - /// dartdoc. - Directory get templatesDirectory => Directory(path.join(configDirectory.path, 'templates')); - - /// Gets the skeleton file to use for the given [SnippetType] and DartPad preference. - File getHtmlSkeletonFile(SnippetType type, {bool showDartPad = false}) { - assert(!showDartPad || type == SnippetType.sample, - 'Only application snippets work with dartpad.'); - final String filename = - '${showDartPad ? 'dartpad-' : ''}${getEnumName(type)}.html'; - return File(path.join(skeletonsDirectory.path, filename)); - } -} diff --git a/dev/snippets/lib/main.dart b/dev/snippets/lib/main.dart deleted file mode 100644 index 6fa01276980..00000000000 --- a/dev/snippets/lib/main.dart +++ /dev/null @@ -1,241 +0,0 @@ -// Copyright 2014 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'dart:io' show exit, stderr, stdout, File, ProcessResult; - -import 'package:args/args.dart'; -import 'package:meta/meta.dart'; -import 'package:path/path.dart' as path; -import 'package:platform/platform.dart'; -import 'package:process/process.dart'; - -import 'configuration.dart'; -import 'snippets.dart'; - -const String _kSerialOption = 'serial'; -const String _kElementOption = 'element'; -const String _kHelpOption = 'help'; -const String _kInputOption = 'input'; -const String _kLibraryOption = 'library'; -const String _kOutputOption = 'output'; -const String _kPackageOption = 'package'; -const String _kTemplateOption = 'template'; -const String _kTypeOption = 'type'; -const String _kShowDartPad = 'dartpad'; - -class GitStatusFailed implements Exception { - GitStatusFailed(this.gitResult); - - final ProcessResult gitResult; - - @override - String toString() => 'git status exited with a non-zero exit code: ${gitResult.exitCode}:\n${gitResult.stderr}\n${gitResult.stdout}'; -} - -/// Get the name of the channel these docs are from. -/// -/// First check env variable LUCI_BRANCH, then refer to the currently -/// checked out git branch. -String getChannelName({ - @visibleForTesting - Platform platform = const LocalPlatform(), - @visibleForTesting - ProcessManager processManager = const LocalProcessManager(), -}) { - final String? envReleaseChannel = platform.environment['LUCI_BRANCH']?.trim(); - if (['master', 'stable'].contains(envReleaseChannel)) { - return envReleaseChannel!; - } - final RegExp gitBranchRegexp = RegExp(r'^## (?.*)'); - final ProcessResult gitResult = processManager.runSync(['git', 'status', '-b', '--porcelain'], - environment: { - 'GIT_TRACE': '2', - 'GIT_TRACE_SETUP': '2' - }, - includeParentEnvironment: true - ); - if (gitResult.exitCode != 0) { - throw GitStatusFailed(gitResult); - } - final RegExpMatch? gitBranchMatch = gitBranchRegexp.firstMatch((gitResult.stdout as String).trim().split('\n').first); - return gitBranchMatch == null ? '' : gitBranchMatch.namedGroup('branch')!.split('...').first; -} - -// This is a hack to workaround the fact that git status inexplicably fails -// (random non-zero error code) about 2% of the time. -String getChannelNameWithRetries() { - int retryCount = 0; - while(retryCount < 2) { - try { - return getChannelName(); - } on GitStatusFailed catch (e) { - retryCount += 1; - stderr.write('git status failed, retrying ($retryCount)\nError report:\n$e'); - } - } - return getChannelName(); -} - -/// Generates snippet dartdoc output for a given input, and creates any sample -/// applications needed by the snippet. -void main(List argList) { - const Platform platform = LocalPlatform(); - final Map environment = platform.environment; - final ArgParser parser = ArgParser(); - final List snippetTypes = - SnippetType.values.map((SnippetType type) => getEnumName(type)).toList(); - parser.addOption( - _kTypeOption, - defaultsTo: getEnumName(SnippetType.sample), - allowed: snippetTypes, - allowedHelp: { - getEnumName(SnippetType.sample): - 'Produce a code sample application complete with embedding the sample in an ' - 'application template.', - getEnumName(SnippetType.snippet): - 'Produce a nicely formatted piece of sample code. Does not embed the ' - 'sample into an application template.', - }, - help: 'The type of snippet to produce.', - ); - parser.addOption( - _kTemplateOption, - defaultsTo: null, - help: 'The name of the template to inject the code into.', - ); - parser.addOption( - _kOutputOption, - defaultsTo: null, - help: 'The output path for the generated sample application. Overrides ' - 'the naming generated by the --package/--library/--element arguments. ' - 'Metadata will be written alongside in a .json file. ' - 'The basename of this argument is used as the ID', - ); - parser.addOption( - _kInputOption, - defaultsTo: environment['INPUT'], - help: 'The input file containing the sample code to inject.', - ); - parser.addOption( - _kPackageOption, - defaultsTo: environment['PACKAGE_NAME'], - help: 'The name of the package that this sample belongs to.', - ); - parser.addOption( - _kLibraryOption, - defaultsTo: environment['LIBRARY_NAME'], - help: 'The name of the library that this sample belongs to.', - ); - parser.addOption( - _kElementOption, - defaultsTo: environment['ELEMENT_NAME'], - help: 'The name of the element that this sample belongs to.', - ); - parser.addOption( - _kSerialOption, - defaultsTo: environment['INVOCATION_INDEX'], - help: 'A unique serial number for this snippet tool invocation.', - ); - parser.addFlag( - _kHelpOption, - defaultsTo: false, - negatable: false, - help: 'Prints help documentation for this command', - ); - parser.addFlag( - _kShowDartPad, - defaultsTo: false, - negatable: false, - help: "Indicates whether DartPad should be included in the sample's " - 'final HTML output. This flag only applies when the type parameter is ' - '"sample".', - ); - - final ArgResults args = parser.parse(argList); - - if (args[_kHelpOption] as bool) { - stderr.writeln(parser.usage); - exit(0); - } - - final SnippetType snippetType = SnippetType.values - .firstWhere((SnippetType type) => getEnumName(type) == args[_kTypeOption]); - - if (args[_kShowDartPad] == true && snippetType != SnippetType.sample) { - errorExit('${args[_kTypeOption]} was selected, but the --dartpad flag is only valid ' - 'for application sample code.'); - } - - if (args[_kInputOption] == null) { - stderr.writeln(parser.usage); - errorExit('The --$_kInputOption option must be specified, either on the command ' - 'line, or in the INPUT environment variable.'); - } - - final File input = File(args['input'] as String); - if (!input.existsSync()) { - errorExit('The input file ${input.path} does not exist.'); - } - - String? template; - if (snippetType == SnippetType.sample) { - final String templateArg = args[_kTemplateOption] as String; - if (templateArg == null || templateArg.isEmpty) { - stderr.writeln(parser.usage); - errorExit('The --$_kTemplateOption option must be specified on the command ' - 'line for application samples.'); - } - template = templateArg.replaceAll(RegExp(r'.tmpl$'), ''); - } - - final String packageName = args[_kPackageOption] as String? ?? ''; - final String libraryName = args[_kLibraryOption] as String? ?? ''; - final String elementName = args[_kElementOption] as String? ?? ''; - final String serial = args[_kSerialOption] as String? ?? ''; - final List id = []; - if (args[_kOutputOption] != null) { - id.add(path.basename(path.basenameWithoutExtension(args[_kOutputOption] as String))); - } else { - if (packageName.isNotEmpty && packageName != 'flutter') { - id.add(packageName); - } - if (libraryName.isNotEmpty) { - id.add(libraryName); - } - if (elementName.isNotEmpty) { - id.add(elementName); - } - if (serial.isNotEmpty) { - id.add(serial); - } - if (id.isEmpty) { - errorExit('Unable to determine ID. At least one of --$_kPackageOption, ' - '--$_kLibraryOption, --$_kElementOption, -$_kSerialOption, or the environment variables ' - 'PACKAGE_NAME, LIBRARY_NAME, ELEMENT_NAME, or INVOCATION_INDEX must be non-empty.'); - } - } - - final SnippetGenerator generator = SnippetGenerator(); - stdout.write(generator.generate( - input, - snippetType, - showDartPad: args[_kShowDartPad] as bool, - template: template, - output: args[_kOutputOption] != null ? File(args[_kOutputOption] as String) : null, - metadata: { - 'sourcePath': environment['SOURCE_PATH'], - 'sourceLine': environment['SOURCE_LINE'] != null - ? int.tryParse(environment['SOURCE_LINE']!) - : null, - 'id': id.join('.'), - 'channel': getChannelNameWithRetries(), - 'serial': serial, - 'package': packageName, - 'library': libraryName, - 'element': elementName, - }, - )); - - exit(0); -} diff --git a/dev/snippets/lib/snippets.dart b/dev/snippets/lib/snippets.dart deleted file mode 100644 index 2dddca0b810..00000000000 --- a/dev/snippets/lib/snippets.dart +++ /dev/null @@ -1,350 +0,0 @@ -// Copyright 2014 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'dart:convert'; -import 'dart:io'; - -import 'package:dart_style/dart_style.dart'; -import 'package:path/path.dart' as path; - -import 'configuration.dart'; - -void errorExit(String message) { - stderr.writeln(message); - exit(1); -} - -// A Tuple containing the name and contents associated with a code block in a -// snippet. -class _ComponentTuple { - _ComponentTuple(this.name, this.contents, {this.language = ''}); - final String name; - final List contents; - final String language; - String get mergedContent => contents.join('\n').trim(); -} - -/// Generates the snippet HTML, as well as saving the output snippet main to -/// the output directory. -class SnippetGenerator { - SnippetGenerator({Configuration? configuration}) - : configuration = configuration ?? - // Flutter's root is four directories up from this script. - Configuration(flutterRoot: Directory(Platform.environment['FLUTTER_ROOT'] - ?? path.canonicalize(path.join(path.dirname(path.fromUri(Platform.script)), '..', '..', '..')))) { - this.configuration.createOutputDirectory(); - } - - /// The configuration used to determine where to get/save data for the - /// snippet. - final Configuration configuration; - - static const JsonEncoder jsonEncoder = JsonEncoder.withIndent(' '); - - /// A Dart formatted used to format the snippet code and finished application - /// code. - static DartFormatter formatter = DartFormatter(pageWidth: 80, fixes: StyleFix.all); - - /// This returns the output file for a given snippet ID. Only used for - /// [SnippetType.sample] snippets. - File getOutputFile(String id) => File(path.join(configuration.outputDirectory.path, '$id.dart')); - - /// Gets the path to the template file requested. - File? getTemplatePath(String templateName, {Directory? templatesDir}) { - final Directory templateDir = templatesDir ?? configuration.templatesDirectory; - final File templateFile = File(path.join(templateDir.path, '$templateName.tmpl')); - return templateFile.existsSync() ? templateFile : null; - } - - /// Injects the [injections] into the [template], and turning the - /// "description" injection into a comment. Only used for - /// [SnippetType.sample] snippets. - String _interpolateTemplate(List<_ComponentTuple> injections, String template, Map metadata) { - final RegExp moustacheRegExp = RegExp('{{([^}]+)}}'); - final String interpolated = template.replaceAllMapped(moustacheRegExp, (Match match) { - if (match[1] == 'description') { - // Place the description into a comment. - final List description = injections - .firstWhere((_ComponentTuple tuple) => tuple.name == match[1]) - .contents - .map((String line) => '// $line') - .toList(); - // Remove any leading/trailing empty comment lines. - // We don't want to remove ALL empty comment lines, only the ones at the - // beginning and the end. - while (description.isNotEmpty && description.last == '// ') { - description.removeLast(); - } - while (description.isNotEmpty && description.first == '// ') { - description.removeAt(0); - } - return description.join('\n').trim(); - } else { - // If the match isn't found in the injections, then just remove the - // mustache reference, since we want to allow the sections to be - // "optional" in the input: users shouldn't be forced to add an empty - // "```dart preamble" section if that section would be empty. - final int componentIndex = injections - .indexWhere((_ComponentTuple tuple) => tuple.name == match[1]); - if (componentIndex == -1) { - return (metadata[match[1]] ?? '').toString(); - } - return injections[componentIndex].mergedContent; - } - }).trim(); - return _sortImports(interpolated); - } - - String _sortImports(String code) { - final List result = []; - final List lines = code.split('\n'); - final List imports = []; - int firstImport = -1; - int lineNumber =0; - for (final String line in lines) { - if (RegExp(r'^\s*import').matchAsPrefix(line) != null) { - if (firstImport < 0) { - firstImport = lineNumber; - } - imports.add(line); - } else { - result.add(line); - } - lineNumber++; - } - if (firstImport > 0) { - final List dartImports = []; - final List packageImports = []; - final List otherImports = []; - final RegExp typeRegExp = RegExp(r'''import\s+['"](?\w+)'''); - imports.sort(); - for (final String importLine in imports) { - final RegExpMatch? match = typeRegExp.firstMatch(importLine); - if (match != null) { - switch (match.namedGroup('type')) { - case 'dart': - dartImports.add(importLine); - break; - case 'package': - packageImports.add(importLine); - break; - default: - otherImports.add(importLine); - break; - } - } else { - otherImports.add(importLine); - } - } - - // Insert the sorted sections in the proper order, with a blank line in between - // sections. - result.insertAll(firstImport, [ - ...dartImports, - if (dartImports.isNotEmpty) '', - ...packageImports, - if (packageImports.isNotEmpty) '', - ...otherImports, - ]); - } - return result.join('\n'); - } - - /// Interpolates the [injections] into an HTML skeleton file. - /// - /// Similar to interpolateTemplate, but we are only looking for `code-` - /// components, and we care about the order of the injections. - /// - /// Takes into account the [type] and doesn't substitute in the id and the app - /// if not a [SnippetType.sample] snippet. - String _interpolateSkeleton( - SnippetType type, - List<_ComponentTuple> injections, - String skeleton, - Map metadata, - ) { - final List result = []; - const HtmlEscape htmlEscape = HtmlEscape(); - String language = 'dart'; - for (final _ComponentTuple injection in injections) { - if (!injection.name.startsWith('code')) { - continue; - } - result.addAll(injection.contents); - if (injection.language.isNotEmpty) { - language = injection.language; - } - result.addAll(['', '// ...', '']); - } - if (result.length > 3) { - result.removeRange(result.length - 3, result.length); - } - // Only insert a div for the description if there actually is some text there. - // This means that the {{description}} marker in the skeleton needs to - // be inside of an {@inject-html} block. - String description = injections.firstWhere((_ComponentTuple tuple) => tuple.name == 'description').mergedContent; - description = description.trim().isNotEmpty - ? '
{@end-inject-html}$description{@inject-html}
' - : ''; - - // DartPad only supports stable or master as valid channels. Use master - // if not on stable so that local runs will work (although they will - // still take their sample code from the master docs server). - final String channel = metadata['channel'] == 'stable' ? 'stable' : 'master'; - - final Map substitutions = { - 'description': description, - 'code': htmlEscape.convert(result.join('\n')), - 'language': language, - 'serial': '', - 'id': metadata['id']! as String, - 'channel': channel, - 'element': (metadata['element'] ?? '') as String, - 'app': '', - }; - if (type == SnippetType.sample) { - substitutions - ..['serial'] = metadata['serial']?.toString() ?? '0' - ..['app'] = htmlEscape.convert(injections.firstWhere((_ComponentTuple tuple) => tuple.name == 'app').mergedContent); - } - return skeleton.replaceAllMapped(RegExp('{{(${substitutions.keys.join('|')})}}'), (Match match) { - return substitutions[match[1]] ?? ''; - }); - } - - /// Parses the input for the various code and description segments, and - /// returns them in the order found. - List<_ComponentTuple> _parseInput(String input) { - bool inCodeBlock = false; - input = input.trim(); - final List description = []; - final List<_ComponentTuple> components = <_ComponentTuple>[]; - String? language; - final RegExp codeStartEnd = RegExp(r'^\s*```(?[-\w]+|[-\w]+ (?
[-\w]+))?\s*$'); - for (final String line in input.split('\n')) { - final RegExpMatch? match = codeStartEnd.firstMatch(line); - if (match != null) { // If we saw the start or end of a code block - inCodeBlock = !inCodeBlock; - if (match.namedGroup('language') != null) { - language = match[1]; - assert(language != null); - language = language!; - if (match.namedGroup('section') != null) { - components.add(_ComponentTuple('code-${match.namedGroup('section')}', [], language: language)); - } else { - components.add(_ComponentTuple('code', [], language: language)); - } - } else { - language = null; - } - continue; - } - if (!inCodeBlock) { - description.add(line); - } else { - assert(language != null); - if (components.isNotEmpty) { - components.last.contents.add(line); - } - } - } - return <_ComponentTuple>[ - _ComponentTuple('description', description), - ...components, - ]; - } - - String _loadFileAsUtf8(File file) { - return file.readAsStringSync(encoding: utf8); - } - - String _addLineNumbers(String app) { - final StringBuffer buffer = StringBuffer(); - int count = 0; - for (final String line in app.split('\n')) { - count++; - buffer.writeln('${count.toString().padLeft(5, ' ')}: $line'); - } - return buffer.toString(); - } - - /// The main routine for generating snippets. - /// - /// The [input] is the file containing the dartdoc comments (minus the leading - /// comment markers). - /// - /// The [type] is the type of snippet to create: either a - /// [SnippetType.sample] or a [SnippetType.snippet]. - /// - /// [showDartPad] indicates whether DartPad should be shown where possible. - /// Currently, this value only has an effect if [type] is - /// [SnippetType.sample], in which case an alternate skeleton file is - /// used to create the final HTML output. - /// - /// The [template] must not be null if the [type] is - /// [SnippetType.sample], and specifies the name of the template to use - /// for the application code. - /// - /// The [id] is a string ID to use for the output file, and to tell the user - /// about in the `flutter create` hint. It must not be null if the [type] is - /// [SnippetType.sample]. - String generate( - File input, - SnippetType type, { - bool showDartPad = false, - String? template, - File? output, - required Map metadata, - }) { - assert(template != null || type != SnippetType.sample); - assert(metadata['id'] != null); - assert(!showDartPad || type == SnippetType.sample, 'Only application samples work with dartpad.'); - final List<_ComponentTuple> snippetData = _parseInput(_loadFileAsUtf8(input)); - switch (type) { - case SnippetType.sample: - final Directory templatesDir = configuration.templatesDirectory; - if (templatesDir == null) { - stderr.writeln('Unable to find the templates directory.'); - exit(1); - } - final File? templateFile = getTemplatePath(template!, templatesDir: templatesDir); - if (templateFile == null) { - stderr.writeln('The template $template was not found in the templates directory ${templatesDir.path}'); - exit(1); - } - final String templateContents = _loadFileAsUtf8(templateFile); - String app = _interpolateTemplate(snippetData, templateContents, metadata); - - try { - app = formatter.format(app); - } on FormatterException catch (exception) { - stderr.write('Code to format:\n${_addLineNumbers(app)}\n'); - errorExit('Unable to format snippet app template: $exception'); - } - - snippetData.add(_ComponentTuple('app', app.split('\n'))); - final File outputFile = output ?? getOutputFile(metadata['id']! as String); - stderr.writeln('Writing to ${outputFile.absolute.path}'); - outputFile.writeAsStringSync(app); - - final File metadataFile = File(path.join(path.dirname(outputFile.path), - '${path.basenameWithoutExtension(outputFile.path)}.json')); - stderr.writeln('Writing metadata to ${metadataFile.absolute.path}'); - final int descriptionIndex = snippetData.indexWhere( - (_ComponentTuple data) => data.name == 'description'); - final String descriptionString = descriptionIndex == -1 ? '' : snippetData[descriptionIndex].mergedContent; - metadata.addAll({ - 'file': path.basename(outputFile.path), - 'description': descriptionString, - }); - metadataFile.writeAsStringSync(jsonEncoder.convert(metadata)); - break; - case SnippetType.snippet: - break; - } - final String skeleton = - _loadFileAsUtf8(configuration.getHtmlSkeletonFile(type, showDartPad: showDartPad)); - return _interpolateSkeleton(type, snippetData, skeleton, metadata); - } -} diff --git a/dev/snippets/pubspec.yaml b/dev/snippets/pubspec.yaml deleted file mode 100644 index 5b2f176544a..00000000000 --- a/dev/snippets/pubspec.yaml +++ /dev/null @@ -1,99 +0,0 @@ -name: snippets -version: 0.1.0 -description: A code snippet dartdoc extension for Flutter API docs. -homepage: https://github.com/flutter/flutter - -environment: - sdk: ">=2.12.1 <3.0.0" - -dartdoc: - # Exclude this package from the hosted API docs (Ironically...). - nodoc: true - -dependencies: - args: 2.2.0 - dart_style: 2.0.3 - meta: 1.7.0 - platform: 3.0.0 - process: 4.2.3 - - _fe_analyzer_shared: 23.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - analyzer: 2.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - async: 2.8.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - charcode: 1.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - cli_util: 0.3.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - collection: 1.15.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - convert: 3.0.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - crypto: 3.0.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - file: 6.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - glob: 2.0.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - package_config: 2.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - path: 1.8.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - pedantic: 1.11.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - pub_semver: 2.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - source_span: 1.8.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - string_scanner: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - term_glyph: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - typed_data: 1.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - watcher: 1.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - yaml: 3.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - -dev_dependencies: - test: 1.17.10 - - boolean_selector: 2.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - coverage: 1.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - frontend_server_client: 2.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - http_multi_server: 3.0.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - http_parser: 4.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - io: 1.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - js: 0.6.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - logging: 1.0.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - matcher: 0.12.10 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - mime: 1.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - node_preamble: 2.0.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - pool: 1.5.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - shelf: 1.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - shelf_packages_handler: 3.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - shelf_static: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - shelf_web_socket: 1.0.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - source_map_stack_trace: 2.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - source_maps: 0.10.10 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - stack_trace: 1.10.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - stream_channel: 2.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.4.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_core: 0.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - vm_service: 7.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - web_socket_channel: 2.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - webkit_inspection_protocol: 1.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - -executables: - snippets: null - - boolean_selector: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - http: 0.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - http_multi_server: 2.0.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - http_parser: 3.1.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - io: 0.3.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - js: 0.6.1+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - json_rpc_2: 2.0.9 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - matcher: 0.12.3+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - mime: 0.9.6+2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - multi_server_socket: 1.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - node_preamble: 1.4.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - package_resolver: 1.0.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - pool: 1.3.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - pub_semver: 1.4.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - shelf: 0.7.3+3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - shelf_packages_handler: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - shelf_static: 0.2.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - shelf_web_socket: 0.2.2+4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - source_map_stack_trace: 1.1.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - source_maps: 0.10.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - stack_trace: 1.9.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - stream_channel: 1.6.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - term_glyph: 1.0.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - vm_service_client: 0.2.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - web_socket_channel: 1.0.9 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - -# PUBSPEC CHECKSUM: c51d diff --git a/dev/snippets/test/configuration_test.dart b/dev/snippets/test/configuration_test.dart deleted file mode 100644 index 26131ab655b..00000000000 --- a/dev/snippets/test/configuration_test.dart +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright 2014 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'dart:io'; - -import 'package:snippets/configuration.dart'; -import 'package:test/test.dart' hide TypeMatcher, isInstanceOf; - -void main() { - group('Configuration', () { - late Configuration config; - - setUp(() { - config = Configuration(flutterRoot: Directory('/flutter sdk')); - }); - test('config directory is correct', () async { - expect(config.configDirectory.path, - matches(RegExp(r'[/\\]flutter sdk[/\\]dev[/\\]snippets[/\\]config'))); - }); - test('output directory is correct', () async { - expect(config.outputDirectory.path, - matches(RegExp(r'[/\\]flutter sdk[/\\]dev[/\\]docs[/\\]doc[/\\]snippets'))); - }); - test('skeleton directory is correct', () async { - expect(config.skeletonsDirectory.path, - matches(RegExp(r'[/\\]flutter sdk[/\\]dev[/\\]snippets[/\\]config[/\\]skeletons'))); - }); - test('templates directory is correct', () async { - expect(config.templatesDirectory.path, - matches(RegExp(r'[/\\]flutter sdk[/\\]dev[/\\]snippets[/\\]config[/\\]templates'))); - }); - test('html skeleton file for sample is correct', () async { - expect( - config.getHtmlSkeletonFile(SnippetType.snippet).path, - matches(RegExp( - r'[/\\]flutter sdk[/\\]dev[/\\]snippets[/\\]config[/\\]skeletons[/\\]snippet.html'))); - }); - test('html skeleton file for app with no dartpad is correct', () async { - expect( - config.getHtmlSkeletonFile(SnippetType.sample).path, - matches(RegExp( - r'[/\\]flutter sdk[/\\]dev[/\\]snippets[/\\]config[/\\]skeletons[/\\]sample.html'))); - }); - test('html skeleton file for app with dartpad is correct', () async { - expect( - config.getHtmlSkeletonFile(SnippetType.sample, showDartPad: true).path, - matches(RegExp( - r'[/\\]flutter sdk[/\\]dev[/\\]snippets[/\\]config[/\\]skeletons[/\\]dartpad-sample.html'))); - }); - }); -} diff --git a/dev/snippets/test/snippets_test.dart b/dev/snippets/test/snippets_test.dart deleted file mode 100644 index 7e54896d6e8..00000000000 --- a/dev/snippets/test/snippets_test.dart +++ /dev/null @@ -1,334 +0,0 @@ -// Copyright 2014 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'dart:convert'; -import 'dart:io' show Directory, File, Process, ProcessResult, ProcessSignal, ProcessStartMode, SystemEncoding; -import 'package:path/path.dart' as path; -import 'package:platform/platform.dart'; -import 'package:process/process.dart'; -import 'package:snippets/configuration.dart'; -import 'package:snippets/main.dart' show getChannelName; -import 'package:snippets/snippets.dart'; -import 'package:test/test.dart' hide TypeMatcher, isInstanceOf; - -void main() { - group('Generator', () { - late Configuration configuration; - late SnippetGenerator generator; - late Directory tmpDir; - late File template; - - setUp(() { - tmpDir = Directory.systemTemp.createTempSync('flutter_snippets_test.'); - configuration = Configuration(flutterRoot: Directory(path.join( - tmpDir.absolute.path, 'flutter'))); - configuration.createOutputDirectory(); - configuration.templatesDirectory.createSync(recursive: true); - configuration.skeletonsDirectory.createSync(recursive: true); - template = File(path.join(configuration.templatesDirectory.path, 'template.tmpl')); - template.writeAsStringSync(''' -// Flutter code sample for {{element}} - -{{description}} - -import 'package:flutter/material.dart'; -import '../foo.dart'; - -{{code-imports}} - -{{code-my-preamble}} - -main() { - {{code}} -} -'''); - configuration.getHtmlSkeletonFile(SnippetType.sample).writeAsStringSync(''' -
HTML Bits
-{{description}} -
{{code}}
-
{{app}}
-
More HTML Bits
-'''); - configuration.getHtmlSkeletonFile(SnippetType.snippet).writeAsStringSync(''' -
HTML Bits
-{{description}} -
{{code}}
-
More HTML Bits
-'''); - configuration.getHtmlSkeletonFile(SnippetType.sample, showDartPad: true).writeAsStringSync(''' -
HTML Bits (DartPad-style)
- -
More HTML Bits
-'''); - generator = SnippetGenerator(configuration: configuration); - }); - tearDown(() { - tmpDir.deleteSync(recursive: true); - }); - - test('generates samples', () async { - final File inputFile = File(path.join(tmpDir.absolute.path, 'snippet_in.txt')) - ..createSync(recursive: true) - ..writeAsStringSync(r''' -A description of the snippet. - -On several lines. - -```dart imports -import 'dart:ui'; -``` - -```my-dart_language my-preamble -const String name = 'snippet'; -``` - -```dart -void main() { - print('The actual $name.'); -} -``` -'''); - final File outputFile = File(path.join(tmpDir.absolute.path, 'snippet_out.txt')); - - final String html = generator.generate( - inputFile, - SnippetType.sample, - template: 'template', - metadata: { - 'id': 'id', - 'channel': 'stable', - 'element': 'MyElement', - }, - output: outputFile, - ); - expect(html, contains('
HTML Bits
')); - expect(html, contains('
More HTML Bits
')); - expect(html, contains(r'print('The actual $name.');')); - expect(html, contains('A description of the snippet.\n')); - expect(html, isNot(contains('sample_channel=stable'))); - expect( - html, - contains('// A description of the snippet.\n' - '//\n' - '// On several lines.\n')); - expect(html, contains('void main() {')); - - final String outputContents = outputFile.readAsStringSync(); - expect(outputContents, contains('// Flutter code sample for MyElement')); - expect(outputContents, contains('A description of the snippet.')); - expect(outputContents, contains('void main() {')); - expect(outputContents, contains("const String name = 'snippet';")); - final List lines = outputContents.split('\n'); - final int dartUiLine = lines.indexOf("import 'dart:ui';"); - final int materialLine = lines.indexOf("import 'package:flutter/material.dart';"); - final int otherLine = lines.indexOf("import '../foo.dart';"); - expect(dartUiLine, lessThan(materialLine)); - expect(materialLine, lessThan(otherLine)); - }); - - test('generates snippets', () async { - final File inputFile = File(path.join(tmpDir.absolute.path, 'snippet_in.txt')) - ..createSync(recursive: true) - ..writeAsStringSync(r''' -A description of the snippet. - -On several lines. - -```code -void main() { - print('The actual $name.'); -} -``` -'''); - - final String html = generator.generate( - inputFile, - SnippetType.snippet, - metadata: {'id': 'id'}, - ); - expect(html, contains('
HTML Bits
')); - expect(html, contains('
More HTML Bits
')); - expect(html, contains(r' print('The actual $name.');')); - expect(html, contains('
{@end-inject-html}A description of the snippet.\n\n' - 'On several lines.{@inject-html}
\n')); - expect(html, contains('main() {')); - }); - - test('generates dartpad samples', () async { - final File inputFile = File(path.join(tmpDir.absolute.path, 'snippet_in.txt')) - ..createSync(recursive: true) - ..writeAsStringSync(r''' -A description of the snippet. - -On several lines. - -```code -void main() { - print('The actual $name.'); -} -``` -'''); - - final String html = generator.generate( - inputFile, - SnippetType.sample, - showDartPad: true, - template: 'template', - metadata: {'id': 'id', 'channel': 'stable'}, - ); - expect(html, contains('
HTML Bits (DartPad-style)
')); - expect(html, contains('
More HTML Bits
')); - expect(html, contains('')); - }); - - test('generates sample metadata', () async { - final File inputFile = File(path.join(tmpDir.absolute.path, 'snippet_in.txt')) - ..createSync(recursive: true) - ..writeAsStringSync(r''' -A description of the snippet. - -On several lines. - -```code -void main() { - print('The actual $name.'); -} -``` -'''); - - final File outputFile = File(path.join(tmpDir.absolute.path, 'snippet_out.dart')); - final File expectedMetadataFile = File(path.join(tmpDir.absolute.path, 'snippet_out.json')); - - generator.generate( - inputFile, - SnippetType.sample, - template: 'template', - output: outputFile, - metadata: {'sourcePath': 'some/path.dart', 'id': 'id', 'channel': 'stable'}, - ); - expect(expectedMetadataFile.existsSync(), isTrue); - final Map json = jsonDecode(expectedMetadataFile.readAsStringSync()) as Map; - expect(json['id'], equals('id')); - expect(json['channel'], equals('stable')); - expect(json['file'], equals('snippet_out.dart')); - expect(json['description'], equals('A description of the snippet.\n\nOn several lines.')); - // Ensure any passed metadata is included in the output JSON too. - expect(json['sourcePath'], equals('some/path.dart')); - }); - }); - - group('getChannelName()', () { - test('does not call git if LUCI_BRANCH env var provided', () { - const String branch = 'stable'; - final FakePlatform platform = FakePlatform( - environment: {'LUCI_BRANCH': branch}, - ); - final FakeProcessManager processManager = FakeProcessManager([]); - expect( - getChannelName( - platform: platform, - processManager: processManager, - ), - branch, - ); - expect(processManager.hasRemainingExpectations, false); - }); - - test('calls git if LUCI_BRANCH env var is not provided', () { - const String branch = 'stable'; - final FakePlatform platform = FakePlatform( - environment: {}, - ); - final ProcessResult result = ProcessResult(0, 0, '## $branch...refs/heads/master', ''); - final FakeProcessManager processManager = FakeProcessManager( - [FakeCommand('git status -b --porcelain', result)], - ); - expect( - getChannelName( - platform: platform, - processManager: processManager, - ), - branch, - ); - expect(processManager.hasRemainingExpectations, false); - }); - }); -} - -const SystemEncoding systemEncoding = SystemEncoding(); - -class FakeCommand { - FakeCommand(this.command, [ProcessResult? result]) : _result = result; - final String command; - - final ProcessResult? _result; - ProcessResult get result => _result ?? ProcessResult(0, 0, '', ''); -} - -class FakeProcessManager implements ProcessManager { - FakeProcessManager(this.remainingExpectations); - - final List remainingExpectations; - - @override - bool canRun(dynamic command, {String? workingDirectory}) => true; - - @override - Future start( - List command, { - String? workingDirectory, - Map? environment, - bool includeParentEnvironment = true, - bool runInShell = false, - ProcessStartMode mode = ProcessStartMode.normal, - }) { - throw Exception('not implemented'); - } - - @override - Future run( - List command, { - String? workingDirectory, - Map? environment, - bool includeParentEnvironment = true, - bool runInShell = false, - Encoding stdoutEncoding = systemEncoding, - Encoding stderrEncoding = systemEncoding, - }) { - throw Exception('not implemented'); - } - - @override - ProcessResult runSync( - List command, { - String? workingDirectory, - Map? environment, - bool includeParentEnvironment = true, - bool runInShell = false, - Encoding stdoutEncoding = systemEncoding, - Encoding stderrEncoding = systemEncoding, - }) { - if (remainingExpectations.isEmpty) { - fail( - 'Called FakeProcessManager with $command when no further commands were expected!', - ); - } - final FakeCommand expectedCommand = remainingExpectations.removeAt(0); - final String expectedName = expectedCommand.command; - final String actualName = command.join(' '); - if (expectedName != actualName) { - fail( - 'FakeProcessManager expected the command $expectedName but received $actualName', - ); - } - return expectedCommand.result; - } - - bool get hasRemainingExpectations => remainingExpectations.isNotEmpty; - - @override - bool killPid(int pid, [ProcessSignal signal = ProcessSignal.sigterm]) { - throw Exception('not implemented'); - } -} diff --git a/dev/tools/dartdoc.dart b/dev/tools/dartdoc.dart index 2bd01565516..5cbcaa5eb74 100644 --- a/dev/tools/dartdoc.dart +++ b/dev/tools/dartdoc.dart @@ -118,14 +118,25 @@ Future main(List arguments) async { 'dartdoc', ]; - // Verify which version of dartdoc we're using. - final ProcessResult result = Process.runSync( + // Verify which version of snippets and dartdoc we're using. + final ProcessResult snippetsResult = Process.runSync( pubExecutable, - [...dartdocBaseArgs, '--version'], + [ + 'global', + 'list', + ], workingDirectory: kDocsRoot, environment: pubEnvironment, + stdoutEncoding: utf8, ); - print('\n${result.stdout}flutter version: $version\n'); + print(''); + final Iterable versionMatches = RegExp(r'^(?snippets|dartdoc) (?[^\s]+)', multiLine: true) + .allMatches(snippetsResult.stdout as String); + for (final RegExpMatch match in versionMatches) { + print('${match.namedGroup('name')} version: ${match.namedGroup('version')}'); + } + + print('flutter version: $version\n'); // Dartdoc warnings and errors in these packages are considered fatal. // All packages owned by flutter should be in the list. diff --git a/packages/flutter/lib/src/cupertino/page_scaffold.dart b/packages/flutter/lib/src/cupertino/page_scaffold.dart index 65b330868e5..5fa3e59b176 100644 --- a/packages/flutter/lib/src/cupertino/page_scaffold.dart +++ b/packages/flutter/lib/src/cupertino/page_scaffold.dart @@ -32,7 +32,7 @@ import 'theme.dart'; /// // Uncomment to change the background color /// // backgroundColor: CupertinoColors.systemPink, /// navigationBar: const CupertinoNavigationBar( -/// middle: const Text('Sample Code'), +/// middle: Text('Sample Code'), /// ), /// child: ListView( /// children: [ diff --git a/packages/flutter/lib/src/material/app_bar.dart b/packages/flutter/lib/src/material/app_bar.dart index 6f6c81e9979..88c61ada3c8 100644 --- a/packages/flutter/lib/src/material/app_bar.dart +++ b/packages/flutter/lib/src/material/app_bar.dart @@ -1410,7 +1410,7 @@ class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate { /// child: SizedBox( /// height: 20, /// child: Center( -/// child: const Text('Scroll to see the SliverAppBar in effect.'), +/// child: Text('Scroll to see the SliverAppBar in effect.'), /// ), /// ), /// ), diff --git a/packages/flutter/lib/src/material/data_table.dart b/packages/flutter/lib/src/material/data_table.dart index 4389ef24b0c..0bc190f2c16 100644 --- a/packages/flutter/lib/src/material/data_table.dart +++ b/packages/flutter/lib/src/material/data_table.dart @@ -382,7 +382,7 @@ class DataCell { /// child: DataTable( /// columns: const [ /// DataColumn( -/// label: const Text('Number'), +/// label: Text('Number'), /// ), /// ], /// rows: List.generate( diff --git a/packages/flutter/lib/src/material/input_decorator.dart b/packages/flutter/lib/src/material/input_decorator.dart index 18ea3d756ec..a946c4c4867 100644 --- a/packages/flutter/lib/src/material/input_decorator.dart +++ b/packages/flutter/lib/src/material/input_decorator.dart @@ -2426,7 +2426,7 @@ class _InputDecoratorState extends State with TickerProviderStat /// hintText: 'Hint Text', /// helperText: 'Helper Text', /// counterText: '0 characters', -/// border: const OutlineInputBorder(), +/// border: OutlineInputBorder(), /// ), /// ); /// } diff --git a/packages/flutter/lib/src/material/progress_indicator.dart b/packages/flutter/lib/src/material/progress_indicator.dart index 89f056fab38..7f7f8dce5f6 100644 --- a/packages/flutter/lib/src/material/progress_indicator.dart +++ b/packages/flutter/lib/src/material/progress_indicator.dart @@ -281,7 +281,7 @@ class _LinearProgressIndicatorPainter extends CustomPainter { /// children: [ /// const Text( /// 'Linear progress indicator with a fixed color', -/// style: const TextStyle(fontSize: 20), +/// style: TextStyle(fontSize: 20), /// ), /// LinearProgressIndicator( /// value: controller.value, diff --git a/packages/flutter/lib/src/painting/gradient.dart b/packages/flutter/lib/src/painting/gradient.dart index d416ebf5a7d..03cdeb3e072 100644 --- a/packages/flutter/lib/src/painting/gradient.dart +++ b/packages/flutter/lib/src/painting/gradient.dart @@ -339,10 +339,10 @@ abstract class Gradient { /// Widget build(BuildContext context) { /// return Container( /// decoration: const BoxDecoration( -/// gradient: const LinearGradient( +/// gradient: LinearGradient( /// begin: Alignment.topLeft, /// end: Alignment(0.8, 0.0), // 10% of the width, so there are ten blinds. -/// colors: const [Color(0xffee0000), Color(0xffeeee00)], // red to yellow +/// colors: [Color(0xffee0000), Color(0xffeeee00)], // red to yellow /// tileMode: TileMode.repeated, // repeats the gradient over the canvas /// ), /// ), diff --git a/packages/flutter/lib/src/widgets/autocomplete.dart b/packages/flutter/lib/src/widgets/autocomplete.dart index 9684fcdb284..5eb804c71cf 100644 --- a/packages/flutter/lib/src/widgets/autocomplete.dart +++ b/packages/flutter/lib/src/widgets/autocomplete.dart @@ -444,7 +444,7 @@ typedef AutocompleteOptionToString = String Function(T option) /// ), /// ElevatedButton( /// onPressed: () { -/// FocusScope.of(context).requestFocus(new FocusNode()); +/// FocusScope.of(context).unfocus(); /// if (!_formKey.currentState!.validate()) { /// return; /// } diff --git a/packages/flutter/lib/src/widgets/focus_manager.dart b/packages/flutter/lib/src/widgets/focus_manager.dart index 9efd4b7f1bc..ea1715aff15 100644 --- a/packages/flutter/lib/src/widgets/focus_manager.dart +++ b/packages/flutter/lib/src/widgets/focus_manager.dart @@ -901,7 +901,7 @@ class FocusNode with DiagnosticableTreeMixin, ChangeNotifier { /// return const SizedBox( /// width: 200, /// child: Padding( - /// padding: const EdgeInsets.all(8.0), + /// padding: EdgeInsets.all(8.0), /// child: TextField( /// decoration: InputDecoration(border: OutlineInputBorder()), /// ), diff --git a/packages/flutter/lib/src/widgets/framework.dart b/packages/flutter/lib/src/widgets/framework.dart index 30fd905ddd4..5309a36811e 100644 --- a/packages/flutter/lib/src/widgets/framework.dart +++ b/packages/flutter/lib/src/widgets/framework.dart @@ -4433,7 +4433,7 @@ typedef ErrorWidgetBuilder = Widget Function(FlutterErrorDetails details); /// alignment: Alignment.center, /// child: const Text( /// 'Error!', -/// style: const TextStyle(color: Colors.yellow), +/// style: TextStyle(color: Colors.yellow), /// textDirection: TextDirection.ltr, /// ), /// );