diff --git a/dev/bots/analyze.dart b/dev/bots/analyze.dart index a799ed26ca..695e442129 100644 --- a/dev/bots/analyze.dart +++ b/dev/bots/analyze.dart @@ -177,7 +177,7 @@ Future run(List arguments) async { // Analyze the code in `{@tool snippet}` sections in the repo. print('$clock Snippet code...'); await runCommand(dart, - [path.join(flutterRoot, 'dev', 'bots', 'analyze_snippet_code.dart'), '--verbose'], + ['--enable-asserts', path.join(flutterRoot, 'dev', 'bots', 'analyze_snippet_code.dart'), '--verbose'], workingDirectory: flutterRoot, ); diff --git a/dev/bots/analyze_snippet_code.dart b/dev/bots/analyze_snippet_code.dart index 90b4b742d6..5d4fb1466d 100644 --- a/dev/bots/analyze_snippet_code.dart +++ b/dev/bots/analyze_snippet_code.dart @@ -2,16 +2,70 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// See ../snippets/README.md for documentation. - // To run this, from the root of the Flutter repository: -// bin/cache/dart-sdk/bin/dart dev/bots/analyze_snippet_code.dart +// bin/cache/dart-sdk/bin/dart --enable-asserts dev/bots/analyze_snippet_code.dart + +// In general, please prefer using full inline examples in API docs. +// +// For documentation on creating sample code, see ../../examples/api/README.md +// See also our style guide's discussion on documentation and sample code: +// https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo#documentation-dartdocs-javadocs-etc +// +// This tool is used to analyze smaller snippets of code in the API docs. +// Such snippets are wrapped in ```dart ... ``` blocks, which may themselves +// be wrapped in {@tool snippet} ... {@endtool} blocks to set them apart +// in the rendered output. +// +// Such snippets: +// +// * If they start with `import` are treated as full application samples; avoid +// doing this in general, it's better to use samples as described above. (One +// exception might be in dart:ui where the sample code would end up in a +// different repository which would be awkward.) +// +// * If they start with a comment that says `// continuing from previous example...`, +// they automatically import the previous test's file. +// +// * If they start with a comment that says `// (e.g. in a stateful widget)`, +// are analyzed after being inserted into a class that inherits from State. +// +// * If they start with what looks like a getter, function declaration, or +// other top-level keyword (`class`, `typedef`, etc), or if they start with +// the keyword `final`, they are analyzed directly. +// +// * If they end with a trailing semicolon or have a line starting with a +// statement keyword like `while` or `try`, are analyzed after being inserted +// into a function body. +// +// * If they start with the word `static`, are placed in a class body before +// analysis. +// +// * Otherwise, are used as an initializer for a global variable for the +// purposes of analysis; in this case, any leading label (`foo:`) +// and any trailing comma are removed. +// +// In particular, these rules imply that starting an example with `const` means +// it is an _expression_, not a top-level declaration. This is because mostly +// `const` indicates a Widget. +// +// A line that contains just a comment with an ellipsis (`// ...`) adds an ignore +// for the `non_abstract_class_inherits_abstract_member` error for the snippet. +// This is useful when you're writing an example that extends an abstract class +// with lots of members, but you only care to show one. +// +// At the top of a file you can say `// Examples can assume:` and then list some +// commented-out declarations that will be included in the analysis for snippets +// in that file. +// +// Snippets generally import all the main Flutter packages (including material +// and flutter_test), as well as most core Dart packages with the usual prefixes. import 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'package:args/args.dart'; +import 'package:meta/meta.dart'; import 'package:path/path.dart' as path; import 'package:watcher/watcher.dart'; @@ -21,9 +75,22 @@ final String _defaultDartUiLocation = path.join(_flutterRoot, 'bin', 'cache', 'p final String _flutter = path.join(_flutterRoot, 'bin', Platform.isWindows ? 'flutter.bat' : 'flutter'); Future main(List arguments) async { - final ArgParser argParser = ArgParser(); + bool asserts = false; + assert(() { asserts = true; return true; }()); + if (!asserts) { + print('You must run this script with asserts enabled.'); + exit(1); + } + int width; + try { + width = stdout.terminalColumns; + } on StdoutException { + width = 80; + } + final ArgParser argParser = ArgParser(usageLineLength: width); argParser.addOption( 'temp', + valueHelp: 'path', help: 'A location where temporary files may be written. Defaults to a ' 'directory in the system temp folder. If specified, will not be ' 'automatically removed at the end of execution.', @@ -36,6 +103,7 @@ Future main(List arguments) async { argParser.addOption( 'dart-ui-location', defaultsTo: _defaultDartUiLocation, + valueHelp: 'path', help: 'A location where the dart:ui dart files are to be found. Defaults to ' 'the sky_engine directory installed in this flutter repo. This ' 'is typically the engine/src/flutter/lib/ui directory in an engine dev setup. ' @@ -54,14 +122,23 @@ Future main(List arguments) async { argParser.addOption( 'interactive', abbr: 'i', - help: 'Analyzes the snippet code in the specified file interactively.', + valueHelp: 'file', + help: 'Analyzes the snippet code in a specified file interactively.', ); - final ArgResults parsedArguments = argParser.parse(arguments); + final ArgResults parsedArguments; + try { + parsedArguments = argParser.parse(arguments); + } on FormatException catch (e) { + print(e.message); + print('dart --enable-asserts analyze_snippet_code.dart [options]'); + print(argParser.usage); + exit(1); + } if (parsedArguments['help'] as bool) { + print('dart --enable-asserts analyze_snippet_code.dart [options]'); print(argParser.usage); - print('See dev/snippets/README.md for documentation.'); exit(0); } @@ -83,69 +160,179 @@ Future main(List arguments) async { } if (!dartUiLocation.existsSync()) { stderr.writeln('Unable to find dart:ui directory ${dartUiLocation.path}'); - exit(-1); - } - - Directory? tempDirectory; - if (parsedArguments.wasParsed('temp')) { - final String tempArg = parsedArguments['temp'] as String; - tempDirectory = Directory(path.join(Directory.systemTemp.absolute.path, path.basename(tempArg))); - if (path.basename(tempArg) != tempArg) { - stderr.writeln('Supplied temporary directory name should be a name, not a path. Using ${tempDirectory.absolute.path} instead.'); - } - print('Leaving temporary output in ${tempDirectory.absolute.path}.'); - // Make sure that any directory left around from a previous run is cleared - // out. - if (tempDirectory.existsSync()) { - tempDirectory.deleteSync(recursive: true); - } - tempDirectory.createSync(); + exit(1); } if (parsedArguments['interactive'] != null) { await _runInteractive( - tempDir: tempDirectory, flutterPackage: flutterPackage, + tempDirectory: parsedArguments['temp'] as String?, filePath: parsedArguments['interactive'] as String, dartUiLocation: includeDartUi ? dartUiLocation : null, ); } else { - try { - exitCode = await _SnippetChecker( + if (await _SnippetChecker( flutterPackage, - tempDirectory: tempDirectory, + tempDirectory: parsedArguments['temp'] as String?, verbose: parsedArguments['verbose'] as bool, dartUiLocation: includeDartUi ? dartUiLocation : null, - ).checkSnippets(); - } on _SnippetCheckerException catch (e) { - stderr.write(e); + ).checkSnippets()) { + stderr.writeln('See the documentation at the top of dev/bots/analyze_snippet_code.dart for details.'); exit(1); } } } -class _SnippetCheckerException implements Exception { - _SnippetCheckerException(this.message, {this.file, this.line}); - final String message; +/// A class to represent a line of input code. +@immutable +class _Line { + const _Line({this.code = '', this.line = -1, this.indent = 0}) + : generated = false; + const _Line.generated({this.code = ''}) + : line = -1, + indent = 0, + generated = true; + + final int line; + final int indent; + final String code; + final bool generated; + + String asLocation(String filename, int column) { + return '$filename:$line:${column + indent}'; + } + + @override + String toString() => code; + + @override + bool operator ==(Object other) { + if (other.runtimeType != runtimeType) { + return false; + } + return other is _Line + && other.line == line + && other.indent == indent + && other.code == code + && other.generated == generated; + } + + @override + int get hashCode => Object.hash(line, indent, code, generated); +} + +@immutable +class _ErrorBase implements Comparable { + const _ErrorBase({this.file, this.line, this.column}); final String? file; final int? line; + final int? column; + + @override + int compareTo(Object other) { + if (other is _ErrorBase) { + if (other.file != file) { + if (other.file == null) { + return -1; + } + if (file == null) { + return 1; + } + return file!.compareTo(other.file!); + } + if (other.line != line) { + if (other.line == null) { + return -1; + } + if (line == null) { + return 1; + } + return line!.compareTo(other.line!); + } + if (other.column != column) { + if (other.column == null) { + return -1; + } + if (column == null) { + return 1; + } + return column!.compareTo(other.column!); + } + } + return toString().compareTo(other.toString()); + } +} + +@immutable +class _SnippetCheckerException extends _ErrorBase implements Exception { + const _SnippetCheckerException(this.message, {super.file, super.line}); + final String message; @override String toString() { if (file != null || line != null) { final String fileStr = file == null ? '' : '$file:'; final String lineStr = line == null ? '' : '$line:'; - return '$fileStr$lineStr Error: $message'; + return '$fileStr$lineStr $message'; } else { - return 'Error: $message'; + return message; } } + + @override + bool operator ==(Object other) { + if (other.runtimeType != runtimeType) { + return false; + } + return other is _SnippetCheckerException + && other.message == message + && other.file == file + && other.line == line; + } + + @override + int get hashCode => Object.hash(message, file, line); } -class _AnalysisResult { - const _AnalysisResult(this.exitCode, this.errors); - final int exitCode; - final Map> errors; +/// A class representing an analysis error along with the context of the error. +/// +/// Changes how it converts to a string based on the source of the error. +@immutable +class _AnalysisError extends _ErrorBase { + const _AnalysisError( + String file, + int line, + int column, + this.message, + this.errorCode, + this.source, + ) : super(file: file, line: line, column: column); + + final String message; + final String errorCode; + final _Line source; + + @override + String toString() { + return '${source.asLocation(file!, column!)}: $message ($errorCode)'; + } + + @override + bool operator ==(Object other) { + if (other.runtimeType != runtimeType) { + return false; + } + return other is _AnalysisError + && other.file == file + && other.line == line + && other.column == column + && other.message == message + && other.errorCode == errorCode + && other.source == source; + } + + @override + int get hashCode => Object.hash(file, line, column, message, errorCode, source); } /// Checks code snippets for analysis errors. @@ -174,10 +361,10 @@ class _SnippetChecker { /// repository is used (i.e. "/bin/cache/pkg/sky_engine/lib/ui"). _SnippetChecker( this._flutterPackage, { - Directory? tempDirectory, + String? tempDirectory, this.verbose = false, Directory? dartUiLocation, - }) : _tempDirectory = tempDirectory ?? Directory.systemTemp.createTempSync('flutter_analyze_snippet_code.'), + }) : _tempDirectory = _createTempDirectory(tempDirectory), _keepTmp = tempDirectory != null, _dartUiLocation = dartUiLocation; @@ -188,22 +375,56 @@ class _SnippetChecker { static const String _dartDocPrefixWithSpace = '$_dartDocPrefix '; /// A RegExp that matches the beginning of a dartdoc snippet. - static final RegExp _dartDocSnippetBeginRegex = RegExp(r'{@tool snippet(?:| ([^}]*))}'); + static final RegExp _dartDocSnippetBeginRegex = RegExp(r'{@tool ([^ }]+)(?:| ([^}]*))}'); /// A RegExp that matches the end of a dartdoc snippet. static final RegExp _dartDocSnippetEndRegex = RegExp(r'{@end-tool}'); /// A RegExp that matches the start of a code block within dartdoc. - static final RegExp _codeBlockStartRegex = RegExp(r'///\s+```dart.*$'); + static final RegExp _codeBlockStartRegex = RegExp(r'^ */// *```dart$'); + + /// A RegExp that matches the start of a code block within a regular comment. + /// Such blocks are not analyzed. They can be used to give sample code for + /// internal (private) APIs where visibilty would make analyzing the sample + /// code problematic. + static final RegExp _uncheckedCodeBlockStartRegex = RegExp(r'^ *// *```dart$'); /// A RegExp that matches the end of a code block within dartdoc. - static final RegExp _codeBlockEndRegex = RegExp(r'///\s+```\s*$'); + static final RegExp _codeBlockEndRegex = RegExp(r'^ */// *``` *$'); - /// A RegExp that matches a Dart constructor. - static final RegExp _constructorRegExp = RegExp(r'(const\s+)?_*[A-Z][a-zA-Z0-9<>._]*\('); + /// A RegExp that matches a line starting with a comment or annotation + static final RegExp _nonCodeRegExp = RegExp(r'^ *(//|@)'); - /// A RegExp that matches a dart version specification in an example preamble. - static final RegExp _dartVersionRegExp = RegExp(r'\/\/ \/\/ @dart = ([0-9]+\.[0-9]+)'); + /// A RegExp that matches things that look like a function declaration. + static final RegExp _maybeFunctionDeclarationRegExp = RegExp(r'^([A-Z][A-Za-z0-9_<>, ?]*|int|double|num|bool|void)\?? (_?[a-z][A-Za-z0-9_<>]*)\(.*'); + + /// A RegExp that matches things that look like a getter. + static final RegExp _maybeGetterDeclarationRegExp = RegExp(r'^([A-Z][A-Za-z0-9_<>?]*|int|double|num|bool)\?? get (_?[a-z][A-Za-z0-9_<>]*) (?:=>|{).*'); + + /// A RegExp that matches an identifier followed by a colon, potentially with two spaces of indent. + static final RegExp _namedArgumentRegExp = RegExp(r'^(?: )?([a-zA-Z0-9_]+): '); + + /// A RegExp that matches things that look unambiguously like top-level declarations. + static final RegExp _topLevelDeclarationRegExp = RegExp(r'^(abstract|class|mixin|enum|typedef|final|extension) '); + + /// A RegExp that matches things that look unambiguously like statements. + static final RegExp _statementRegExp = RegExp(r'^(if|while|for|try) '); + + /// A RegExp that matches things that look unambiguously like declarations that must be in a class. + static final RegExp _classDeclarationRegExp = RegExp(r'^(static) '); + + /// A RegExp that matches a line that ends with a comma (and maybe a comment) + static final RegExp _trailingCommaRegExp = RegExp(r'^(.*),(| *//.*)$'); + + /// A RegExp that matches a line that ends with a semicolon (and maybe a comment) + static final RegExp _trailingSemicolonRegExp = RegExp(r'^(.*);(| *//.*)$'); + + /// A RegExp that matches a line that ends with a closing blace (and maybe a comment) + static final RegExp _trailingCloseBraceRegExp = RegExp(r'^(.*)}(| *//.*)$'); + + /// A RegExp that matches a line that only contains a commented-out ellipsis + /// (and maybe whitespace). Has three groups: before, ellipsis, after. + static final RegExp _ellipsisRegExp = RegExp(r'^( *)(// \.\.\.)( *)$'); /// Whether or not to print verbose output. final bool verbose; @@ -214,7 +435,7 @@ class _SnippetChecker { final bool _keepTmp; /// The temporary directory where all output is written. This will be deleted - /// automatically if there are no errors. + /// automatically if there are no errors unless _keepTmp is true. final Directory _tempDirectory; /// The package directory for the flutter package within the flutter root dir. @@ -227,67 +448,96 @@ class _SnippetChecker { /// contains the dart:ui code supplied by the engine. final Directory? _dartUiLocation; - /// A serial number so that we can create unique expression names when we - /// generate them. - int _expressionId = 0; - 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(); } + static const List ignoresDirectives = [ + '// ignore_for_file: duplicate_ignore', + '// ignore_for_file: directives_ordering', + '// ignore_for_file: prefer_final_locals', + '// ignore_for_file: unnecessary_import', + '// ignore_for_file: unused_element', + '// ignore_for_file: unused_local_variable', + ]; + /// Computes the headers needed for each snippet file. - List<_Line> get headers { - return _headers ??= [ - '// ignore_for_file: directives_ordering', - '// ignore_for_file: unnecessary_import', + List<_Line> get headersWithoutImports { + return _headersWithoutImports ??= ignoresDirectives.map<_Line>((String code) => _Line.generated(code: code)).toList(); + } + List<_Line>? _headersWithoutImports; + + /// Computes the headers needed for each snippet file. + List<_Line> get headersWithImports { + return _headersWithImports ??= [ + ...ignoresDirectives, '// ignore_for_file: unused_import', - '// ignore_for_file: unused_element', - '// ignore_for_file: unused_local_variable', "import 'dart:async';", "import 'dart:convert';", + "import 'dart:io';", "import 'dart:math' as math;", "import 'dart:typed_data';", "import 'dart:ui' as ui;", "import 'package:flutter_test/flutter_test.dart';", for (final File file in _listDartFiles(Directory(_defaultFlutterPackage))) "import 'package:flutter/${path.basename(file.path)}';", - ].map<_Line>((String code) => _Line.generated(code: code, filename: 'headers')).toList(); + ].map<_Line>((String code) => _Line.generated(code: code)).toList(); } - - List<_Line>? _headers; + List<_Line>? _headersWithImports; /// Checks all the snippets in the Dart files in [_flutterPackage] for errors. - Future checkSnippets() async { - _AnalysisResult? analysisResult; - try { - final Map sections = {}; - if (_dartUiLocation != null && !_dartUiLocation!.existsSync()) { - stderr.writeln('Unable to analyze engine dart snippets at ${_dartUiLocation!.path}.'); + /// Returns true if any errors are found, false otherwise. + Future checkSnippets() async { + final Map snippets = {}; + if (_dartUiLocation != null && !_dartUiLocation!.existsSync()) { + stderr.writeln('Unable to analyze engine dart snippets at ${_dartUiLocation!.path}.'); + } + final List filesToAnalyze = [ + ..._listDartFiles(_flutterPackage, recursive: true), + if (_dartUiLocation != null && _dartUiLocation!.existsSync()) + ..._listDartFiles(_dartUiLocation!, recursive: true), + ]; + final Set errors = {}; + errors.addAll(await _extractSnippets(filesToAnalyze, snippetMap: snippets)); + errors.addAll(_analyze(snippets)); + (errors.toList()..sort()).map(_stringify).forEach(stderr.writeln); + stderr.writeln('Found ${errors.length} snippet code errors.'); + cleanupTempDirectory(); + return errors.isNotEmpty; + } + + static Directory _createTempDirectory(String? tempArg) { + if (tempArg != null) { + final Directory tempDirectory = Directory(path.join(Directory.systemTemp.absolute.path, path.basename(tempArg))); + if (path.basename(tempArg) != tempArg) { + stderr.writeln('Supplied temporary directory name should be a name, not a path. Using ${tempDirectory.absolute.path} instead.'); } - final List filesToAnalyze = [ - ..._listDartFiles(_flutterPackage, recursive: true), - if (_dartUiLocation != null && _dartUiLocation!.existsSync()) ... _listDartFiles(_dartUiLocation!, recursive: true), - ]; - await _extractSnippets(filesToAnalyze, sectionMap: sections); - analysisResult = _analyze(_tempDirectory, sections); - } finally { - if (analysisResult != null && analysisResult.errors.isNotEmpty) { - for (final String filePath in analysisResult.errors.keys) { - analysisResult.errors[filePath]!.forEach(stderr.writeln); - } - stderr.writeln('\nFound ${analysisResult.errors.length} snippet code errors.'); + print('Leaving temporary output in ${tempDirectory.absolute.path}.'); + // Make sure that any directory left around from a previous run is cleared out. + if (tempDirectory.existsSync()) { + tempDirectory.deleteSync(recursive: true); } - if (_keepTmp) { - print('Leaving temporary directory ${_tempDirectory.path} around for your perusal.'); - } else { - try { - _tempDirectory.deleteSync(recursive: true); - } on FileSystemException catch (e) { - stderr.writeln('Failed to delete ${_tempDirectory.path}: $e'); - } + tempDirectory.createSync(); + return tempDirectory; + } + return Directory.systemTemp.createTempSync('flutter_analyze_snippet_code.'); + } + + void recreateTempDirectory() { + _tempDirectory.deleteSync(recursive: true); + _tempDirectory.createSync(); + } + + void cleanupTempDirectory() { + if (_keepTmp) { + print('Leaving temporary directory ${_tempDirectory.path} around for your perusal.'); + } else { + try { + _tempDirectory.deleteSync(recursive: true); + } on FileSystemException catch (e) { + stderr.writeln('Failed to delete ${_tempDirectory.path}: $e'); } } - return analysisResult.exitCode; } /// Creates a name for the snippets tool to use for the snippet ID from a @@ -300,61 +550,63 @@ class _SnippetChecker { } /// Extracts the snippets from the Dart files in [files], writes them - /// to disk, and adds them to the [sectionMap]. - Future _extractSnippets( + /// to disk, and adds them to the [snippetMap]. + Future> _extractSnippets( List files, { - required Map sectionMap, - bool silent = false, + required Map snippetMap, }) async { - final List<_Section> sections = <_Section>[]; - + final List errors = []; + _SnippetFile? lastExample; for (final File file in files) { - final String relativeFilePath = path.relative(file.path, from: _flutterRoot); - final List snippetLine = file.readAsLinesSync(); - final List<_Section> preambleSections = <_Section>[]; - // Whether or not we're in the file-wide preamble section ("Examples can assume"). - bool inPreamble = false; - // Whether or not we're in a code snippet - bool inSnippetSection = false; - // Whether or not we're in a '```dart' segment. - bool inDart = false; - String? dartVersionOverride; - int lineNumber = 0; - final List block = []; - late _Line startLine; - for (final String line in snippetLine) { - lineNumber += 1; - final String trimmedLine = line.trim(); - if (inPreamble) { - if (line.isEmpty) { - inPreamble = false; - // If there's only a dartVersionOverride in the preamble, don't add - // it as a section. The dartVersionOverride was processed below. - if (dartVersionOverride == null || block.isNotEmpty) { - preambleSections.add(_processBlock(startLine, block)); + try { + final String relativeFilePath = path.relative(file.path, from: _flutterRoot); + final List fileLines = file.readAsLinesSync(); + final List<_Line> ignorePreambleLinesOnly = <_Line>[]; + final List<_Line> preambleLines = <_Line>[]; + bool inExamplesCanAssumePreamble = false; // Whether or not we're in the file-wide preamble section ("Examples can assume"). + bool inToolSection = false; // Whether or not we're in a code snippet + bool inDartSection = false; // Whether or not we're in a '```dart' segment. + bool inOtherBlock = false; // Whether we're in some other '```' segment. + int lineNumber = 0; + final List block = []; + late _Line startLine; + for (final String line in fileLines) { + lineNumber += 1; + final String trimmedLine = line.trim(); + if (inExamplesCanAssumePreamble) { + if (line.isEmpty) { + // end of preamble + inExamplesCanAssumePreamble = false; + } else if (!line.startsWith('// ')) { + throw _SnippetCheckerException('Unexpected content in snippet code preamble.', file: relativeFilePath, line: lineNumber); + } else { + final _Line newLine = _Line(line: lineNumber, indent: 3, code: line.substring(3)); + preambleLines.add(newLine); + if (line.startsWith('// // ignore_for_file: ')) { + ignorePreambleLinesOnly.add(newLine); + } } - block.clear(); - } else if (!line.startsWith('// ')) { - throw _SnippetCheckerException('Unexpected content in snippet code preamble.', file: relativeFilePath, line: lineNumber); - } else if (_dartVersionRegExp.hasMatch(line)) { - dartVersionOverride = line.substring(3); - } else { - block.add(line.substring(3)); - } - } else if (inSnippetSection) { - if (_dartDocSnippetEndRegex.hasMatch(trimmedLine)) { - if (inDart) { + } else if (trimmedLine.startsWith(_dartDocSnippetEndRegex)) { + if (!inToolSection) { + throw _SnippetCheckerException('{@tool-end} marker detected without matching {@tool}.', file: relativeFilePath, line: lineNumber); + } + if (inDartSection) { throw _SnippetCheckerException("Dart section didn't terminate before end of snippet", file: relativeFilePath, line: lineNumber); } - inSnippetSection = false; - } - if (inDart) { - if (_codeBlockEndRegex.hasMatch(trimmedLine)) { - inDart = false; - final _Section processed = _processBlock(startLine, block); - final _Section combinedSection = preambleSections.isEmpty ? processed : _Section.combine(preambleSections..add(processed)); - sections.add(combinedSection.copyWith(dartVersionOverride: dartVersionOverride)); + inToolSection = false; + } else if (inDartSection) { + final RegExpMatch? snippetMatch = _dartDocSnippetBeginRegex.firstMatch(trimmedLine); + if (snippetMatch != null) { + throw _SnippetCheckerException('{@tool} found inside Dart section', file: relativeFilePath, line: lineNumber); + } + if (trimmedLine.startsWith(_codeBlockEndRegex)) { + inDartSection = false; + final _SnippetFile snippet = _processBlock(startLine, block, preambleLines, ignorePreambleLinesOnly, relativeFilePath, lastExample); + final String path = _writeSnippetFile(snippet).path; + assert(!snippetMap.containsKey(path)); + snippetMap[path] = snippet; block.clear(); + lastExample = snippet; } else if (trimmedLine == _dartDocPrefix) { block.add(''); } else { @@ -368,49 +620,236 @@ class _SnippetChecker { } block.add(line.substring(index + 4)); } - } else if (_codeBlockStartRegex.hasMatch(trimmedLine)) { + } else if (trimmedLine.startsWith(_codeBlockStartRegex)) { + if (inOtherBlock) { + throw _SnippetCheckerException( + 'Found "```dart" section in another "```" section.', + file: relativeFilePath, + line: lineNumber, + ); + } assert(block.isEmpty); startLine = _Line( - filename: relativeFilePath, line: lineNumber + 1, indent: line.indexOf(_dartDocPrefixWithSpace) + _dartDocPrefixWithSpace.length, ); - inDart = true; + inDartSection = true; + } else if (line.contains('```')) { + if (inOtherBlock) { + inOtherBlock = false; + } else if (line.contains('```yaml') || + line.contains('```ascii') || + line.contains('```java') || + line.contains('```objectivec') || + line.contains('```kotlin') || + line.contains('```swift') || + line.contains('```glsl') || + line.contains('```csv')) { + inOtherBlock = true; + } else if (line.startsWith(_uncheckedCodeBlockStartRegex)) { + // this is an intentionally-unchecked block that doesn't appear in the API docs. + inOtherBlock = true; + } else { + throw _SnippetCheckerException( + 'Found "```" in code but it did not match $_codeBlockStartRegex so something is wrong. Line was: "$line"', + file: relativeFilePath, + line: lineNumber, + ); + } + } else if (!inToolSection) { + final RegExpMatch? snippetMatch = _dartDocSnippetBeginRegex.firstMatch(trimmedLine); + if (snippetMatch != null) { + inToolSection = true; + } else if (line == '// Examples can assume:') { + if (inToolSection || inDartSection) { + throw _SnippetCheckerException( + '"// Examples can assume:" sections must come before all sample code.', + file: relativeFilePath, + line: lineNumber, + ); + } + inExamplesCanAssumePreamble = true; + } } } - if (!inSnippetSection) { - final RegExpMatch? snippetMatch = _dartDocSnippetBeginRegex.firstMatch(trimmedLine); - if (line == '// Examples can assume:') { - assert(block.isEmpty); - startLine = _Line.generated(filename: relativeFilePath, line: lineNumber + 1, indent: 3); - inPreamble = true; - } else if (snippetMatch != null) { - inSnippetSection = true; - } else if (RegExp(r'///\s*#+\s+[Ss]ample\s+[Cc]ode:?$').hasMatch(trimmedLine)) { - throw _SnippetCheckerException( - "Found deprecated '## Sample code' section: use {@tool snippet}...{@end-tool} instead.", - file: relativeFilePath, - line: lineNumber, - ); + } on _SnippetCheckerException catch (e) { + errors.add(e); + } + } + return errors; + } + + /// Process one block of snippet code (the part inside of "```" markers). Uses + /// a primitive heuristic to make snippet blocks into valid Dart code. + /// + /// `block` argument will get mutated, but is copied before this function returns. + _SnippetFile _processBlock(_Line startingLine, List block, List<_Line> assumptions, List<_Line> ignoreAssumptionsOnly, String filename, _SnippetFile? lastExample) { + if (block.isEmpty) { + throw _SnippetCheckerException('${startingLine.asLocation(filename, 0)}: Empty ```dart block in snippet code.'); + } + bool hasEllipsis = false; + for (int index = 0; index < block.length; index += 1) { + final Match? match = _ellipsisRegExp.matchAsPrefix(block[index]); + if (match != null) { + hasEllipsis = true; // in case the "..." is implying some overridden members, add an ignore to silence relevant warnings + break; + } + } + bool hasStatefulWidgetComment = false; + bool importPreviousExample = false; + int index = startingLine.line; + for (final String line in block) { + if (line == '// (e.g. in a stateful widget)') { + if (hasStatefulWidgetComment) { + throw _SnippetCheckerException('Example says it is in a stateful widget twice.', file: filename, line: index); + } + hasStatefulWidgetComment = true; + } else if (line == '// continuing from previous example...') { + if (importPreviousExample) { + throw _SnippetCheckerException('Example says it continues from the previous example twice.', file: filename, line: index); + } + if (lastExample == null) { + throw _SnippetCheckerException('Example says it continues from the previous example but it is the first example in the file.', file: filename, line: index); + } + importPreviousExample = true; + } else { + break; + } + index += 1; + } + final List<_Line> preamble; + if (importPreviousExample) { + preamble = <_Line>[ + ...lastExample!.code, // includes assumptions + if (hasEllipsis || hasStatefulWidgetComment) + const _Line.generated(code: '// ignore_for_file: non_abstract_class_inherits_abstract_member'), + ]; + } else { + preamble = <_Line>[ + if (hasEllipsis || hasStatefulWidgetComment) + const _Line.generated(code: '// ignore_for_file: non_abstract_class_inherits_abstract_member'), + ...assumptions, + ]; + } + final String firstCodeLine = block.firstWhere((String line) => !line.startsWith(_nonCodeRegExp)).trim(); + final String lastCodeLine = block.lastWhere((String line) => !line.startsWith(_nonCodeRegExp)).trim(); + if (firstCodeLine.startsWith('import ')) { + // probably an entire program + if (importPreviousExample) { + throw _SnippetCheckerException('An example cannot both be self-contained (with its own imports) and say it wants to import the previous example.', file: filename, line: startingLine.line); + } + if (hasStatefulWidgetComment) { + throw _SnippetCheckerException('An example cannot both be self-contained (with its own imports) and say it is in a stateful widget.', file: filename, line: startingLine.line); + } + return _SnippetFile.fromStrings( + startingLine, + block.toList(), + importPreviousExample ? <_Line>[] : headersWithoutImports, + <_Line>[ + ...ignoreAssumptionsOnly, + if (hasEllipsis) + const _Line.generated(code: '// ignore_for_file: non_abstract_class_inherits_abstract_member'), + ], + 'self-contained program', + filename, + ); + } else if (hasStatefulWidgetComment) { + return _SnippetFile.fromStrings( + startingLine, + prefix: 'class _State extends State {', + block.toList(), + postfix: '}', + importPreviousExample ? <_Line>[] : headersWithImports, + preamble, + 'stateful widget', + filename, + ); + } else if (firstCodeLine.startsWith(_maybeGetterDeclarationRegExp) || + (firstCodeLine.startsWith(_maybeFunctionDeclarationRegExp) && lastCodeLine.startsWith(_trailingCloseBraceRegExp)) || + block.any((String line) => line.startsWith(_topLevelDeclarationRegExp))) { + // probably a top-level declaration + return _SnippetFile.fromStrings( + startingLine, + block.toList(), + importPreviousExample ? <_Line>[] : headersWithImports, + preamble, + 'top-level declaration', + filename, + ); + } else if (lastCodeLine.startsWith(_trailingSemicolonRegExp) || + block.any((String line) => line.startsWith(_statementRegExp))) { + // probably a statement + return _SnippetFile.fromStrings( + startingLine, + prefix: 'Future function() async {', + block.toList(), + postfix: '}', + importPreviousExample ? <_Line>[] : headersWithImports, + preamble, + 'statement', + filename, + ); + } else if (firstCodeLine.startsWith(_classDeclarationRegExp)) { + // probably a static method + return _SnippetFile.fromStrings( + startingLine, + prefix: 'class Class {', + block.toList(), + postfix: '}', + importPreviousExample ? <_Line>[] : headersWithImports, + <_Line>[ + ...preamble, + const _Line.generated(code: '// ignore_for_file: avoid_classes_with_only_static_members'), + ], + 'class declaration', + filename, + ); + } else { + // probably an expression + if (firstCodeLine.startsWith(_namedArgumentRegExp)) { + // This is for snippets like: + // + // ```dart + // // bla bla + // foo: 2, + // ``` + // + // This section removes the label. + for (int index = 0; index < block.length; index += 1) { + final Match? prefix = _namedArgumentRegExp.matchAsPrefix(block[index]); + if (prefix != null) { + block[index] = block[index].substring(prefix.group(0)!.length); + break; } } } - } - if (!silent) { - print('Found ${sections.length} snippet code blocks'); - } - for (final _Section section in sections) { - final String path = _writeSection(section).path; - if (sectionMap != null) { - sectionMap[path] = section; + // strip trailing comma, if any + for (int index = block.length - 1; index >= 0; index -= 1) { + if (!block[index].startsWith(_nonCodeRegExp)) { + final Match? lastLine = _trailingCommaRegExp.matchAsPrefix(block[index]); + if (lastLine != null) { + block[index] = lastLine.group(1)! + lastLine.group(2)!; + } + break; + } } + return _SnippetFile.fromStrings( + startingLine, + prefix: 'dynamic expression = ', + block.toList(), + postfix: ';', + importPreviousExample ? <_Line>[] : headersWithImports, + preamble, + 'expression', + filename, + ); } } /// 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 targetPubSpec = File(path.join(directory.path, 'pubspec.yaml')); + void _createConfigurationFiles() { + final File targetPubSpec = File(path.join(_tempDirectory.path, 'pubspec.yaml')); if (!targetPubSpec.existsSync()) { // Copying pubspec.yaml from examples/api into temp directory. final File sourcePubSpec = File(path.join(_flutterRoot, 'examples', 'api', 'pubspec.yaml')); @@ -419,7 +858,7 @@ class _SnippetChecker { } sourcePubSpec.copySync(targetPubSpec.path); } - final File targetAnalysisOptions = File(path.join(directory.path, 'analysis_options.yaml')); + final File targetAnalysisOptions = File(path.join(_tempDirectory.path, 'analysis_options.yaml')); if (!targetAnalysisOptions.existsSync()) { // Use the same analysis_options.yaml configuration that's used for examples/api. final File sourceAnalysisOptions = File(path.join(_flutterRoot, 'examples', 'api', 'analysis_options.yaml')); @@ -433,31 +872,118 @@ class _SnippetChecker { } /// Writes out a snippet section to the disk and returns the file. - File _writeSection(_Section section) { - final String sectionId = _createNameFromSource('snippet', section.start.filename, section.start.line); - final File outputFile = File(path.join(_tempDirectory.path, '$sectionId.dart'))..createSync(recursive: true); - final List<_Line> mainContents = <_Line>[ - _Line.generated(code: section.dartVersionOverride ?? '', filename: section.start.filename), - ...headers, - _Line.generated(filename: section.start.filename), - _Line.generated(code: '// From: ${section.start.filename}:${section.start.line}', filename: section.start.filename), - ...section.code, - _Line.generated(filename: section.start.filename), // empty line at EOF - ]; - outputFile.writeAsStringSync(mainContents.map((_Line line) => line.code).join('\n')); + File _writeSnippetFile(_SnippetFile snippetFile) { + final String snippetFileId = _createNameFromSource('snippet', snippetFile.filename, snippetFile.indexLine); + final File outputFile = File(path.join(_tempDirectory.path, '$snippetFileId.dart'))..createSync(recursive: true); + final String contents = snippetFile.code.map((_Line line) => line.code).join('\n').trimRight(); + outputFile.writeAsStringSync('$contents\n'); return outputFile; } - /// Invokes the analyzer on the given [directory] and returns the stdout. - int _runAnalyzer(Directory directory, {bool silent = true, required List output}) { - if (!silent) { - print('Starting analysis of code snippets.'); + /// Starts the analysis phase of checking the snippets by invoking the analyzer + /// and parsing its output. Returns the errors, if any. + List _analyze(Map snippets) { + final List analyzerOutput = _runAnalyzer(); + final List errors = []; + final String kBullet = Platform.isWindows ? ' - ' : ' • '; + // RegExp to match an error output line of the analyzer. + final RegExp errorPattern = RegExp( + '^ *(?[a-z]+)' + '$kBullet(?.+)' + '$kBullet(?.+):(?[0-9]+):(?[0-9]+)' + '$kBullet(?[-a-z_]+)\$', + caseSensitive: false, + ); + + for (final String error in analyzerOutput) { + final RegExpMatch? match = errorPattern.firstMatch(error); + if (match == null) { + errors.add(_SnippetCheckerException('Could not parse analyzer output: $error')); + continue; + } + final String message = match.namedGroup('description')!; + final File file = File(path.join(_tempDirectory.path, match.namedGroup('file'))); + final List fileContents = file.readAsLinesSync(); + final String lineString = match.namedGroup('line')!; + final String columnString = match.namedGroup('column')!; + final String errorCode = match.namedGroup('code')!; + final int lineNumber = int.parse(lineString, radix: 10); + final int columnNumber = int.parse(columnString, radix: 10); + + if (lineNumber < 1 || lineNumber > fileContents.length + 1) { + errors.add(_AnalysisError( + file.path, + lineNumber, + columnNumber, + message, + errorCode, + _Line(line: lineNumber), + )); + errors.add(_SnippetCheckerException('Error message points to non-existent line number: $error', file: file.path, line: lineNumber)); + continue; + } + + final _SnippetFile snippet = snippets[file.path]!; + if (snippet == null) { + errors.add(_SnippetCheckerException( + "Unknown section for ${file.path}. Maybe the temporary directory wasn't empty?", + file: file.path, + line: lineNumber, + )); + continue; + } + if (fileContents.length != snippet.code.length) { + errors.add(_SnippetCheckerException( + 'Unexpected file contents for ${file.path}. File has ${fileContents.length} lines but we generated ${snippet.code.length} lines:\n${snippet.code.join("\n")}', + file: file.path, + line: lineNumber, + )); + continue; + } + + late final _Line actualSource; + late final int actualLine; + late final int actualColumn; + late final String actualMessage; + int delta = 0; + while (true) { + // find the nearest non-generated line to the error + if ((lineNumber - delta > 0) && (lineNumber - delta <= snippet.code.length) && !snippet.code[lineNumber - delta - 1].generated) { + actualSource = snippet.code[lineNumber - delta - 1]; + actualLine = actualSource.line; + actualColumn = delta == 0 ? columnNumber : actualSource.code.length + 1; + actualMessage = delta == 0 ? message : '$message -- in later generated code'; + break; + } + if ((lineNumber + delta < snippet.code.length) && (lineNumber + delta >= 0) && !snippet.code[lineNumber + delta].generated) { + actualSource = snippet.code[lineNumber + delta]; + actualLine = actualSource.line; + actualColumn = 1; + actualMessage = '$message -- in earlier generated code'; + break; + } + delta += 1; + assert((lineNumber - delta > 0) || (lineNumber + delta < snippet.code.length)); + } + errors.add(_AnalysisError( + snippet.filename, + actualLine, + actualColumn, + '$actualMessage (${snippet.generatorComment})', + errorCode, + actualSource, + )); } - _createConfigurationFiles(directory); + return errors; + } + + /// Invokes the analyzer on the given [directory] and returns the stdout (with some lines filtered). + List _runAnalyzer() { + _createConfigurationFiles(); final ProcessResult result = Process.runSync( _flutter, ['--no-wrap', 'analyze', '--no-preamble', '--no-congratulate', '.'], - workingDirectory: directory.absolute.path, + workingDirectory: _tempDirectory.absolute.path, ); final List stderr = result.stderr.toString().trim().split('\n'); final List stdout = result.stdout.toString().trim().split('\n'); @@ -475,7 +1001,7 @@ class _SnippetChecker { } } if (stderr.isNotEmpty && stderr.any((String line) => line.isNotEmpty)) { - throw 'Cannot analyze dartdocs; unexpected error output:\n$stderr'; + throw _SnippetCheckerException('Cannot analyze dartdocs; unexpected error output:\n$stderr'); } if (stdout.isNotEmpty && stdout.first == 'Building flutter tool...') { stdout.removeAt(0); @@ -483,305 +1009,72 @@ class _SnippetChecker { if (stdout.isNotEmpty && stdout.first.startsWith('Running "flutter pub get" in ')) { stdout.removeAt(0); } - output.addAll(stdout); - return result.exitCode; + return stdout; } +} - /// Starts the analysis phase of checking the snippets by invoking the analyzer - /// and parsing its output to create a map of filename to [_AnalysisError]s. - _AnalysisResult _analyze( - Directory directory, - Map sections, { - bool silent = false, - }) { - final List errors = []; - int exitCode = _runAnalyzer(directory, silent: silent, output: errors); +/// A class to represent a section of snippet code, marked by "```dart ... ```", that ends up +/// in a file we then analyze (each snippet is in its own file). +class _SnippetFile { + const _SnippetFile(this.code, this.generatorComment, this.filename, this.indexLine); - final Map> analysisErrors = >{}; - void addAnalysisError(File file, _AnalysisError error) { - if (analysisErrors.containsKey(file.path)) { - analysisErrors[file.path]!.add(error); - } else { - analysisErrors[file.path] = <_AnalysisError>[error]; - } + factory _SnippetFile.fromLines( + List<_Line> code, + List<_Line> headers, + List<_Line> preamble, + String generatorComment, + String filename, + ) { + while (code.isNotEmpty && code.last.code.isEmpty) { + code.removeLast(); } - - final String kBullet = Platform.isWindows ? ' - ' : ' • '; - // RegExp to match an error output line of the analyzer. - final RegExp errorPattern = RegExp( - '^ +(?[a-z]+)' - '$kBullet(?.+)' - '$kBullet(?.+):(?[0-9]+):(?[0-9]+)' - '$kBullet(?[-a-z_]+)\$', - caseSensitive: false, + assert(code.isNotEmpty); + final _Line firstLine = code.firstWhere((_Line line) => !line.generated); + return _SnippetFile( + <_Line>[ + ...headers, + const _Line.generated(), // blank line + if (preamble.isNotEmpty) + ...preamble, + if (preamble.isNotEmpty) + const _Line.generated(), // blank line + _Line.generated(code: '// From: $filename:${firstLine.line}'), + ...code, + ], + generatorComment, + filename, + firstLine.line, ); - bool unknownAnalyzerErrors = false; - final int headerLength = headers.length + 3; - for (final String error in errors) { - final RegExpMatch? match = errorPattern.firstMatch(error); - if (match == null) { - stderr.writeln('Analyzer output: $error'); - unknownAnalyzerErrors = true; - continue; - } - final String type = match.namedGroup('type')!; - final String message = match.namedGroup('description')!; - final File file = File(path.join(_tempDirectory.path, match.namedGroup('file'))); - final List fileContents = file.readAsLinesSync(); - final String lineString = match.namedGroup('line')!; - final String columnString = match.namedGroup('column')!; - final String errorCode = match.namedGroup('code')!; - final int lineNumber = int.parse(lineString, radix: 10) - headerLength; - final int columnNumber = int.parse(columnString, radix: 10); - - if (lineNumber < 1 || lineNumber > fileContents.length) { - addAnalysisError( - file, - _AnalysisError( - type, - lineNumber, - columnNumber, - message, - errorCode, - _Line(filename: file.path, line: lineNumber), - ), - ); - throw _SnippetCheckerException('Failed to parse error message: $error', file: file.path, line: lineNumber); - } - - final _Section actualSection = sections[file.path]!; - if (actualSection == null) { - throw _SnippetCheckerException( - "Unknown section for ${file.path}. Maybe the temporary directory wasn't empty?", - file: file.path, - line: lineNumber, - ); - } - final _Line actualLine = actualSection.code[lineNumber - 1]; - - late int line; - late int column; - String errorMessage = message; - _Line source = actualLine; - if (actualLine.generated) { - // Since generated lines don't appear in the original, we just provide the line - // in the generated file. - line = lineNumber - 1; - column = columnNumber; - if (errorCode == 'missing_identifier' && lineNumber > 1) { - // For a missing identifier on a generated line, it is very often because of a - // trailing comma on the previous line, and so we want to provide a better message - // and the previous line as the error location, since that appears in the original - // source, and can be more easily located. - final _Line previousCodeLine = sections[file.path]!.code[lineNumber - 2]; - if (previousCodeLine.code.contains(RegExp(r',\s*$'))) { - line = previousCodeLine.line; - column = previousCodeLine.indent + previousCodeLine.code.length - 1; - errorMessage = 'Unexpected comma at end of snippet code.'; - source = previousCodeLine; - } - } - } else { - line = actualLine.line; - column = actualLine.indent + columnNumber; - } - addAnalysisError( - file, - _AnalysisError( - type, - line, - column, - errorMessage, - errorCode, - source, - ), - ); - } - if (exitCode == 1 && analysisErrors.isEmpty && !unknownAnalyzerErrors) { - exitCode = 0; - } - if (exitCode == 0) { - if (!silent) { - print('No analysis errors in snippets!'); - } - assert(analysisErrors.isEmpty); - } - return _AnalysisResult(exitCode, analysisErrors); } - /// Process one block of snippet code (the part inside of "```" markers). - /// Splits any sections denoted by "// ..." into separate blocks to be - /// processed separately. Uses a primitive heuristic to make snippet blocks - /// into valid Dart code. - _Section _processBlock(_Line line, List block) { - if (block.isEmpty) { - throw _SnippetCheckerException('$line: Empty ```dart block in snippet code.'); - } - if (block.first.startsWith('new ') || block.first.startsWith(_constructorRegExp)) { - _expressionId += 1; - return _Section.surround(line, 'dynamic expression$_expressionId = ', block.toList(), ';'); - } else if (block.first.startsWith('await ')) { - _expressionId += 1; - return _Section.surround(line, 'Future expression$_expressionId() async { ', block.toList(), ' }'); - } else if (block.first.startsWith('class ') || block.first.startsWith('enum ')) { - return _Section.fromStrings(line, block.toList()); - } else if ((block.first.startsWith('_') || block.first.startsWith('final ')) && block.first.contains(' = ')) { - _expressionId += 1; - return _Section.surround(line, 'void expression$_expressionId() { ', block.toList(), ' }'); - } else { - final List buffer = []; - int subblocks = 0; - _Line? subline; - final List<_Section> subsections = <_Section>[]; - for (int index = 0; index < block.length; index += 1) { - // Each section of the dart code that is either split by a blank line, or with '// ...' is - // treated as a separate code block. - if (block[index] == '' || block[index] == '// ...') { - if (subline == null) { - throw _SnippetCheckerException('${_Line(filename: line.filename, line: line.line + index, indent: line.indent)}: ' - 'Unexpected blank line or "// ..." line near start of subblock in snippet code.'); - } - subblocks += 1; - subsections.add(_processBlock(subline, buffer)); - buffer.clear(); - assert(buffer.isEmpty); - subline = null; - } else if (block[index].startsWith('// ')) { - if (buffer.length > 1) { - buffer.add('/${block[index]}'); // so that it doesn't start with "// " and get caught in this again - } - } else { - subline ??= _Line( - code: block[index], - filename: line.filename, - line: line.line + index, - indent: line.indent, - ); - buffer.add(block[index]); - } - } - if (subblocks > 0) { - if (subline != null) { - subsections.add(_processBlock(subline, buffer)); - } - // Combine all of the subsections into one section, now that they've been processed. - return _Section.combine(subsections); - } else { - return _Section.fromStrings(line, block.toList()); - } - } - } -} - -/// A class to represent a line of input code. -class _Line { - const _Line({this.code = '', required this.filename, this.line = -1, this.indent = 0}) - : generated = false; - const _Line.generated({this.code = '', required this.filename, this.line = -1, this.indent = 0}) - : generated = true; - - /// The file that this line came from, or the file that the line was generated for, if [generated] is true. - final String filename; - final int line; - final int indent; - final String code; - final bool generated; - - String toStringWithColumn(int column) { - if (column != null && indent != null) { - return '$filename:$line:${column + indent}: $code'; - } - return toString(); + factory _SnippetFile.fromStrings( + _Line firstLine, + List code, + List<_Line> headers, + List<_Line> preamble, + String generatorComment, + String filename, { + String? prefix, String? postfix, + }) { + final List<_Line> codeLines = <_Line>[ + if (prefix != null) + _Line.generated(code: prefix), + for (int i = 0; i < code.length; i += 1) + _Line(code: code[i], line: firstLine.line + i, indent: firstLine.indent), + if (postfix != null) + _Line.generated(code: postfix), + ]; + return _SnippetFile.fromLines(codeLines, headers, preamble, generatorComment, filename); } - @override - String toString() => '$filename:$line: $code'; -} - -/// A class to represent a section of snippet code, marked by "{@tool snippet}...{@end-tool}". -class _Section { - const _Section(this.code, {this.dartVersionOverride}); - factory _Section.combine(List<_Section> sections) { - final List<_Line> code = sections - .expand((_Section section) => section.code) - .toList(); - return _Section(code); - } - factory _Section.fromStrings(_Line firstLine, List code) { - final List<_Line> codeLines = <_Line>[]; - for (int i = 0; i < code.length; ++i) { - codeLines.add( - _Line( - code: code[i], - filename: firstLine.filename, - line: firstLine.line + i, - indent: firstLine.indent, - ), - ); - } - return _Section(codeLines); - } - factory _Section.surround(_Line firstLine, String prefix, List code, String postfix) { - assert(prefix != null); - assert(postfix != null); - final List<_Line> codeLines = <_Line>[]; - for (int i = 0; i < code.length; ++i) { - codeLines.add( - _Line( - code: code[i], - filename: firstLine.filename, - line: firstLine.line + i, - indent: firstLine.indent, - ), - ); - } - return _Section(<_Line>[ - _Line.generated(code: prefix, filename: firstLine.filename, line: 0), - ...codeLines, - _Line.generated(code: postfix, filename: firstLine.filename, line: 0), - ]); - } - _Line get start => code.firstWhere((_Line line) => !line.generated); final List<_Line> code; - final String? dartVersionOverride; - - _Section copyWith({String? dartVersionOverride}) { - return _Section(code, dartVersionOverride: dartVersionOverride ?? this.dartVersionOverride); - } -} - -/// A class representing an analysis error along with the context of the error. -/// -/// Changes how it converts to a string based on the source of the error. -class _AnalysisError { - const _AnalysisError( - this.type, - this.line, - this.column, - this.message, - this.errorCode, - this.source, - ); - - final String type; - final int line; - final int column; - final String message; - final String errorCode; - final _Line? source; - - @override - String toString() { - if (source != null) { - return '${source!.toStringWithColumn(column)}\n>>> $type: $message ($errorCode)'; - } else { - return ':$line:$column\n>>> $type: $message ($errorCode)'; - } - } + final String generatorComment; + final String filename; + final int indexLine; } Future _runInteractive({ - required Directory? tempDir, + required String? tempDirectory, required Directory flutterPackage, required String filePath, required Directory? dartUiLocation, @@ -789,69 +1082,68 @@ Future _runInteractive({ filePath = path.isAbsolute(filePath) ? filePath : path.join(path.current, filePath); final File file = File(filePath); if (!file.existsSync()) { - throw 'Path ${file.absolute.path} does not exist ($filePath).'; + stderr.writeln('Specified file ${file.absolute.path} does not exist or is not a file.'); + exit(1); } if (!path.isWithin(_flutterRoot, file.absolute.path) && (dartUiLocation == null || !path.isWithin(dartUiLocation.path, file.absolute.path))) { - throw 'Path ${file.absolute.path} is not within the flutter root: ' - '$_flutterRoot${dartUiLocation != null ? ' or the dart:ui location: $dartUiLocation' : ''}'; + stderr.writeln( + 'Specified file ${file.absolute.path} is not within the flutter root: ' + "$_flutterRoot${dartUiLocation != null ? ' or the dart:ui location: $dartUiLocation' : ''}" + ); + exit(1); } - if (tempDir == null) { - tempDir = Directory.systemTemp.createTempSync('flutter_analyze_snippet_code.'); - ProcessSignal.sigint.watch().listen((_) { - print('Deleting temp files...'); - tempDir!.deleteSync(recursive: true); - exit(0); - }); - print('Using temp dir ${tempDir.path}'); - } print('Starting up in interactive mode on ${path.relative(filePath, from: _flutterRoot)} ...'); + print('Type "q" to quit, or "r" to force a reload.'); - Future analyze(_SnippetChecker checker, File file) async { - final Map sections = {}; - await checker._extractSnippets([file], silent: true, sectionMap: sections); - final _AnalysisResult analysisResult = checker._analyze(checker._tempDirectory, sections, silent: true); - stderr.writeln('\u001B[2J\u001B[H'); // Clears the old results from the terminal. - if (analysisResult.errors.isNotEmpty) { - for (final String filePath in analysisResult.errors.keys) { - analysisResult.errors[filePath]!.forEach(stderr.writeln); - } - stderr.writeln('\nFound ${analysisResult.errors.length} errors.'); - } else { - stderr.writeln('\nNo issues found.'); - } - } + final _SnippetChecker checker = _SnippetChecker(flutterPackage, tempDirectory: tempDirectory) + .._createConfigurationFiles(); - final _SnippetChecker checker = _SnippetChecker(flutterPackage, tempDirectory: tempDir) - .._createConfigurationFiles(tempDir); - await analyze(checker, file); + ProcessSignal.sigint.watch().listen((_) { + checker.cleanupTempDirectory(); + exit(0); + }); - print('Type "q" to quit, or "r" to delete temp dir and manually reload.'); - - void rerun() { - print('\n\nRerunning...'); + bool busy = false; + Future rerun() async { + assert(!busy); try { - analyze(checker, file); - } on _SnippetCheckerException catch (e) { - print('Caught Exception (${e.runtimeType}), press "r" to retry:\n$e'); + busy = true; + print('\nAnalyzing...'); + checker.recreateTempDirectory(); + final Map snippets = {}; + final Set errors = {}; + errors.addAll(await checker._extractSnippets([file], snippetMap: snippets)); + errors.addAll(checker._analyze(snippets)); + stderr.writeln('\u001B[2J\u001B[H'); // Clears the old results from the terminal. + if (errors.isNotEmpty) { + (errors.toList()..sort()).map(_stringify).forEach(stderr.writeln); + stderr.writeln('Found ${errors.length} errors.'); + } else { + stderr.writeln('No issues found.'); + } + } finally { + busy = false; } } + await rerun(); stdin.lineMode = false; stdin.echoMode = false; - stdin.transform(utf8.decoder).listen((String input) { - switch (input) { + stdin.transform(utf8.decoder).listen((String input) async { + switch (input.trim()) { case 'q': - print('Exiting...'); + checker.cleanupTempDirectory(); exit(0); case 'r': - print('Deleting temp files...'); - tempDir!.deleteSync(recursive: true); - rerun(); + if (!busy) { + rerun(); + } break; } }); - Watcher(file.absolute.path).events.listen((_) => rerun()); } + +String _stringify(Object object) => object.toString(); diff --git a/dev/bots/test/analyze-snippet-code-test-dart-ui/ui.dart b/dev/bots/test/analyze-snippet-code-test-dart-ui/ui.dart index e206228b0e..479e28c880 100644 --- a/dev/bots/test/analyze-snippet-code-test-dart-ui/ui.dart +++ b/dev/bots/test/analyze-snippet-code-test-dart-ui/ui.dart @@ -8,28 +8,15 @@ 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 snippet} -/// A sample if using keepToString to prevent replacement by a supercall. +/// Bla bla bla bla bla bla bla bla bla. /// /// ```dart /// class MyStringBuffer { -/// error; +/// error; // error (missing_const_final_var_or_type, always_specify_types) /// -/// StringBuffer _buffer = StringBuffer(); -/// -/// @keepToString -/// @override -/// String toString() { -/// return _buffer.toString(); -/// } +/// StringBuffer _buffer = StringBuffer(); // error (prefer_final_fields, unused_field) /// } /// ``` -/// {@end-tool} -const Object keepToString = _KeepToString(); - -class _KeepToString { - const _KeepToString(); +class Foo { + const Foo(); } diff --git a/dev/bots/test/analyze-snippet-code-test-input/known_broken_documentation.dart b/dev/bots/test/analyze-snippet-code-test-input/known_broken_documentation.dart index ffe82f05ef..6334e772b9 100644 --- a/dev/bots/test/analyze-snippet-code-test-input/known_broken_documentation.dart +++ b/dev/bots/test/analyze-snippet-code-test-input/known_broken_documentation.dart @@ -8,8 +8,8 @@ // Examples can assume: // bool _visible = true; // class _Text extends Text { -// const _Text(String text) : super(text); -// const _Text.__(String text) : super(text); +// const _Text(super.text); +// const _Text.__(super.text); // } /// A blabla that blabla its blabla blabla blabla. @@ -27,7 +27,7 @@ /// blabla it when it is blabla: /// /// ```dart -/// new Opacity( +/// new Opacity( // error (unnecessary_new) /// opacity: _visible ? 1.0 : 0.0, /// child: const Text('Poor wandering ones!'), /// ) @@ -38,12 +38,12 @@ /// Bla blabla blabla some [Text] when the `_blabla` blabla blabla is true, and /// blabla it when it is blabla: /// -/// ```dart preamble -/// bool _visible = true; +/// ```dart /// final GlobalKey globalKey = GlobalKey(); /// ``` /// /// ```dart +/// // continuing from previous example... /// Widget build(BuildContext context) { /// return Opacity( /// key: globalKey, @@ -59,7 +59,7 @@ /// blabla finale blabla: /// /// ```dart -/// new Opacity( +/// Opacity( /// opacity: _visible ? 1.0 : 0.0, /// child: const Text('Poor wandering ones!'), /// ) @@ -92,7 +92,7 @@ /// const variable /// /// ```dart -/// const text0 = Text('Poor wandering ones!'); +/// const Widget text0 = Text('Poor wandering ones!'); /// ``` /// {@end-tool} /// @@ -100,7 +100,7 @@ /// more const variables /// /// ```dart -/// const text1 = _Text('Poor wandering ones!'); +/// const text1 = _Text('Poor wandering ones!'); // error (always_specify_types) /// ``` /// {@end-tool} /// @@ -108,35 +108,61 @@ /// Snippet with null-safe syntax /// /// ```dart -/// final String? bar = 'Hello'; -/// final int foo = null; +/// final String? bar = 'Hello'; // error (unnecessary_nullable_for_final_variable_declarations, prefer_const_declarations) +/// final int foo = null; // error (invalid_assignment, prefer_const_declarations) /// ``` /// {@end-tool} /// -/// {@tool snippet} /// snippet with trailing comma /// /// ```dart /// const SizedBox(), /// ``` -/// {@end-tool} /// -/// {@tool snippet} +/// {@tool dartpad} /// Dartpad with null-safe syntax /// -/// ```dart preamble -/// bool? _visible = true; +/// ```dart /// final GlobalKey globalKey = GlobalKey(); /// ``` /// /// ```dart +/// // not continuing from previous example... /// Widget build(BuildContext context) { /// final String title; /// return Opacity( -/// key: globalKey, -/// opacity: _visible! ? 1.0 : 0.0, -/// child: Text(title), +/// key: globalKey, // error (undefined_identifier, argument_type_not_assignable) +/// opacity: _visible ? 1.0 : 0.0, +/// child: Text(title), // error (read_potentially_unassigned_final) /// ); /// } /// ``` /// {@end-tool} +/// +/// ```csv +/// this,is,fine +/// ``` +/// +/// ```dart +/// import 'dart:io'; // error (unused_import) +/// final Widget p = Placeholder(); // error (undefined_class, undefined_function, avoid_dynamic_calls) +/// ``` +/// +/// ```dart +/// // (e.g. in a stateful widget) +/// void initState() { // error (must_call_super, annotate_overrides) +/// widget.toString(); // error (undefined_identifier, return_of_invalid_type) +/// } +/// ``` +/// +/// ```dart +/// // not in a stateful widget +/// void initState() { +/// widget.toString(); // error (undefined_identifier) +/// } +/// ``` +/// +/// ``` +/// error (something about backticks) +/// this must be the last error, since it aborts parsing of this file +/// ``` diff --git a/dev/bots/test/analyze-snippet-code-test-input/short_but_still_broken.dart b/dev/bots/test/analyze-snippet-code-test-input/short_but_still_broken.dart new file mode 100644 index 0000000000..2f991c57d6 --- /dev/null +++ b/dev/bots/test/analyze-snippet-code-test-input/short_but_still_broken.dart @@ -0,0 +1,17 @@ +// 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. + +// This file is used by ../analyze_snippet_code_test.dart, which depends on the +// precise contents (including especially the comments) of this file. + +// Examples can assume: +// int x = ''; // error (invalid_assignment) + +/// ```dart +/// print(x); +/// ``` + +/// error: empty dart block +/// ```dart +/// ``` diff --git a/dev/bots/test/analyze_snippet_code_test.dart b/dev/bots/test/analyze_snippet_code_test.dart index c5fadf161f..7c0706e82c 100644 --- a/dev/bots/test/analyze_snippet_code_test.dart +++ b/dev/bots/test/analyze_snippet_code_test.dart @@ -2,10 +2,43 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +// we ignore these so that the format of the strings below matches what package:test prints, to make maintenance easier +// ignore_for_file: avoid_escaping_inner_quotes +// ignore_for_file: use_raw_strings + import 'dart:io'; import 'common.dart'; +const List expectedMainErrors = [ + 'dev/bots/test/analyze-snippet-code-test-input/known_broken_documentation.dart:30:5: Unnecessary new keyword (expression) (unnecessary_new)', + 'dev/bots/test/analyze-snippet-code-test-input/known_broken_documentation.dart:103:5: Specify type annotations (statement) (always_specify_types)', + 'dev/bots/test/analyze-snippet-code-test-input/known_broken_documentation.dart:111:5: Prefer const over final for declarations (top-level declaration) (prefer_const_declarations)', + 'dev/bots/test/analyze-snippet-code-test-input/known_broken_documentation.dart:111:19: Use a non-nullable type for a final variable initialized with a non-nullable value (top-level declaration) (unnecessary_nullable_for_final_variable_declarations)', + 'dev/bots/test/analyze-snippet-code-test-input/known_broken_documentation.dart:112:5: Prefer const over final for declarations (top-level declaration) (prefer_const_declarations)', + 'dev/bots/test/analyze-snippet-code-test-input/known_broken_documentation.dart:112:21: A value of type \'Null\' can\'t be assigned to a variable of type \'int\' (top-level declaration) (invalid_assignment)', + 'dev/bots/test/analyze-snippet-code-test-input/known_broken_documentation.dart:134:14: The argument type \'dynamic\' can\'t be assigned to the parameter type \'Key?\' (top-level declaration) (argument_type_not_assignable)', + 'dev/bots/test/analyze-snippet-code-test-input/known_broken_documentation.dart:134:14: Undefined name \'globalKey\' (top-level declaration) (undefined_identifier)', + 'dev/bots/test/analyze-snippet-code-test-input/known_broken_documentation.dart:136:21: The final variable \'title\' can\'t be read because it\'s potentially unassigned at this point (top-level declaration) (read_potentially_unassigned_final)', + 'dev/bots/test/analyze-snippet-code-test-input/known_broken_documentation.dart:147:12: Unused import: \'dart:io\' (self-contained program) (unused_import)', + 'dev/bots/test/analyze-snippet-code-test-input/known_broken_documentation.dart:148:11: Undefined class \'Widget\' (self-contained program) (undefined_class)', + 'dev/bots/test/analyze-snippet-code-test-input/known_broken_documentation.dart:148:22: Avoid method calls or property accesses on a "dynamic" target (self-contained program) (avoid_dynamic_calls)', + 'dev/bots/test/analyze-snippet-code-test-input/known_broken_documentation.dart:148:22: The function \'Placeholder\' isn\'t defined (self-contained program) (undefined_function)', + 'dev/bots/test/analyze-snippet-code-test-input/known_broken_documentation.dart:153:10: Annotate overridden members (stateful widget) (annotate_overrides)', + 'dev/bots/test/analyze-snippet-code-test-input/known_broken_documentation.dart:153:10: This method overrides a method annotated as \'@mustCallSuper\' in \'State\', but doesn\'t invoke the overridden method (stateful widget) (must_call_super)', + 'dev/bots/test/analyze-snippet-code-test-input/known_broken_documentation.dart:161:7: Undefined name \'widget\' (top-level declaration) (undefined_identifier)', + 'dev/bots/test/analyze-snippet-code-test-input/known_broken_documentation.dart:165: Found "```" in code but it did not match RegExp: pattern=^ */// *```dart\$ flags= so something is wrong. Line was: "/// ```"', + 'dev/bots/test/analyze-snippet-code-test-input/short_but_still_broken.dart:9:12: A value of type \'String\' can\'t be assigned to a variable of type \'int\' (statement) (invalid_assignment)', + 'dev/bots/test/analyze-snippet-code-test-input/short_but_still_broken.dart:17:4: Empty ```dart block in snippet code.', +]; + +const List expectedUiErrors = [ + 'dev/bots/test/analyze-snippet-code-test-dart-ui/ui.dart:15:7: Prefer typing uninitialized variables and fields (top-level declaration) (prefer_typing_uninitialized_variables)', + 'dev/bots/test/analyze-snippet-code-test-dart-ui/ui.dart:15:7: Variables must be declared using the keywords \'const\', \'final\', \'var\' or a type name (top-level declaration) (missing_const_final_var_or_type)', + 'dev/bots/test/analyze-snippet-code-test-dart-ui/ui.dart:17:20: Private field could be final (top-level declaration) (prefer_final_fields)', + 'dev/bots/test/analyze-snippet-code-test-dart-ui/ui.dart:17:20: The value of the field \'_buffer\' isn\'t used (top-level declaration) (unused_field)', +]; + void main() { // These tests don't run on Windows because the sample analyzer doesn't // support Windows as a platform, since it is only run on Linux in the @@ -17,55 +50,45 @@ void main() { test('analyze_snippet_code smoke test', () { final ProcessResult process = Process.runSync( '../../bin/cache/dart-sdk/bin/dart', - ['analyze_snippet_code.dart', '--no-include-dart-ui', 'test/analyze-snippet-code-test-input'], + [ + '--enable-asserts', + 'analyze_snippet_code.dart', + '--no-include-dart-ui', + 'test/analyze-snippet-code-test-input', + ], ); - final List stdoutLines = process.stdout.toString().split('\n'); + expect(process.stdout, isEmpty); final List stderrLines = process.stderr.toString().split('\n'); - expect(process.exitCode, isNot(equals(0))); - expect(stderrLines, containsAll([ - 'dev/bots/test/analyze-snippet-code-test-input/known_broken_documentation.dart:138:25: child: Text(title),', - matches(RegExp(r">>> error: The final variable 'title' can't be read because (it is|it's) potentially unassigned at this point \(read_potentially_unassigned_final\)")), - 'dev/bots/test/analyze-snippet-code-test-input/known_broken_documentation.dart:30:9: new Opacity(', - '>>> info: Unnecessary new keyword (unnecessary_new)', - 'dev/bots/test/analyze-snippet-code-test-input/known_broken_documentation.dart:62:9: new Opacity(', - '>>> info: Unnecessary new keyword (unnecessary_new)', - "dev/bots/test/analyze-snippet-code-test-input/known_broken_documentation.dart:111:9: final String? bar = 'Hello';", - '>>> info: Prefer const over final for declarations (prefer_const_declarations)', - 'dev/bots/test/analyze-snippet-code-test-input/known_broken_documentation.dart:112:9: final int foo = null;', - '>>> info: Prefer const over final for declarations (prefer_const_declarations)', - 'dev/bots/test/analyze-snippet-code-test-input/known_broken_documentation.dart:112:25: final int foo = null;', - ">>> error: A value of type 'Null' can't be assigned to a variable of type 'int' (invalid_assignment)", - 'dev/bots/test/analyze-snippet-code-test-input/known_broken_documentation.dart:120:24: const SizedBox(),', - '>>> error: Unexpected comma at end of snippet code. (missing_identifier)', - 'Found 1 snippet code errors.', - ])); - expect(stdoutLines, containsAll([ - 'Found 13 snippet code blocks', - 'Starting analysis of code snippets.', - ])); + expect(stderrLines.length, stderrLines.toSet().length, reason: 'found duplicates in $stderrLines'); + expect(stderrLines, [ + ...expectedMainErrors, + 'Found 19 snippet code errors.', + 'See the documentation at the top of dev/bots/analyze_snippet_code.dart for details.', + '', // because we end with a newline, split gives us an extra blank line + ]); + expect(process.exitCode, 1); }); + test('Analyzes dart:ui code', () { final ProcessResult process = Process.runSync( '../../bin/cache/dart-sdk/bin/dart', [ + '--enable-asserts', 'analyze_snippet_code.dart', '--dart-ui-location=test/analyze-snippet-code-test-dart-ui', 'test/analyze-snippet-code-test-input', ], ); - final List stdoutLines = process.stdout.toString().split('\n'); + expect(process.stdout, isEmpty); final List stderrLines = process.stderr.toString().split('\n'); - expect(process.exitCode, isNot(equals(0))); - expect(stderrLines, containsAll([ - 'dev/bots/test/analyze-snippet-code-test-dart-ui/ui.dart:19:11: error;', - ">>> error: Variables must be declared using the keywords 'const', 'final', 'var' or a type name (missing_const_final_var_or_type)", - 'dev/bots/test/analyze-snippet-code-test-dart-ui/ui.dart:23:11: @keepToString', - ">>> error: Undefined name 'keepToString' used as an annotation (undefined_annotation)", - ])); - expect(stdoutLines, containsAll([ - // There is one snippet code section in the test's dummy dart:ui code. - 'Found 14 snippet code blocks', - 'Starting analysis of code snippets.', - ])); + expect(stderrLines.length, stderrLines.toSet().length, reason: 'found duplicates in $stderrLines'); + expect(stderrLines, [ + ...expectedUiErrors, + ...expectedMainErrors, + 'Found 23 snippet code errors.', + 'See the documentation at the top of dev/bots/analyze_snippet_code.dart for details.', + '', // because we end with a newline, split gives us an extra blank line + ]); + expect(process.exitCode, 1); }); } diff --git a/examples/api/lib/widgets/sliver/sliver_opacity.1.dart b/examples/api/lib/widgets/sliver/sliver_opacity.1.dart new file mode 100644 index 0000000000..5a83acd10f --- /dev/null +++ b/examples/api/lib/widgets/sliver/sliver_opacity.1.dart @@ -0,0 +1,75 @@ +// 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. + +// Flutter code sample for SliverOpacity + +import 'package:flutter/material.dart'; + +void main() => runApp(const MyApp()); + +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + static const String _title = 'Flutter Code Sample'; + + @override + Widget build(BuildContext context) { + return const MaterialApp( + title: _title, + home: MyStatefulWidget(), + ); + } +} + +class MyStatefulWidget extends StatefulWidget { + const MyStatefulWidget({super.key}); + + @override + State createState() => _MyStatefulWidgetState(); +} + +class _MyStatefulWidgetState extends State { + static const List _listItems = [ + ListTile(title: Text('Now you see me,')), + ListTile(title: Text("Now you don't!")), + ]; + + bool _visible = true; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('SliverOpacity demo'), + ), + body: CustomScrollView( + slivers: [ + const SliverToBoxAdapter( + child: ListTile(title: Text('Press on the button to toggle the list visibilty.')), + ), + const SliverToBoxAdapter( + child: ListTile(title: Text('Before the list...')), + ), + SliverOpacity( + opacity: _visible ? 1.0 : 0.0, + sliver: SliverList( + delegate: SliverChildListDelegate(_listItems), + ), + ), + const SliverToBoxAdapter( + child: ListTile(title: Text('Before the list...')), + ), + ], + ), + floatingActionButton: FloatingActionButton( + child: const Icon(Icons.disabled_visible), + onPressed: () { + setState(() { + _visible = !_visible; + }); + }, + ), + ); + } +} diff --git a/examples/api/test/widgets/sliver/sliver_opacity.1_test.dart b/examples/api/test/widgets/sliver/sliver_opacity.1_test.dart new file mode 100644 index 0000000000..08ea90697c --- /dev/null +++ b/examples/api/test/widgets/sliver/sliver_opacity.1_test.dart @@ -0,0 +1,23 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/material.dart'; +import 'package:flutter_api_samples/widgets/sliver/sliver_opacity.1.dart' + as example; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + testWidgets('SliverOpacity example', (WidgetTester tester) async { + await tester.pumpWidget( + const example.MyApp(), + ); + + final Finder button = find.byType(FloatingActionButton); + final Finder opacity = find.byType(SliverOpacity); + expect((tester.widget(opacity) as SliverOpacity).opacity, 1.0); + await tester.tap(button); + await tester.pump(); + expect((tester.widget(opacity) as SliverOpacity).opacity, 0.0); + }); +} diff --git a/packages/flutter/lib/cupertino.dart b/packages/flutter/lib/cupertino.dart index 7638e29ef7..72df249bca 100644 --- a/packages/flutter/lib/cupertino.dart +++ b/packages/flutter/lib/cupertino.dart @@ -31,6 +31,7 @@ export 'src/cupertino/constants.dart'; export 'src/cupertino/context_menu.dart'; export 'src/cupertino/context_menu_action.dart'; export 'src/cupertino/date_picker.dart'; +export 'src/cupertino/debug.dart'; export 'src/cupertino/desktop_text_selection.dart'; export 'src/cupertino/dialog.dart'; export 'src/cupertino/form_row.dart'; diff --git a/packages/flutter/lib/src/animation/animation.dart b/packages/flutter/lib/src/animation/animation.dart index 01d4ffe5d8..cfae7f8b60 100644 --- a/packages/flutter/lib/src/animation/animation.dart +++ b/packages/flutter/lib/src/animation/animation.dart @@ -173,7 +173,7 @@ abstract class Animation extends Listenable implements ValueListenable { /// controller goes from 0.0 to 1.0: /// /// ```dart - /// Animation _alignment1 = _controller.drive( + /// Animation alignment1 = _controller.drive( /// AlignmentTween( /// begin: Alignment.topLeft, /// end: Alignment.topRight, @@ -208,7 +208,7 @@ abstract class Animation extends Listenable implements ValueListenable { /// values that depend on other variables: /// /// ```dart - /// Animation _alignment3 = _controller + /// Animation alignment3 = _controller /// .drive(CurveTween(curve: Curves.easeIn)) /// .drive(AlignmentTween( /// begin: Alignment.topLeft, @@ -226,7 +226,7 @@ abstract class Animation extends Listenable implements ValueListenable { /// of red. /// /// ```dart - /// Animation _offset1 = Animation.fromValueListenable(_scrollPosition) + /// Animation color = Animation.fromValueListenable(_scrollPosition) /// .drive(Animatable.fromCallback((double value) { /// return Color.fromRGBO(value.round() % 255, 0, 0, 1); /// })); diff --git a/packages/flutter/lib/src/cupertino/debug.dart b/packages/flutter/lib/src/cupertino/debug.dart index b7f65bf787..294dc27f54 100644 --- a/packages/flutter/lib/src/cupertino/debug.dart +++ b/packages/flutter/lib/src/cupertino/debug.dart @@ -6,6 +6,9 @@ import 'package:flutter/widgets.dart'; import 'localizations.dart'; +// Examples can assume: +// late BuildContext context; + /// Asserts that the given context has a [Localizations] ancestor that contains /// a [CupertinoLocalizations] delegate. /// diff --git a/packages/flutter/lib/src/cupertino/localizations.dart b/packages/flutter/lib/src/cupertino/localizations.dart index 493d4d6a82..2a68276c40 100644 --- a/packages/flutter/lib/src/cupertino/localizations.dart +++ b/packages/flutter/lib/src/cupertino/localizations.dart @@ -7,6 +7,9 @@ import 'package:flutter/widgets.dart'; import 'debug.dart'; +// Examples can assume: +// late BuildContext context; + /// Determines the order of the columns inside [CupertinoDatePicker] in /// time and date time mode. enum DatePickerDateTimeOrder { diff --git a/packages/flutter/lib/src/cupertino/switch.dart b/packages/flutter/lib/src/cupertino/switch.dart index f690252872..550c9531b4 100644 --- a/packages/flutter/lib/src/cupertino/switch.dart +++ b/packages/flutter/lib/src/cupertino/switch.dart @@ -2,6 +2,9 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +// Examples can assume: +// bool _giveVerse = false; + import 'dart:ui' show lerpDouble; import 'package:flutter/foundation.dart'; diff --git a/packages/flutter/lib/src/foundation/assertions.dart b/packages/flutter/lib/src/foundation/assertions.dart index a2bdae4e2c..3143a022bd 100644 --- a/packages/flutter/lib/src/foundation/assertions.dart +++ b/packages/flutter/lib/src/foundation/assertions.dart @@ -20,6 +20,8 @@ export 'stack_frame.dart' show StackFrame; // late bool draconisAmulet; // late Diagnosticable draconis; // void methodThatMayThrow() { } +// class Trace implements StackTrace { late StackTrace vmTrace; } +// class Chain implements StackTrace { Trace toTrace() => Trace(); } /// Signature for [FlutterError.onError] handler. typedef FlutterExceptionHandler = void Function(FlutterErrorDetails details); @@ -933,24 +935,27 @@ class FlutterError extends Error with DiagnosticableTreeMixin implements Asserti /// Called by the Flutter framework before attempting to parse a [StackTrace]. /// - /// Some [StackTrace] implementations have a different toString format from - /// what the framework expects, like ones from package:stack_trace. To make + /// Some [StackTrace] implementations have a different [toString] format from + /// what the framework expects, like ones from `package:stack_trace`. To make /// sure we can still parse and filter mangled [StackTrace]s, the framework /// first calls this function to demangle them. /// - /// This should be set in any environment that could propagate a non-standard + /// This should be set in any environment that could propagate an unusual /// stack trace to the framework. Otherwise, the default behavior is to assume - /// all stack traces are in a standard format. + /// all stack traces are in a format usually generated by Dart. /// - /// The following example demangles package:stack_trace traces by converting - /// them into vm traces, which the framework is able to parse: + /// The following example demangles `package:stack_trace` traces by converting + /// them into VM traces, which the framework is able to parse: /// /// ```dart - /// FlutterError.demangleStackTrace = (StackTrace stackTrace) { - /// if (stack is stack_trace.Trace) + /// FlutterError.demangleStackTrace = (StackTrace stack) { + /// // Trace and Chain are classes in package:stack_trace + /// if (stack is Trace) { /// return stack.vmTrace; - /// if (stack is stack_trace.Chain) + /// } + /// if (stack is Chain) { /// return stack.toTrace().vmTrace; + /// } /// return stack; /// }; /// ``` diff --git a/packages/flutter/lib/src/foundation/basic_types.dart b/packages/flutter/lib/src/foundation/basic_types.dart index 41e377c454..dc58411541 100644 --- a/packages/flutter/lib/src/foundation/basic_types.dart +++ b/packages/flutter/lib/src/foundation/basic_types.dart @@ -115,9 +115,10 @@ class CachingIterable extends IterableBase { /// /// ```dart /// Iterable range(int start, int end) sync* { - /// for (int index = start; index <= end; index += 1) + /// for (int index = start; index <= end; index += 1) { /// yield index; - /// } + /// } + /// } /// /// Iterable i = CachingIterable(range(1, 5).iterator); /// print(i.length); // walks the list diff --git a/packages/flutter/lib/src/foundation/binding.dart b/packages/flutter/lib/src/foundation/binding.dart index 87e95965f4..ead94b2971 100644 --- a/packages/flutter/lib/src/foundation/binding.dart +++ b/packages/flutter/lib/src/foundation/binding.dart @@ -119,6 +119,7 @@ typedef ServiceExtensionCallback = Future> Function(Map boxShadow; +// late Size size; +// late bool hasSize; +// late Matrix4 transform; +// late Color color; +// late Map? handles; +// late DiagnosticsTreeStyle style; +// late IconData icon; /// The various priority levels used to filter which diagnostics are shown and /// omitted. @@ -383,30 +409,29 @@ class TextTreeConfiguration { /// Default text tree configuration. /// /// Example: -/// ``` -/// : -/// │ -/// │ -/// │ ... -/// │ -/// ├─: -/// │ │ -/// │ │ -/// │ │ ... -/// │ │ -/// │ │ -/// │ └─: -/// │ -/// │ -/// │ ... -/// │ -/// │ -/// └─: ' -/// -/// -/// ... -/// -/// ``` +/// +/// : +/// │ +/// │ +/// │ ... +/// │ +/// ├─: +/// │ │ +/// │ │ +/// │ │ ... +/// │ │ +/// │ │ +/// │ └─: +/// │ +/// │ +/// │ ... +/// │ +/// │ +/// └─: ' +/// +/// +/// ... +/// /// /// See also: /// @@ -425,42 +450,41 @@ final TextTreeConfiguration sparseTextConfiguration = TextTreeConfiguration( /// parent to children are dashed. /// /// Example: -/// ``` -/// : -/// │ -/// │ -/// │ ... -/// │ -/// ├─: -/// ╎ │ -/// ╎ │ -/// ╎ │ ... -/// ╎ │ -/// ╎ │ -/// ╎ └─: -/// ╎ -/// ╎ -/// ╎ ... -/// ╎ -/// ╎ -/// ╎╌: -/// ╎ │ -/// ╎ │ -/// ╎ │ ... -/// ╎ │ -/// ╎ │ -/// ╎ └─: -/// ╎ -/// ╎ -/// ╎ ... -/// ╎ -/// ╎ -/// └╌: ' -/// -/// -/// ... -/// -/// ``` +/// +/// : +/// │ +/// │ +/// │ ... +/// │ +/// ├─: +/// ╎ │ +/// ╎ │ +/// ╎ │ ... +/// ╎ │ +/// ╎ │ +/// ╎ └─: +/// ╎ +/// ╎ +/// ╎ ... +/// ╎ +/// ╎ +/// ╎╌: +/// ╎ │ +/// ╎ │ +/// ╎ │ ... +/// ╎ │ +/// ╎ │ +/// ╎ └─: +/// ╎ +/// ╎ +/// ╎ ... +/// ╎ +/// ╎ +/// └╌: ' +/// +/// +/// ... +/// /// /// See also: /// @@ -480,11 +504,10 @@ final TextTreeConfiguration dashedTextConfiguration = TextTreeConfiguration( /// Dense text tree configuration that minimizes horizontal whitespace. /// /// Example: -/// ``` -/// : (; ) -/// ├: (, , ) -/// └: (, , ) -/// ``` +/// +/// : (; ) +/// ├: (, , ) +/// └: (, , ) /// /// See also: /// @@ -511,19 +534,18 @@ final TextTreeConfiguration denseTextConfiguration = TextTreeConfiguration( /// contents of a node. /// /// Example: -/// ``` -/// -/// ╞═╦══ ═══ -/// │ ║ : -/// │ ║ -/// │ ║ ... -/// │ ╚═══════════ -/// ╘═╦══ ═══ -/// ║ : -/// ║ -/// ║ ... -/// ╚═══════════ -/// ``` +/// +/// +/// ╞═╦══ ═══ +/// │ ║ : +/// │ ║ +/// │ ║ ... +/// │ ╚═══════════ +/// ╘═╦══ ═══ +/// ║ : +/// ║ +/// ║ ... +/// ╚═══════════ /// /// See also: /// @@ -563,37 +585,36 @@ final TextTreeConfiguration transitionTextConfiguration = TextTreeConfiguration( /// Used to draw a decorative box around detailed descriptions of an exception. /// /// Example: -/// ``` -/// ══╡ : ╞═════════════════════════════════════ -/// -/// ... -/// ├─: -/// ╎ │ -/// ╎ │ -/// ╎ │ ... -/// ╎ │ -/// ╎ │ -/// ╎ └─: -/// ╎ -/// ╎ -/// ╎ ... -/// ╎ -/// ╎ -/// ╎╌: -/// ╎ │ -/// ╎ │ -/// ╎ │ ... -/// ╎ │ -/// ╎ │ -/// ╎ └─: -/// ╎ -/// ╎ -/// ╎ ... -/// ╎ -/// ╎ -/// └╌: ' -/// ════════════════════════════════════════════════════════════════ -/// ``` +/// +/// ══╡ : ╞═════════════════════════════════════ +/// +/// ... +/// ├─: +/// ╎ │ +/// ╎ │ +/// ╎ │ ... +/// ╎ │ +/// ╎ │ +/// ╎ └─: +/// ╎ +/// ╎ +/// ╎ ... +/// ╎ +/// ╎ +/// ╎╌: +/// ╎ │ +/// ╎ │ +/// ╎ │ ... +/// ╎ │ +/// ╎ │ +/// ╎ └─: +/// ╎ +/// ╎ +/// ╎ ... +/// ╎ +/// ╎ +/// └╌: ' +/// ════════════════════════════════════════════════════════════════ /// /// See also: /// @@ -626,15 +647,14 @@ final TextTreeConfiguration errorTextConfiguration = TextTreeConfiguration( /// draws line art would be visually distracting for those cases. /// /// Example: -/// ``` -/// -/// : : -/// -/// -/// : : -/// -/// -/// ``` +/// +/// +/// : : +/// +/// +/// : : +/// +/// /// /// See also: /// @@ -660,15 +680,14 @@ final TextTreeConfiguration whitespaceTextConfiguration = TextTreeConfiguration( /// children as in the case of a [DiagnosticsStackTrace]. /// /// Example: -/// ``` -/// -/// : : -/// -/// -/// : : -/// -/// -/// ``` +/// +/// +/// : : +/// +/// +/// : : +/// +/// /// /// See also: /// @@ -716,10 +735,9 @@ final TextTreeConfiguration singleLineTextConfiguration = TextTreeConfiguration( /// line omitting the children. /// /// Example: -/// ``` -/// : -/// (, , ..., ) -/// ``` +/// +/// : +/// (, , ..., ) /// /// See also: /// @@ -3162,7 +3180,7 @@ mixin Diagnosticable { /// Shows using `showSeparator` to get output `child(3, 4) is null` which /// is more polished than `child(3, 4): is null`. /// ```dart - /// DiagnosticsProperty('icon', icon, ifNull: '', showName: false)).toString() + /// DiagnosticsProperty('icon', icon, ifNull: '', showName: false).toString() /// ``` /// Shows using `showName` to omit the property name as in this context the /// property name does not add useful information. @@ -3238,7 +3256,7 @@ mixin Diagnosticable { /// properties.add(DoubleProperty('hitTestExtent', hitTestExtent, defaultValue: paintExtent)); /// /// // maxWidth of double.infinity indicates the width is unconstrained and - /// // so maxWidth has no impact., + /// // so maxWidth has no impact. /// properties.add(DoubleProperty('maxWidth', maxWidth, defaultValue: double.infinity)); /// /// // Progress is a value between 0 and 1 or null. Showing it as a diff --git a/packages/flutter/lib/src/gestures/events.dart b/packages/flutter/lib/src/gestures/events.dart index ab9d4097cd..4130cf1dbf 100644 --- a/packages/flutter/lib/src/gestures/events.dart +++ b/packages/flutter/lib/src/gestures/events.dart @@ -184,9 +184,10 @@ int nthStylusButton(int number) => (kPrimaryStylusButton << (number - 1)) & kMax /// Example: /// /// ```dart -/// assert(rightmostButton(0x1) == 0x1); -/// assert(rightmostButton(0x11) == 0x1); -/// assert(rightmostButton(0) == 0); +/// assert(smallestButton(0x01) == 0x01); +/// assert(smallestButton(0x11) == 0x01); +/// assert(smallestButton(0x10) == 0x10); +/// assert(smallestButton(0) == 0); /// ``` /// /// See also: diff --git a/packages/flutter/lib/src/gestures/force_press.dart b/packages/flutter/lib/src/gestures/force_press.dart index 9153548fda..956b8f0c52 100644 --- a/packages/flutter/lib/src/gestures/force_press.dart +++ b/packages/flutter/lib/src/gestures/force_press.dart @@ -204,7 +204,7 @@ class ForcePressGestureRecognizer extends OneSequenceGestureRecognizer { /// value: /// /// ```dart - /// static double interpolateWithEasing(double min, double max, double t) { + /// double interpolateWithEasing(double min, double max, double t) { /// final double lerp = (t - min) / (max - min); /// return Curves.easeIn.transform(lerp); /// } diff --git a/packages/flutter/lib/src/material/about.dart b/packages/flutter/lib/src/material/about.dart index 01a406e74b..7247d61bba 100644 --- a/packages/flutter/lib/src/material/about.dart +++ b/packages/flutter/lib/src/material/about.dart @@ -30,6 +30,9 @@ import 'text_button.dart'; import 'text_theme.dart'; import 'theme.dart'; +// Examples can assume: +// BuildContext context; + /// A [ListTile] that shows an about box. /// /// This widget is often added to an app's [Drawer]. When tapped it shows @@ -1062,14 +1065,14 @@ class _MasterDetailFlow extends StatefulWidget { @override _MasterDetailFlowState createState() => _MasterDetailFlowState(); - /// The master detail flow proxy from the closest instance of this class that encloses the given - /// context. - /// - /// Typical usage is as follows: - /// - /// ```dart - /// _MasterDetailFlow.of(context).openDetailPage(arguments); - /// ``` + // The master detail flow proxy from the closest instance of this class that encloses the given + // context. + // + // Typical usage is as follows: + // + // ```dart + // _MasterDetailFlow.of(context).openDetailPage(arguments); + // ``` static _MasterDetailFlowProxy? of(BuildContext context) { _PageOpener? pageOpener = context.findAncestorStateOfType<_MasterDetailScaffoldState>(); pageOpener ??= context.findAncestorStateOfType<_MasterDetailFlowState>(); diff --git a/packages/flutter/lib/src/material/app.dart b/packages/flutter/lib/src/material/app.dart index 48d9b62f41..f21db89166 100644 --- a/packages/flutter/lib/src/material/app.dart +++ b/packages/flutter/lib/src/material/app.dart @@ -19,6 +19,10 @@ import 'scrollbar.dart'; import 'theme.dart'; import 'tooltip.dart'; +// Examples can assume: +// typedef GlobalWidgetsLocalizations = DefaultWidgetsLocalizations; +// typedef GlobalMaterialLocalizations = DefaultMaterialLocalizations; + /// [MaterialApp] uses this [TextStyle] as its [DefaultTextStyle] to encourage /// developers to be intentional about their [DefaultTextStyle]. /// @@ -171,7 +175,7 @@ enum ThemeMode { /// a [Material] widget that defines its default text style. /// /// ```dart -/// MaterialApp( +/// const MaterialApp( /// title: 'Material App', /// home: Scaffold( /// body: Center( @@ -513,16 +517,19 @@ class MaterialApp extends StatefulWidget { /// and list the [supportedLocales] that the application can handle. /// /// ```dart - /// import 'package:flutter_localizations/flutter_localizations.dart'; - /// MaterialApp( - /// localizationsDelegates: [ - /// // ... app-specific localization delegate[s] here + /// // The GlobalMaterialLocalizations and GlobalWidgetsLocalizations + /// // classes require the following import: + /// // import 'package:flutter_localizations/flutter_localizations.dart'; + /// + /// const MaterialApp( + /// localizationsDelegates: >[ + /// // ... app-specific localization delegate(s) here /// GlobalMaterialLocalizations.delegate, /// GlobalWidgetsLocalizations.delegate, /// ], - /// supportedLocales: [ - /// const Locale('en', 'US'), // English - /// const Locale('he', 'IL'), // Hebrew + /// supportedLocales: [ + /// Locale('en', 'US'), // English + /// Locale('he', 'IL'), // Hebrew /// // ... other locales the app supports /// ], /// // ... @@ -542,33 +549,39 @@ class MaterialApp extends StatefulWidget { /// [LocalizationsDelegate] whose load methods return /// custom versions of [WidgetsLocalizations] or [MaterialLocalizations]. /// - /// For example: to add support to [MaterialLocalizations] for a - /// locale it doesn't already support, say `const Locale('foo', 'BR')`, - /// one could just extend [DefaultMaterialLocalizations]: + /// For example: to add support to [MaterialLocalizations] for a locale it + /// doesn't already support, say `const Locale('foo', 'BR')`, one first + /// creates a subclass of [MaterialLocalizations] that provides the + /// translations: /// /// ```dart - /// class FooLocalizations extends DefaultMaterialLocalizations { - /// FooLocalizations(Locale locale) : super(locale); + /// class FooLocalizations extends MaterialLocalizations { + /// FooLocalizations(); /// @override - /// String get okButtonLabel { - /// if (locale == const Locale('foo', 'BR')) - /// return 'foo'; - /// return super.okButtonLabel; - /// } + /// String get okButtonLabel => 'foo'; + /// // ... + /// // lots of other getters and methods to override! /// } - /// /// ``` /// - /// A `FooLocalizationsDelegate` is essentially just a method that constructs - /// a `FooLocalizations` object. We return a [SynchronousFuture] here because - /// no asynchronous work takes place upon "loading" the localizations object. + /// One must then create a [LocalizationsDelegate] subclass that can provide + /// an instance of the [MaterialLocalizations] subclass. In this case, this is + /// essentially just a method that constructs a `FooLocalizations` object. A + /// [SynchronousFuture] is used here because no asynchronous work takes place + /// upon "loading" the localizations object. /// /// ```dart + /// // continuing from previous example... /// class FooLocalizationsDelegate extends LocalizationsDelegate { /// const FooLocalizationsDelegate(); /// @override + /// bool isSupported(Locale locale) { + /// return locale == const Locale('foo', 'BR'); + /// } + /// @override /// Future load(Locale locale) { - /// return SynchronousFuture(FooLocalizations(locale)); + /// assert(locale == const Locale('foo', 'BR')); + /// return SynchronousFuture(FooLocalizations()); /// } /// @override /// bool shouldReload(FooLocalizationsDelegate old) => false; @@ -582,9 +595,10 @@ class MaterialApp extends StatefulWidget { /// [localizationsDelegates] list. /// /// ```dart - /// MaterialApp( - /// localizationsDelegates: [ - /// const FooLocalizationsDelegate(), + /// // continuing from previous example... + /// const MaterialApp( + /// localizationsDelegates: >[ + /// FooLocalizationsDelegate(), /// ], /// // ... /// ) diff --git a/packages/flutter/lib/src/material/app_bar.dart b/packages/flutter/lib/src/material/app_bar.dart index bdf23f9163..a6d1a6b822 100644 --- a/packages/flutter/lib/src/material/app_bar.dart +++ b/packages/flutter/lib/src/material/app_bar.dart @@ -25,6 +25,10 @@ import 'tabs.dart'; import 'text_theme.dart'; import 'theme.dart'; +// Examples can assume: +// late String _logoAsset; +// double _myToolbarHeight = 250.0; + const double _kLeadingWidth = kToolbarHeight; // So the leading button is square. const double _kMaxTitleTextScaleFactor = 1.34; // TODO(perc): Add link to Material spec when available, https://github.com/flutter/flutter/issues/58769. @@ -319,10 +323,10 @@ class AppBar extends StatefulWidget implements PreferredSizeWidget { /// home: Scaffold( /// appBar: AppBar( /// title: SizedBox( - /// height: toolbarHeight, - /// child: Image.asset(logoAsset), + /// height: _myToolbarHeight, + /// child: Image.asset(_logoAsset), /// ), - /// toolbarHeight: toolbarHeight, + /// toolbarHeight: _myToolbarHeight, /// ), /// ), /// ) diff --git a/packages/flutter/lib/src/material/banner.dart b/packages/flutter/lib/src/material/banner.dart index c5959862f2..15da6e828c 100644 --- a/packages/flutter/lib/src/material/banner.dart +++ b/packages/flutter/lib/src/material/banner.dart @@ -10,6 +10,9 @@ import 'material.dart'; import 'scaffold.dart'; import 'theme.dart'; +// Examples can assume: +// late BuildContext context; + const Duration _materialBannerTransitionDuration = Duration(milliseconds: 250); const Curve _materialBannerHeightCurve = Curves.fastOutSlowIn; @@ -24,9 +27,14 @@ const Curve _materialBannerHeightCurve = Curves.fastOutSlowIn; /// /// ```dart /// ScaffoldMessenger.of(context).showMaterialBanner( -/// MaterialBanner( ... ) +/// const MaterialBanner( +/// content: Text('Message...'), +/// actions: [ +/// // ... +/// ], +/// ) /// ).closed.then((MaterialBannerClosedReason reason) { -/// ... +/// // ... /// }); /// ``` enum MaterialBannerClosedReason { diff --git a/packages/flutter/lib/src/material/banner_theme.dart b/packages/flutter/lib/src/material/banner_theme.dart index e114a4a7fe..5081f8825e 100644 --- a/packages/flutter/lib/src/material/banner_theme.dart +++ b/packages/flutter/lib/src/material/banner_theme.dart @@ -9,6 +9,9 @@ import 'package:flutter/widgets.dart'; import 'theme.dart'; +// Examples can assume: +// late BuildContext context; + /// Defines the visual properties of [MaterialBanner] widgets. /// /// Descendant widgets obtain the current [MaterialBannerThemeData] object using diff --git a/packages/flutter/lib/src/material/bottom_navigation_bar_theme.dart b/packages/flutter/lib/src/material/bottom_navigation_bar_theme.dart index 4f83db84f7..ed775e38a1 100644 --- a/packages/flutter/lib/src/material/bottom_navigation_bar_theme.dart +++ b/packages/flutter/lib/src/material/bottom_navigation_bar_theme.dart @@ -11,6 +11,9 @@ import 'bottom_navigation_bar.dart'; import 'material_state.dart'; import 'theme.dart'; +// Examples can assume: +// late BuildContext context; + /// Defines default property values for descendant [BottomNavigationBar] /// widgets. /// diff --git a/packages/flutter/lib/src/material/button_bar_theme.dart b/packages/flutter/lib/src/material/button_bar_theme.dart index 0b61682d72..c2a4b7fd35 100644 --- a/packages/flutter/lib/src/material/button_bar_theme.dart +++ b/packages/flutter/lib/src/material/button_bar_theme.dart @@ -10,6 +10,9 @@ import 'package:flutter/widgets.dart'; import 'button_theme.dart'; import 'theme.dart'; +// Examples can assume: +// late BuildContext context; + /// Defines the visual properties of [ButtonBar] widgets. /// /// Used by [ButtonBarTheme] to control the visual properties of [ButtonBar] diff --git a/packages/flutter/lib/src/material/button_style.dart b/packages/flutter/lib/src/material/button_style.dart index 2067ed85cb..6e90614be5 100644 --- a/packages/flutter/lib/src/material/button_style.dart +++ b/packages/flutter/lib/src/material/button_style.dart @@ -12,6 +12,10 @@ import 'ink_well.dart'; import 'material_state.dart'; import 'theme_data.dart'; +// Examples can assume: +// late BuildContext context; +// typedef MyAppHome = Placeholder; + /// The visual properties that most buttons have in common. /// /// Buttons and their themes have a ButtonStyle property which defines the visual @@ -37,13 +41,18 @@ import 'theme_data.dart'; /// style: ButtonStyle( /// backgroundColor: MaterialStateProperty.resolveWith( /// (Set states) { -/// if (states.contains(MaterialState.pressed)) +/// if (states.contains(MaterialState.pressed)) { /// return Theme.of(context).colorScheme.primary.withOpacity(0.5); +/// } /// return null; // Use the component's default. /// }, /// ), /// ), -/// ) +/// child: const Text('Fly me to the moon'), +/// onPressed: () { +/// // ... +/// }, +/// ), /// ``` /// /// In this case the background color for all other button states would fallback @@ -52,10 +61,14 @@ import 'theme_data.dart'; /// /// ```dart /// ElevatedButton( -/// style: ButtonStyle( +/// style: const ButtonStyle( /// backgroundColor: MaterialStatePropertyAll(Colors.green), /// ), -/// ) +/// child: const Text('Let me play among the stars'), +/// onPressed: () { +/// // ... +/// }, +/// ), /// ``` /// /// Configuring a ButtonStyle directly makes it possible to very @@ -76,11 +89,16 @@ import 'theme_data.dart'; /// ```dart /// TextButton( /// style: TextButton.styleFrom(foregroundColor: Colors.green), -/// ) +/// child: const Text('Let me see what spring is like'), +/// onPressed: () { +/// // ... +/// }, +/// ), /// ``` /// /// To configure all of the application's text buttons in the same /// way, specify the overall theme's `textButtonTheme`: +/// /// ```dart /// MaterialApp( /// theme: ThemeData( @@ -88,8 +106,8 @@ import 'theme_data.dart'; /// style: TextButton.styleFrom(foregroundColor: Colors.green), /// ), /// ), -/// home: MyAppHome(), -/// ) +/// home: const MyAppHome(), +/// ), /// ``` /// /// ## Material 3 button types @@ -285,7 +303,7 @@ class ButtonStyle with Diagnosticable { /// splashFactory: NoSplash.splashFactory, /// ), /// onPressed: () { }, - /// child: Text('No Splash'), + /// child: const Text('No Splash'), /// ) /// ``` final InteractiveInkFeatureFactory? splashFactory; diff --git a/packages/flutter/lib/src/material/button_theme.dart b/packages/flutter/lib/src/material/button_theme.dart index b35b38212e..0f734abbd5 100644 --- a/packages/flutter/lib/src/material/button_theme.dart +++ b/packages/flutter/lib/src/material/button_theme.dart @@ -13,6 +13,9 @@ import 'material_state.dart'; import 'theme.dart'; import 'theme_data.dart' show MaterialTapTargetSize; +// Examples can assume: +// late BuildContext context; + /// Used with [ButtonTheme] and [ButtonThemeData] to define a button's base /// colors, and the defaults for the button's minimum size, internal padding, /// and shape. @@ -232,14 +235,7 @@ class ButtonThemeData with Diagnosticable { final ButtonBarLayoutBehavior layoutBehavior; /// Simply a convenience that returns [minWidth] and [height] as a - /// [BoxConstraints] object: - /// - /// ```dart - /// return BoxConstraints( - /// minWidth: minWidth, - /// minHeight: height, - /// ); - /// ``` + /// [BoxConstraints] object. BoxConstraints get constraints { return BoxConstraints( minWidth: minWidth, diff --git a/packages/flutter/lib/src/material/calendar_date_picker.dart b/packages/flutter/lib/src/material/calendar_date_picker.dart index 09934e0976..7a146930ee 100644 --- a/packages/flutter/lib/src/material/calendar_date_picker.dart +++ b/packages/flutter/lib/src/material/calendar_date_picker.dart @@ -906,19 +906,18 @@ class _DayPickerState extends State<_DayPicker> { /// /// Examples: /// - /// ``` - /// ┌ Sunday is the first day of week in the US (en_US) - /// | - /// S M T W T F S <-- the returned list contains these widgets - /// _ _ _ _ _ 1 2 - /// 3 4 5 6 7 8 9 + /// ┌ Sunday is the first day of week in the US (en_US) + /// | + /// S M T W T F S ← the returned list contains these widgets + /// _ _ _ _ _ 1 2 + /// 3 4 5 6 7 8 9 + /// + /// ┌ But it's Monday in the UK (en_GB) + /// | + /// M T W T F S S ← the returned list contains these widgets + /// _ _ _ _ 1 2 3 + /// 4 5 6 7 8 9 10 /// - /// ┌ But it's Monday in the UK (en_GB) - /// | - /// M T W T F S S <-- the returned list contains these widgets - /// _ _ _ _ 1 2 3 - /// 4 5 6 7 8 9 10 - /// ``` List _dayHeaders(TextStyle? headerStyle, MaterialLocalizations localizations) { final List result = []; for (int i = localizations.firstDayOfWeekIndex; true; i = (i + 1) % 7) { diff --git a/packages/flutter/lib/src/material/checkbox.dart b/packages/flutter/lib/src/material/checkbox.dart index a274c695ca..ecc05710ed 100644 --- a/packages/flutter/lib/src/material/checkbox.dart +++ b/packages/flutter/lib/src/material/checkbox.dart @@ -12,6 +12,10 @@ import 'theme.dart'; import 'theme_data.dart'; import 'toggleable.dart'; +// Examples can assume: +// bool _throwShotAway = false; +// late StateSetter setState; + /// A Material Design checkbox. /// /// The checkbox itself does not maintain any state. Instead, when the state of diff --git a/packages/flutter/lib/src/material/checkbox_list_tile.dart b/packages/flutter/lib/src/material/checkbox_list_tile.dart index 94e7731fb2..837692141f 100644 --- a/packages/flutter/lib/src/material/checkbox_list_tile.dart +++ b/packages/flutter/lib/src/material/checkbox_list_tile.dart @@ -13,7 +13,7 @@ import 'theme.dart'; import 'theme_data.dart'; // Examples can assume: -// bool? _throwShotAway = false; +// late bool? _throwShotAway; // void setState(VoidCallback fn) { } /// A [ListTile] with a [Checkbox]. In other words, a checkbox with a label. diff --git a/packages/flutter/lib/src/material/checkbox_theme.dart b/packages/flutter/lib/src/material/checkbox_theme.dart index 30d1448607..b959683e66 100644 --- a/packages/flutter/lib/src/material/checkbox_theme.dart +++ b/packages/flutter/lib/src/material/checkbox_theme.dart @@ -12,6 +12,9 @@ import 'material_state.dart'; import 'theme.dart'; import 'theme_data.dart'; +// Examples can assume: +// late BuildContext context; + /// Defines default property values for descendant [Checkbox] widgets. /// /// Descendant widgets obtain the current [CheckboxThemeData] object using diff --git a/packages/flutter/lib/src/material/data_table.dart b/packages/flutter/lib/src/material/data_table.dart index 73eb1ef53c..82f40ace62 100644 --- a/packages/flutter/lib/src/material/data_table.dart +++ b/packages/flutter/lib/src/material/data_table.dart @@ -21,6 +21,11 @@ import 'material_state.dart'; import 'theme.dart'; import 'tooltip.dart'; +// Examples can assume: +// late BuildContext context; +// late List _columns; +// late List _rows; + /// Signature for [DataColumn.onSort] callback. typedef DataColumnSortCallback = void Function(int columnIndex, bool ascending); @@ -183,10 +188,14 @@ class DataRow { /// ```dart /// DataRow( /// color: MaterialStateProperty.resolveWith((Set states) { - /// if (states.contains(MaterialState.selected)) + /// if (states.contains(MaterialState.selected)) { /// return Theme.of(context).colorScheme.primary.withOpacity(0.08); + /// } /// return null; // Use the default value. /// }), + /// cells: const [ + /// // ... + /// ], /// ) /// ``` /// @@ -474,10 +483,13 @@ class DataTable extends StatelessWidget { /// ```dart /// DataTable( /// dataRowColor: MaterialStateProperty.resolveWith((Set states) { - /// if (states.contains(MaterialState.selected)) + /// if (states.contains(MaterialState.selected)) { /// return Theme.of(context).colorScheme.primary.withOpacity(0.08); + /// } /// return null; // Use the default value. /// }), + /// columns: _columns, + /// rows: _rows, /// ) /// ``` /// @@ -521,9 +533,12 @@ class DataTable extends StatelessWidget { /// {@template flutter.material.DataTable.headingRowColor} /// ```dart /// DataTable( + /// columns: _columns, + /// rows: _rows, /// headingRowColor: MaterialStateProperty.resolveWith((Set states) { - /// if (states.contains(MaterialState.hovered)) + /// if (states.contains(MaterialState.hovered)) { /// return Theme.of(context).colorScheme.primary.withOpacity(0.08); + /// } /// return null; // Use the default value. /// }), /// ) diff --git a/packages/flutter/lib/src/material/data_table_theme.dart b/packages/flutter/lib/src/material/data_table_theme.dart index 6a79264d5a..219d43e2c7 100644 --- a/packages/flutter/lib/src/material/data_table_theme.dart +++ b/packages/flutter/lib/src/material/data_table_theme.dart @@ -10,6 +10,9 @@ import 'package:flutter/widgets.dart'; import 'material_state.dart'; import 'theme.dart'; +// Examples can assume: +// late BuildContext context; + /// Defines default property values for descendant [DataTable] /// widgets. /// diff --git a/packages/flutter/lib/src/material/date.dart b/packages/flutter/lib/src/material/date.dart index 6f08f7dc4c..3b313bc944 100644 --- a/packages/flutter/lib/src/material/date.dart +++ b/packages/flutter/lib/src/material/date.dart @@ -47,10 +47,11 @@ class DateUtils { /// Determines the number of months between two [DateTime] objects. /// /// For example: - /// ``` - /// DateTime date1 = DateTime(year: 2019, month: 6, day: 15); - /// DateTime date2 = DateTime(year: 2020, month: 1, day: 15); - /// int delta = monthDelta(date1, date2); + /// + /// ```dart + /// DateTime date1 = DateTime(2019, 6, 15); + /// DateTime date2 = DateTime(2020, 1, 15); + /// int delta = DateUtils.monthDelta(date1, date2); /// ``` /// /// The value for `delta` would be `7`. @@ -62,8 +63,9 @@ class DateUtils { /// of months and the day set to 1 and time set to midnight. /// /// For example: - /// ``` - /// DateTime date = DateTime(year: 2019, month: 1, day: 15); + /// + /// ```dart + /// DateTime date = DateTime(2019, 1, 15); /// DateTime futureDate = DateUtils.addMonthsToMonthDate(date, 3); /// ``` /// @@ -85,10 +87,8 @@ class DateUtils { /// For example, September 1, 2017 falls on a Friday, which in the calendar /// localized for United States English appears as: /// - /// ``` - /// S M T W T F S - /// _ _ _ _ _ 1 2 - /// ``` + /// S M T W T F S + /// _ _ _ _ _ 1 2 /// /// The offset for the first day of the months is the number of leading blanks /// in the calendar, i.e. 5. @@ -96,10 +96,8 @@ class DateUtils { /// The same date localized for the Russian calendar has a different offset, /// because the first day of week is Monday rather than Sunday: /// - /// ``` - /// M T W T F S S - /// _ _ _ _ 1 2 3 - /// ``` + /// M T W T F S S + /// _ _ _ _ 1 2 3 /// /// So the offset is 4, rather than 5. /// diff --git a/packages/flutter/lib/src/material/date_picker.dart b/packages/flutter/lib/src/material/date_picker.dart index 6f6b598959..40881cc81f 100644 --- a/packages/flutter/lib/src/material/date_picker.dart +++ b/packages/flutter/lib/src/material/date_picker.dart @@ -1954,19 +1954,18 @@ class _DayHeaders extends StatelessWidget { /// /// Examples: /// - /// ``` - /// ┌ Sunday is the first day of week in the US (en_US) - /// | - /// S M T W T F S <-- the returned list contains these widgets - /// _ _ _ _ _ 1 2 - /// 3 4 5 6 7 8 9 + /// ┌ Sunday is the first day of week in the US (en_US) + /// | + /// S M T W T F S ← the returned list contains these widgets + /// _ _ _ _ _ 1 2 + /// 3 4 5 6 7 8 9 + /// + /// ┌ But it's Monday in the UK (en_GB) + /// | + /// M T W T F S S ← the returned list contains these widgets + /// _ _ _ _ 1 2 3 + /// 4 5 6 7 8 9 10 /// - /// ┌ But it's Monday in the UK (en_GB) - /// | - /// M T W T F S S <-- the returned list contains these widgets - /// _ _ _ _ 1 2 3 - /// 4 5 6 7 8 9 10 - /// ``` List _getDayHeaders(TextStyle headerStyle, MaterialLocalizations localizations) { final List result = []; for (int i = localizations.firstDayOfWeekIndex; true; i = (i + 1) % 7) { diff --git a/packages/flutter/lib/src/material/debug.dart b/packages/flutter/lib/src/material/debug.dart index 8f2e97f5f6..25a8816d2a 100644 --- a/packages/flutter/lib/src/material/debug.dart +++ b/packages/flutter/lib/src/material/debug.dart @@ -8,6 +8,9 @@ import 'material.dart'; import 'material_localizations.dart'; import 'scaffold.dart' show Scaffold, ScaffoldMessenger; +// Examples can assume: +// late BuildContext context; + /// Asserts that the given context has a [Material] ancestor. /// /// Used by many Material Design widgets to make sure that they are diff --git a/packages/flutter/lib/src/material/divider_theme.dart b/packages/flutter/lib/src/material/divider_theme.dart index d7f377c06c..3b25c6710a 100644 --- a/packages/flutter/lib/src/material/divider_theme.dart +++ b/packages/flutter/lib/src/material/divider_theme.dart @@ -9,6 +9,9 @@ import 'package:flutter/widgets.dart'; import 'theme.dart'; +// Examples can assume: +// late BuildContext context; + /// Defines the visual properties of [Divider], [VerticalDivider], dividers /// between [ListTile]s, and dividers between rows in [DataTable]s. /// diff --git a/packages/flutter/lib/src/material/drawer.dart b/packages/flutter/lib/src/material/drawer.dart index fdce69027d..fcafbdd521 100644 --- a/packages/flutter/lib/src/material/drawer.dart +++ b/packages/flutter/lib/src/material/drawer.dart @@ -14,6 +14,9 @@ import 'material.dart'; import 'material_localizations.dart'; import 'theme.dart'; +// Examples can assume: +// late BuildContext context; + /// The possible alignments of a [Drawer]. enum DrawerAlignment { /// Denotes that the [Drawer] is at the start side of the [Scaffold]. @@ -117,8 +120,8 @@ const Duration _kBaseSettleDuration = Duration(milliseconds: 246); /// /// ```dart /// ListTile( -/// leading: Icon(Icons.change_history), -/// title: Text('Change history'), +/// leading: const Icon(Icons.change_history), +/// title: const Text('Change history'), /// onTap: () { /// // change app state... /// Navigator.pop(context); // close the drawer diff --git a/packages/flutter/lib/src/material/drawer_theme.dart b/packages/flutter/lib/src/material/drawer_theme.dart index d60e3343e7..1aabcae4aa 100644 --- a/packages/flutter/lib/src/material/drawer_theme.dart +++ b/packages/flutter/lib/src/material/drawer_theme.dart @@ -10,6 +10,9 @@ import 'package:flutter/widgets.dart'; import 'theme.dart'; +// Examples can assume: +// late BuildContext context; + /// Defines default property values for descendant [Drawer] widgets. /// /// Descendant widgets obtain the current [DrawerThemeData] object diff --git a/packages/flutter/lib/src/material/elevated_button.dart b/packages/flutter/lib/src/material/elevated_button.dart index 750c6b7e8e..399cbd0707 100644 --- a/packages/flutter/lib/src/material/elevated_button.dart +++ b/packages/flutter/lib/src/material/elevated_button.dart @@ -130,7 +130,11 @@ class ElevatedButton extends ButtonStyleButton { /// ```dart /// ElevatedButton( /// style: ElevatedButton.styleFrom(foregroundColor: Colors.green), - /// ) + /// onPressed: () { + /// // ... + /// }, + /// child: const Text('Jump'), + /// ), /// ``` /// /// And to change the fill color: @@ -138,7 +142,11 @@ class ElevatedButton extends ButtonStyleButton { /// ```dart /// ElevatedButton( /// style: ElevatedButton.styleFrom(backgroundColor: Colors.green), - /// ) + /// onPressed: () { + /// // ... + /// }, + /// child: const Text('Meow'), + /// ), /// ``` /// static ButtonStyle styleFrom({ diff --git a/packages/flutter/lib/src/material/elevated_button_theme.dart b/packages/flutter/lib/src/material/elevated_button_theme.dart index 6287b41cd8..dfac0f8220 100644 --- a/packages/flutter/lib/src/material/elevated_button_theme.dart +++ b/packages/flutter/lib/src/material/elevated_button_theme.dart @@ -8,6 +8,9 @@ import 'package:flutter/widgets.dart'; import 'button_style.dart'; import 'theme.dart'; +// Examples can assume: +// late BuildContext context; + /// A [ButtonStyle] that overrides the default appearance of /// [ElevatedButton]s when it's used with [ElevatedButtonTheme] or with the /// overall [Theme]'s [ThemeData.elevatedButtonTheme]. diff --git a/packages/flutter/lib/src/material/expansion_tile_theme.dart b/packages/flutter/lib/src/material/expansion_tile_theme.dart index e3b133a479..86c408b204 100644 --- a/packages/flutter/lib/src/material/expansion_tile_theme.dart +++ b/packages/flutter/lib/src/material/expansion_tile_theme.dart @@ -2,12 +2,14 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. - import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; import 'theme.dart'; +// Examples can assume: +// late BuildContext context; + /// Used with [ExpansionTileTheme] to define default property values for /// descendant [ExpansionTile] widgets. /// diff --git a/packages/flutter/lib/src/material/icon_button.dart b/packages/flutter/lib/src/material/icon_button.dart index a72c91083e..a1c8cbd0d0 100644 --- a/packages/flutter/lib/src/material/icon_button.dart +++ b/packages/flutter/lib/src/material/icon_button.dart @@ -23,6 +23,9 @@ import 'theme.dart'; import 'theme_data.dart'; import 'tooltip.dart'; +// Examples can assume: +// late BuildContext context; + // Minimum logical pixel size of the IconButton. // See: . const double _kMinButtonSize = kMinInteractiveDimension; @@ -61,16 +64,27 @@ const double _kMinButtonSize = kMinInteractiveDimension; /// /// When creating an icon button with an [Icon], do not override the /// icon's size with its [Icon.size] parameter, use the icon button's -/// [iconSize] parameter instead. For example do this: +/// [iconSize] parameter instead. For example do this: /// /// ```dart -/// IconButton(iconSize: 72, icon: Icon(Icons.favorite), ...) +/// IconButton( +/// iconSize: 72, +/// icon: const Icon(Icons.favorite), +/// onPressed: () { +/// // ... +/// }, +/// ), /// ``` /// /// Avoid doing this: /// /// ```dart -/// IconButton(icon: Icon(Icons.favorite, size: 72), ...) +/// IconButton( +/// icon: const Icon(Icons.favorite, size: 72), +/// onPressed: () { +/// // ... +/// }, +/// ), /// ``` /// /// If you do, the button's size will be based on the default icon @@ -257,6 +271,10 @@ class IconButton extends StatelessWidget { /// ```dart /// IconButton( /// focusColor: Colors.orange.withOpacity(0.3), + /// icon: const Icon(Icons.sunny), + /// onPressed: () { + /// // ... + /// }, /// ) /// ``` /// @@ -273,6 +291,10 @@ class IconButton extends StatelessWidget { /// ```dart /// IconButton( /// hoverColor: Colors.orange.withOpacity(0.3), + /// icon: const Icon(Icons.ac_unit), + /// onPressed: () { + /// // ... + /// }, /// ) /// ``` /// @@ -287,8 +309,10 @@ class IconButton extends StatelessWidget { /// ```dart /// IconButton( /// color: Colors.blue, - /// onPressed: _handleTap, - /// icon: Icon(Icons.widgets), + /// icon: const Icon(Icons.sunny_snowing), + /// onPressed: () { + /// // ... + /// }, /// ) /// ``` final Color? color; @@ -320,6 +344,10 @@ class IconButton extends StatelessWidget { /// ```dart /// IconButton( /// highlightColor: Colors.orange.withOpacity(0.3), + /// icon: const Icon(Icons.question_mark), + /// onPressed: () { + /// // ... + /// }, /// ) /// ``` /// @@ -459,8 +487,12 @@ class IconButton extends StatelessWidget { /// /// ```dart /// IconButton( + /// icon: const Icon(Icons.pets), /// style: IconButton.styleFrom(foregroundColor: Colors.green), - /// ) + /// onPressed: () { + /// // ... + /// }, + /// ), /// ``` static ButtonStyle styleFrom({ Color? foregroundColor, diff --git a/packages/flutter/lib/src/material/icon_button_theme.dart b/packages/flutter/lib/src/material/icon_button_theme.dart index 5480b37c64..e57f7be842 100644 --- a/packages/flutter/lib/src/material/icon_button_theme.dart +++ b/packages/flutter/lib/src/material/icon_button_theme.dart @@ -8,6 +8,9 @@ import 'package:flutter/widgets.dart'; import 'button_style.dart'; import 'theme.dart'; +// Examples can assume: +// late BuildContext context; + /// A [ButtonStyle] that overrides the default appearance of /// [IconButton]s when it's used with the [IconButton], the [IconButtonTheme] or the /// overall [Theme]'s [ThemeData.iconButtonTheme]. diff --git a/packages/flutter/lib/src/material/ink_well.dart b/packages/flutter/lib/src/material/ink_well.dart index 1f93c8600d..1fee93405f 100644 --- a/packages/flutter/lib/src/material/ink_well.dart +++ b/packages/flutter/lib/src/material/ink_well.dart @@ -16,6 +16,9 @@ import 'material.dart'; import 'material_state.dart'; import 'theme.dart'; +// Examples can assume: +// late BuildContext context; + /// An ink feature that displays a [color] "splash" in response to a user /// gesture that can be confirmed or canceled. /// diff --git a/packages/flutter/lib/src/material/input_decorator.dart b/packages/flutter/lib/src/material/input_decorator.dart index f731f350fa..48def3297f 100644 --- a/packages/flutter/lib/src/material/input_decorator.dart +++ b/packages/flutter/lib/src/material/input_decorator.dart @@ -19,6 +19,9 @@ import 'text_theme.dart'; import 'theme.dart'; import 'theme_data.dart'; +// Examples can assume: +// late Widget _myIcon; + const Duration _kTransitionDuration = Duration(milliseconds: 200); const Curve _kTransitionCurve = Curves.fastOutSlowIn; const double _kFinalLabelScale = 0.75; @@ -2877,7 +2880,7 @@ class InputDecoration { /// ```dart /// prefixIcon: Padding( /// padding: const EdgeInsetsDirectional.only(start: 12.0), - /// child: myIcon, // myIcon is a 48px-wide widget. + /// child: _myIcon, // _myIcon is a 48px-wide widget. /// ) /// ``` /// @@ -2999,7 +3002,7 @@ class InputDecoration { /// ```dart /// suffixIcon: Padding( /// padding: const EdgeInsetsDirectional.only(end: 12.0), - /// child: myIcon, // myIcon is a 48px-wide widget. + /// child: _myIcon, // myIcon is a 48px-wide widget. /// ) /// ``` /// diff --git a/packages/flutter/lib/src/material/list_tile.dart b/packages/flutter/lib/src/material/list_tile.dart index 90664d4dfe..dc20750765 100644 --- a/packages/flutter/lib/src/material/list_tile.dart +++ b/packages/flutter/lib/src/material/list_tile.dart @@ -18,6 +18,9 @@ import 'material_state.dart'; import 'theme.dart'; import 'theme_data.dart'; +// Examples can assume: +// int _act = 1; + /// Defines the title font used for [ListTile] descendants of a [ListTileTheme]. /// /// List tiles that appear in a [Drawer] use the theme's [TextTheme.bodyText1] @@ -166,8 +169,6 @@ enum ListTileControlAffinity { /// tapped, the whole row has an ink splash effect (see [InkWell]). /// /// ```dart -/// int _act = 1; -/// // ... /// ListTile( /// leading: const Icon(Icons.flight_land), /// title: const Text("Trix's airplane"), diff --git a/packages/flutter/lib/src/material/list_tile_theme.dart b/packages/flutter/lib/src/material/list_tile_theme.dart index 7c15d6695d..501a608dd5 100644 --- a/packages/flutter/lib/src/material/list_tile_theme.dart +++ b/packages/flutter/lib/src/material/list_tile_theme.dart @@ -13,6 +13,9 @@ import 'material_state.dart'; import 'theme.dart'; import 'theme_data.dart'; +// Examples can assume: +// late BuildContext context; + /// Used with [ListTileTheme] to define default property values for /// descendant [ListTile] widgets, as well as classes that build /// [ListTile]s, like [CheckboxListTile], [RadioListTile], and diff --git a/packages/flutter/lib/src/material/material.dart b/packages/flutter/lib/src/material/material.dart index 284471b8ee..15b1ce3c80 100644 --- a/packages/flutter/lib/src/material/material.dart +++ b/packages/flutter/lib/src/material/material.dart @@ -10,6 +10,9 @@ import 'constants.dart'; import 'elevation_overlay.dart'; import 'theme.dart'; +// Examples can assume: +// late BuildContext context; + /// Signature for the callback used by ink effects to obtain the rectangle for the effect. /// /// Used by [InkHighlight] and [InkSplash], for example. @@ -345,7 +348,7 @@ class Material extends StatefulWidget { /// Typical usage is as follows: /// /// ```dart - /// MaterialInkController inkController = Material.of(context); + /// MaterialInkController? inkController = Material.of(context); /// ``` /// /// This method can be expensive (it walks the element tree). diff --git a/packages/flutter/lib/src/material/material_localizations.dart b/packages/flutter/lib/src/material/material_localizations.dart index cd04b28d92..497c664b42 100644 --- a/packages/flutter/lib/src/material/material_localizations.dart +++ b/packages/flutter/lib/src/material/material_localizations.dart @@ -9,6 +9,9 @@ import 'debug.dart'; import 'time.dart'; import 'typography.dart'; +// Examples can assume: +// late BuildContext context; + // ADDING A NEW STRING // // If you (someone contributing to the Flutter framework) want to add a new @@ -28,16 +31,15 @@ import 'typography.dart'; // // Then you need to add new entries for the string to all of the other // language locale files by running: -// ``` -// dart dev/tools/localization/bin/gen_missing_localizations.dart -// ``` +// +// dart dev/tools/localization/bin/gen_missing_localizations.dart +// // Which will copy the english strings into the other locales as placeholders // until they can be translated. // // Finally you need to re-generate lib/src/l10n/localizations.dart by running: -// ``` -// dart dev/tools/localization/bin/gen_localizations.dart --overwrite -// ``` +// +// dart dev/tools/localization/bin/gen_localizations.dart --overwrite // // There is a README file with further information in the lib/src/l10n/ // directory. @@ -57,9 +59,8 @@ import 'typography.dart'; // strings in lib/src/l10n/material_en.arb. // // You also need to re-generate lib/src/l10n/localizations.dart by running: -// ``` -// dart dev/tools/localization/bin/gen_localizations.dart --overwrite -// ``` +// +// dart dev/tools/localization/bin/gen_localizations.dart --overwrite // // This script may result in your updated getters being created in newer // locales and set to the old value of the strings. This is to be expected. @@ -348,9 +349,9 @@ abstract class MaterialLocalizations { /// This getter is compatible with [narrowWeekdays]. For example: /// /// ```dart - /// var localizations = MaterialLocalizations.of(context); + /// MaterialLocalizations localizations = MaterialLocalizations.of(context); /// // The name of the first day of week for the current locale. - /// var firstDayOfWeek = localizations.narrowWeekdays[localizations.firstDayOfWeekIndex]; + /// String firstDayOfWeek = localizations.narrowWeekdays[localizations.firstDayOfWeekIndex]; /// ``` int get firstDayOfWeekIndex; diff --git a/packages/flutter/lib/src/material/material_state.dart b/packages/flutter/lib/src/material/material_state.dart index 503cd574e9..29f9425da0 100644 --- a/packages/flutter/lib/src/material/material_state.dart +++ b/packages/flutter/lib/src/material/material_state.dart @@ -8,6 +8,9 @@ import 'package:flutter/services.dart'; import 'input_border.dart'; +// Examples can assume: +// late BuildContext context; + /// Interactive states that some of the Material widgets can take on when /// receiving input from the user. /// @@ -321,30 +324,35 @@ abstract class MaterialStateBorderSide extends BorderSide implements MaterialSta /// (the empty set of states) will be used. /// /// Usage: + /// /// ```dart /// ChipTheme( /// data: Theme.of(context).chipTheme.copyWith( /// side: MaterialStateBorderSide.resolveWith((Set states) { /// if (states.contains(MaterialState.selected)) { - /// return const BorderSide(width: 1, color: Colors.red); + /// return const BorderSide(color: Colors.red); /// } /// return null; // Defer to default value on the theme or widget. /// }), /// ), - /// child: Chip(), - /// ) + /// child: const Chip( + /// label: Text('Transceiver'), + /// ), + /// ), + /// ``` /// - /// // OR + /// Alternatively: /// + /// ```dart /// Chip( - /// ... + /// label: const Text('Transceiver'), /// side: MaterialStateBorderSide.resolveWith((Set states) { /// if (states.contains(MaterialState.selected)) { - /// return const BorderSide(width: 1, color: Colors.red); + /// return const BorderSide(color: Colors.red); /// } /// return null; // Defer to default value on the theme or widget. /// }), - /// ) + /// ), /// ``` static MaterialStateBorderSide resolveWith(MaterialPropertyResolver callback) => _MaterialStateBorderSide(callback); diff --git a/packages/flutter/lib/src/material/navigation_bar.dart b/packages/flutter/lib/src/material/navigation_bar.dart index c76e17314a..85cc7e2c36 100644 --- a/packages/flutter/lib/src/material/navigation_bar.dart +++ b/packages/flutter/lib/src/material/navigation_bar.dart @@ -17,6 +17,10 @@ import 'text_theme.dart'; import 'theme.dart'; import 'tooltip.dart'; +// Examples can assume: +// late BuildContext context; +// late bool _isDrawerOpen; + /// Material 3 Navigation Bar component. /// /// Navigation bars offer a persistent and convenient way to switch between @@ -444,12 +448,25 @@ class _NavigationDestinationInfo extends InheritedWidget { /// Which destination index is this in the navigation bar. /// /// For example: + /// /// ```dart /// NavigationBar( - /// destinations: [ - /// NavigationDestination(), // This is destination index 0. - /// NavigationDestination(), // This is destination index 1. - /// NavigationDestination(), // This is destination index 2. + /// destinations: const [ + /// NavigationDestination( + /// // This is destination index 0. + /// icon: Icon(Icons.surfing), + /// label: 'Surfing', + /// ), + /// NavigationDestination( + /// // This is destination index 1. + /// icon: Icon(Icons.support), + /// label: 'Support', + /// ), + /// NavigationDestination( + /// // This is destination index 2. + /// icon: Icon(Icons.local_hospital), + /// label: 'Hospital', + /// ), /// ] /// ) /// ``` @@ -888,17 +905,18 @@ class _NavigationDestinationLayoutDelegate extends MultiChildLayoutDelegate { /// Utility Widgets -/// Clamps [MediaQueryData.textScaleFactor] so that if it is greater than -/// [upperLimit] or less than [lowerLimit], [upperLimit] or [lowerLimit] will be -/// used instead for the [child] widget. -/// -/// Example: -/// ``` -/// _ClampTextScaleFactor( -/// upperLimit: 2.0, -/// child: Text('Foo'), // If textScaleFactor is 3.0, this will only scale 2x. -/// ) -/// ``` +// Clamps [MediaQueryData.textScaleFactor] so that if it is greater than +// [upperLimit] or less than [lowerLimit], [upperLimit] or [lowerLimit] will be +// used instead for the [child] widget. +// +// Example: +// +// ```dart +// _ClampTextScaleFactor( +// upperLimit: 2.0, +// child: const Text('Foo'), // If textScaleFactor is 3.0, this will only scale 2x. +// ) +// ``` class _ClampTextScaleFactor extends StatelessWidget { /// Clamps the text scale factor of descendants by modifying the [MediaQuery] /// surrounding [child]. @@ -965,36 +983,36 @@ class _StatusTransitionWidgetBuilder extends StatusTransitionWidget { Widget build(BuildContext context) => builder(context, child); } -/// Builder widget for widgets that need to be animated from 0 (unselected) to -/// 1.0 (selected). -/// -/// This widget creates and manages an [AnimationController] that it passes down -/// to the child through the [builder] function. -/// -/// When [isSelected] is `true`, the animation controller will animate from -/// 0 to 1 (for [duration] time). -/// -/// When [isSelected] is `false`, the animation controller will animate from -/// 1 to 0 (for [duration] time). -/// -/// If [isSelected] is updated while the widget is animating, the animation will -/// be reversed until it is either 0 or 1 again. If [alwaysDoFullAnimation] is -/// true, the animation will reset to 0 or 1 before beginning the animation, so -/// that the full animation is done. -/// -/// Usage: -/// ```dart -/// _SelectableAnimatedBuilder( -/// isSelected: _isDrawerOpen, -/// builder: (context, animation) { -/// return AnimatedIcon( -/// icon: AnimatedIcons.menu_arrow, -/// progress: animation, -/// semanticLabel: 'Show menu', -/// ); -/// } -/// ) -/// ``` +// Builder widget for widgets that need to be animated from 0 (unselected) to +// 1.0 (selected). +// +// This widget creates and manages an [AnimationController] that it passes down +// to the child through the [builder] function. +// +// When [isSelected] is `true`, the animation controller will animate from +// 0 to 1 (for [duration] time). +// +// When [isSelected] is `false`, the animation controller will animate from +// 1 to 0 (for [duration] time). +// +// If [isSelected] is updated while the widget is animating, the animation will +// be reversed until it is either 0 or 1 again. If [alwaysDoFullAnimation] is +// true, the animation will reset to 0 or 1 before beginning the animation, so +// that the full animation is done. +// +// Usage: +// ```dart +// _SelectableAnimatedBuilder( +// isSelected: _isDrawerOpen, +// builder: (context, animation) { +// return AnimatedIcon( +// icon: AnimatedIcons.menu_arrow, +// progress: animation, +// semanticLabel: 'Show menu', +// ); +// } +// ) +// ``` class _SelectableAnimatedBuilder extends StatefulWidget { /// Builds and maintains an [AnimationController] that will animate from 0 to /// 1 and back depending on when [isSelected] is true. diff --git a/packages/flutter/lib/src/material/navigation_bar_theme.dart b/packages/flutter/lib/src/material/navigation_bar_theme.dart index 94e4c561ec..140144dcbc 100644 --- a/packages/flutter/lib/src/material/navigation_bar_theme.dart +++ b/packages/flutter/lib/src/material/navigation_bar_theme.dart @@ -12,6 +12,9 @@ import 'material_state.dart'; import 'navigation_bar.dart'; import 'theme.dart'; +// Examples can assume: +// late BuildContext context; + /// Defines default property values for descendant [NavigationBar] /// widgets. /// diff --git a/packages/flutter/lib/src/material/navigation_rail_theme.dart b/packages/flutter/lib/src/material/navigation_rail_theme.dart index dd5de72ac5..45e7514c0b 100644 --- a/packages/flutter/lib/src/material/navigation_rail_theme.dart +++ b/packages/flutter/lib/src/material/navigation_rail_theme.dart @@ -11,6 +11,9 @@ import 'package:flutter/widgets.dart'; import 'navigation_rail.dart'; import 'theme.dart'; +// Examples can assume: +// late BuildContext context; + /// Defines default property values for descendant [NavigationRail] /// widgets. /// diff --git a/packages/flutter/lib/src/material/no_splash.dart b/packages/flutter/lib/src/material/no_splash.dart index 106086743f..561d1f0d3d 100644 --- a/packages/flutter/lib/src/material/no_splash.dart +++ b/packages/flutter/lib/src/material/no_splash.dart @@ -38,13 +38,14 @@ class _NoSplashFactory extends InteractiveInkFeatureFactory { /// Use [NoSplash.splashFactory] to defeat the default ink splash drawn by /// an [InkWell] or [ButtonStyle]. For example, to create an [ElevatedButton] /// that does not draw the default "ripple" ink splash when it's tapped: +/// /// ```dart /// ElevatedButton( /// style: ElevatedButton.styleFrom( /// splashFactory: NoSplash.splashFactory, /// ), /// onPressed: () { }, -/// child: Text('No Splash'), +/// child: const Text('No Splash'), /// ) /// ``` class NoSplash extends InteractiveInkFeature { diff --git a/packages/flutter/lib/src/material/outlined_button.dart b/packages/flutter/lib/src/material/outlined_button.dart index 4bf4e3fb7d..5d95f6e117 100644 --- a/packages/flutter/lib/src/material/outlined_button.dart +++ b/packages/flutter/lib/src/material/outlined_button.dart @@ -127,10 +127,14 @@ class OutlinedButton extends ButtonStyleButton { /// ```dart /// OutlinedButton( /// style: OutlinedButton.styleFrom( - /// shape: StadiumBorder(), - /// side: BorderSide(width: 2, color: Colors.green), + /// shape: const StadiumBorder(), + /// side: const BorderSide(width: 2, color: Colors.green), /// ), - /// ) + /// child: const Text('Seasons of Love'), + /// onPressed: () { + /// // ... + /// }, + /// ), /// ``` static ButtonStyle styleFrom({ Color? foregroundColor, diff --git a/packages/flutter/lib/src/material/outlined_button_theme.dart b/packages/flutter/lib/src/material/outlined_button_theme.dart index 20eb53f6a6..1d29b8622d 100644 --- a/packages/flutter/lib/src/material/outlined_button_theme.dart +++ b/packages/flutter/lib/src/material/outlined_button_theme.dart @@ -8,6 +8,9 @@ import 'package:flutter/widgets.dart'; import 'button_style.dart'; import 'theme.dart'; +// Examples can assume: +// late BuildContext context; + /// A [ButtonStyle] that overrides the default appearance of /// [OutlinedButton]s when it's used with [OutlinedButtonTheme] or with the /// overall [Theme]'s [ThemeData.outlinedButtonTheme]. diff --git a/packages/flutter/lib/src/material/popup_menu_theme.dart b/packages/flutter/lib/src/material/popup_menu_theme.dart index 65142052b8..4479def0dd 100644 --- a/packages/flutter/lib/src/material/popup_menu_theme.dart +++ b/packages/flutter/lib/src/material/popup_menu_theme.dart @@ -10,6 +10,9 @@ import 'package:flutter/widgets.dart'; import 'material_state.dart'; import 'theme.dart'; +// Examples can assume: +// late BuildContext context; + /// Defines the visual properties of the routes used to display popup menus /// as well as [PopupMenuItem] and [PopupMenuDivider] widgets. /// diff --git a/packages/flutter/lib/src/material/progress_indicator_theme.dart b/packages/flutter/lib/src/material/progress_indicator_theme.dart index 946f519c48..eb415dc8f6 100644 --- a/packages/flutter/lib/src/material/progress_indicator_theme.dart +++ b/packages/flutter/lib/src/material/progress_indicator_theme.dart @@ -9,6 +9,9 @@ import 'package:flutter/widgets.dart'; import 'theme.dart'; +// Examples can assume: +// late BuildContext context; + @immutable /// Defines the visual properties of [ProgressIndicator] widgets. /// diff --git a/packages/flutter/lib/src/material/radio.dart b/packages/flutter/lib/src/material/radio.dart index 128688daee..389058da8d 100644 --- a/packages/flutter/lib/src/material/radio.dart +++ b/packages/flutter/lib/src/material/radio.dart @@ -12,6 +12,12 @@ import 'theme.dart'; import 'theme_data.dart'; import 'toggleable.dart'; +// Examples can assume: +// late BuildContext context; +// enum SingingCharacter { lafayette } +// late SingingCharacter? _character; +// late StateSetter setState; + const double _kOuterRadius = 8.0; const double _kInnerRadius = 4.5; @@ -116,7 +122,7 @@ class Radio extends StatefulWidget { /// Radio( /// value: SingingCharacter.lafayette, /// groupValue: _character, - /// onChanged: (SingingCharacter newValue) { + /// onChanged: (SingingCharacter? newValue) { /// setState(() { /// _character = newValue; /// }); diff --git a/packages/flutter/lib/src/material/radio_list_tile.dart b/packages/flutter/lib/src/material/radio_list_tile.dart index 6c64e1ba52..224438fb2e 100644 --- a/packages/flutter/lib/src/material/radio_list_tile.dart +++ b/packages/flutter/lib/src/material/radio_list_tile.dart @@ -15,6 +15,8 @@ import 'theme_data.dart'; // Examples can assume: // void setState(VoidCallback fn) { } // enum Meridiem { am, pm } +// enum SingingCharacter { lafayette } +// late SingingCharacter? _character; /// A [ListTile] with a [Radio]. In other words, a radio button with a label. /// @@ -206,7 +208,7 @@ class RadioListTile extends StatelessWidget { /// title: const Text('Lafayette'), /// value: SingingCharacter.lafayette, /// groupValue: _character, - /// onChanged: (SingingCharacter newValue) { + /// onChanged: (SingingCharacter? newValue) { /// setState(() { /// _character = newValue; /// }); diff --git a/packages/flutter/lib/src/material/radio_theme.dart b/packages/flutter/lib/src/material/radio_theme.dart index 8631525743..b1530d16dc 100644 --- a/packages/flutter/lib/src/material/radio_theme.dart +++ b/packages/flutter/lib/src/material/radio_theme.dart @@ -12,6 +12,9 @@ import 'material_state.dart'; import 'theme.dart'; import 'theme_data.dart'; +// Examples can assume: +// late BuildContext context; + /// Defines default property values for descendant [Radio] widgets. /// /// Descendant widgets obtain the current [RadioThemeData] object using diff --git a/packages/flutter/lib/src/material/refresh_indicator.dart b/packages/flutter/lib/src/material/refresh_indicator.dart index 906fd024be..8f445b2688 100644 --- a/packages/flutter/lib/src/material/refresh_indicator.dart +++ b/packages/flutter/lib/src/material/refresh_indicator.dart @@ -98,7 +98,7 @@ enum RefreshIndicatorTriggerMode { /// ```dart /// ListView( /// physics: const AlwaysScrollableScrollPhysics(), -/// children: ... +/// // ... /// ) /// ``` /// diff --git a/packages/flutter/lib/src/material/scrollbar_theme.dart b/packages/flutter/lib/src/material/scrollbar_theme.dart index d1bece8e4b..38574e2c8d 100644 --- a/packages/flutter/lib/src/material/scrollbar_theme.dart +++ b/packages/flutter/lib/src/material/scrollbar_theme.dart @@ -10,6 +10,9 @@ import 'package:flutter/widgets.dart'; import 'material_state.dart'; import 'theme.dart'; +// Examples can assume: +// late BuildContext context; + /// Defines default property values for descendant [Scrollbar] widgets. /// /// Descendant widgets obtain the current [ScrollbarThemeData] object with diff --git a/packages/flutter/lib/src/material/selectable_text.dart b/packages/flutter/lib/src/material/selectable_text.dart index b5aecadf56..f2ea90cca9 100644 --- a/packages/flutter/lib/src/material/selectable_text.dart +++ b/packages/flutter/lib/src/material/selectable_text.dart @@ -15,6 +15,10 @@ import 'magnifier.dart'; import 'text_selection.dart'; import 'theme.dart'; +// Examples can assume: +// late BuildContext context; +// late FocusNode myFocusNode; + /// An eyeballed value that moves the cursor slightly left of where it is /// rendered for text on Android so its positioning more accurately matches the /// native iOS text cursor positioning. @@ -313,7 +317,7 @@ class SelectableText extends StatefulWidget { /// to the [focusNode]: /// /// ```dart - /// focusNode.addListener(() { print(myFocusNode.hasFocus); }); + /// myFocusNode.addListener(() { print(myFocusNode.hasFocus); }); /// ``` /// /// If null, this widget will create its own [FocusNode] with diff --git a/packages/flutter/lib/src/material/snack_bar.dart b/packages/flutter/lib/src/material/snack_bar.dart index 5cc3c49d1a..2a73a78f1f 100644 --- a/packages/flutter/lib/src/material/snack_bar.dart +++ b/packages/flutter/lib/src/material/snack_bar.dart @@ -15,6 +15,9 @@ import 'text_button.dart'; import 'text_button_theme.dart'; import 'theme.dart'; +// Examples can assume: +// late BuildContext context; + const double _singleLineVerticalPadding = 14.0; // TODO(ianh): We should check if the given text and actions are going to fit on @@ -40,9 +43,11 @@ const Curve _snackBarFadeOutCurve = Interval(0.72, 1.0, curve: Curves.fastOutSlo /// /// ```dart /// ScaffoldMessenger.of(context).showSnackBar( -/// SnackBar( ... ) +/// const SnackBar( +/// content: Text('He likes me. I think he likes me.'), +/// ) /// ).closed.then((SnackBarClosedReason reason) { -/// ... +/// // ... /// }); /// ``` enum SnackBarClosedReason { @@ -256,29 +261,29 @@ class SnackBar extends StatefulWidget { /// The amount of padding to apply to the snack bar's content and optional /// action. /// - /// If this property is null, the default padding values for: + /// If this property is null, the default padding values are as follows: /// /// * [content] /// * Top and bottom paddings are 14. /// * Left padding is 24 if [behavior] is [SnackBarBehavior.fixed], - /// 16 if [behavior] is [SnackBarBehavior.floating] - /// * Right padding is same as start padding if there is no [action], otherwise 0. + /// 16 if [behavior] is [SnackBarBehavior.floating]. + /// * Right padding is same as start padding if there is no [action], + /// otherwise 0. /// * [action] - /// * Top and bottom paddings are 14 + /// * Top and bottom paddings are 14. /// * Left and right paddings are half of [content]'s left padding. /// - /// If this property is not null, the padding assignment for: + /// If this property is not null, the padding is as follows: /// /// * [content] /// * Left, top and bottom paddings are assigned normally. - /// * Right padding is assigned normally if there is no [action], otherwise 0. + /// * Right padding is assigned normally if there is no [action], + /// otherwise 0. /// * [action] - /// * Left padding is replaced with half value of right padding. + /// * Left padding is replaced with half the right padding. /// * Top and bottom paddings are assigned normally. - /// * Right padding has an additional half value of right padding. - /// ```dart - /// right + (right / 2) - /// ``` + /// * Right padding is replaced with one and a half times the + /// right padding. final EdgeInsetsGeometry? padding; /// The width of the snack bar. diff --git a/packages/flutter/lib/src/material/switch.dart b/packages/flutter/lib/src/material/switch.dart index 7732a30e33..e5204356dc 100644 --- a/packages/flutter/lib/src/material/switch.dart +++ b/packages/flutter/lib/src/material/switch.dart @@ -17,6 +17,10 @@ import 'theme.dart'; import 'theme_data.dart'; import 'toggleable.dart'; +// Examples can assume: +// bool _giveVerse = true; +// late StateSetter setState; + const double _kTrackHeight = 14.0; const double _kTrackWidth = 33.0; const double _kTrackRadius = _kTrackHeight / 2.0; diff --git a/packages/flutter/lib/src/material/switch_theme.dart b/packages/flutter/lib/src/material/switch_theme.dart index 3ac3b45018..4660ceb12c 100644 --- a/packages/flutter/lib/src/material/switch_theme.dart +++ b/packages/flutter/lib/src/material/switch_theme.dart @@ -12,6 +12,9 @@ import 'material_state.dart'; import 'theme.dart'; import 'theme_data.dart'; +// Examples can assume: +// late BuildContext context; + /// Defines default property values for descendant [Switch] widgets. /// /// Descendant widgets obtain the current [SwitchThemeData] object using diff --git a/packages/flutter/lib/src/material/tab_controller.dart b/packages/flutter/lib/src/material/tab_controller.dart index ff196c868e..b9d9f5a1c4 100644 --- a/packages/flutter/lib/src/material/tab_controller.dart +++ b/packages/flutter/lib/src/material/tab_controller.dart @@ -302,7 +302,9 @@ class _TabControllerScope extends InheritedWidget { /// /// ```dart /// class MyDemo extends StatelessWidget { -/// final List myTabs = [ +/// const MyDemo({super.key}); +/// +/// static const List myTabs = [ /// Tab(text: 'LEFT'), /// Tab(text: 'RIGHT'), /// ]; @@ -313,13 +315,13 @@ class _TabControllerScope extends InheritedWidget { /// length: myTabs.length, /// child: Scaffold( /// appBar: AppBar( -/// bottom: TabBar( +/// bottom: const TabBar( /// tabs: myTabs, /// ), /// ), /// body: TabBarView( /// children: myTabs.map((Tab tab) { -/// final String label = tab.text.toLowerCase(); +/// final String label = tab.text!.toLowerCase(); /// return Center( /// child: Text( /// 'This is the $label tab', diff --git a/packages/flutter/lib/src/material/tabs.dart b/packages/flutter/lib/src/material/tabs.dart index d1f16219cd..39b1e8d7dc 100644 --- a/packages/flutter/lib/src/material/tabs.dart +++ b/packages/flutter/lib/src/material/tabs.dart @@ -843,6 +843,7 @@ class TabBar extends StatefulWidget implements PreferredSizeWidget { /// Use [NoSplash.splashFactory] to defeat ink splash rendering. For example /// to defeat both the splash and the hover/pressed overlay, but not the /// keyboard focused overlay: + /// /// ```dart /// TabBar( /// splashFactory: NoSplash.splashFactory, @@ -851,7 +852,9 @@ class TabBar extends StatefulWidget implements PreferredSizeWidget { /// return states.contains(MaterialState.focused) ? null : Colors.transparent; /// }, /// ), - /// ... + /// tabs: const [ + /// // ... + /// ], /// ) /// ``` final InteractiveInkFeatureFactory? splashFactory; @@ -859,13 +862,16 @@ class TabBar extends StatefulWidget implements PreferredSizeWidget { /// Defines the clipping radius of splashes that extend outside the bounds of the tab. /// /// This can be useful to match the [BoxDecoration.borderRadius] provided as [indicator]. + /// /// ```dart /// TabBar( /// indicator: BoxDecoration( /// borderRadius: BorderRadius.circular(40), /// ), /// splashBorderRadius: BorderRadius.circular(40), - /// ... + /// tabs: const [ + /// // ... + /// ], /// ) /// ``` /// diff --git a/packages/flutter/lib/src/material/text_button.dart b/packages/flutter/lib/src/material/text_button.dart index cb82b5ff28..d2de36ac24 100644 --- a/packages/flutter/lib/src/material/text_button.dart +++ b/packages/flutter/lib/src/material/text_button.dart @@ -137,7 +137,11 @@ class TextButton extends ButtonStyleButton { /// ```dart /// TextButton( /// style: TextButton.styleFrom(foregroundColor: Colors.green), - /// ) + /// child: const Text('Give Kate a mix tape'), + /// onPressed: () { + /// // ... + /// }, + /// ), /// ``` static ButtonStyle styleFrom({ Color? foregroundColor, diff --git a/packages/flutter/lib/src/material/text_button_theme.dart b/packages/flutter/lib/src/material/text_button_theme.dart index 798f31000f..52968a6c5e 100644 --- a/packages/flutter/lib/src/material/text_button_theme.dart +++ b/packages/flutter/lib/src/material/text_button_theme.dart @@ -8,6 +8,9 @@ import 'package:flutter/widgets.dart'; import 'button_style.dart'; import 'theme.dart'; +// Examples can assume: +// late BuildContext context; + /// A [ButtonStyle] that overrides the default appearance of /// [TextButton]s when it's used with [TextButtonTheme] or with the /// overall [Theme]'s [ThemeData.textButtonTheme]. diff --git a/packages/flutter/lib/src/material/text_field.dart b/packages/flutter/lib/src/material/text_field.dart index 0d86eebde5..5f21bba04d 100644 --- a/packages/flutter/lib/src/material/text_field.dart +++ b/packages/flutter/lib/src/material/text_field.dart @@ -23,6 +23,10 @@ import 'theme.dart'; export 'package:flutter/services.dart' show SmartDashesType, SmartQuotesType, TextCapitalization, TextInputAction, TextInputType; +// Examples can assume: +// late BuildContext context; +// late FocusNode myFocusNode; + /// Signature for the [TextField.buildCounter] callback. typedef InputCounterWidgetBuilder = Widget? Function( /// The build context for the TextField. @@ -427,7 +431,7 @@ class TextField extends StatefulWidget { /// to the [focusNode]: /// /// ```dart - /// focusNode.addListener(() { print(myFocusNode.hasFocus); }); + /// myFocusNode.addListener(() { print(myFocusNode.hasFocus); }); /// ``` /// /// If null, this widget will create its own [FocusNode]. diff --git a/packages/flutter/lib/src/material/text_selection_theme.dart b/packages/flutter/lib/src/material/text_selection_theme.dart index dc65e1f032..0a9408897e 100644 --- a/packages/flutter/lib/src/material/text_selection_theme.dart +++ b/packages/flutter/lib/src/material/text_selection_theme.dart @@ -7,6 +7,9 @@ import 'package:flutter/widgets.dart'; import 'theme.dart'; +// Examples can assume: +// late BuildContext context; + /// Defines the visual properties needed for text selection in [TextField] and /// [SelectableText] widgets. /// diff --git a/packages/flutter/lib/src/material/text_theme.dart b/packages/flutter/lib/src/material/text_theme.dart index 8fe6336311..b62c2f9d95 100644 --- a/packages/flutter/lib/src/material/text_theme.dart +++ b/packages/flutter/lib/src/material/text_theme.dart @@ -32,22 +32,22 @@ import 'typography.dart'; /// bodyText1 and bodyText2. /// /// The 2018 spec has thirteen text styles: -/// ``` -/// NAME SIZE WEIGHT SPACING -/// headline1 96.0 light -1.5 -/// headline2 60.0 light -0.5 -/// headline3 48.0 regular 0.0 -/// headline4 34.0 regular 0.25 -/// headline5 24.0 regular 0.0 -/// headline6 20.0 medium 0.15 -/// subtitle1 16.0 regular 0.15 -/// subtitle2 14.0 medium 0.1 -/// body1 16.0 regular 0.5 (bodyText1) -/// body2 14.0 regular 0.25 (bodyText2) -/// button 14.0 medium 1.25 -/// caption 12.0 regular 0.4 -/// overline 10.0 regular 1.5 -/// ``` +/// +/// | NAME | SIZE | WEIGHT | SPACING | | +/// |------------|------|---------|----------|-------------| +/// | headline1 | 96.0 | light | -1.5 | | +/// | headline2 | 60.0 | light | -0.5 | | +/// | headline3 | 48.0 | regular | 0.0 | | +/// | headline4 | 34.0 | regular | 0.25 | | +/// | headline5 | 24.0 | regular | 0.0 | | +/// | headline6 | 20.0 | medium | 0.15 | | +/// | subtitle1 | 16.0 | regular | 0.15 | | +/// | subtitle2 | 14.0 | medium | 0.1 | | +/// | body1 | 16.0 | regular | 0.5 | (bodyText1) | +/// | body2 | 14.0 | regular | 0.25 | (bodyText2) | +/// | button | 14.0 | medium | 1.25 | | +/// | caption | 12.0 | regular | 0.4 | | +/// | overline | 10.0 | regular | 1.5 | | /// /// ...where "light" is `FontWeight.w300`, "regular" is `FontWeight.w400` and /// "medium" is `FontWeight.w500`. diff --git a/packages/flutter/lib/src/material/theme.dart b/packages/flutter/lib/src/material/theme.dart index dbbc616e7d..ee27565303 100644 --- a/packages/flutter/lib/src/material/theme.dart +++ b/packages/flutter/lib/src/material/theme.dart @@ -89,7 +89,7 @@ class Theme extends StatelessWidget { /// Widget build(BuildContext context) { /// return MaterialApp( /// theme: ThemeData.light(), - /// body: Builder( + /// home: Builder( /// // Create an inner BuildContext so that we can refer to /// // the Theme with Theme.of(). /// builder: (BuildContext context) { diff --git a/packages/flutter/lib/src/material/theme_data.dart b/packages/flutter/lib/src/material/theme_data.dart index cdb9934476..c7c0c75249 100644 --- a/packages/flutter/lib/src/material/theme_data.dart +++ b/packages/flutter/lib/src/material/theme_data.dart @@ -57,6 +57,9 @@ import 'typography.dart'; export 'package:flutter/services.dart' show Brightness; +// Examples can assume: +// late BuildContext context; + /// An interface that defines custom additions to a [ThemeData] object. /// /// Typically used for custom colors. To use, subclass [ThemeExtension], @@ -1576,9 +1579,10 @@ class ThemeData with Diagnosticable { /// /// ```dart /// final ThemeData theme = Theme.of(context); - /// theme.textTheme.headline1.copyWith( + /// final TextStyle style = theme.textTheme.headline1!.copyWith( /// color: theme.colorScheme.onSecondary, - /// ) + /// ); + /// // ...use style... /// ``` @Deprecated( 'No longer used by the framework, please remove any reference to it. ' diff --git a/packages/flutter/lib/src/material/time_picker_theme.dart b/packages/flutter/lib/src/material/time_picker_theme.dart index 30afbdd747..b9ad528c10 100644 --- a/packages/flutter/lib/src/material/time_picker_theme.dart +++ b/packages/flutter/lib/src/material/time_picker_theme.dart @@ -8,6 +8,9 @@ import 'package:flutter/widgets.dart'; import 'input_decorator.dart'; import 'theme.dart'; +// Examples can assume: +// late BuildContext context; + /// Defines the visual properties of the widget displayed with [showTimePicker]. /// /// Descendant widgets obtain the current [TimePickerThemeData] object using @@ -133,7 +136,8 @@ class TimePickerThemeData with Diagnosticable { /// The color of the entry mode [IconButton]. /// /// If this is null, the time picker defaults to: - /// ``` + /// + /// ```dart /// Theme.of(context).colorScheme.onSurface.withOpacity( /// Theme.of(context).colorScheme.brightness == Brightness.dark ? 1.0 : 0.6, /// ) @@ -173,8 +177,9 @@ class TimePickerThemeData with Diagnosticable { /// The shape of the day period that the time picker uses. /// /// If this is null, the time picker defaults to: - /// ``` - /// RoundedRectangleBorder( + /// + /// ```dart + /// const RoundedRectangleBorder( /// borderRadius: BorderRadius.all(Radius.circular(4.0)), /// side: BorderSide(), /// ) @@ -184,10 +189,14 @@ class TimePickerThemeData with Diagnosticable { /// The color and weight of the day period's outline. /// /// If this is null, the time picker defaults to: - /// ``` + /// + /// ```dart /// BorderSide( - /// color: Color.alphaBlend(colorScheme.onBackground.withOpacity(0.38), colorScheme.surface), - /// ) + /// color: Color.alphaBlend( + /// Theme.of(context).colorScheme.onBackground.withOpacity(0.38), + /// Theme.of(context).colorScheme.surface, + /// ), + /// ), /// ``` final BorderSide? dayPeriodBorderSide; diff --git a/packages/flutter/lib/src/material/toggle_buttons.dart b/packages/flutter/lib/src/material/toggle_buttons.dart index 2636baf79d..7fe10a3a61 100644 --- a/packages/flutter/lib/src/material/toggle_buttons.dart +++ b/packages/flutter/lib/src/material/toggle_buttons.dart @@ -18,6 +18,10 @@ import 'theme.dart'; import 'theme_data.dart'; import 'toggle_buttons_theme.dart'; +// Examples can assume: +// List isSelected = []; +// void setState(dynamic arg) { } + /// A set of toggle buttons. /// /// The list of [children] are laid out along [direction]. The state of each button @@ -43,34 +47,32 @@ import 'toggle_buttons_theme.dart'; /// Here is an implementation that allows for multiple buttons to be /// simultaneously selected, while requiring none of the buttons to be /// selected. +/// /// ```dart /// ToggleButtons( -/// children: [ -/// Icon(Icons.ac_unit), -/// Icon(Icons.call), -/// Icon(Icons.cake), -/// ], +/// isSelected: isSelected, /// onPressed: (int index) { /// setState(() { /// isSelected[index] = !isSelected[index]; /// }); /// }, -/// isSelected: isSelected, +/// children: const [ +/// Icon(Icons.ac_unit), +/// Icon(Icons.call), +/// Icon(Icons.cake), +/// ], /// ), /// ``` /// /// {@animation 700 150 https://flutter.github.io/assets-for-api-docs/assets/material/toggle_buttons_required_mutually_exclusive.mp4} /// -/// Here is an implementation that requires mutually exclusive selection -/// while requiring at least one selection. Note that this assumes that -/// [isSelected] was properly initialized with one selection. +/// Here is an implementation that requires mutually exclusive selection while +/// requiring at least one selection. This assumes that [isSelected] was +/// properly initialized with one selection. +/// /// ```dart /// ToggleButtons( -/// children: [ -/// Icon(Icons.ac_unit), -/// Icon(Icons.call), -/// Icon(Icons.cake), -/// ], +/// isSelected: isSelected, /// onPressed: (int index) { /// setState(() { /// for (int buttonIndex = 0; buttonIndex < isSelected.length; buttonIndex++) { @@ -82,7 +84,11 @@ import 'toggle_buttons_theme.dart'; /// } /// }); /// }, -/// isSelected: isSelected, +/// children: const [ +/// Icon(Icons.ac_unit), +/// Icon(Icons.call), +/// Icon(Icons.cake), +/// ], /// ), /// ``` /// @@ -90,13 +96,10 @@ import 'toggle_buttons_theme.dart'; /// /// Here is an implementation that requires mutually exclusive selection, /// but allows for none of the buttons to be selected. +/// /// ```dart /// ToggleButtons( -/// children: [ -/// Icon(Icons.ac_unit), -/// Icon(Icons.call), -/// Icon(Icons.cake), -/// ], +/// isSelected: isSelected, /// onPressed: (int index) { /// setState(() { /// for (int buttonIndex = 0; buttonIndex < isSelected.length; buttonIndex++) { @@ -108,37 +111,42 @@ import 'toggle_buttons_theme.dart'; /// } /// }); /// }, -/// isSelected: isSelected, +/// children: const [ +/// Icon(Icons.ac_unit), +/// Icon(Icons.call), +/// Icon(Icons.cake), +/// ], /// ), /// ``` /// /// {@animation 700 150 https://flutter.github.io/assets-for-api-docs/assets/material/toggle_buttons_required.mp4} /// /// Here is an implementation that allows for multiple buttons to be -/// simultaneously selected, while requiring at least one selection. Note -/// that this assumes that [isSelected] was properly initialized with one -/// selection. +/// simultaneously selected, while requiring at least one selection. This +/// assumes that [isSelected] was properly initialized with one selection. +/// /// ```dart /// ToggleButtons( -/// children: [ -/// Icon(Icons.ac_unit), -/// Icon(Icons.call), -/// Icon(Icons.cake), -/// ], +/// isSelected: isSelected, /// onPressed: (int index) { /// int count = 0; -/// isSelected.forEach((bool val) { -/// if (val) count++; -/// }); -/// -/// if (isSelected[index] && count < 2) +/// for (final bool value in isSelected) { +/// if (value) { +/// count += 1; +/// } +/// } +/// if (isSelected[index] && count < 2) { /// return; -/// +/// } /// setState(() { /// isSelected[index] = !isSelected[index]; /// }); /// }, -/// isSelected: isSelected, +/// children: const [ +/// Icon(Icons.ac_unit), +/// Icon(Icons.call), +/// Icon(Icons.cake), +/// ], /// ), /// ``` /// diff --git a/packages/flutter/lib/src/material/toggle_buttons_theme.dart b/packages/flutter/lib/src/material/toggle_buttons_theme.dart index 8c57dc586e..f78c6a1f83 100644 --- a/packages/flutter/lib/src/material/toggle_buttons_theme.dart +++ b/packages/flutter/lib/src/material/toggle_buttons_theme.dart @@ -10,6 +10,9 @@ import 'package:flutter/widgets.dart'; import 'theme.dart'; +// Examples can assume: +// late BuildContext context; + /// Defines the color and border properties of [ToggleButtons] widgets. /// /// Used by [ToggleButtonsTheme] to control the color and border properties @@ -263,7 +266,7 @@ class ToggleButtonsTheme extends InheritedTheme { /// Typical usage is as follows: /// /// ```dart - /// ToggleButtonsTheme theme = ToggleButtonsTheme.of(context); + /// ToggleButtonsThemeData theme = ToggleButtonsTheme.of(context); /// ``` static ToggleButtonsThemeData of(BuildContext context) { final ToggleButtonsTheme? toggleButtonsTheme = context.dependOnInheritedWidgetOfExactType(); diff --git a/packages/flutter/lib/src/material/tooltip_theme.dart b/packages/flutter/lib/src/material/tooltip_theme.dart index 36efc3c6bb..09b3b73cbe 100644 --- a/packages/flutter/lib/src/material/tooltip_theme.dart +++ b/packages/flutter/lib/src/material/tooltip_theme.dart @@ -9,6 +9,9 @@ import 'package:flutter/widgets.dart'; import 'theme.dart'; +// Examples can assume: +// late BuildContext context; + /// Defines the visual properties of [Tooltip] widgets. /// /// Used by [TooltipTheme] to control the visual properties of tooltips in a diff --git a/packages/flutter/lib/src/material/typography.dart b/packages/flutter/lib/src/material/typography.dart index 7b9a99b17e..4d5a1c85b7 100644 --- a/packages/flutter/lib/src/material/typography.dart +++ b/packages/flutter/lib/src/material/typography.dart @@ -8,6 +8,9 @@ import 'package:flutter/painting.dart'; import 'colors.dart'; import 'text_theme.dart'; +// Examples can assume: +// late TargetPlatform platform; + /// A characterization of the of a [TextTheme]'s glyphs that is used to define /// its localized [TextStyle] geometry for [ThemeData.textTheme]. /// diff --git a/packages/flutter/lib/src/painting/decoration.dart b/packages/flutter/lib/src/painting/decoration.dart index fc1f89e27b..d561269bcc 100644 --- a/packages/flutter/lib/src/painting/decoration.dart +++ b/packages/flutter/lib/src/painting/decoration.dart @@ -8,6 +8,9 @@ import 'basic_types.dart'; import 'edge_insets.dart'; import 'image_provider.dart'; +// Examples can assume: +// late Decoration myDecoration; + // This group of classes is intended for painting in cartesian coordinates. /// A description of a box decoration (a decoration applied to a [Rect]). diff --git a/packages/flutter/lib/src/painting/image_provider.dart b/packages/flutter/lib/src/painting/image_provider.dart index ff0ca9ff55..cfb5a7f98a 100644 --- a/packages/flutter/lib/src/painting/image_provider.dart +++ b/packages/flutter/lib/src/painting/image_provider.dart @@ -1162,11 +1162,9 @@ class MemoryImage extends ImageProvider { /// bundled, the app has to specify which ones to include. For instance a /// package named `fancy_backgrounds` could have: /// -/// ``` -/// lib/backgrounds/background1.png -/// lib/backgrounds/background2.png -/// lib/backgrounds/background3.png -/// ``` +/// lib/backgrounds/background1.png +/// lib/backgrounds/background2.png +/// lib/backgrounds/background3.png /// /// To include, say the first image, the `pubspec.yaml` of the app should specify /// it in the `assets` section: diff --git a/packages/flutter/lib/src/painting/image_resolution.dart b/packages/flutter/lib/src/painting/image_resolution.dart index 663beb66af..c67b75e039 100644 --- a/packages/flutter/lib/src/painting/image_resolution.dart +++ b/packages/flutter/lib/src/painting/image_resolution.dart @@ -40,11 +40,9 @@ const double _kLowDprLimit = 2.0; /// as 2.0 and 4.0 pixel ratios (variants). The asset bundle should then /// contain the following assets: /// -/// ``` -/// heart.png -/// 2.0x/heart.png -/// 4.0x/heart.png -/// ``` +/// heart.png +/// 2.0x/heart.png +/// 4.0x/heart.png /// /// On a device with a 1.0 device pixel ratio, the image chosen would be /// heart.png; on a device with a 2.0 device pixel ratio, the image chosen @@ -88,16 +86,13 @@ const double _kLowDprLimit = 2.0; /// at the equivalent level; that is, the following is also a valid bundle /// structure: /// -/// ``` -/// icons/heart.png -/// icons/1.5x/heart.png -/// icons/2.0x/heart.png -/// ``` +/// icons/heart.png +/// icons/1.5x/heart.png +/// icons/2.0x/heart.png /// /// assets/icons/3.0x/heart.png would be a valid variant of /// assets/icons/heart.png. /// -/// /// ## Fetching assets /// /// When fetching an image provided by the app itself, use the [assetName] @@ -113,7 +108,7 @@ const double _kLowDprLimit = 2.0; /// /// Then, to fetch the image, use: /// ```dart -/// AssetImage('icons/heart.png') +/// const AssetImage('icons/heart.png') /// ``` /// /// {@tool snippet} @@ -204,7 +199,7 @@ const double _kLowDprLimit = 2.0; /// `my_icons`. Then to fetch the image, use: /// /// ```dart -/// AssetImage('icons/heart.png', package: 'my_icons') +/// const AssetImage('icons/heart.png', package: 'my_icons') /// ``` /// /// Assets used by the package itself should also be fetched using the [package] @@ -219,11 +214,9 @@ const double _kLowDprLimit = 2.0; /// bundled, the app has to specify which ones to include. For instance a /// package named `fancy_backgrounds` could have: /// -/// ``` -/// lib/backgrounds/background1.png -/// lib/backgrounds/background2.png -/// lib/backgrounds/background3.png -/// ``` +/// lib/backgrounds/background1.png +/// lib/backgrounds/background2.png +/// lib/backgrounds/background3.png /// /// To include, say the first image, the `pubspec.yaml` of the app should specify /// it in the `assets` section: diff --git a/packages/flutter/lib/src/painting/image_stream.dart b/packages/flutter/lib/src/painting/image_stream.dart index 1fee74b149..b733158101 100644 --- a/packages/flutter/lib/src/painting/image_stream.dart +++ b/packages/flutter/lib/src/painting/image_stream.dart @@ -62,9 +62,11 @@ class ImageInfo { /// {@tool snippet} /// /// The following sample shows how to appropriately check whether the - /// [ImageInfo] reference refers to new image data or not. + /// [ImageInfo] reference refers to new image data or not (in this case in a + /// setter). /// /// ```dart + /// ImageInfo? get imageInfo => _imageInfo; /// ImageInfo? _imageInfo; /// set imageInfo (ImageInfo? value) { /// // If the image reference is exactly the same, do nothing. @@ -78,9 +80,12 @@ class ImageInfo { /// value.dispose(); /// return; /// } + /// // It is a new image. Dispose of the old one and take a reference + /// // to the new one. /// _imageInfo?.dispose(); /// _imageInfo = value; - /// // Perform work to determine size, or paint the image. + /// // Perform work to determine size, paint the image, etc. + /// // ... /// } /// ``` /// {@end-tool} diff --git a/packages/flutter/lib/src/painting/inline_span.dart b/packages/flutter/lib/src/painting/inline_span.dart index a0e6f898bb..0050607456 100644 --- a/packages/flutter/lib/src/painting/inline_span.dart +++ b/packages/flutter/lib/src/painting/inline_span.dart @@ -12,6 +12,9 @@ import 'text_painter.dart'; import 'text_span.dart'; import 'text_style.dart'; +// Examples can assume: +// late InlineSpan myInlineSpan; + /// Mutable wrapper of an integer that can be passed by reference to track a /// value across a recursive stack. class Accumulator { diff --git a/packages/flutter/lib/src/painting/matrix_utils.dart b/packages/flutter/lib/src/painting/matrix_utils.dart index 151f0ad867..0573b6e6aa 100644 --- a/packages/flutter/lib/src/painting/matrix_utils.dart +++ b/packages/flutter/lib/src/painting/matrix_utils.dart @@ -235,12 +235,12 @@ class MatrixUtils { // First, consider that a full point transform using the vector math // package involves expanding it out into a vector3 with a Z coordinate // of 0.0 and then performing 3 multiplies and 3 adds per coordinate: - // ``` - // xt = x*m00 + y*m10 + z*m20 + m30; - // yt = x*m01 + y*m11 + z*m21 + m31; - // zt = x*m02 + y*m12 + z*m22 + m32; - // wt = x*m03 + y*m13 + z*m23 + m33; - // ``` + // + // xt = x*m00 + y*m10 + z*m20 + m30; + // yt = x*m01 + y*m11 + z*m21 + m31; + // zt = x*m02 + y*m12 + z*m22 + m32; + // wt = x*m03 + y*m13 + z*m23 + m33; + // // Immediately we see that we can get rid of the 3rd column of multiplies // since we know that Z=0.0. We can also get rid of the 3rd row because // we ignore the resulting Z coordinate. Finally we can get rid of the @@ -307,26 +307,26 @@ class MatrixUtils { // you combine both of them, the resulting "opposite corner" will // actually be between the limits they established by pushing the walls // away from each other, as below: - // ``` - // +---------(originx,originy)--------------+ - // | -----^---- | - // | ----- ---- | - // | ----- ---- | - // (+hx,+hy)< ---- | - // | ---- ---- | - // | ---- >(+wx,+wy) - // | ---- ----- | - // | ---- ----- | - // | ---- ----- | - // | v | - // +---------------(+wx+hx,+wy+hy)----------+ - // ``` + // + // +---------(originx,originy)--------------+ + // | -----^---- | + // | ----- ---- | + // | ----- ---- | + // (+hx,+hy)< ---- | + // | ---- ---- | + // | ---- >(+wx,+wy) + // | ---- ----- | + // | ---- ----- | + // | ---- ----- | + // | v | + // +---------------(+wx+hx,+wy+hy)----------+ + // // In this diagram, consider that: - // ``` - // wx would be a positive number - // hx would be a negative number - // wy and hy would both be positive numbers - // ``` + // + // * wx would be a positive number + // * hx would be a negative number + // * wy and hy would both be positive numbers + // // As a result, wx pushes out the right wall, hx pushes out the left wall, // and both wy and hy push down the bottom wall of the bounding box. The // wx,hx pair (of opposite signs) worked on opposite walls and the final diff --git a/packages/flutter/lib/src/painting/text_span.dart b/packages/flutter/lib/src/painting/text_span.dart index 9a3a220a96..b9b386d1ae 100644 --- a/packages/flutter/lib/src/painting/text_span.dart +++ b/packages/flutter/lib/src/painting/text_span.dart @@ -12,6 +12,9 @@ import 'basic_types.dart'; import 'inline_span.dart'; import 'text_painter.dart'; +// Examples can assume: +// late TextSpan myTextSpan; + /// An immutable span of text. /// /// A [TextSpan] object can be styled using its [style] property. The style will @@ -214,7 +217,7 @@ class TextSpan extends InlineSpan implements HitTestTarget, MouseTrackerAnnotati /// text value: /// /// ```dart - /// TextSpan(text: r'$$', semanticsLabel: 'Double dollars') + /// const TextSpan(text: r'$$', semanticsLabel: 'Double dollars') /// ``` final String? semanticsLabel; diff --git a/packages/flutter/lib/src/painting/text_style.dart b/packages/flutter/lib/src/painting/text_style.dart index 731769e197..0301f8d6f6 100644 --- a/packages/flutter/lib/src/painting/text_style.dart +++ b/packages/flutter/lib/src/painting/text_style.dart @@ -357,9 +357,7 @@ const double _kDefaultFontSize = 14.0; /// the app can use these selectively when declaring a font. Suppose a package /// named `my_package` has: /// -/// ``` -/// lib/fonts/Raleway-Medium.ttf -/// ``` +/// lib/fonts/Raleway-Medium.ttf /// /// Then the app can declare a font like in the example below: /// @@ -790,9 +788,9 @@ class TextStyle with Diagnosticable { /// For example, to control the weight axis of the Roboto Slab variable font /// (https://fonts.google.com/specimen/Roboto+Slab): /// ```dart - /// TextStyle( + /// const TextStyle( /// fontFamily: 'RobotoSlab', - /// fontVariations: [FontVariation('wght', 900.0)] + /// fontVariations: [ui.FontVariation('wght', 900.0)] /// ) /// ``` final List? fontVariations; diff --git a/packages/flutter/lib/src/rendering/box.dart b/packages/flutter/lib/src/rendering/box.dart index d9b5b699c8..a81353af22 100644 --- a/packages/flutter/lib/src/rendering/box.dart +++ b/packages/flutter/lib/src/rendering/box.dart @@ -14,6 +14,11 @@ import 'package:vector_math/vector_math_64.dart'; import 'debug.dart'; import 'object.dart'; +// Examples can assume: +// abstract class RenderBar extends RenderBox { } +// late RenderBox firstChild; +// void markNeedsLayout() { } + // This class should only be used in debug builds. class _DebugSize extends Size { _DebugSize(super.source, this._owner, this._canBeUsedByParent) : super.copy(); @@ -994,11 +999,12 @@ class _IntrinsicDimensionsCacheEntry { /// /// ```dart /// AxisDirection get axis => _axis; -/// AxisDirection _axis; +/// AxisDirection _axis = AxisDirection.down; // or initialized in constructor /// set axis(AxisDirection value) { -/// assert(value != null); // same check as in the constructor -/// if (value == _axis) +/// assert(value != null); // same checks as in the constructor +/// if (value == _axis) { /// return; +/// } /// _axis = value; /// markNeedsLayout(); /// } @@ -1135,6 +1141,7 @@ class _IntrinsicDimensionsCacheEntry { /// The declaration of the `RenderFoo` class itself would thus look like this: /// /// ```dart +/// // continuing from previous example... /// class RenderFoo extends RenderBox with /// ContainerRenderObjectMixin, /// RenderBoxContainerDefaultsMixin { @@ -1148,9 +1155,10 @@ class _IntrinsicDimensionsCacheEntry { /// children's [parentData] fields): /// /// ```dart -/// RenderBox child = firstChild; +/// // continuing from previous example... +/// RenderBox? child = firstChild; /// while (child != null) { -/// final FooParentData childParentData = child.parentData; +/// final FooParentData childParentData = child.parentData! as FooParentData; /// // ...operate on child and childParentData... /// assert(child.parentData == childParentData); /// child = childParentData.nextSibling; @@ -2675,10 +2683,16 @@ abstract class RenderBox extends RenderObject { /// so that they support [debugPaintPointersEnabled]: /// /// ```dart - /// @override - /// void handleEvent(PointerEvent event, HitTestEntry entry) { - /// assert(debugHandleEvent(event, entry)); - /// // ... handle the event ... + /// class RenderFoo extends RenderBox { + /// // ... + /// + /// @override + /// void handleEvent(PointerEvent event, HitTestEntry entry) { + /// assert(debugHandleEvent(event, entry)); + /// // ... handle the event ... + /// } + /// + /// // ... /// } /// ``` @override @@ -2694,10 +2708,16 @@ abstract class RenderBox extends RenderObject { /// [debugHandleEvent] from their [handleEvent] method, as follows: /// /// ```dart - /// @override - /// void handleEvent(PointerEvent event, HitTestEntry entry) { - /// assert(debugHandleEvent(event, entry)); - /// // ... handle the event ... + /// class RenderFoo extends RenderBox { + /// // ... + /// + /// @override + /// void handleEvent(PointerEvent event, HitTestEntry entry) { + /// assert(debugHandleEvent(event, entry)); + /// // ... handle the event ... + /// } + /// + /// // ... /// } /// ``` /// diff --git a/packages/flutter/lib/src/services/deferred_component.dart b/packages/flutter/lib/src/services/deferred_component.dart index 46e8434338..5102979022 100644 --- a/packages/flutter/lib/src/services/deferred_component.dart +++ b/packages/flutter/lib/src/services/deferred_component.dart @@ -6,6 +6,10 @@ import 'dart:async'; import 'system_channels.dart'; +// Examples can assume: +// // so that we can import the fake "split_component.dart" in an example below: +// // ignore_for_file: uri_does_not_exist + /// Manages the installation and loading of deferred components. /// /// Deferred components allow Flutter applications to download precompiled AOT @@ -41,13 +45,17 @@ class DeferredComponent { /// the assets from a component with both dart code and assets. Deferred components /// containing dart code should call `loadLibrary()` on a deferred imported /// library's prefix to ensure that the dart code is properly loaded as - /// `loadLibrary()` will provide the loading unit id needed for the dart + /// `loadLibrary()` will provide the loading unit ID needed for the Dart /// library loading process. For example: /// /// ```dart - /// import 'split_component.dart' deferred as SplitComponent; - /// ... - /// SplitComponent.loadLibrary(); + /// import 'split_component.dart' deferred as split_component; + /// // ... + /// void _showSplit() { + /// // ... + /// split_component.loadLibrary(); + /// // ... + /// } /// ``` /// /// This method will not load associated dart libraries contained in the component, @@ -60,9 +68,9 @@ class DeferredComponent { /// See also: /// /// * [uninstallDeferredComponent], a method to request the uninstall of a component. - /// * [loadLibrary](https://api.dart.dev/dart-mirrors/LibraryDependencyMirror/loadLibrary.html), - /// the dart method to trigger the installation of the corresponding deferred component that - /// contains the dart library. + /// * [loadLibrary](https://dart.dev/guides/language/language-tour#lazily-loading-a-library), + /// the Dart method to trigger the installation of the corresponding deferred component that + /// contains the Dart library. static Future installDeferredComponent({required String componentName}) async { await SystemChannels.deferredComponent.invokeMethod( 'installDeferredComponent', diff --git a/packages/flutter/lib/src/services/message_codec.dart b/packages/flutter/lib/src/services/message_codec.dart index 7bc5dad9c3..16af277ea8 100644 --- a/packages/flutter/lib/src/services/message_codec.dart +++ b/packages/flutter/lib/src/services/message_codec.dart @@ -137,7 +137,7 @@ class PlatformException implements Exception { /// /// ```dart /// try { - /// ... + /// // ... /// } catch (e, stacktrace) { /// print(stacktrace); /// } diff --git a/packages/flutter/lib/src/services/system_channels.dart b/packages/flutter/lib/src/services/system_channels.dart index ee9712ec67..5d38359e5b 100644 --- a/packages/flutter/lib/src/services/system_channels.dart +++ b/packages/flutter/lib/src/services/system_channels.dart @@ -409,7 +409,7 @@ class SystemChannels { /// a list of [MenuItem]s, and ends up looking like this example: /// /// ```dart - /// List> menu = { + /// Map menu = { /// '0': >[ /// { /// 'id': 1, diff --git a/packages/flutter/lib/src/services/text_formatter.dart b/packages/flutter/lib/src/services/text_formatter.dart index ff2dd5e29a..d565cc9aab 100644 --- a/packages/flutter/lib/src/services/text_formatter.dart +++ b/packages/flutter/lib/src/services/text_formatter.dart @@ -14,6 +14,14 @@ export 'package:flutter/foundation.dart' show TargetPlatform; export 'text_input.dart' show TextEditingValue; +// Examples can assume: +// late RegExp _pattern; + +/// Mechanisms for enforcing maximum length limits. +/// +/// This is used by [TextField] to specify how the [TextField.maxLength] should +/// be applied. +/// /// {@template flutter.services.textFormatter.maxLengthEnforcement} /// ### [MaxLengthEnforcement.enforced] versus /// [MaxLengthEnforcement.truncateAfterCompositionEnds] @@ -40,8 +48,8 @@ export 'text_input.dart' show TextEditingValue; /// See also: /// /// * [TextField.maxLengthEnforcement] which is used in conjunction with -/// [TextField.maxLength] to limit the length of user input. [TextField] also -/// provides a character counter to provide visual feedback. +/// [TextField.maxLength] to limit the length of user input. [TextField] also +/// provides a character counter to provide visual feedback. enum MaxLengthEnforcement { /// No enforcement applied to the editing value. It's possible to exceed the /// max length. @@ -227,9 +235,16 @@ class _TextEditingValueAccumulator { /// [TextEditingValue] and falls back to the current [TextEditingValue] when the /// given [filterPattern] fails to match. Consider using a different /// [TextInputFormatter] such as: +/// /// ```dart -/// TextInputFormatter.withFunction((oldValue, newValue) => regExp.hasMatch(newValue.text) ? newValue : oldValue) +/// // _pattern is a RegExp or other Pattern object +/// TextInputFormatter.withFunction( +/// (TextEditingValue oldValue, TextEditingValue newValue) { +/// return _pattern.hasMatch(newValue.text) ? newValue : oldValue; +/// }, +/// ), /// ``` +/// /// for accepting/rejecting new input based on a predicate on the full string. /// As an example, [FilteringTextInputFormatter] typically shouldn't be used /// with [RegExp]s that contain positional matchers (`^` or `$`) since these diff --git a/packages/flutter/lib/src/services/text_input.dart b/packages/flutter/lib/src/services/text_input.dart index 2d6320c630..50f0631e0e 100644 --- a/packages/flutter/lib/src/services/text_input.dart +++ b/packages/flutter/lib/src/services/text_input.dart @@ -1248,17 +1248,22 @@ mixin DeltaTextInputClient implements TextInputClient { /// This example shows what an implementation of this method could look like. /// /// ```dart - /// TextEditingValue? _localValue; - /// @override - /// void updateEditingValueWithDeltas(List textEditingDeltas) { - /// if (_localValue == null) { - /// return; + /// class MyClient with DeltaTextInputClient { + /// TextEditingValue? _localValue; + /// + /// @override + /// void updateEditingValueWithDeltas(List textEditingDeltas) { + /// if (_localValue == null) { + /// return; + /// } + /// TextEditingValue newValue = _localValue!; + /// for (final TextEditingDelta delta in textEditingDeltas) { + /// newValue = delta.apply(newValue); + /// } + /// _localValue = newValue; /// } - /// TextEditingValue newValue = _localValue!; - /// for (final TextEditingDelta delta in textEditingDeltas) { - /// newValue = delta.apply(newValue); - /// } - /// _localValue = newValue; + /// + /// // ... /// } /// ``` /// {@end-tool} diff --git a/packages/flutter/lib/src/widgets/actions.dart b/packages/flutter/lib/src/widgets/actions.dart index 2655a2f64d..e348406861 100644 --- a/packages/flutter/lib/src/widgets/actions.dart +++ b/packages/flutter/lib/src/widgets/actions.dart @@ -260,12 +260,12 @@ abstract class Action with Diagnosticable { /// changing the return type of the override to match the type of the returned /// value provides more type safety. /// - /// For instance, if your override of `invoke` returns an `int`, then define - /// it like so: + /// For instance, if an override of `invoke` returned an `int`, then it might + /// be defined like so: /// /// ```dart /// class IncrementIntent extends Intent { - /// const IncrementIntent({this.index}); + /// const IncrementIntent({required this.index}); /// /// final int index; /// } @@ -479,19 +479,19 @@ abstract class ContextAction extends Action { /// changing the return type of the override to match the type of the returned /// value provides more type safety. /// - /// For instance, if your override of `invoke` returns an `int`, then define - /// it like so: + /// For instance, if an override of `invoke` returned an `int`, then it might + /// be defined like so: /// /// ```dart /// class IncrementIntent extends Intent { - /// const IncrementIntent({this.index}); + /// const IncrementIntent({required this.index}); /// /// final int index; /// } /// /// class MyIncrementAction extends ContextAction { /// @override - /// int invoke(IncrementIntent intent, [BuildContext context]) { + /// int invoke(IncrementIntent intent, [BuildContext? context]) { /// return intent.index + 1; /// } /// } diff --git a/packages/flutter/lib/src/widgets/animated_list.dart b/packages/flutter/lib/src/widgets/animated_list.dart index 4d0195980a..d45bf2e465 100644 --- a/packages/flutter/lib/src/widgets/animated_list.dart +++ b/packages/flutter/lib/src/widgets/animated_list.dart @@ -252,11 +252,27 @@ class AnimatedList extends StatefulWidget { /// can refer to the [AnimatedList]'s state with a global key: /// /// ```dart +/// // (e.g. in a stateful widget) /// GlobalKey listKey = GlobalKey(); -/// ... -/// AnimatedList(key: listKey, ...); -/// ... -/// listKey.currentState.insert(123); +/// +/// // ... +/// +/// @override +/// Widget build(BuildContext context) { +/// return AnimatedList( +/// key: listKey, +/// itemBuilder: (BuildContext context, int index, Animation animation) { +/// return const Placeholder(); +/// }, +/// ); +/// } +/// +/// // ... +/// +/// void _updateList() { +/// // adds "123" to the AnimatedList +/// listKey.currentState!.insertItem(123); +/// } /// ``` /// /// [AnimatedList] item input handlers can also refer to their [AnimatedListState] @@ -440,11 +456,27 @@ class SliverAnimatedList extends StatefulWidget { /// can refer to the [SliverAnimatedList]'s state with a global key: /// /// ```dart -/// GlobalKey listKey = GlobalKey(); -/// ... -/// SliverAnimatedList(key: listKey, ...); -/// ... -/// listKey.currentState.insert(123); +/// // (e.g. in a stateful widget) +/// GlobalKey listKey = GlobalKey(); +/// +/// // ... +/// +/// @override +/// Widget build(BuildContext context) { +/// return AnimatedList( +/// key: listKey, +/// itemBuilder: (BuildContext context, int index, Animation animation) { +/// return const Placeholder(); +/// }, +/// ); +/// } +/// +/// // ... +/// +/// void _updateList() { +/// // adds "123" to the AnimatedList +/// listKey.currentState!.insertItem(123); +/// } /// ``` /// /// [SliverAnimatedList] item input handlers can also refer to their diff --git a/packages/flutter/lib/src/widgets/app.dart b/packages/flutter/lib/src/widgets/app.dart index ad3a52e71b..6ac93473ae 100644 --- a/packages/flutter/lib/src/widgets/app.dart +++ b/packages/flutter/lib/src/widgets/app.dart @@ -33,6 +33,9 @@ import 'widget_inspector.dart'; export 'dart:ui' show Locale; +// Examples can assume: +// late Widget myWidget; + /// The signature of [WidgetsApp.localeListResolutionCallback]. /// /// A [LocaleListResolutionCallback] is responsible for computing the locale of the app's @@ -947,7 +950,7 @@ class WidgetsApp extends StatefulWidget { /// /// ```dart /// // Full Chinese support for CN, TW, and HK - /// supportedLocales: [ + /// supportedLocales: [ /// const Locale.fromSubtags(languageCode: 'zh'), // generic Chinese 'zh' /// const Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hans'), // generic simplified Chinese 'zh_Hans' /// const Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hant'), // generic traditional Chinese 'zh_Hant' diff --git a/packages/flutter/lib/src/widgets/autocomplete.dart b/packages/flutter/lib/src/widgets/autocomplete.dart index d5a3bb5af2..22760222d2 100644 --- a/packages/flutter/lib/src/widgets/autocomplete.dart +++ b/packages/flutter/lib/src/widgets/autocomplete.dart @@ -18,6 +18,9 @@ import 'overlay.dart'; import 'shortcuts.dart'; import 'tap_region.dart'; +// Examples can assume: +// late BuildContext context; + /// The type of the [RawAutocomplete] callback which computes the list of /// optional completions for the widget's field, based on the text the user has /// entered so far. @@ -593,7 +596,7 @@ class AutocompleteNextOptionIntent extends Intent { /// by using the static [of] method: /// /// ```dart -/// final highlightedIndex = AutocompleteHighlightedOption.of(context); +/// int highlightedIndex = AutocompleteHighlightedOption.of(context); /// ``` /// /// which can then be used to tell which option should be given a visual @@ -614,7 +617,7 @@ class AutocompleteHighlightedOption extends InheritedNotifier /// Typical usage is as follows: /// /// ```dart - /// final highlightedIndex = AutocompleteHighlightedOption.of(context); + /// int highlightedIndex = AutocompleteHighlightedOption.of(context); /// ``` static int of(BuildContext context) { return context.dependOnInheritedWidgetOfExactType()?.notifier?.value ?? 0; diff --git a/packages/flutter/lib/src/widgets/basic.dart b/packages/flutter/lib/src/widgets/basic.dart index 6137714401..e59cd13b01 100644 --- a/packages/flutter/lib/src/widgets/basic.dart +++ b/packages/flutter/lib/src/widgets/basic.dart @@ -4641,10 +4641,10 @@ class Flex extends MultiChildRenderObjectWidget { /// /// ```dart /// Row( -/// children: [ -/// const FlutterLogo(), -/// const Text("Flutter's hot reload helps you quickly and easily experiment, build UIs, add features, and fix bug faster. Experience sub-second reload times, without losing state, on emulators, simulators, and hardware for iOS and Android."), -/// const Icon(Icons.sentiment_very_satisfied), +/// children: const [ +/// FlutterLogo(), +/// Text("Flutter's hot reload helps you quickly and easily experiment, build UIs, add features, and fix bug faster. Experience sub-second reload times, without losing state, on emulators, simulators, and hardware for iOS and Android."), +/// Icon(Icons.sentiment_very_satisfied), /// ], /// ) /// ``` @@ -4668,12 +4668,12 @@ class Flex extends MultiChildRenderObjectWidget { /// /// ```dart /// Row( -/// children: [ -/// const FlutterLogo(), -/// const Expanded( +/// children: const [ +/// FlutterLogo(), +/// Expanded( /// child: Text("Flutter's hot reload helps you quickly and easily experiment, build UIs, add features, and fix bug faster. Experience sub-second reload times, without losing state, on emulators, simulators, and hardware for iOS and Android."), /// ), -/// const Icon(Icons.sentiment_very_satisfied), +/// Icon(Icons.sentiment_very_satisfied), /// ], /// ) /// ``` @@ -4698,12 +4698,12 @@ class Flex extends MultiChildRenderObjectWidget { /// ```dart /// Row( /// textDirection: TextDirection.rtl, -/// children: [ -/// const FlutterLogo(), -/// const Expanded( +/// children: const [ +/// FlutterLogo(), +/// Expanded( /// child: Text("Flutter's hot reload helps you quickly and easily experiment, build UIs, add features, and fix bug faster. Experience sub-second reload times, without losing state, on emulators, simulators, and hardware for iOS and Android."), /// ), -/// const Icon(Icons.sentiment_very_satisfied), +/// Icon(Icons.sentiment_very_satisfied), /// ], /// ) /// ``` @@ -5999,6 +5999,7 @@ class RawImage extends LeafRenderObjectWidget { /// bundle implementation: /// /// ```dart +/// // continuing from previous example... /// await tester.pumpWidget( /// MaterialApp( /// home: DefaultAssetBundle( @@ -7274,25 +7275,31 @@ class KeyedSubtree extends StatelessWidget { /// /// {@youtube 560 315 https://www.youtube.com/watch?v=xXNOkIuSYuA} /// -/// This widget is a simple inline alternative to defining a [StatelessWidget] -/// subclass. For example a widget defined and used like this: +/// This widget is an inline alternative to defining a [StatelessWidget] +/// subclass. For example, instead of defining a widget as follows: /// /// ```dart /// class Foo extends StatelessWidget { +/// const Foo({super.key}); /// @override -/// Widget build(BuildContext context) => Text('foo'); +/// Widget build(BuildContext context) => const Text('foo'); /// } -/// -/// Center(child: Foo()) /// ``` /// -/// Could equally well be defined and used like this, without +/// ...and using it in the usual way: +/// +/// ```dart +/// // continuing from previous example... +/// const Center(child: Foo()) +/// ``` +/// +/// ...one could instead define and use it in a single step, without /// defining a new widget class: /// /// ```dart /// Center( /// child: Builder( -/// builder: (BuildContext context) => Text('foo'); +/// builder: (BuildContext context) => const Text('foo'), /// ), /// ) /// ``` @@ -7317,7 +7324,7 @@ class KeyedSubtree extends StatelessWidget { /// // above this widget's context. /// print(Scaffold.of(context).hasAppBar); /// }, -/// child: Text('hasAppBar'), +/// child: const Text('hasAppBar'), /// ) /// ), /// ); @@ -7337,7 +7344,7 @@ class KeyedSubtree extends StatelessWidget { /// onPressed: () { /// print(Scaffold.of(context).hasAppBar); /// }, -/// child: Text('hasAppBar'), +/// child: const Text('hasAppBar'), /// ), /// ); /// }, diff --git a/packages/flutter/lib/src/widgets/binding.dart b/packages/flutter/lib/src/widgets/binding.dart index a0c9a9cfff..c64fdc410e 100644 --- a/packages/flutter/lib/src/widgets/binding.dart +++ b/packages/flutter/lib/src/widgets/binding.dart @@ -39,7 +39,7 @@ export 'dart:ui' show AppLifecycleState, Locale; /// lifecycle messages. See [didChangeAppLifecycleState]. /// /// ** See code in examples/api/lib/widgets/binding/widget_binding_observer.0.dart ** -/// ``` +/// /// {@end-tool} /// /// To respond to other notifications, replace the [didChangeAppLifecycleState] diff --git a/packages/flutter/lib/src/widgets/debug.dart b/packages/flutter/lib/src/widgets/debug.dart index 151a27c2b1..b70c5b624f 100644 --- a/packages/flutter/lib/src/widgets/debug.dart +++ b/packages/flutter/lib/src/widgets/debug.dart @@ -14,6 +14,11 @@ import 'media_query.dart'; import 'overlay.dart'; import 'table.dart'; +// Examples can assume: +// late BuildContext context; +// List children = []; +// List items = []; + // Any changes to this file should be reflected in the debugAssertAllWidgetVarsUnset() // function below. @@ -177,26 +182,35 @@ Key? _firstNonUniqueKey(Iterable widgets) { /// Asserts if the given child list contains any duplicate non-null keys. /// -/// To invoke this function, use the following pattern, typically in the -/// relevant Widget's constructor: +/// To invoke this function, use the following pattern: /// /// ```dart -/// assert(!debugChildrenHaveDuplicateKeys(this, children)); +/// class MyWidget extends StatelessWidget { +/// MyWidget({ super.key, required this.children }) { +/// assert(!debugChildrenHaveDuplicateKeys(this, children)); +/// } +/// +/// final List children; +/// +/// // ... +/// } /// ``` /// +/// If specified, the `message` overrides the default message. +/// /// For a version of this function that can be used in contexts where /// the list of items does not have a particular parent, see /// [debugItemsHaveDuplicateKeys]. /// -/// Does nothing if asserts are disabled. Always returns true. -bool debugChildrenHaveDuplicateKeys(Widget parent, Iterable children) { +/// Does nothing if asserts are disabled. Always returns false. +bool debugChildrenHaveDuplicateKeys(Widget parent, Iterable children, { String? message }) { assert(() { final Key? nonUniqueKey = _firstNonUniqueKey(children); if (nonUniqueKey != null) { throw FlutterError( - 'Duplicate keys found.\n' - 'If multiple keyed nodes exist as children of another node, they must have unique keys.\n' - '$parent has multiple children with key $nonUniqueKey.', + "${message ?? 'Duplicate keys found.\n' + 'If multiple keyed widgets exist as children of another widget, they must have unique keys.'}" + '\n$parent has multiple children with key $nonUniqueKey.', ); } return true; @@ -215,7 +229,7 @@ bool debugChildrenHaveDuplicateKeys(Widget parent, Iterable children) { /// For a version of this function specifically intended for parents /// checking their children lists, see [debugChildrenHaveDuplicateKeys]. /// -/// Does nothing if asserts are disabled. Always returns true. +/// Does nothing if asserts are disabled. Always returns false. bool debugItemsHaveDuplicateKeys(Iterable items) { assert(() { final Key? nonUniqueKey = _firstNonUniqueKey(items); diff --git a/packages/flutter/lib/src/widgets/default_selection_style.dart b/packages/flutter/lib/src/widgets/default_selection_style.dart index ad3e46bf39..26c480b7c6 100644 --- a/packages/flutter/lib/src/widgets/default_selection_style.dart +++ b/packages/flutter/lib/src/widgets/default_selection_style.dart @@ -7,6 +7,8 @@ import 'dart:ui'; import 'framework.dart'; import 'inherited_theme.dart'; +// Examples can assume: +// late BuildContext context; /// The selection style to apply to descendant [EditableText] widgets which /// don't have an explicit style. diff --git a/packages/flutter/lib/src/widgets/editable_text.dart b/packages/flutter/lib/src/widgets/editable_text.dart index d75122fa23..10793d0ab1 100644 --- a/packages/flutter/lib/src/widgets/editable_text.dart +++ b/packages/flutter/lib/src/widgets/editable_text.dart @@ -43,6 +43,9 @@ import 'widget_span.dart'; export 'package:flutter/services.dart' show SelectionChangedCause, SmartDashesType, SmartQuotesType, TextEditingValue, TextInputType, TextSelection; +// Examples can assume: +// late BuildContext context; + /// Signature for the callback that reports when the user changes the selection /// (including the cursor location). typedef SelectionChangedCallback = void Function(TextSelection selection, SelectionChangedCause? cause); @@ -535,11 +538,13 @@ class _DiscreteKeyFrameSimulation extends Simulation { /// incorrectly when a [TextInputFormatter] inserts a thousands separator to /// a currency value text field. The following example demonstrates how to /// suppress the default accessibility announcements by always announcing -/// the content of the text field as a US currency value: +/// the content of the text field as a US currency value (the `\$` inserts +/// a dollar sign, the `$newText interpolates the `newText` variable): +/// /// ```dart /// onChanged: (String newText) { /// if (newText.isNotEmpty) { -/// SemanticsService.announce('\$' + newText, Directionality.of(context)); +/// SemanticsService.announce('\$$newText', Directionality.of(context)); /// } /// } /// ``` @@ -956,26 +961,26 @@ class EditableText extends StatefulWidget { /// /// Input that occupies a single line and scrolls horizontally as needed. /// ```dart - /// TextField() + /// const TextField() /// ``` /// /// Input whose height grows from one line up to as many lines as needed for /// the text that was entered. If a height limit is imposed by its parent, it /// will scroll vertically when its height reaches that limit. /// ```dart - /// TextField(maxLines: null) + /// const TextField(maxLines: null) /// ``` /// /// The input's height is large enough for the given number of lines. If /// additional lines are entered the input scrolls vertically. /// ```dart - /// TextField(maxLines: 2) + /// const TextField(maxLines: 2) /// ``` /// /// Input whose height grows with content between a min and max. An infinite /// max is possible with `maxLines: null`. /// ```dart - /// TextField(minLines: 2, maxLines: 4) + /// const TextField(minLines: 2, maxLines: 4) /// ``` /// /// See also: @@ -1019,7 +1024,7 @@ class EditableText extends StatefulWidget { /// point the height limit is reached. If additional lines are entered it will /// scroll vertically. /// ```dart - /// TextField(minLines:2, maxLines: 4) + /// const TextField(minLines:2, maxLines: 4) /// ``` /// /// Defaults to null. @@ -1050,7 +1055,7 @@ class EditableText extends StatefulWidget { /// /// Input that matches the height of its parent: /// ```dart - /// Expanded( + /// const Expanded( /// child: TextField(maxLines: null, expands: true), /// ) /// ``` diff --git a/packages/flutter/lib/src/widgets/focus_traversal.dart b/packages/flutter/lib/src/widgets/focus_traversal.dart index 4d57ec029d..ad1ec78265 100644 --- a/packages/flutter/lib/src/widgets/focus_traversal.dart +++ b/packages/flutter/lib/src/widgets/focus_traversal.dart @@ -12,6 +12,10 @@ import 'framework.dart'; import 'scroll_position.dart'; import 'scrollable.dart'; +// Examples can assume: +// late BuildContext context; +// FocusNode focusNode = FocusNode(); + // BuildContext/Element doesn't have a parent accessor, but it can be simulated // with visitAncestorElements. _getAncestor is needed because // context.getElementForInheritedWidgetOfExactType will return itself if it @@ -1625,7 +1629,7 @@ class RequestFocusIntent extends Intent { /// [Action.invoke] like so: /// /// ```dart -/// Actions.invoke(context, const RequestFocusIntent(focusNode)); +/// Actions.invoke(context, RequestFocusIntent(focusNode)); /// ``` /// /// Where the `focusNode` is the node for which the focus will be requested. diff --git a/packages/flutter/lib/src/widgets/form.dart b/packages/flutter/lib/src/widgets/form.dart index 08b315fd2a..4be2572eb7 100644 --- a/packages/flutter/lib/src/widgets/form.dart +++ b/packages/flutter/lib/src/widgets/form.dart @@ -8,6 +8,9 @@ import 'restoration.dart'; import 'restoration_properties.dart'; import 'will_pop_scope.dart'; +// Examples can assume: +// late BuildContext context; + /// An optional container for grouping together multiple form field widgets /// (e.g. [TextField] widgets). /// @@ -46,12 +49,13 @@ class Form extends StatefulWidget { }) : assert(child != null), autovalidateMode = autovalidateMode ?? AutovalidateMode.disabled; - /// Returns the closest [FormState] which encloses the given context. + /// Returns the closest [FormState] which encloses the given context, + /// or null if there is no such form. /// /// Typical usage is as follows: /// /// ```dart - /// FormState form = Form.of(context); + /// FormState form = Form.of(context)!; /// form.save(); /// ``` static FormState? of(BuildContext context) { diff --git a/packages/flutter/lib/src/widgets/framework.dart b/packages/flutter/lib/src/widgets/framework.dart index 041de2631f..01d1d0032f 100644 --- a/packages/flutter/lib/src/widgets/framework.dart +++ b/packages/flutter/lib/src/widgets/framework.dart @@ -36,6 +36,16 @@ export 'package:flutter/rendering.dart' show RenderBox, RenderObject, debugDumpL // abstract class RenderFrogJar extends RenderObject { } // abstract class FrogJar extends RenderObjectWidget { const FrogJar({super.key}); } // abstract class FrogJarParentData extends ParentData { late Size size; } +// abstract class SomeWidget extends StatefulWidget { const SomeWidget({super.key}); } +// typedef ChildWidget = Placeholder; +// class _SomeWidgetState extends State { @override Widget build(BuildContext context) => widget; } +// abstract class RenderFoo extends RenderObject { } +// abstract class Foo extends RenderObjectWidget { const Foo({super.key}); } +// abstract class StatefulWidgetX { const StatefulWidgetX({this.key}); final Key? key; Widget build(BuildContext context, State state); } +// class SpecialWidget extends StatelessWidget { const SpecialWidget({ super.key, this.handler }); final VoidCallback? handler; @override Widget build(BuildContext context) => this; } +// late Object? _myState, newValue; +// int _counter = 0; +// Future getApplicationDocumentsDirectory() async => Directory(''); // An annotation used by test_analysis package to verify patterns are followed // that allow for tree-shaking of both fields and their initializers. This @@ -211,6 +221,8 @@ class LabeledGlobalKey> extends GlobalKey { /// Used to tie the identity of a widget to the identity of an object used to /// generate that widget. /// +/// Any [GlobalObjectKey] created for the same object will match. +/// /// If the object is not private, then it is possible that collisions will occur /// where independent widgets will reuse the same object as their /// [GlobalObjectKey] value in a different part of the tree, leading to a global @@ -219,15 +231,13 @@ class LabeledGlobalKey> extends GlobalKey { /// /// ```dart /// class _MyKey extends GlobalObjectKey { -/// const _MyKey(Object value) : super(value); +/// const _MyKey(super.value); /// } /// ``` /// /// Since the [runtimeType] of the key is part of its identity, this will /// prevent clashes with other [GlobalObjectKey]s even if they have the same /// value. -/// -/// Any [GlobalObjectKey] created for the same value will match. @optionalTypeArgs class GlobalObjectKey> extends GlobalKey { /// Creates a global key that uses [identical] on [value] for its [operator==]. @@ -769,7 +779,7 @@ abstract class StatefulWidget extends Widget { /// /// ```dart /// @override - /// State createState() => _MyWidgetState(); + /// State createState() => _SomeWidgetState(); /// ``` /// /// The framework can call this method multiple times over the lifetime of @@ -1061,9 +1071,9 @@ abstract class State with Diagnosticable { /// setState(() { /// _counter++; /// }); - /// Directory directory = await getApplicationDocumentsDirectory(); + /// Directory directory = await getApplicationDocumentsDirectory(); // from path_provider package /// final String dirName = directory.path; - /// await File('$dir/counter.txt').writeAsString('$_counter'); + /// await File('$dirName/counter.txt').writeAsString('$_counter'); /// } /// ``` /// @@ -1289,13 +1299,17 @@ abstract class State with Diagnosticable { /// instance in scope: /// /// ```dart - /// class MyButton extends StatefulWidget { - /// ... + /// // (this is not valid Flutter code) + /// class MyButton extends StatefulWidgetX { + /// MyButton({super.key, required this.color}); + /// /// final Color color; /// /// @override - /// Widget build(BuildContext context, MyButtonState state) { - /// ... () { print("color: $color"); } ... + /// Widget build(BuildContext context, State state) { + /// return SpecialWidget( + /// handler: () { print('color: $color'); }, + /// ); /// } /// } /// ``` @@ -1305,18 +1319,28 @@ abstract class State with Diagnosticable { /// suppose the parent rebuilds `MyButton` with green. The closure created by /// the first build still implicitly refers to the original widget and the /// `$color` still prints blue even through the widget has been updated to - /// green. + /// green; should that closure outlive its widget, it would print outdated + /// information. /// /// In contrast, with the [build] function on the [State] object, closures /// created during [build] implicitly capture the [State] instance instead of /// the widget instance: /// /// ```dart + /// class MyButton extends StatefulWidget { + /// const MyButton({super.key, this.color = Colors.teal}); + /// + /// final Color color; + /// // ... + /// } + /// /// class MyButtonState extends State { - /// ... + /// // ... /// @override /// Widget build(BuildContext context) { - /// ... () { print("color: ${widget.color}"); } ... + /// return SpecialWidget( + /// handler: () { print('color: ${widget.color}'); }, + /// ); /// } /// } /// ``` @@ -1609,9 +1633,10 @@ abstract class ParentDataWidget extends ProxyWidget { /// {@tool snippet} /// /// In this example, the `context` used is the one from the [Builder], which is -/// a child of the FrogColor widget, so this works. +/// a child of the `FrogColor` widget, so this works. /// /// ```dart +/// // continuing from previous example... /// class MyPage extends StatelessWidget { /// const MyPage({super.key}); /// @@ -1637,10 +1662,12 @@ abstract class ParentDataWidget extends ProxyWidget { /// /// {@tool snippet} /// -/// In this example, the `context` used is the one from the MyOtherPage widget, -/// which is a parent of the FrogColor widget, so this does not work. +/// In this example, the `context` used is the one from the `MyOtherPage` widget, +/// which is a parent of the `FrogColor` widget, so this does not work. /// /// ```dart +/// // continuing from previous example... +/// /// class MyOtherPage extends StatelessWidget { /// const MyOtherPage({super.key}); /// @@ -1836,20 +1863,18 @@ abstract class MultiChildRenderObjectWidget extends RenderObjectWidget { /// children list is modified, a new list object should be provided. /// /// ```dart + /// // This code is incorrect. /// class SomeWidgetState extends State { - /// List _children; - /// - /// void initState() { - /// _children = []; - /// } + /// final List _children = []; /// /// void someHandler() { /// setState(() { - /// _children.add(...); + /// _children.add(const ChildWidget()); /// }); /// } /// - /// Widget build(...) { + /// @override + /// Widget build(BuildContext context) { /// // Reusing `List _children` here is problematic. /// return Row(children: _children); /// } @@ -1860,23 +1885,20 @@ abstract class MultiChildRenderObjectWidget extends RenderObjectWidget { /// /// ```dart /// class SomeWidgetState extends State { - /// List _children; - /// - /// void initState() { - /// _children = []; - /// } + /// final List _children = []; /// /// void someHandler() { /// setState(() { /// // The key here allows Flutter to reuse the underlying render /// // objects even if the children list is recreated. - /// _children.add(ChildWidget(key: ...)); + /// _children.add(ChildWidget(key: UniqueKey())); /// }); /// } /// - /// Widget build(...) { + /// @override + /// Widget build(BuildContext context) { /// // Always create a new list of children as a Widget is immutable. - /// return Row(children: List.of(_children)); + /// return Row(children: _children.toList()); /// } /// } /// ``` @@ -2015,7 +2037,7 @@ typedef ElementVisitor = void Function(Element element); /// Widget build(BuildContext context) { /// // here, Scaffold.of(context) returns null /// return Scaffold( -/// appBar: const AppBar(title: Text('Demo')), +/// appBar: AppBar(title: const Text('Demo')), /// body: Builder( /// builder: (BuildContext context) { /// return TextButton( @@ -2035,7 +2057,7 @@ typedef ElementVisitor = void Function(Element element); /// ElevatedButton( /// child: const Text('Close BottomSheet'), /// onPressed: () { -/// Navigator.pop(context), +/// Navigator.pop(context); /// }, /// ) /// ], @@ -5494,13 +5516,21 @@ class InheritedElement extends ProxyElement { /// /// ```dart /// class FooElement extends RenderObjectElement { +/// FooElement(super.widget); /// +/// // Specializing the renderObject getter is fine because +/// // it is not performance sensitive. /// @override /// RenderFoo get renderObject => super.renderObject as RenderFoo; /// /// void _foo() { +/// // For the widget getter, though, we prefer to cast locally +/// // since that results in better overall performance where the +/// // casting isn't needed: /// final Foo foo = widget as Foo; +/// // ... /// } +/// /// // ... /// } /// ``` diff --git a/packages/flutter/lib/src/widgets/icon_theme.dart b/packages/flutter/lib/src/widgets/icon_theme.dart index 91b6546c2e..4b0811c076 100644 --- a/packages/flutter/lib/src/widgets/icon_theme.dart +++ b/packages/flutter/lib/src/widgets/icon_theme.dart @@ -9,6 +9,9 @@ import 'framework.dart'; import 'icon_theme_data.dart'; import 'inherited_theme.dart'; +// Examples can assume: +// late BuildContext context; + /// Controls the default properties of icons in a widget subtree. /// /// The icon theme is honored by [Icon] and [ImageIcon] widgets. diff --git a/packages/flutter/lib/src/widgets/image.dart b/packages/flutter/lib/src/widgets/image.dart index a70318a178..16e59c475d 100644 --- a/packages/flutter/lib/src/widgets/image.dart +++ b/packages/flutter/lib/src/widgets/image.dart @@ -32,6 +32,10 @@ export 'package:flutter/painting.dart' show MemoryImage, NetworkImage; +// Examples can assume: +// late Widget image; +// late ImageProvider _image; + /// Creates an [ImageConfiguration] based on the given [BuildContext] (and /// optionally size). /// @@ -580,11 +584,9 @@ class Image extends StatefulWidget { /// bundled, the app has to specify which ones to include. For instance a /// package named `fancy_backgrounds` could have: /// - /// ``` - /// lib/backgrounds/background1.png - /// lib/backgrounds/background2.png - /// lib/backgrounds/background3.png - /// ``` + /// lib/backgrounds/background1.png + /// lib/backgrounds/background2.png + /// lib/backgrounds/background3.png /// /// To include, say the first image, the `pubspec.yaml` of the app should /// specify it in the assets section: @@ -732,10 +734,10 @@ class Image extends StatefulWidget { /// {@template flutter.widgets.Image.frameBuilder.chainedBuildersExample} /// ```dart /// Image( - /// ... - /// frameBuilder: (BuildContext context, Widget child, int frame, bool wasSynchronouslyLoaded) { + /// image: _image, + /// frameBuilder: (BuildContext context, Widget child, int? frame, bool? wasSynchronouslyLoaded) { /// return Padding( - /// padding: EdgeInsets.all(8.0), + /// padding: const EdgeInsets.all(8.0), /// child: child, /// ); /// }, @@ -749,11 +751,11 @@ class Image extends StatefulWidget { /// /// ```dart /// Center( - /// Padding( - /// padding: EdgeInsets.all(8.0), - /// child: , + /// child: Padding( + /// padding: const EdgeInsets.all(8.0), + /// child: image, /// ), - /// ) + /// ), /// ``` /// {@endtemplate} /// diff --git a/packages/flutter/lib/src/widgets/implicit_animations.dart b/packages/flutter/lib/src/widgets/implicit_animations.dart index 0c13604fe0..07187e9c83 100644 --- a/packages/flutter/lib/src/widgets/implicit_animations.dart +++ b/packages/flutter/lib/src/widgets/implicit_animations.dart @@ -21,7 +21,7 @@ import 'transitions.dart'; // const MyWidget({super.key, this.targetColor = Colors.black}) : super(duration: const Duration(seconds: 1)); // final Color targetColor; // @override -// MyWidgetState createState() => MyWidgetState(); +// ImplicitlyAnimatedWidgetState createState() => throw UnimplementedError(); // ignore: no_logic_in_create_state // } // void setState(VoidCallback fn) { } diff --git a/packages/flutter/lib/src/widgets/inherited_model.dart b/packages/flutter/lib/src/widgets/inherited_model.dart index 93ebfcd0ff..2eda456d36 100644 --- a/packages/flutter/lib/src/widgets/inherited_model.dart +++ b/packages/flutter/lib/src/widgets/inherited_model.dart @@ -33,6 +33,7 @@ import 'framework.dart'; /// /// ```dart /// class MyModel extends InheritedModel { +/// const MyModel({super.key, required super.child}); /// // ... /// static MyModel? of(BuildContext context, String aspect) { /// return InheritedModel.inheritFrom(context, aspect: aspect); diff --git a/packages/flutter/lib/src/widgets/inherited_theme.dart b/packages/flutter/lib/src/widgets/inherited_theme.dart index 0f6a43595d..920ebaf3fe 100644 --- a/packages/flutter/lib/src/widgets/inherited_theme.dart +++ b/packages/flutter/lib/src/widgets/inherited_theme.dart @@ -4,6 +4,9 @@ import 'framework.dart'; +// Examples can assume: +// TooltipThemeData data = const TooltipThemeData(); + /// An [InheritedWidget] that defines visual properties like colors /// and text styles, which the [child]'s subtree depends on. /// diff --git a/packages/flutter/lib/src/widgets/interactive_viewer.dart b/packages/flutter/lib/src/widgets/interactive_viewer.dart index 69e1aa1957..c1cbf3d542 100644 --- a/packages/flutter/lib/src/widgets/interactive_viewer.dart +++ b/packages/flutter/lib/src/widgets/interactive_viewer.dart @@ -15,6 +15,12 @@ import 'gesture_detector.dart'; import 'layout_builder.dart'; import 'ticker_provider.dart'; +// Examples can assume: +// late BuildContext context; +// late Offset? _childWasTappedAt; +// late TransformationController _transformationController; +// Widget child = const Placeholder(); + /// A signature for widget builders that take a [Quad] of the current viewport. /// /// See also: @@ -1141,7 +1147,7 @@ class TransformationController extends ValueNotifier { /// /// ```dart /// @override - /// void build(BuildContext context) { + /// Widget build(BuildContext context) { /// return GestureDetector( /// onTapUp: (TapUpDetails details) { /// _childWasTappedAt = _transformationController.toScene( diff --git a/packages/flutter/lib/src/widgets/localizations.dart b/packages/flutter/lib/src/widgets/localizations.dart index a490130ff9..5e22744ee3 100644 --- a/packages/flutter/lib/src/widgets/localizations.dart +++ b/packages/flutter/lib/src/widgets/localizations.dart @@ -13,6 +13,9 @@ import 'framework.dart'; // Examples can assume: // class Intl { Intl._(); static String message(String s, { String? name, String? locale }) => ''; } // Future initializeMessages(String locale) => Future.value(); +// late BuildContext context; +// class Foo { } +// const Widget myWidget = Placeholder(); // Used by loadAll() to record LocalizationsDelegate.load() futures we're // waiting for. @@ -92,6 +95,9 @@ Future> _loadAll(Locale locale, Iterable { /// Abstract const constructor. This constructor enables subclasses to provide /// const constructors so that they can be used in const expressions. @@ -106,9 +112,9 @@ abstract class LocalizationsDelegate { /// Start loading the resources for `locale`. The returned future completes /// when the resources have finished loading. /// - /// It's assumed that the this method will return an object that contains - /// a collection of related resources (typically defined with one method per - /// resource). The object will be retrieved with [Localizations.of]. + /// It's assumed that this method will return an object that contains a + /// collection of related string resources (typically defined with one method + /// per resource). The object will be retrieved with [Localizations.of]. Future load(Locale locale); /// Returns true if the resources for this delegate should be loaded @@ -125,8 +131,9 @@ abstract class LocalizationsDelegate { /// [LocalizationsDelegate] from the [Localizations] inherited widget. /// For example the object loaded by `LocalizationsDelegate` would /// be retrieved with: + /// /// ```dart - /// Foo foo = Localizations.of(context, Foo); + /// Foo foo = Localizations.of(context, Foo)!; /// ``` /// /// It's rarely necessary to override this getter. @@ -245,60 +252,7 @@ class _LocalizationsScope extends InheritedWidget { /// Defines the [Locale] for its `child` and the localized resources that the /// child depends on. /// -/// Localized resources are loaded by the list of [LocalizationsDelegate] -/// `delegates`. Each delegate is essentially a factory for a collection -/// of localized resources. There are multiple delegates because there are -/// multiple sources for localizations within an app. -/// -/// Delegates are typically simple subclasses of [LocalizationsDelegate] that -/// override [LocalizationsDelegate.load]. For example a delegate for the -/// `MyLocalizations` class defined below would be: -/// -/// ```dart -/// class _MyDelegate extends LocalizationsDelegate { -/// @override -/// Future load(Locale locale) => MyLocalizations.load(locale); -/// -/// @override -/// bool shouldReload(MyLocalizationsDelegate old) => false; -/// } -/// ``` -/// -/// Each delegate can be viewed as a factory for objects that encapsulate a -/// a set of localized resources. These objects are retrieved with -/// by runtime type with [Localizations.of]. -/// -/// The [WidgetsApp] class creates a `Localizations` widget so most apps -/// will not need to create one. The widget app's `Localizations` delegates can -/// be initialized with [WidgetsApp.localizationsDelegates]. The [MaterialApp] -/// class also provides a `localizationsDelegates` parameter that's just -/// passed along to the [WidgetsApp]. -/// -/// Apps should retrieve collections of localized resources with -/// `Localizations.of(context, MyLocalizations)`, -/// where MyLocalizations is an app specific class defines one function per -/// resource. This is conventionally done by a static `.of` method on the -/// MyLocalizations class. -/// -/// For example, using the `MyLocalizations` class defined below, one would -/// lookup a localized title string like this: -/// ```dart -/// MyLocalizations.of(context).title() -/// ``` -/// If `Localizations` were to be rebuilt with a new `locale` then -/// the widget subtree that corresponds to [BuildContext] `context` would -/// be rebuilt after the corresponding resources had been loaded. -/// -/// This class is effectively an [InheritedWidget]. If it's rebuilt with -/// a new `locale` or a different list of delegates or any of its -/// delegates' [LocalizationsDelegate.shouldReload()] methods returns true, -/// then widgets that have created a dependency by calling -/// `Localizations.of(context)` will be rebuilt after the resources -/// for the new locale have been loaded. -/// -/// The `Localizations` widget also instantiates [Directionality] in order to -/// support the appropriate [Directionality.textDirection] of the localized -/// resources. +/// ## Defining localized resources /// /// {@tool snippet} /// @@ -336,6 +290,76 @@ class _LocalizationsScope extends InheritedWidget { /// /// One could choose another approach for loading localized resources and looking them up while /// still conforming to the structure of this example. +/// +/// ## Loading localized resources +/// +/// Localized resources are loaded by the list of [LocalizationsDelegate] +/// `delegates`. Each delegate is essentially a factory for a collection +/// of localized resources. There are multiple delegates because there are +/// multiple sources for localizations within an app. +/// +/// Delegates are typically simple subclasses of [LocalizationsDelegate] that +/// override [LocalizationsDelegate.load]. For example a delegate for the +/// `MyLocalizations` class defined above would be: +/// +/// ```dart +/// // continuing from previous example... +/// class _MyDelegate extends LocalizationsDelegate { +/// @override +/// Future load(Locale locale) => MyLocalizations.load(locale); +/// +/// @override +/// bool isSupported(Locale locale) { +/// // in a real implementation this would only return true for +/// // locales that are definitely supported. +/// return true; +/// } +/// +/// @override +/// bool shouldReload(_MyDelegate old) => false; +/// } +/// ``` +/// +/// Each delegate can be viewed as a factory for objects that encapsulate a +/// a set of localized resources. These objects are retrieved with +/// by runtime type with [Localizations.of]. +/// +/// The [WidgetsApp] class creates a `Localizations` widget so most apps +/// will not need to create one. The widget app's `Localizations` delegates can +/// be initialized with [WidgetsApp.localizationsDelegates]. The [MaterialApp] +/// class also provides a `localizationsDelegates` parameter that's just +/// passed along to the [WidgetsApp]. +/// +/// ## Obtaining localized resources for use in user interfaces +/// +/// Apps should retrieve collections of localized resources with +/// `Localizations.of(context, MyLocalizations)`, +/// where MyLocalizations is an app specific class defines one function per +/// resource. This is conventionally done by a static `.of` method on the +/// custom localized resource class (`MyLocalizations` in the example above). +/// +/// For example, using the `MyLocalizations` class defined above, one would +/// lookup a localized title string like this: +/// +/// ```dart +/// // continuing from previous example... +/// MyLocalizations.of(context).title() +/// ``` +/// +/// If `Localizations` were to be rebuilt with a new `locale` then +/// the widget subtree that corresponds to [BuildContext] `context` would +/// be rebuilt after the corresponding resources had been loaded. +/// +/// This class is effectively an [InheritedWidget]. If it's rebuilt with +/// a new `locale` or a different list of delegates or any of its +/// delegates' [LocalizationsDelegate.shouldReload()] methods returns true, +/// then widgets that have created a dependency by calling +/// `Localizations.of(context)` will be rebuilt after the resources +/// for the new locale have been loaded. +/// +/// The `Localizations` widget also instantiates [Directionality] in order to +/// support the appropriate [Directionality.textDirection] of the localized +/// resources. class Localizations extends StatefulWidget { /// Create a widget from which localizations (like translated strings) can be obtained. Localizations({ @@ -463,7 +487,7 @@ class Localizations extends StatefulWidget { /// /// ```dart /// static MaterialLocalizations of(BuildContext context) { - /// return Localizations.of(context, MaterialLocalizations); + /// return Localizations.of(context, MaterialLocalizations)!; /// } /// ``` static T? of(BuildContext context, Type type) { diff --git a/packages/flutter/lib/src/widgets/media_query.dart b/packages/flutter/lib/src/widgets/media_query.dart index b25637c69a..db87ff08af 100644 --- a/packages/flutter/lib/src/widgets/media_query.dart +++ b/packages/flutter/lib/src/widgets/media_query.dart @@ -13,6 +13,9 @@ import 'binding.dart'; import 'debug.dart'; import 'framework.dart'; +// Examples can assume: +// late BuildContext context; + /// Whether in portrait or landscape. enum Orientation { /// Taller than wide. diff --git a/packages/flutter/lib/src/widgets/navigator.dart b/packages/flutter/lib/src/widgets/navigator.dart index 46ec06f4c2..d5b22b51c0 100644 --- a/packages/flutter/lib/src/widgets/navigator.dart +++ b/packages/flutter/lib/src/widgets/navigator.dart @@ -26,8 +26,9 @@ import 'routes.dart'; import 'ticker_provider.dart'; // Examples can assume: -// class MyPage extends Placeholder { const MyPage({super.key}); } -// class MyHomePage extends Placeholder { const MyHomePage({super.key}); } +// typedef MyAppHome = Placeholder; +// typedef MyHomePage = Placeholder; +// typedef MyPage = ListTile; // any const widget with a Widget "title" constructor argument would do // late NavigatorState navigator; // late BuildContext context; @@ -1118,7 +1119,7 @@ class DefaultTransitionDelegate extends TransitionDelegate { /// /// ```dart /// void main() { -/// runApp(MaterialApp(home: MyAppHome())); +/// runApp(const MaterialApp(home: MyAppHome())); /// } /// ``` /// @@ -1130,10 +1131,10 @@ class DefaultTransitionDelegate extends TransitionDelegate { /// Navigator.push(context, MaterialPageRoute( /// builder: (BuildContext context) { /// return Scaffold( -/// appBar: AppBar(title: Text('My Page')), +/// appBar: AppBar(title: const Text('My Page')), /// body: Center( /// child: TextButton( -/// child: Text('POP'), +/// child: const Text('POP'), /// onPressed: () { /// Navigator.pop(context); /// }, @@ -1176,11 +1177,11 @@ class DefaultTransitionDelegate extends TransitionDelegate { /// ```dart /// void main() { /// runApp(MaterialApp( -/// home: MyAppHome(), // becomes the route named '/' +/// home: const MyAppHome(), // becomes the route named '/' /// routes: { -/// '/a': (BuildContext context) => MyPage(title: 'page A'), -/// '/b': (BuildContext context) => MyPage(title: 'page B'), -/// '/c': (BuildContext context) => MyPage(title: 'page C'), +/// '/a': (BuildContext context) => const MyPage(title: Text('page A')), +/// '/b': (BuildContext context) => const MyPage(title: Text('page B')), +/// '/c': (BuildContext context) => const MyPage(title: Text('page C')), /// }, /// )); /// } @@ -1205,11 +1206,11 @@ class DefaultTransitionDelegate extends TransitionDelegate { /// operation we could `await` the result of [Navigator.push]: /// /// ```dart -/// bool value = await Navigator.push(context, MaterialPageRoute( +/// bool? value = await Navigator.push(context, MaterialPageRoute( /// builder: (BuildContext context) { /// return Center( /// child: GestureDetector( -/// child: Text('OK'), +/// child: const Text('OK'), /// onTap: () { Navigator.pop(context, true); } /// ), /// ); @@ -1257,10 +1258,10 @@ class DefaultTransitionDelegate extends TransitionDelegate { /// screen because it specifies `opaque: false`, just as a popup route does. /// /// ```dart -/// Navigator.push(context, PageRouteBuilder( +/// Navigator.push(context, PageRouteBuilder( /// opaque: false, /// pageBuilder: (BuildContext context, _, __) { -/// return Center(child: Text('My PageRoute')); +/// return const Center(child: Text('My PageRoute')); /// }, /// transitionsBuilder: (___, Animation animation, ____, Widget child) { /// return FadeTransition( @@ -1281,6 +1282,9 @@ class DefaultTransitionDelegate extends TransitionDelegate { /// and `__` in this example). The transition is built on every frame /// for its duration. /// +/// (In this example, `void` is used as the return type for the route, because +/// it does not return a value.) +/// /// ### Nesting Navigators /// /// An app can use more than one [Navigator]. Nesting one [Navigator] below @@ -3558,8 +3562,8 @@ class NavigatorState extends State with TickerProviderStateMixin, Res }()); // This attempts to diff the new pages list (widget.pages) with - // the old _RouteEntry[s] list (_history), and produces a new list of - // _RouteEntry[s] to be the new list of _history. This method roughly + // the old _RouteEntry(s) list (_history), and produces a new list of + // _RouteEntry(s) to be the new list of _history. This method roughly // follows the same outline of RenderObjectElement.updateChildren. // // The cases it tries to optimize for are: @@ -3587,11 +3591,11 @@ class NavigatorState extends State with TickerProviderStateMixin, Res // transitionDelegate. // * Sync keyed items with the source if it exists. // 5. Walk the narrowed part of the old list again to records the - // _RouteEntry[s], as well as pageless routes, needed to be removed for + // _RouteEntry(s), as well as pageless routes, needed to be removed for // transitionDelegate. // 5. Walk the top of the list again, syncing the nodes and recording // pageless routes. - // 6. Use transitionDelegate for explicit decisions on how _RouteEntry[s] + // 6. Use transitionDelegate for explicit decisions on how _RouteEntry(s) // transition in or off the screens. // 7. Fill pageless routes back into the new history. diff --git a/packages/flutter/lib/src/widgets/overlay.dart b/packages/flutter/lib/src/widgets/overlay.dart index 0b55e8d63f..380a6f934e 100644 --- a/packages/flutter/lib/src/widgets/overlay.dart +++ b/packages/flutter/lib/src/widgets/overlay.dart @@ -13,6 +13,9 @@ import 'basic.dart'; import 'framework.dart'; import 'ticker_provider.dart'; +// Examples can assume: +// late BuildContext context; + /// A place in an [Overlay] that can contain a widget. /// /// Overlay entries are inserted into an [Overlay] using the @@ -338,7 +341,7 @@ class Overlay extends StatefulWidget { /// Typical usage is as follows: /// /// ```dart - /// OverlayState overlay = Overlay.of(context); + /// OverlayState overlay = Overlay.of(context)!; /// ``` /// /// If `rootOverlay` is set to true, the state from the furthest instance of diff --git a/packages/flutter/lib/src/widgets/page_storage.dart b/packages/flutter/lib/src/widgets/page_storage.dart index 5e9406755b..21ead5eafd 100644 --- a/packages/flutter/lib/src/widgets/page_storage.dart +++ b/packages/flutter/lib/src/widgets/page_storage.dart @@ -6,6 +6,9 @@ import 'package:flutter/foundation.dart'; import 'framework.dart'; +// Examples can assume: +// late BuildContext context; + /// A key can be used to persist the widget state in storage after /// the destruction and will be restored when recreated. /// @@ -178,7 +181,7 @@ class PageStorage extends StatelessWidget { /// Typical usage is as follows: /// /// ```dart - /// PageStorageBucket bucket = PageStorage.of(context); + /// PageStorageBucket bucket = PageStorage.of(context)!; /// ``` /// /// This method can be expensive (it walks the element tree). diff --git a/packages/flutter/lib/src/widgets/platform_view.dart b/packages/flutter/lib/src/widgets/platform_view.dart index 672619741b..2dac509fd9 100644 --- a/packages/flutter/lib/src/widgets/platform_view.dart +++ b/packages/flutter/lib/src/widgets/platform_view.dart @@ -13,6 +13,11 @@ import 'focus_manager.dart'; import 'focus_scope.dart'; import 'framework.dart'; +// Examples can assume: +// PlatformViewController createFooWebView(PlatformViewCreationParams params) { return (null as dynamic) as PlatformViewController; } +// Set> gestureRecognizers = >{}; +// late PlatformViewController _controller; + /// Embeds an Android view in the Widget hierarchy. /// /// Requires Android API level 23 or greater. @@ -126,7 +131,7 @@ class AndroidView extends StatefulWidget { /// ```dart /// GestureDetector( /// onVerticalDragStart: (DragStartDetails d) {}, - /// child: AndroidView( + /// child: const AndroidView( /// viewType: 'webview', /// ), /// ) @@ -143,11 +148,11 @@ class AndroidView extends StatefulWidget { /// height: 100.0, /// child: AndroidView( /// viewType: 'webview', - /// gestureRecognizers: >[ + /// gestureRecognizers: >{ /// Factory( /// () => EagerGestureRecognizer(), /// ), - /// ].toSet(), + /// }, /// ), /// ), /// ) @@ -266,7 +271,7 @@ class UiKitView extends StatefulWidget { /// ```dart /// GestureDetector( /// onVerticalDragStart: (DragStartDetails details) {}, - /// child: UiKitView( + /// child: const UiKitView( /// viewType: 'webview', /// ), /// ) @@ -283,11 +288,11 @@ class UiKitView extends StatefulWidget { /// height: 100.0, /// child: UiKitView( /// viewType: 'webview', - /// gestureRecognizers: >[ + /// gestureRecognizers: >{ /// Factory( /// () => EagerGestureRecognizer(), /// ), - /// ].toSet(), + /// }, /// ), /// ), /// ) @@ -801,19 +806,21 @@ typedef CreatePlatformViewCallback = PlatformViewController Function(PlatformVie /// /// To implement a new platform view widget, return this widget in the `build` method. /// For example: +/// /// ```dart /// class FooPlatformView extends StatelessWidget { +/// const FooPlatformView({super.key}); /// @override /// Widget build(BuildContext context) { /// return PlatformViewLink( /// viewType: 'webview', /// onCreatePlatformView: createFooWebView, /// surfaceFactory: (BuildContext context, PlatformViewController controller) { -/// return PlatformViewSurface( -/// gestureRecognizers: gestureRecognizers, -/// controller: controller, -/// hitTestBehavior: PlatformViewHitTestBehavior.opaque, -/// ); +/// return PlatformViewSurface( +/// gestureRecognizers: gestureRecognizers, +/// controller: controller, +/// hitTestBehavior: PlatformViewHitTestBehavior.opaque, +/// ); /// }, /// ); /// } @@ -987,8 +994,11 @@ class PlatformViewSurface extends LeafRenderObjectWidget { /// /// ```dart /// GestureDetector( - /// onVerticalDragStart: (DragStartDetails details) {}, + /// onVerticalDragStart: (DragStartDetails details) { }, /// child: PlatformViewSurface( + /// gestureRecognizers: const >{}, + /// controller: _controller, + /// hitTestBehavior: PlatformViewHitTestBehavior.opaque, /// ), /// ) /// ``` @@ -998,16 +1008,18 @@ class PlatformViewSurface extends LeafRenderObjectWidget { /// /// ```dart /// GestureDetector( - /// onVerticalDragStart: (DragStartDetails details) {}, + /// onVerticalDragStart: (DragStartDetails details) { }, /// child: SizedBox( /// width: 200.0, /// height: 100.0, /// child: PlatformViewSurface( - /// gestureRecognizers: >[ + /// gestureRecognizers: >{ /// Factory( /// () => EagerGestureRecognizer(), /// ), - /// ].toSet(), + /// }, + /// controller: _controller, + /// hitTestBehavior: PlatformViewHitTestBehavior.opaque, /// ), /// ), /// ) diff --git a/packages/flutter/lib/src/widgets/reorderable_list.dart b/packages/flutter/lib/src/widgets/reorderable_list.dart index 9cd2c38302..2cfcca04af 100644 --- a/packages/flutter/lib/src/widgets/reorderable_list.dart +++ b/packages/flutter/lib/src/widgets/reorderable_list.dart @@ -323,10 +323,19 @@ class ReorderableList extends StatefulWidget { /// /// ```dart /// GlobalKey listKey = GlobalKey(); -/// ... -/// ReorderableList(key: listKey, ...); -/// ... -/// listKey.currentState.cancelReorder(); +/// // ... +/// Widget build(BuildContext context) { +/// return ReorderableList( +/// key: listKey, +/// itemBuilder: (BuildContext context, int index) => const SizedBox(height: 10.0), +/// itemCount: 5, +/// onReorder: (int oldIndex, int newIndex) { +/// // ... +/// }, +/// ); +/// } +/// // ... +/// listKey.currentState!.cancelReorder(); /// ``` class ReorderableListState extends State { final GlobalKey _sliverReorderableListKey = GlobalKey(); @@ -540,11 +549,28 @@ class SliverReorderableList extends StatefulWidget { /// can refer to the [SliverReorderableList]'s state with a global key: /// /// ```dart +/// // (e.g. in a stateful widget) /// GlobalKey listKey = GlobalKey(); -/// ... -/// SliverReorderableList(key: listKey, ...); -/// ... -/// listKey.currentState.cancelReorder(); +/// +/// // ... +/// +/// @override +/// Widget build(BuildContext context) { +/// return SliverReorderableList( +/// key: listKey, +/// itemBuilder: (BuildContext context, int index) => const SizedBox(height: 10.0), +/// itemCount: 5, +/// onReorder: (int oldIndex, int newIndex) { +/// // ... +/// }, +/// ); +/// } +/// +/// // ... +/// +/// void _stop() { +/// listKey.currentState!.cancelReorder(); +/// } /// ``` /// /// [ReorderableDragStartListener] and [ReorderableDelayedDragStartListener] diff --git a/packages/flutter/lib/src/widgets/routes.dart b/packages/flutter/lib/src/widgets/routes.dart index 62f876799f..116a347a80 100644 --- a/packages/flutter/lib/src/widgets/routes.dart +++ b/packages/flutter/lib/src/widgets/routes.dart @@ -26,11 +26,12 @@ import 'scroll_controller.dart'; import 'transitions.dart'; // Examples can assume: -// late RouteObserver> routeObserver; // late NavigatorState navigator; // late BuildContext context; // Future askTheUserIfTheyAreSure() async { return true; } // abstract class MyWidget extends StatefulWidget { const MyWidget({super.key}); } +// late dynamic _myState, newValue; +// late StateSetter setState; /// A route that displays widgets in the [Navigator]'s [Overlay]. abstract class OverlayRoute extends Route { @@ -987,7 +988,7 @@ abstract class ModalRoute extends TransitionRoute with LocalHistoryRoute extends ModalRoute { /// ```dart /// // Register the RouteObserver as a navigation observer. /// final RouteObserver> routeObserver = RouteObserver>(); +/// /// void main() { /// runApp(MaterialApp( /// home: Container(), diff --git a/packages/flutter/lib/src/widgets/scroll_notification_observer.dart b/packages/flutter/lib/src/widgets/scroll_notification_observer.dart index cf840c1137..8fae00e9b5 100644 --- a/packages/flutter/lib/src/widgets/scroll_notification_observer.dart +++ b/packages/flutter/lib/src/widgets/scroll_notification_observer.dart @@ -11,6 +11,10 @@ import 'notification_listener.dart'; import 'scroll_notification.dart'; import 'scroll_position.dart'; +// Examples can assume: +// void _listener(ScrollNotification notification) { } +// late BuildContext context; + /// A [ScrollNotification] listener for [ScrollNotificationObserver]. /// /// [ScrollNotificationObserver] is similar to @@ -39,25 +43,34 @@ class _ListenerEntry extends LinkedListEntry<_ListenerEntry> { /// Notifies its listeners when a descendant scrolls. /// /// To add a listener to a [ScrollNotificationObserver] ancestor: +/// /// ```dart -/// void listener(ScrollNotification notification) { -/// // Do something, maybe setState() -/// } -/// ScrollNotificationObserver.of(context).addListener(listener) +/// ScrollNotificationObserver.of(context)!.addListener(_listener); /// ``` /// /// To remove the listener from a [ScrollNotificationObserver] ancestor: +/// /// ```dart -/// ScrollNotificationObserver.of(context).removeListener(listener); +/// ScrollNotificationObserver.of(context)!.removeListener(_listener); /// ``` /// /// Stateful widgets that share an ancestor [ScrollNotificationObserver] typically /// add a listener in [State.didChangeDependencies] (removing the old one /// if necessary) and remove the listener in their [State.dispose] method. /// -/// This widget is similar to [NotificationListener]. It supports -/// a listener list instead of just a single listener and its listeners -/// run unconditionally, they do not require a gating boolean return value. +/// Any function with the [ScrollNotificationCallback] signature can act as a +/// listener: +/// +/// ```dart +/// // (e.g. in a stateful widget) +/// void _listener(ScrollNotification notification) { +/// // Do something, maybe setState() +/// } +/// ``` +/// +/// This widget is similar to [NotificationListener]. It supports a listener +/// list instead of just a single listener and its listeners run +/// unconditionally, they do not require a gating boolean return value. class ScrollNotificationObserver extends StatefulWidget { /// Create a [ScrollNotificationObserver]. /// diff --git a/packages/flutter/lib/src/widgets/scroll_physics.dart b/packages/flutter/lib/src/widgets/scroll_physics.dart index 6c66d9183c..90e513073e 100644 --- a/packages/flutter/lib/src/widgets/scroll_physics.dart +++ b/packages/flutter/lib/src/widgets/scroll_physics.dart @@ -52,7 +52,7 @@ export 'package:flutter/physics.dart' show ScrollSpringSimulation, Simulation, T /// ```dart /// ScrollPhysics physics = const BouncingScrollPhysics(); /// // ... -/// physics.applyTo(const AlwaysScrollableScrollPhysics()) +/// final ScrollPhysics mergedPhysics = physics.applyTo(const AlwaysScrollableScrollPhysics()); /// ``` @immutable class ScrollPhysics { @@ -82,8 +82,15 @@ class ScrollPhysics { /// This method is typically used to define [applyTo] methods like: /// /// ```dart - /// FooScrollPhysics applyTo(ScrollPhysics ancestor) { - /// return FooScrollPhysics(parent: buildParent(ancestor)); + /// class MyScrollPhysics extends ScrollPhysics { + /// const MyScrollPhysics({ super.parent }); + /// + /// @override + /// MyScrollPhysics applyTo(ScrollPhysics? ancestor) { + /// return MyScrollPhysics(parent: buildParent(ancestor)); + /// } + /// + /// // ... /// } /// ``` @protected @@ -127,8 +134,15 @@ class ScrollPhysics { /// [buildParent], as follows: /// /// ```dart - /// FooScrollPhysics applyTo(ScrollPhysics ancestor) { - /// return FooScrollPhysics(parent: buildParent(ancestor)); + /// class MyScrollPhysics extends ScrollPhysics { + /// const MyScrollPhysics({ super.parent }); + /// + /// @override + /// MyScrollPhysics applyTo(ScrollPhysics? ancestor) { + /// return MyScrollPhysics(parent: buildParent(ancestor)); + /// } + /// + /// // ... /// } /// ``` /// diff --git a/packages/flutter/lib/src/widgets/scroll_view.dart b/packages/flutter/lib/src/widgets/scroll_view.dart index f67be84150..26fb66b3a9 100644 --- a/packages/flutter/lib/src/widgets/scroll_view.dart +++ b/packages/flutter/lib/src/widgets/scroll_view.dart @@ -797,17 +797,19 @@ abstract class BoxScrollView extends ScrollView { /// final List entries = ['A', 'B', 'C']; /// final List colorCodes = [600, 500, 100]; /// -/// ListView.builder( -/// padding: const EdgeInsets.all(8), -/// itemCount: entries.length, -/// itemBuilder: (BuildContext context, int index) { -/// return Container( -/// height: 50, -/// color: Colors.amber[colorCodes[index]], -/// child: Center(child: Text('Entry ${entries[index]}')), -/// ); -/// } -/// ); +/// Widget build(BuildContext context) { +/// return ListView.builder( +/// padding: const EdgeInsets.all(8), +/// itemCount: entries.length, +/// itemBuilder: (BuildContext context, int index) { +/// return Container( +/// height: 50, +/// color: Colors.amber[colorCodes[index]], +/// child: Center(child: Text('Entry ${entries[index]}')), +/// ); +/// } +/// ); +/// } /// ``` /// {@end-tool} /// @@ -823,18 +825,20 @@ abstract class BoxScrollView extends ScrollView { /// final List entries = ['A', 'B', 'C']; /// final List colorCodes = [600, 500, 100]; /// -/// ListView.separated( -/// padding: const EdgeInsets.all(8), -/// itemCount: entries.length, -/// itemBuilder: (BuildContext context, int index) { -/// return Container( -/// height: 50, -/// color: Colors.amber[colorCodes[index]], -/// child: Center(child: Text('Entry ${entries[index]}')), -/// ); -/// }, -/// separatorBuilder: (BuildContext context, int index) => const Divider(), -/// ); +/// Widget build(BuildContext context) { +/// return ListView.separated( +/// padding: const EdgeInsets.all(8), +/// itemCount: entries.length, +/// itemBuilder: (BuildContext context, int index) { +/// return Container( +/// height: 50, +/// color: Colors.amber[colorCodes[index]], +/// child: Center(child: Text('Entry ${entries[index]}')), +/// ); +/// }, +/// separatorBuilder: (BuildContext context, int index) => const Divider(), +/// ); +/// } /// ``` /// {@end-tool} /// diff --git a/packages/flutter/lib/src/widgets/scrollable.dart b/packages/flutter/lib/src/widgets/scrollable.dart index 2a65b2e498..1be6deccfe 100644 --- a/packages/flutter/lib/src/widgets/scrollable.dart +++ b/packages/flutter/lib/src/widgets/scrollable.dart @@ -35,6 +35,9 @@ import 'viewport.dart'; export 'package:flutter/physics.dart' show Tolerance; +// Examples can assume: +// late BuildContext context; + /// Signature used by [Scrollable] to build the viewport through which the /// scrollable content is displayed. typedef ViewportBuilder = Widget Function(BuildContext context, ViewportOffset position); @@ -291,7 +294,7 @@ class Scrollable extends StatefulWidget { /// Typical usage is as follows: /// /// ```dart - /// ScrollableState scrollable = Scrollable.of(context); + /// ScrollableState scrollable = Scrollable.of(context)!; /// ``` /// /// Calling this method will create a dependency on the closest [Scrollable] diff --git a/packages/flutter/lib/src/widgets/scrollbar.dart b/packages/flutter/lib/src/widgets/scrollbar.dart index 971d9ee0fe..26434e3bf5 100644 --- a/packages/flutter/lib/src/widgets/scrollbar.dart +++ b/packages/flutter/lib/src/widgets/scrollbar.dart @@ -1030,9 +1030,12 @@ class RawScrollbar extends StatefulWidget { /// scrollbar dragging for multiple independent ListViews: /// /// ```dart + /// // (e.g. in a stateful widget) + /// /// final ScrollController controllerOne = ScrollController(); /// final ScrollController controllerTwo = ScrollController(); /// + /// @override /// Widget build(BuildContext context) { /// return Column( /// children: [ @@ -1091,9 +1094,12 @@ class RawScrollbar extends StatefulWidget { /// {@tool snippet} /// /// ```dart + /// // (e.g. in a stateful widget) + /// /// final ScrollController controllerOne = ScrollController(); /// final ScrollController controllerTwo = ScrollController(); /// + /// @override /// Widget build(BuildContext context) { /// return Column( /// children: [ @@ -1173,9 +1179,12 @@ class RawScrollbar extends StatefulWidget { /// {@tool snippet} /// /// ```dart + /// // (e.g. in a stateful widget) + /// /// final ScrollController controllerOne = ScrollController(); /// final ScrollController controllerTwo = ScrollController(); /// + /// @override /// Widget build(BuildContext context) { /// return Column( /// children: [ diff --git a/packages/flutter/lib/src/widgets/selectable_region.dart b/packages/flutter/lib/src/widgets/selectable_region.dart index 7d3acc5d77..2aa7624a6f 100644 --- a/packages/flutter/lib/src/widgets/selectable_region.dart +++ b/packages/flutter/lib/src/widgets/selectable_region.dart @@ -92,7 +92,6 @@ const Set _kLongPressSelectionDevices = { /// ``` /// {@end-tool} /// -/// ``` /// /// SelectionContainer /// (SelectableRegion) @@ -110,7 +109,6 @@ const Set _kLongPressSelectionDevices = { /// Selectable Selectable /// ("Item 0") ("Item 1") /// -///``` /// /// ## Making a widget selectable /// diff --git a/packages/flutter/lib/src/widgets/sliver.dart b/packages/flutter/lib/src/widgets/sliver.dart index 4794e3c71e..69cacdb2b3 100644 --- a/packages/flutter/lib/src/widgets/sliver.dart +++ b/packages/flutter/lib/src/widgets/sliver.dart @@ -19,6 +19,8 @@ export 'package:flutter/rendering.dart' show // Examples can assume: // late SliverGridDelegateWithMaxCrossAxisExtent _gridDelegate; +// abstract class SomeWidget extends StatefulWidget { const SomeWidget({super.key}); } +// typedef ChildWidget = Placeholder; /// A callback which produces a semantic index given a widget and the local index. /// @@ -677,11 +679,7 @@ class SliverChildListDelegate extends SliverChildDelegate { /// /// ```dart /// class SomeWidgetState extends State { - /// List _children; - /// - /// void initState() { - /// _children = []; - /// } + /// final List _children = []; /// /// void someHandler() { /// setState(() { @@ -691,6 +689,7 @@ class SliverChildListDelegate extends SliverChildDelegate { /// }); /// } /// + /// @override /// Widget build(BuildContext context) { /// // Always create a new list of children as a Widget is immutable. /// return PageView(children: List.of(_children)); @@ -1639,30 +1638,18 @@ class SliverMultiBoxAdaptorElement extends RenderObjectElement implements Render /// For the value 1.0, the sliver child is painted immediately without an /// intermediate buffer. /// -/// {@tool snippet} +/// {@tool dartpad} /// /// This example shows a [SliverList] when the `_visible` member field is true, -/// and hides it when it is false: +/// and hides it when it is false. /// -/// ```dart -/// bool _visible = true; -/// List listItems = const [ -/// Text('Now you see me,'), -/// Text("Now you don't!"), -/// ]; +/// This is more efficient than adding and removing the sliver child widget from +/// the tree on demand, but it does not affect how much the list scrolls (the +/// [SliverList] is still present, merely invisible). /// -/// SliverOpacity( -/// opacity: _visible ? 1.0 : 0.0, -/// sliver: SliverList( -/// delegate: SliverChildListDelegate(listItems), -/// ), -/// ) -/// ``` +/// ** See code in examples/api/lib/widgets/sliver/sliver_opacity.1.dart ** /// {@end-tool} /// -/// This is more efficient than adding and removing the sliver child widget -/// from the tree on demand. -/// /// See also: /// /// * [Opacity], which can apply a uniform alpha effect to its child using the diff --git a/packages/flutter/lib/src/widgets/table.dart b/packages/flutter/lib/src/widgets/table.dart index be2f9f0b68..62b3603a70 100644 --- a/packages/flutter/lib/src/widgets/table.dart +++ b/packages/flutter/lib/src/widgets/table.dart @@ -174,15 +174,12 @@ class Table extends RenderObjectWidget { : null { assert(() { final List flatChildren = children.expand((TableRow row) => row.children!).toList(growable: false); - if (debugChildrenHaveDuplicateKeys(this, flatChildren)) { - throw FlutterError( - 'Two or more cells in this Table contain widgets with the same key.\n' - 'Every widget child of every TableRow in a Table must have different keys. The cells of a Table are ' - 'flattened out for processing, so separate cells cannot have duplicate keys even if they are in ' - 'different rows.', - ); - } - return true; + return !debugChildrenHaveDuplicateKeys(this, flatChildren, message: + 'Two or more cells in this Table contain widgets with the same key.\n' + 'Every widget child of every TableRow in a Table must have different keys. The cells of a Table are ' + 'flattened out for processing, so separate cells cannot have duplicate keys even if they are in ' + 'different rows.', + ); }()); } diff --git a/packages/flutter/lib/src/widgets/text.dart b/packages/flutter/lib/src/widgets/text.dart index ac0842c675..e656f01139 100644 --- a/packages/flutter/lib/src/widgets/text.dart +++ b/packages/flutter/lib/src/widgets/text.dart @@ -15,6 +15,7 @@ import 'selection_container.dart'; // Examples can assume: // late String _name; +// late BuildContext context; /// The text style to apply to descendant [Text] widgets which don't have an /// explicit style. @@ -251,7 +252,7 @@ class DefaultTextHeightBehavior extends InheritedTheme { /// Typical usage is as follows: /// /// ```dart - /// DefaultTextHeightBehavior defaultTextHeightBehavior = DefaultTextHeightBehavior.of(context); + /// TextHeightBehavior defaultTextHeightBehavior = DefaultTextHeightBehavior.of(context)!; /// ``` static TextHeightBehavior? of(BuildContext context) { return context.dependOnInheritedWidgetOfExactType()?.textHeightBehavior; @@ -520,7 +521,7 @@ class Text extends StatelessWidget { /// text value: /// /// ```dart - /// Text(r'$$', semanticsLabel: 'Double dollars') + /// const Text(r'$$', semanticsLabel: 'Double dollars') /// ``` /// {@endtemplate} final String? semanticsLabel; diff --git a/packages/flutter/lib/src/widgets/ticker_provider.dart b/packages/flutter/lib/src/widgets/ticker_provider.dart index 2d6603e924..56202ba8c2 100644 --- a/packages/flutter/lib/src/widgets/ticker_provider.dart +++ b/packages/flutter/lib/src/widgets/ticker_provider.dart @@ -9,6 +9,9 @@ import 'framework.dart'; export 'package:flutter/scheduler.dart' show TickerProvider; +// Examples can assume: +// late BuildContext context; + /// Enables or disables tickers (and thus animation controllers) in the widget /// subtree. /// diff --git a/packages/flutter/lib/src/widgets/widget_inspector.dart b/packages/flutter/lib/src/widgets/widget_inspector.dart index 55d3dbbc30..0416b26d30 100644 --- a/packages/flutter/lib/src/widgets/widget_inspector.dart +++ b/packages/flutter/lib/src/widgets/widget_inspector.dart @@ -3297,45 +3297,6 @@ class InspectorSerializationDelegate implements DiagnosticsSerializationDelegate /// This callback can be used to customize the serialization of DiagnosticsNode /// objects for experimental features in widget inspector clients such as /// [Dart DevTools](https://github.com/flutter/devtools). - /// For example, [Dart DevTools](https://github.com/flutter/devtools) - /// can evaluate the following expression to register a VM Service API - /// with a custom serialization to experiment with visualizing layouts. - /// - /// The following code samples demonstrates adding the [RenderObject] associated - /// with an [Element] to the serialized data for all elements in the tree: - /// - /// ```dart - /// Map getDetailsSubtreeWithRenderObject( - /// String id, - /// String groupName, - /// int subtreeDepth, - /// ) { - /// return _nodeToJson( - /// root, - /// InspectorSerializationDelegate( - /// groupName: groupName, - /// summaryTree: false, - /// subtreeDepth: subtreeDepth, - /// includeProperties: true, - /// service: this, - /// addAdditionalPropertiesCallback: (DiagnosticsNode node, _SerializationDelegate delegate) { - /// final Map additionalJson = {}; - /// final Object value = node.value; - /// if (value is Element) { - /// final renderObject = value.renderObject; - /// additionalJson['renderObject'] = renderObject?.toDiagnosticsNode()?.toJsonMap( - /// delegate.copyWith( - /// subtreeDepth: 0, - /// includeProperties: true, - /// ), - /// ); - /// } - /// return additionalJson; - /// }, - /// ), - /// ); - /// } - /// ``` final Map? Function(DiagnosticsNode, InspectorSerializationDelegate)? addAdditionalPropertiesCallback; final List _nodesCreatedByLocalProject = []; diff --git a/packages/flutter/lib/src/widgets/widget_span.dart b/packages/flutter/lib/src/widgets/widget_span.dart index 735f2cd58e..4e6c588f8d 100644 --- a/packages/flutter/lib/src/widgets/widget_span.dart +++ b/packages/flutter/lib/src/widgets/widget_span.dart @@ -8,6 +8,9 @@ import 'package:flutter/painting.dart'; import 'framework.dart'; +// Examples can assume: +// late WidgetSpan myWidgetSpan; + /// An immutable widget that is embedded inline within text. /// /// The [child] property is the widget that will be embedded. Children are diff --git a/packages/flutter/test/cupertino/debug_test.dart b/packages/flutter/test/cupertino/debug_test.dart index f69789f142..85dca9e404 100644 --- a/packages/flutter/test/cupertino/debug_test.dart +++ b/packages/flutter/test/cupertino/debug_test.dart @@ -3,7 +3,6 @@ // found in the LICENSE file. import 'package:flutter/cupertino.dart'; -import 'package:flutter/src/cupertino/debug.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { diff --git a/packages/flutter/test/widgets/debug_test.dart b/packages/flutter/test/widgets/debug_test.dart index 0ddcf3c5a5..15da4c42d2 100644 --- a/packages/flutter/test/widgets/debug_test.dart +++ b/packages/flutter/test/widgets/debug_test.dart @@ -31,8 +31,8 @@ void main() { equalsIgnoringHashCodes( 'FlutterError\n' ' Duplicate keys found.\n' - ' If multiple keyed nodes exist as children of another node, they\n' - ' must have unique keys.\n' + ' If multiple keyed widgets exist as children of another widget,\n' + ' they must have unique keys.\n' ' Flex(direction: vertical, mainAxisAlignment: start,\n' ' crossAxisAlignment: center) has multiple children with key\n' " [<'key'>].\n", diff --git a/packages/flutter/test/widgets/framework_test.dart b/packages/flutter/test/widgets/framework_test.dart index 0787033d17..47eac39c3b 100644 --- a/packages/flutter/test/widgets/framework_test.dart +++ b/packages/flutter/test/widgets/framework_test.dart @@ -514,7 +514,7 @@ void main() { exception.toString(), equalsIgnoringHashCodes( 'Duplicate keys found.\n' - 'If multiple keyed nodes exist as children of another node, they must have unique keys.\n' + 'If multiple keyed widgets exist as children of another widget, they must have unique keys.\n' 'Stack(alignment: AlignmentDirectional.topStart, textDirection: ltr, fit: loose) has multiple children with key [GlobalKey#00000 problematic].' ), ); @@ -541,7 +541,7 @@ void main() { exception.toString(), equalsIgnoringHashCodes( 'Duplicate keys found.\n' - 'If multiple keyed nodes exist as children of another node, they must have unique keys.\n' + 'If multiple keyed widgets exist as children of another widget, they must have unique keys.\n' 'Stack(alignment: AlignmentDirectional.topStart, textDirection: ltr, fit: loose) has multiple children with key [GlobalKey#00000 problematic].' ), ); @@ -717,7 +717,7 @@ void main() { exception.toString(), equalsIgnoringHashCodes( 'Duplicate keys found.\n' - 'If multiple keyed nodes exist as children of another node, they must have unique keys.\n' + 'If multiple keyed widgets exist as children of another widget, they must have unique keys.\n' 'Stack(alignment: AlignmentDirectional.topStart, textDirection: ltr, fit: loose) has multiple children with key [GlobalKey#00000 problematic].', ), );