diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 3967cd447..1b8b4368e 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -33,7 +33,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@c7f292ea4f542c473194b33813ccd4c207a6c725 + uses: github/codeql-action/init@904260d7d935dff982205cbdb42025ce30b7a34f with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -44,7 +44,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@c7f292ea4f542c473194b33813ccd4c207a6c725 + uses: github/codeql-action/autobuild@904260d7d935dff982205cbdb42025ce30b7a34f # ℹ️ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -58,4 +58,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@c7f292ea4f542c473194b33813ccd4c207a6c725 + uses: github/codeql-action/analyze@904260d7d935dff982205cbdb42025ce30b7a34f diff --git a/.github/workflows/scorecards-analysis.yml b/.github/workflows/scorecards-analysis.yml index 725eac6d6..8f64919b5 100644 --- a/.github/workflows/scorecards-analysis.yml +++ b/.github/workflows/scorecards-analysis.yml @@ -17,6 +17,7 @@ jobs: security-events: write actions: read contents: read + id-token: write steps: - name: "Checkout code" @@ -25,7 +26,7 @@ jobs: persist-credentials: false - name: "Run analysis" - uses: ossf/scorecard-action@ce330fde6b1a5c9c75b417e7efc510b822a35564 + uses: ossf/scorecard-action@865b4092859256271290c77adbd10a43f4779972 with: results_file: results.sarif results_format: sarif @@ -48,6 +49,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@c7f292ea4f542c473194b33813ccd4c207a6c725 + uses: github/codeql-action/upload-sarif@904260d7d935dff982205cbdb42025ce30b7a34f with: sarif_file: results.sarif diff --git a/Dockerfile b/Dockerfile index 6018416e7..e37964a43 100644 --- a/Dockerfile +++ b/Dockerfile @@ -33,22 +33,22 @@ ENV PATH=$DART_SDK/bin:$PATH RUN set -eu; \ case "$(dpkg --print-architecture)_${DART_CHANNEL}" in \ amd64_stable) \ - DART_SHA256="e391c4ed8f623b9748f897cb585d629057c1141f9eaf8e9b2be118932ba11632"; \ + DART_SHA256="7369fcbfb3c96ed85511224580a8ce0e5ecfc8227af563d396ede2055cdf84e1"; \ SDK_ARCH="x64";; \ arm64_stable) \ - DART_SHA256="06dd7c6eb6c903f5df8b23f9a35f7b1c35ccb869be6b5019c7dd93868ae2bfbf"; \ + DART_SHA256="1c8d96c0b718873f9ae6d9aa7d866cddefdb86f8b1584cbdf589670d65b7c167"; \ SDK_ARCH="arm64";; \ amd64_beta) \ - DART_SHA256="e391c4ed8f623b9748f897cb585d629057c1141f9eaf8e9b2be118932ba11632"; \ + DART_SHA256="602d5936c3fad0776b999df2090c4c53354891f386fa71e0d7de9923e94c16e6"; \ SDK_ARCH="x64";; \ arm64_beta) \ - DART_SHA256="06dd7c6eb6c903f5df8b23f9a35f7b1c35ccb869be6b5019c7dd93868ae2bfbf"; \ + DART_SHA256="a1538410aed74e4f1c42962044ec7341b5ea6a74f815b049352f72c1f75e988b"; \ SDK_ARCH="arm64";; \ amd64_dev) \ - DART_SHA256="688f8b5238632f53f6cbddee7ded17c665dccb00fb639ccab32a43437f18a4c8"; \ + DART_SHA256="bb9bed8d193c5557f617eb4b89d4aee5894a806d5560157fbe6f5142ac1fadd0"; \ SDK_ARCH="x64";; \ arm64_dev) \ - DART_SHA256="86c628337919bf7802a8551ce338046a95035dd7f075363811576e2828a0d415"; \ + DART_SHA256="08379b473b7b9fd60ebafee20e4646053b0395e3d2eed49b243b93802a1ec201"; \ SDK_ARCH="arm64";; \ esac; \ SDK="dartsdk-linux-${SDK_ARCH}-release.zip"; \ @@ -80,7 +80,7 @@ CMD ["./tool/test.sh"] FROM dart as node RUN set -eu; \ NODE_PPA="node_ppa.sh"; \ - NODE_SHA256=27932797347f900242caaaeba5c1d7c965b3da70566d81123b15be1c0b80cc2c; \ + NODE_SHA256=99b85ba817cafd99b50e292a6690d8663b27a76630ac6339c2fc186e3333f4a3; \ curl -fsSL https://deb.nodesource.com/setup_lts.x -o "$NODE_PPA"; \ echo "$NODE_SHA256 $NODE_PPA" | sha256sum --check --status --strict - || (\ echo -e "\n\nNODE CHECKSUM FAILED! Run tool/fetch-node-ppa-sum.sh for updated values.\n\n" && \ diff --git a/examples/async_await/lib/practice_errors/main.dart b/examples/async_await/lib/practice_errors/main.dart new file mode 100644 index 000000000..f4c6813e3 --- /dev/null +++ b/examples/async_await/lib/practice_errors/main.dart @@ -0,0 +1,6 @@ +// ignore_for_file: always_declare_return_types, type_annotate_public_apis + +// #docregion +// Implement changeUsername here +changeUsername() {} +// #enddocregion diff --git a/examples/async_await/lib/practice_errors/solution.dart b/examples/async_await/lib/practice_errors/solution.dart new file mode 100644 index 000000000..19ba318e0 --- /dev/null +++ b/examples/async_await/lib/practice_errors/solution.dart @@ -0,0 +1,11 @@ +import 'test.dart'; + +// #docregion +Future changeUsername() async { + try { + return await fetchNewUsername(); + } catch (err) { + return err.toString(); + } +} +// #enddocregion diff --git a/examples/async_await/lib/practice_errors/test.dart b/examples/async_await/lib/practice_errors/test.dart new file mode 100644 index 000000000..b5fec94ba --- /dev/null +++ b/examples/async_await/lib/practice_errors/test.dart @@ -0,0 +1,112 @@ +import 'package:examples_util/codelabs.dart'; + +import 'solution.dart'; + +const _result = result; + +// #docregion +List messages = []; +bool logoutSucceeds = false; +const passed = 'PASSED'; +const noCatch = 'NO_CATCH'; +const typoMessage = 'Test failed! Check for typos in your return value'; +const oneSecond = Duration(seconds: 1); + +class UserError implements Exception { + String errMsg() => 'New username is invalid'; +} + +Future fetchNewUsername() { + var str = Future.delayed(oneSecond, () => throw UserError()); + return str; +} + +void main() async { + try { + messages + ..add(makeReadable( + testLabel: '', + testResult: await asyncDidCatchException(changeUsername), + readableErrors: { + typoMessage: typoMessage, + noCatch: + 'Did you remember to call fetchNewUsername within a try/catch block?', + })) + ..add(makeReadable( + testLabel: '', + testResult: await asyncErrorEquals(changeUsername), + readableErrors: { + typoMessage: typoMessage, + noCatch: + 'Did you remember to call fetchNewUsername within a try/catch block?', + })) + ..removeWhere((m) => m.contains(passed)) + ..toList(); + + if (messages.isEmpty) { + _result(true); + } else { + _result(false, messages); + } + } catch (e) { + _result(false, ['Tried to run solution, but received an exception: $e']); + } +} + +//////////////////////////////////////// +///////////// Test Helpers ///////////// +//////////////////////////////////////// +String makeReadable({ + required String testResult, + required Map readableErrors, + required String testLabel, +}) { + if (readableErrors.containsKey(testResult)) { + var readable = readableErrors[testResult]; + return '$testLabel $readable'; + } else { + return '$testLabel $testResult'; + } +} + +void passIfNoMessages(List messages, Map readable) { + if (messages.isEmpty) { + _result(true); + } else { + final userMessages = messages + .where((message) => readable.containsKey(message)) + .map((message) => readable[message]!) + .toList(); + print(messages); + + _result(false, userMessages); + } +} + +/////////////////////////////////////// +//////////// Assertions /////////////// +/////////////////////////////////////// +Future asyncErrorEquals(Function fn) async { + var result = await fn(); + if (result == UserError().toString()) { + return passed; + } else { + return 'Test failed! Did you stringify and return the caught error?'; + } +} + +Future asyncDidCatchException(Function fn) async { + var caught = true; + try { + await fn(); + } on UserError catch (_) { + caught = false; + } + + if (caught == false) { + return noCatch; + } else { + return passed; + } +} +// #enddocregion diff --git a/examples/async_await/lib/practice_using/main.dart b/examples/async_await/lib/practice_using/main.dart new file mode 100644 index 000000000..24ee37700 --- /dev/null +++ b/examples/async_await/lib/practice_using/main.dart @@ -0,0 +1,17 @@ +// ignore_for_file: always_declare_return_types, type_annotate_public_apis +import 'package:examples_util/codelabs.dart'; + +// #docregion +// Part 1 +// You can call the provided async function fetchRole() +// to return the user role. +Future reportUserRole() async { + TODO('Your implementation goes here.'); +} + +// Part 2 +// Implement reportLogins here +// You can call the provided async function fetchLoginAmount() +// to return the number of times that the user has logged in. +reportLogins() {} +// #enddocregion diff --git a/examples/async_await/lib/practice_using/solution.dart b/examples/async_await/lib/practice_using/solution.dart new file mode 100644 index 000000000..84327a2b0 --- /dev/null +++ b/examples/async_await/lib/practice_using/solution.dart @@ -0,0 +1,13 @@ +import 'test.dart'; + +// #docregion +Future reportUserRole() async { + var username = await fetchRole(); + return 'User role: $username'; +} + +Future reportLogins() async { + var logins = await fetchLoginAmount(); + return 'Total number of logins: $logins'; +} +// #enddocregion diff --git a/examples/async_await/lib/practice_using/test.dart b/examples/async_await/lib/practice_using/test.dart new file mode 100644 index 000000000..ea2fb8da2 --- /dev/null +++ b/examples/async_await/lib/practice_using/test.dart @@ -0,0 +1,117 @@ +import 'package:examples_util/codelabs.dart'; + +import 'solution.dart'; + +const _result = result; + +// #docregion +const role = 'administrator'; +const logins = 42; +const passed = 'PASSED'; +const testFailedMessage = 'Test failed for the function:'; +const typoMessage = 'Test failed! Check for typos in your return value'; +const didNotImplement = + 'Test failed! Did you forget to implement or return from '; +const oneSecond = Duration(seconds: 1); +List messages = []; +Future fetchRole() => Future.delayed(oneSecond, () => role); +Future fetchLoginAmount() => Future.delayed(oneSecond, () => logins); + +void main() async { + try { + messages + ..add(makeReadable( + testLabel: 'Part 1', + testResult: await asyncEquals( + expected: 'User role: administrator', + actual: await reportUserRole(), + typoKeyword: role), + readableErrors: { + typoMessage: typoMessage, + 'null': '$didNotImplement reportUserRole?', + 'User role: Instance of \'Future\'': + '$testFailedMessage reportUserRole. Did you use the await keyword?', + 'User role: Instance of \'_Future\'': + '$testFailedMessage reportUserRole. Did you use the await keyword?', + 'User role:': + '$testFailedMessage reportUserRole. Did you return a user role?', + 'User role: ': + '$testFailedMessage reportUserRole. Did you return a user role?', + 'User role: tester': + '$testFailedMessage reportUserRole. Did you invoke fetchRole to fetch the user\'s role?', + })) + ..add(makeReadable( + testLabel: 'Part 2', + testResult: await asyncEquals( + expected: 'Total number of logins: 42', + actual: await reportLogins(), + typoKeyword: logins.toString()), + readableErrors: { + typoMessage: typoMessage, + 'null': '$didNotImplement reportLogins?', + 'Total number of logins: Instance of \'Future\'': + '$testFailedMessage reportLogins. Did you use the await keyword?', + 'Total number of logins: Instance of \'_Future\'': + '$testFailedMessage reportLogins. Did you use the await keyword?', + 'Total number of logins: ': + '$testFailedMessage reportLogins. Did you return the number of logins?', + 'Total number of logins:': + '$testFailedMessage reportLogins. Did you return the number of logins?', + 'Total number of logins: 57': + '$testFailedMessage reportLogins. Did you invoke fetchLoginAmount to fetch the number of user logins?', + })) + ..removeWhere((m) => m.contains(passed)) + ..toList(); + + if (messages.isEmpty) { + _result(true); + } else { + _result(false, messages); + } + } on UnimplementedError { + _result(false, [ + '$didNotImplement reportUserRole?', + ]); + } catch (e) { + _result(false, ['Tried to run solution, but received an exception: $e']); + } +} + +//////////////////////////////////////// +///////////// Test Helpers ///////////// +//////////////////////////////////////// +String makeReadable({ + required String testResult, + required Map readableErrors, + required String testLabel, +}) { + if (readableErrors.containsKey(testResult)) { + var readable = readableErrors[testResult]; + return '$testLabel $readable'; + } else { + return '$testLabel $testResult'; + } +} + +/////////////////////////////////////// +//////////// Assertions /////////////// +/////////////////////////////////////// +Future asyncEquals({ + required String expected, + required dynamic actual, + required String typoKeyword, +}) async { + var strActual = actual is String ? actual : actual.toString(); + try { + if (expected == actual) { + return passed; + } else if (strActual.contains(typoKeyword)) { + return typoMessage; + } else { + return strActual; + } + } catch (e) { + return e.toString(); + } +} +// #enddocregion diff --git a/examples/async_await/lib/putting_together/main.dart b/examples/async_await/lib/putting_together/main.dart new file mode 100644 index 000000000..e32663157 --- /dev/null +++ b/examples/async_await/lib/putting_together/main.dart @@ -0,0 +1,16 @@ +// ignore_for_file: always_declare_return_types, type_annotate_public_apis + +// #docregion +// Part 1 +addHello(String user) {} + +// Part 2 +// You can call the provided async function fetchUsername() +// to return the username. +greetUser() {} + +// Part 3 +// You can call the provided async function logoutUser() +// to log out the user. +sayGoodbye() {} +// #enddocregion diff --git a/examples/async_await/lib/putting_together/solution.dart b/examples/async_await/lib/putting_together/solution.dart new file mode 100644 index 000000000..516ef5c08 --- /dev/null +++ b/examples/async_await/lib/putting_together/solution.dart @@ -0,0 +1,19 @@ +import 'test.dart'; + +// #docregion +String addHello(String user) => 'Hello $user'; + +Future greetUser() async { + var username = await fetchUsername(); + return addHello(username); +} + +Future sayGoodbye() async { + try { + var result = await logoutUser(); + return '$result Thanks, see you next time'; + } catch (e) { + return 'Failed to logout user: $e'; + } +} +// #enddocregion diff --git a/examples/async_await/lib/putting_together/test.dart b/examples/async_await/lib/putting_together/test.dart new file mode 100644 index 000000000..4e513f402 --- /dev/null +++ b/examples/async_await/lib/putting_together/test.dart @@ -0,0 +1,182 @@ +import 'package:examples_util/codelabs.dart'; + +import 'solution.dart'; + +const _result = result; + +// #docregion +List messages = []; +bool logoutSucceeds = false; +const passed = 'PASSED'; +const noCatch = 'NO_CATCH'; +const typoMessage = 'Test failed! Check for typos in your return value'; +const didNotImplement = + 'Test failed! Did you forget to implement or return from '; +const oneSecond = Duration(seconds: 1); + +Future fetchUsername() => Future.delayed(oneSecond, () => 'Jean'); +String failOnce() { + if (logoutSucceeds) { + return 'Success!'; + } else { + logoutSucceeds = true; + throw Exception('Logout failed'); + } +} + +Future logoutUser() => Future.delayed(oneSecond, failOnce); + +void main() async { + try { + messages + ..add(makeReadable( + testLabel: 'Part 1', + testResult: await asyncEquals( + expected: 'Hello Jerry', + actual: addHello('Jerry'), + typoKeyword: 'Jerry'), + readableErrors: { + typoMessage: typoMessage, + 'null': '$didNotImplement addHello?', + 'Hello Instance of \'Future\'': + 'Looks like you forgot to use the \'await\' keyword!', + 'Hello Instance of \'_Future\'': + 'Looks like you forgot to use the \'await\' keyword!', + })) + ..add(makeReadable( + testLabel: 'Part 2', + testResult: await asyncEquals( + expected: 'Hello Jean', + actual: await greetUser(), + typoKeyword: 'Jean'), + readableErrors: { + typoMessage: typoMessage, + 'null': '$didNotImplement greetUser?', + 'HelloJean': + 'Looks like you forgot the space between \'Hello\' and \'Jean\'', + 'Hello Instance of \'Future\'': + 'Looks like you forgot to use the \'await\' keyword!', + 'Hello Instance of \'_Future\'': + 'Looks like you forgot to use the \'await\' keyword!', + '{Closure: (String) => dynamic from Function \'addHello\': static.(await fetchUsername())}': + 'Did you place the \'\$\' character correctly?', + '{Closure \'addHello\'(await fetchUsername())}': + 'Did you place the \'\$\' character correctly?', + })) + ..add(makeReadable( + testLabel: 'Part 3', + testResult: await asyncDidCatchException(sayGoodbye), + readableErrors: { + typoMessage: + '$typoMessage. Did you add the text \'Thanks, see you next time\'?', + 'null': '$didNotImplement sayGoodbye?', + noCatch: + 'Did you remember to call logoutUser within a try/catch block?', + 'Instance of \'Future\' Thanks, see you next time': + 'Did you remember to use the \'await\' keyword in the sayGoodbye function?', + 'Instance of \'_Future\' Thanks, see you next time': + 'Did you remember to use the \'await\' keyword in the sayGoodbye function?', + })) + ..add(makeReadable( + testLabel: 'Part 3', + testResult: await asyncEquals( + expected: 'Success! Thanks, see you next time', + actual: await sayGoodbye(), + typoKeyword: 'Success'), + readableErrors: { + typoMessage: + '$typoMessage. Did you add the text \'Thanks, see you next time\'?', + 'null': '$didNotImplement sayGoodbye?', + noCatch: + 'Did you remember to call logoutUser within a try/catch block?', + 'Instance of \'Future\' Thanks, see you next time': + 'Did you remember to use the \'await\' keyword in the sayGoodbye function?', + 'Instance of \'_Future\' Thanks, see you next time': + 'Did you remember to use the \'await\' keyword in the sayGoodbye function?', + 'Instance of \'_Exception\'': + 'CAUGHT Did you remember to return a string?', + })) + ..removeWhere((m) => m.contains(passed)) + ..toList(); + + if (messages.isEmpty) { + _result(true); + } else { + _result(false, messages); + } + } catch (e) { + _result(false, ['Tried to run solution, but received an exception: $e']); + } +} + +//////////////////////////////////////// +///////////// Test Helpers ///////////// +//////////////////////////////////////// +String makeReadable({ + required String testResult, + required Map readableErrors, + required String testLabel, +}) { + String? readable; + if (readableErrors.containsKey(testResult)) { + readable = readableErrors[testResult]; + return '$testLabel $readable'; + } else if ((testResult != passed) && (testResult.length < 18)) { + readable = typoMessage; + return '$testLabel $readable'; + } else { + return '$testLabel $testResult'; + } +} + +void passIfNoMessages(List messages, Map readable) { + if (messages.isEmpty) { + _result(true); + } else { + final userMessages = messages + .where((message) => readable.containsKey(message)) + .map((message) => readable[message]!) + .toList(); + print(messages); + + _result(false, userMessages); + } +} + +/////////////////////////////////////// +//////////// Assertions /////////////// +/////////////////////////////////////// +Future asyncEquals({ + required String expected, + required dynamic actual, + required String typoKeyword, +}) async { + var strActual = actual is String ? actual : actual.toString(); + try { + if (expected == actual) { + return passed; + } else if (strActual.contains(typoKeyword)) { + return typoMessage; + } else { + return strActual; + } + } catch (e) { + return e.toString(); + } +} + +Future asyncDidCatchException(Function fn) async { + var caught = true; + try { + await fn(); + } on Exception catch (_) { + caught = false; + } + + if (caught == true) { + return passed; + } else { + return noCatch; + } +} +// #enddocregion diff --git a/examples/async_await/pubspec.yaml b/examples/async_await/pubspec.yaml index 3c26c3683..aeccdf327 100644 --- a/examples/async_await/pubspec.yaml +++ b/examples/async_await/pubspec.yaml @@ -4,7 +4,9 @@ description: dart.dev example code. environment: sdk: '>=2.17.0 <3.0.0' -dev_dependencies: +dependencies: examples_util: {path: ../util} + +dev_dependencies: lints: ^2.0.0 test: ^1.19.2 diff --git a/examples/util/lib/codelabs.dart b/examples/util/lib/codelabs.dart new file mode 100644 index 000000000..ee6597820 --- /dev/null +++ b/examples/util/lib/codelabs.dart @@ -0,0 +1,11 @@ +const testKey = '__TESTRESULT__ '; + +void result(bool success, [List messages = const []]) { + // Join messages into a comma-separated list for inclusion in the JSON array. + final joinedMessages = messages.map((m) => '"$m"').join(','); + print('$testKey{"success": \$success, "messages": [$joinedMessages]}'); +} + +// Placeholder for unimplemented methods in dart-pad exercises. +// ignore: non_constant_identifier_names +Never TODO([String message = '']) => throw UnimplementedError(message); diff --git a/examples/vector_victor/analysis_options.yaml b/examples/vector_victor/analysis_options.yaml new file mode 100644 index 000000000..5e2133eb6 --- /dev/null +++ b/examples/vector_victor/analysis_options.yaml @@ -0,0 +1 @@ +include: ../analysis_options.yaml diff --git a/examples/vector_victor/lib/vector_victor.dart b/examples/vector_victor/lib/vector_victor.dart new file mode 100644 index 000000000..1ad6c4c62 --- /dev/null +++ b/examples/vector_victor/lib/vector_victor.dart @@ -0,0 +1,9 @@ +// #docregion import +import 'package:vector_math/vector_math.dart'; +// #enddocregion import + +void test() { + Vector3 x = Vector3.zero(); + Vector4 y = Vector4.all(4.0); + x.zyx = y.xzz; +} diff --git a/examples/vector_victor/pubspec.yaml b/examples/vector_victor/pubspec.yaml new file mode 100644 index 000000000..f79ff2abd --- /dev/null +++ b/examples/vector_victor/pubspec.yaml @@ -0,0 +1,17 @@ +name: vector_victor +description: A sample command-line application. +version: 1.0.0 +# homepage: https://www.example.com + +environment: + sdk: '>=2.18.0 <3.0.0' + +# dependencies: +# path: ^1.8.0 + +dev_dependencies: + lints: ^2.0.0 + test: ^1.16.0 + +dependencies: + vector_math: ^2.1.3 diff --git a/site-shared b/site-shared index 8c92e5bdf..0fe3c62c7 160000 --- a/site-shared +++ b/site-shared @@ -1 +1 @@ -Subproject commit 8c92e5bdfdce14887605de6b5d9d0bd2615876b7 +Subproject commit 0fe3c62c70c0e8c4f1a051a5dec3bbc4eb6c8e57 diff --git a/src/_data/linter_rules.json b/src/_data/linter_rules.json index 955885262..e0e636323 100644 --- a/src/_data/linter_rules.json +++ b/src/_data/linter_rules.json @@ -394,7 +394,7 @@ "flutter" ], "fixStatus": "hasFix", - "details": "**DO** use key in widget constructors.\n\nIt's a good practice to expose the ability to provide a key when creating public\nwidgets.\n\n**BAD:**\n```dart\nclass MyPublicWidget extends StatelessWidget {\n}\n```\n\n**GOOD:**\n```dart\nclass MyPublicWidget extends StatelessWidget {\n MyPublicWidget({Key? key}) : super(key: key);\n}\n```\n" + "details": "**DO** use key in widget constructors.\n\nIt's a good practice to expose the ability to provide a key when creating public\nwidgets.\n\n**BAD:**\n```dart\nclass MyPublicWidget extends StatelessWidget {\n}\n```\n\n**GOOD:**\n```dart\nclass MyPublicWidget extends StatelessWidget {\n MyPublicWidget({super.key});\n}\n```\n" }, { "name": "valid_regexps", @@ -654,7 +654,7 @@ "flutter" ], "fixStatus": "hasFix", - "details": "\n**AVOID** using `forEach` with a function literal.\n\n**BAD:**\n```dart\npeople.forEach((person) {\n ...\n});\n```\n\n**GOOD:**\n```dart\nfor (var person in people) {\n ...\n}\n\npeople.forEach(print);\n```\n" + "details": "\n**AVOID** using `forEach` with a function literal.\n\nThe `for` loop enables a developer to be clear and explicit as to their intent.\nA return in the body of the `for` loop returns from the body of the function, \nwhere as a return in the body of the `forEach` closure only returns a value \nfor that iteration of the `forEach`. The body of a `for` loop can contain \n`await`s, while the closure body of a `forEach` cannot.\n\n**BAD:**\n```dart\npeople.forEach((person) {\n ...\n});\n```\n\n**GOOD:**\n```dart\nfor (var person in people) {\n ...\n}\n```\n" }, { "name": "avoid_implementing_value_types", @@ -958,7 +958,7 @@ "maturity": "stable", "incompatible": [], "sets": [], - "fixStatus": "needsEvaluation", + "fixStatus": "hasFix", "details": "\n**DO** sort combinator names alphabetically.\n\n**BAD:**\n```dart\nimport 'a.dart' show B, A hide D, C;\nexport 'a.dart' show B, A hide D, C;\n```\n\n**GOOD:**\n```dart\nimport 'a.dart' show A, B hide C, D;\nexport 'a.dart' show A, B hide C, D;\n```\n\n" }, { @@ -1266,7 +1266,7 @@ "maturity": "stable", "incompatible": [], "sets": [], - "fixStatus": "needsEvaluation", + "fixStatus": "needsFix", "details": "\nSome operations on primitive types are idempotent and can be removed.\n\n**BAD:**\n\n```dart\ndoubleValue.toDouble();\n\nintValue.toInt();\nintValue.round();\nintValue.ceil();\nintValue.floor();\nintValue.truncate();\n\nstring.toString();\nstring = 'hello\\n'\n 'world\\n'\n ''; // useless empty string\n\n'string with ${x.toString()}';\n```\n" }, { @@ -2298,6 +2298,16 @@ "fixStatus": "needsEvaluation", "details": "\nUnnecessary `toList()` in spreads.\n\n**BAD:**\n```dart\nchildren: [\n ...['foo', 'bar', 'baz'].map((String s) => Text(s)).toList(),\n]\n```\n\n**GOOD:**\n```dart\nchildren: [\n ...['foo', 'bar', 'baz'].map((String s) => Text(s)),\n]\n```\n\n" }, + { + "name": "unreachable_from_main", + "description": "Unreachable top-level members in executable libraries.", + "group": "style", + "maturity": "experimental", + "incompatible": [], + "sets": [], + "fixStatus": "needsEvaluation", + "details": "\nTop-level members in an executable library should be used directly inside this\nlibrary. An executable library is a library that contains a `main` top-level\nfunction or that contains a top-level function annotated with\n`@pragma('vm:entry-point')`). Executable libraries are not usually imported\nand it's better to avoid defining unused members.\n\nThis rule assumes that an executable library isn't imported by other files\nexcept to execute its `main` function.\n\n**BAD:**\n\n```dart\nmain() {}\nvoid f() {}\n```\n\n**GOOD:**\n\n```dart\nmain() {\n f();\n}\nvoid f() {}\n```\n\n" + }, { "name": "use_colored_box", "description": "Use `ColoredBox`.", @@ -2439,6 +2449,16 @@ "fixStatus": "needsEvaluation", "details": "\n**DO** use string buffers to compose strings.\n\nIn most cases, using a string buffer is preferred for composing strings due to\nits improved performance.\n\n**BAD:**\n```dart\nString foo() {\n final buffer = '';\n for (int i = 0; i < 10; i++) {\n buffer += 'a'; // LINT\n }\n return buffer;\n}\n```\n\n**GOOD:**\n```dart\nString foo() {\n final buffer = StringBuffer();\n for (int i = 0; i < 10; i++) {\n buffer.write('a');\n }\n return buffer.toString();\n}\n```\n\n" }, + { + "name": "use_string_in_part_of_directives", + "description": "Use string in part of directives.", + "group": "style", + "maturity": "stable", + "incompatible": [], + "sets": [], + "fixStatus": "needsFix", + "details": "\nFrom [effective dart](https://dart.dev/guides/language/effective-dart/usage#do-use-strings-in-part-of-directives):\n\n**DO** use strings in `part of` directives.\n\n**BAD:**\n\n```dart\npart of my_library;\n```\n\n**GOOD:**\n\n```dart\npart of '../../my_library.dart';\n```\n\n" + }, { "name": "use_super_parameters", "description": "Use super-initializer parameters where possible.", diff --git a/src/_data/pkg-vers.json b/src/_data/pkg-vers.json index c73a91331..08afd2d30 100644 --- a/src/_data/pkg-vers.json +++ b/src/_data/pkg-vers.json @@ -3,6 +3,6 @@ "doc-path": "install", "channel": "stable", "prev-vers": "1.24.3", - "vers": "2.18.0" + "vers": "2.18.1" } } diff --git a/src/_guides/libraries/c-interop.md b/src/_guides/libraries/c-interop.md index 948568efb..25abeea43 100644 --- a/src/_guides/libraries/c-interop.md +++ b/src/_guides/libraries/c-interop.md @@ -281,9 +281,9 @@ For details, see the following: 根据平台和库的类型的不同,捆绑(或 **打包** 和 **分发**) C 库到 package 或应用并进行加载的方式,有所不同。 -* [Flutter `dart:ffi` page][binding] +* Flutter `dart:ffi` pages: [Android][android], [iOS][ios], and [macOS][macos] - [Flutter `dart:ffi` 页面][binding] + Flutter 的 `dart:ffi` 页面: [Android][android]、[iOS][ios] 和 [macOS][macos] * [`dart:ffi` examples]({{page.samples}}) @@ -374,7 +374,9 @@ to automatically create FFI wrappers from C header files. [ABI]: {{site.dart-api}}/{{site.data.pkg-vers.SDK.channel}}/dart-ffi/Abi-class.html [AbiSpecificInteger]: {{site.dart-api}}/{{site.data.pkg-vers.SDK.channel}}/dart-ffi/AbiSpecificInteger-class.html -[binding]: {{site.flutter-docs}}/development/platform-integration/c-interop +[ios]: {{site.flutter-docs}}/development/platform-integration/ios/c-interop +[android]: {{site.flutter-docs}}/development/platform-integration/android/c-interop +[macos]: {{site.flutter-docs}}/development/platform-integration/macos/c-interop [FFI]: https://en.wikipedia.org/wiki/Foreign_function_interface [hello_world]: {{page.hw}} [primitives]: {{page.samples}}/primitives diff --git a/src/_plugins/date_to_rfc2822.rb b/src/_plugins/date_to_rfc2822.rb deleted file mode 100644 index 9fdf2fbe4..000000000 --- a/src/_plugins/date_to_rfc2822.rb +++ /dev/null @@ -1,14 +0,0 @@ -require 'time' - -module RFC2822Filter - def date_to_rfc2822(input) - if input == 'now' - Time.new.rfc2822 - else - # input might be a date and not a string - Time.parse(input.to_s).rfc2822 - end - end -end - -Liquid::Template.register_filter(RFC2822Filter) \ No newline at end of file diff --git a/src/_plugins/format_num_filter.rb b/src/_plugins/format_num_filter.rb deleted file mode 100644 index 9c2c87992..000000000 --- a/src/_plugins/format_num_filter.rb +++ /dev/null @@ -1,7 +0,0 @@ -module FormatNumFilter - def format_num(input, fmt) - fmt % input - end -end - -Liquid::Template.register_filter(FormatNumFilter) \ No newline at end of file diff --git a/src/_plugins/strip_html_filter.rb b/src/_plugins/strip_html_filter.rb deleted file mode 100644 index a3dcb686c..000000000 --- a/src/_plugins/strip_html_filter.rb +++ /dev/null @@ -1,7 +0,0 @@ -module StripHtmlFilter - def self.strip_html(input) - input.to_s.gsub(/<\/?[^>]*>/,"") - end -end - -Liquid::Template.register_filter(StripHtmlFilter) \ No newline at end of file diff --git a/src/_tutorials/libraries/shared-pkgs.md b/src/_tutorials/libraries/shared-pkgs.md index f16a0a1ff..a1e944a53 100644 --- a/src/_tutorials/libraries/shared-pkgs.md +++ b/src/_tutorials/libraries/shared-pkgs.md @@ -104,20 +104,11 @@ such as IntelliJ or WebStorm. 你可以使用命令行工具来调用 Stagehand 工具, 也可以使用类似 IntelliJ 或 WebStorm 这样的 IDE 来间接使用 Stagehand 工具。 -Install or update Stagehand using -[pub global activate](/tools/pub/cmd/pub-global): - -你可以使用 [pub global activate](/tools/pub/cmd/pub-global) -命令安装或更新 Stagehand 工具: - -```terminal -$ pub global activate stagehand -``` - Run the `dart create` command with the `--help` flag to see what kinds of template files it can generate: -现在你可以运行 `dart create` 命令来查看它可以生成的模板文件: +现在你可以运行 `dart create` 命令,使用 +`--help` 来查看它可以生成的模板文件: ```terminal $ dart create --help @@ -147,8 +138,9 @@ The contents of your pubspec.yaml file should look something like this: pubspec.yaml 文件包含了由 YAML 语言撰写的 package 规格。 (访问 Pubspec 格式 获取更多深入的介绍。) -而你的 pubspec.yaml 文件看起来则应该是这样的: +而你的 pubspec.yaml 文件看起来则应该是这样的 + ```yaml name: vector_victor description: A sample command-line application. @@ -156,7 +148,7 @@ version: 1.0.0 # homepage: https://www.example.com environment: - sdk: '>=2.12.0 <3.0.0' + sdk: '>=2.18.0 <3.0.0' # dependencies: # path: ^1.8.0 @@ -182,58 +174,43 @@ of a package that your app uses. 依赖中的每一项都指定了你应用所使用的 package 名称以及版本。 Let's make the vector_victor app have a dependency -on the vector_math package, +on the `vector_math` package, which is available at the [pub.dev site]({{site.pub}}). 下面让我们为 vector_victor 应用添加一个名为 vector_math 的 package, 这个 package 可以在 [pub.dev 网站]({{site.pub}}) 中找到。 - 1. Get the current installation details for the package: - - 获取 package 当前的安装细节信息: - - {: type="a"} - 1. Go to [vector_math's entry on the Package - site.]({{site.pub-pkg}}/vector_math) - - 打开 [pub.dev 网站中 vector_math package 的网页。]({{site.pub-pkg}}/vector_math) - - 2. Click the **Installing** tab. - - 点击**Installing**标签。 +Run the [`dart pub add`](/tools/pub/cmd/pub-add) command +and specify `vector_math` +to add a dependency on the package: - 3. Copy the **vector_math** line from the sample **dependencies** entry. - The entry should look something like this: +运行 [`dart pub add`](/tools/pub/cmd/pub-add) 命令并指定 +使用 `vector_math`,将其加入你的依赖中。 - 拷贝示例 _dependencies_ 实体中有 _vector_math_ 的那一行。 _dependencies_ 实体看起来像下面那样: - - ```yaml - dependencies: - vector_math: ^2.0.8 - ``` - - 2. Edit `pubspec.yaml`. - - 编辑 `pubspec.yaml` 文件。 - - 3. In the dependencies section, add the string you copied from the - pub.dev site. Be careful to keep the indentation the same; YAML is - picky! For example: +```terminal +$ dart pub add vector_math +Resolving dependencies... ++ vector_math 2.1.3 +Downloading vector_math 2.1.3... +Changed 1 dependency! +``` - 在 dependencies 部分,将上面你从 pub.dev 网站拷贝来的文本粘贴添加至这里。 - 这里要注意缩进;必须严格按照 YAML 语言规范!例如: +This will add `vector_math` to the +`dependencies` entry of your `pubspec.yaml`, +resulting in the following: - ```yaml - environment: - sdk: '>=2.8.1 <3.0.0' +这个命令将会将 `vector_math` 加入你工程文件中 +`pubspec.yaml` 的 `dependencies` 部分: - dependencies: - vector_math: ^2.0.8 + +```yaml +dependencies: + vector_math: ^2.1.3 +``` - dev_dependencies: - pedantic: ^1.9.0 - test: ^1.14.4 - ``` +You can also find your desired version on the +[`vector_math` page on pub.dev]({{site.pub-pkg}}/vector_math) +and add it manually to the dependency section. For details of what version numbers mean and how you can format them, @@ -259,10 +236,11 @@ as we have done here. ## 安装依赖的 package -If you're using an IDE or Dart-savvy editor to edit `pubspec.yaml`, +If you're using a Dart-savvy editor or `dart pub` to edit `pubspec.yaml`, it might automatically install the packages your app depends on. -如果你使用 IDE 或适配了 Dart 语言开发的编辑器去编辑 `pubspec.yaml` 文件, +如果你使用适配了 Dart 语言开发的编辑器或者 +`dart pub` 命令了编辑 `pubspec.yaml` 文件, 其可能会在你编辑了该文件后自动下载安装相关依赖的 package。 If not, do it yourself by running @@ -274,7 +252,7 @@ If not, do it yourself by running ```terminal $ dart pub get Resolving dependencies... -+ vector_math 2.0.8 ++ vector_math 2.1.3 Changed 1 dependency! ``` @@ -307,25 +285,26 @@ Pub 会创建一个名为 `pubspec.lock` 的文件来标识哪些 package 的哪 ## 你可以从中获取(或不可获取)什么? Besides the Dart libraries, -the vector_math package has other resources that might be useful to you +the `vector_math` package has other resources that might be useful to you that do not get installed into your app directory. Let's take a step back for a moment to look at what you got and where it came from. -除了 Dart 库以外,vector_math package +除了 Dart 库以外,`vector_math` package 可能包含其它对你有用但不会安装到你应用目录的资源。 让我们后退一步看看你在获取依赖时得到了什么以及它们从何而来。 -To see the contents of the vector_math package, +To see the contents of the `vector_math` package, visit the -Dart vector math repository -at GitHub. +on GitHub. Although many files and directories are in the repository, only one, `lib`, was installed when you ran pub get. -访问 Dart 数学矢量仓库 -的 Github 仓库查看 vector_math package 的具体内容。 +访问 Github 仓库 +Dart 数学矢量仓库 +来查看 `vector_math` package 的具体内容。 尽管该仓库中有大量的文件和目录, 但是只有 `lib` 目录下的文件会在你执行 pub get 命令时安装。 @@ -425,35 +404,34 @@ Dart SDK 库是内置的且由特殊的 `dart:` 前缀标识。 获取 package 中主要库的导入流程: {: type="a"} - 1. Go to [vector_math's entry on the Package - site.]({{site.pub-pkg}}/vector_math) + 1. Go to the [`vector_math` page on pub.dev.]({{site.pub-pkg}}/vector_math) - 打开 - [pub.dev 网站中 vector_math package 的网页。]({{site.pub-pkg}}/vector_math) + 打开 [pub.dev 网站中 `vector_math` package 的网页。]({{site.pub-pkg}}/vector_math) 2. Click the **Installing** tab. - 点击**Installing**标签。 + 点击 **Installing** 标签。 3. Copy the **import** line. It should look something like this: - 拷贝有**import**的这一行代码。其看起来像下面这样: + 拷贝有 **import** 的这一行代码。其看起来像下面这样: + ```dart import 'package:vector_math/vector_math.dart'; ``` 2. In your vector_victor app, edit `lib/vector_victor.dart`, - so that it imports the vector_math library and uses some of its API. + so that it imports the `vector_math` library and uses some of its API. For inspiration, look at the - [vector_math API + [`vector_math` API docs]({{site.pub-api}}/vector_math/latest), which you can find from the pub.dev site entry. 在你的 vector_victor 应用中,编辑 `lib/vector_victor.dart` 文件, 由此它导入 vector_math 库并使用了它的一些 API。 你可以阅读 - [vector_math API 文档]({{site.pub-api}}/documentation/vector_math/latest) + [vector_math API 文档]({{site.pub-api}}/vector_math/latest) 获取更多相关信息。 {{site.alert.note}} diff --git a/src/codelabs/async-await.md b/src/codelabs/async-await.md index 4e761ba16..a6a1bd6f5 100644 --- a/src/codelabs/async-await.md +++ b/src/codelabs/async-await.md @@ -449,8 +449,8 @@ Implement an `async` function `reportLogins()` so that it does the following: * Example return value from `reportLogins()`: `"Total number of logins: 57"` * Gets the number of logins by calling the provided function `fetchLoginAmount()`. -```dart:run-dartpad:theme-dark:height-380px:ga_id-practice_using -{$ begin main.dart $} + +```dart:start-dartpad:theme-dark:height-380px:ga_id-practice_using:file-main.dart // Part 1 // You can call the provided async function fetchRole() // to return the user role. @@ -463,8 +463,10 @@ Future reportUserRole() async { // You can call the provided async function fetchLoginAmount() // to return the number of times that the user has logged in. reportLogins() {} -{$ end main.dart $} -{$ begin solution.dart $} +``` + + +```dart:file-solution.dart Future reportUserRole() async { var username = await fetchRole(); return 'User role: $username'; @@ -474,14 +476,17 @@ Future reportLogins() async { var logins = await fetchLoginAmount(); return 'Total number of logins: $logins'; } -{$ end solution.dart $} -{$ begin test.dart $} +``` + + +```dart:file-test.dart const role = 'administrator'; const logins = 42; const passed = 'PASSED'; const testFailedMessage = 'Test failed for the function:'; const typoMessage = 'Test failed! Check for typos in your return value'; -const didNotImplement = 'Test failed! Did you forget to implement or return from '; +const didNotImplement = + 'Test failed! Did you forget to implement or return from '; const oneSecond = Duration(seconds: 1); List messages = []; Future fetchRole() => Future.delayed(oneSecond, () => role); @@ -491,39 +496,45 @@ void main() async { try { messages ..add(makeReadable( - - testLabel: 'Part 1', - testResult: await asyncEquals( - expected: 'User role: administrator', - actual: await reportUserRole(), - typoKeyword: role - ), - readableErrors: { - typoMessage: typoMessage, - 'null': '$didNotImplement reportUserRole?', - 'User role: Instance of \'Future\'': '$testFailedMessage reportUserRole. Did you use the await keyword?', - 'User role: Instance of \'_Future\'': '$testFailedMessage reportUserRole. Did you use the await keyword?', - 'User role:' : '$testFailedMessage reportUserRole. Did you return a user role?', - 'User role: ' : '$testFailedMessage reportUserRole. Did you return a user role?', - 'User role: tester' : '$testFailedMessage reportUserRole. Did you invoke fetchRole to fetch the user\'s role?', - })) - + testLabel: 'Part 1', + testResult: await asyncEquals( + expected: 'User role: administrator', + actual: await reportUserRole(), + typoKeyword: role), + readableErrors: { + typoMessage: typoMessage, + 'null': '$didNotImplement reportUserRole?', + 'User role: Instance of \'Future\'': + '$testFailedMessage reportUserRole. Did you use the await keyword?', + 'User role: Instance of \'_Future\'': + '$testFailedMessage reportUserRole. Did you use the await keyword?', + 'User role:': + '$testFailedMessage reportUserRole. Did you return a user role?', + 'User role: ': + '$testFailedMessage reportUserRole. Did you return a user role?', + 'User role: tester': + '$testFailedMessage reportUserRole. Did you invoke fetchRole to fetch the user\'s role?', + })) ..add(makeReadable( - testLabel: 'Part 2', - testResult: await asyncEquals( - expected: 'Total number of logins: 42', - actual: await reportLogins(), - typoKeyword: logins.toString() - ), - readableErrors: { - typoMessage: typoMessage, - 'null': '$didNotImplement reportLogins?', - 'Total number of logins: Instance of \'Future\'': '$testFailedMessage reportLogins. Did you use the await keyword?', - 'Total number of logins: Instance of \'_Future\'': '$testFailedMessage reportLogins. Did you use the await keyword?', - 'Total number of logins: ': '$testFailedMessage reportLogins. Did you return the number of logins?', - 'Total number of logins:': '$testFailedMessage reportLogins. Did you return the number of logins?', - 'Total number of logins: 57': '$testFailedMessage reportLogins. Did you invoke fetchLoginAmount to fetch the number of user logins?', - })) + testLabel: 'Part 2', + testResult: await asyncEquals( + expected: 'Total number of logins: 42', + actual: await reportLogins(), + typoKeyword: logins.toString()), + readableErrors: { + typoMessage: typoMessage, + 'null': '$didNotImplement reportLogins?', + 'Total number of logins: Instance of \'Future\'': + '$testFailedMessage reportLogins. Did you use the await keyword?', + 'Total number of logins: Instance of \'_Future\'': + '$testFailedMessage reportLogins. Did you use the await keyword?', + 'Total number of logins: ': + '$testFailedMessage reportLogins. Did you return the number of logins?', + 'Total number of logins:': + '$testFailedMessage reportLogins. Did you return the number of logins?', + 'Total number of logins: 57': + '$testFailedMessage reportLogins. Did you invoke fetchLoginAmount to fetch the number of user logins?', + })) ..removeWhere((m) => m.contains(passed)) ..toList(); @@ -546,7 +557,7 @@ void main() async { //////////////////////////////////////// String makeReadable({ required String testResult, - required Map readableErrors, + required Map readableErrors, required String testLabel, }) { if (readableErrors.containsKey(testResult)) { @@ -561,8 +572,8 @@ String makeReadable({ //////////// Assertions /////////////// /////////////////////////////////////// Future asyncEquals({ - expected, - actual, + required String expected, + required dynamic actual, required String typoKeyword, }) async { var strActual = actual is String ? actual : actual.toString(); @@ -574,18 +585,18 @@ Future asyncEquals({ } else { return strActual; } - } catch(e) { + } catch (e) { return e.toString(); } } -{$ end test.dart $} -{$ begin hint.txt $} +``` + +```text:end-dartpad:file-hint.txt Did you remember to add the async keyword to the reportUserRole() function? Did you remember to use the await keyword before invoking fetchRole()? Remember: reportUserRole() needs to return a future! -{$ end hint.txt $} ``` {{site.alert.note}} @@ -668,12 +679,14 @@ that does the following: and [Errors.]({{site.dart-api}}/{{site.data.pkg-vers.SDK.channel}}/dart-core/Error-class.html) -```dart:run-dartpad:theme-dark:height-380px:ga_id-practice_errors -{$ begin main.dart $} + +```dart:start-dartpad:theme-dark:height-380px:ga_id-practice_errors:file-main.dart // Implement changeUsername here changeUsername() {} -{$ end main.dart $} -{$ begin solution.dart $} +``` + + +```dart:file-solution.dart Future changeUsername() async { try { return await fetchNewUsername(); @@ -681,8 +694,10 @@ Future changeUsername() async { return err.toString(); } } -{$ end solution.dart $} -{$ begin test.dart $} +``` + + +```dart:file-test.dart List messages = []; bool logoutSucceeds = false; const passed = 'PASSED'; @@ -694,31 +709,30 @@ class UserError implements Exception { String errMsg() => 'New username is invalid'; } -Future fetchNewUsername() { +Future fetchNewUsername() { var str = Future.delayed(oneSecond, () => throw UserError()); return str; } void main() async { try { - // ignore: cascade_invocations messages ..add(makeReadable( testLabel: '', testResult: await asyncDidCatchException(changeUsername), readableErrors: { typoMessage: typoMessage, - noCatch: 'Did you remember to call fetchNewUsername within a try/catch block?', - } - )) + noCatch: + 'Did you remember to call fetchNewUsername within a try/catch block?', + })) ..add(makeReadable( testLabel: '', testResult: await asyncErrorEquals(changeUsername), readableErrors: { typoMessage: typoMessage, - noCatch: 'Did you remember to call fetchNewUsername within a try/catch block?', - } - )) + noCatch: + 'Did you remember to call fetchNewUsername within a try/catch block?', + })) ..removeWhere((m) => m.contains(passed)) ..toList(); @@ -737,7 +751,7 @@ void main() async { //////////////////////////////////////// String makeReadable({ required String testResult, - required Map readableErrors, + required Map readableErrors, required String testLabel, }) { if (readableErrors.containsKey(testResult)) { @@ -748,13 +762,11 @@ String makeReadable({ } } -void passIfNoMessages(List messages, Map readable){ +void passIfNoMessages(List messages, Map readable) { if (messages.isEmpty) { _result(true); } else { - - // ignore: omit_local_variable_types - List userMessages = messages + final userMessages = messages .where((message) => readable.containsKey(message)) .map((message) => readable[message]!) .toList(); @@ -763,6 +775,7 @@ void passIfNoMessages(List messages, Map readable){ _result(false, userMessages); } } + /////////////////////////////////////// //////////// Assertions /////////////// /////////////////////////////////////// @@ -779,7 +792,7 @@ Future asyncDidCatchException(Function fn) async { var caught = true; try { await fn(); - } on UserError catch(_) { + } on UserError catch (_) { caught = false; } @@ -789,12 +802,12 @@ Future asyncDidCatchException(Function fn) async { return passed; } } -{$ end test.dart $} -{$ begin hint.txt $} +``` + +```text:end-dartpad:file-hint.txt Implement changeUsername() to return the string from fetchNewUsername() or (if that fails) the string value of any error that occurs. You'll need a try-catch statement to catch and handle errors. -{$ end hint.txt $} ``` {% comment %} @@ -843,8 +856,8 @@ Write the following: `' Thanks, see you next time'`, where `` is the string value returned by calling `logoutUser()`. -```dart:run-dartpad:theme-dark:height-380px:ga_id-putting_it_all_together -{$ begin main.dart $} + +```dart:start-dartpad:theme-dark:height-380px:ga_id-putting_it_all_together:file-main.dart // Part 1 addHello(String user) {} @@ -857,8 +870,10 @@ greetUser() {} // You can call the provided async function logoutUser() // to log out the user. sayGoodbye() {} -{$ end main.dart $} -{$ begin solution.dart $} +``` + + +```dart:file-solution.dart String addHello(String user) => 'Hello $user'; Future greetUser() async { @@ -874,18 +889,21 @@ Future sayGoodbye() async { return 'Failed to logout user: $e'; } } -{$ end solution.dart $} -{$ begin test.dart $} +``` + + +```dart:file-test.dart List messages = []; bool logoutSucceeds = false; const passed = 'PASSED'; const noCatch = 'NO_CATCH'; const typoMessage = 'Test failed! Check for typos in your return value'; -const didNotImplement = 'Test failed! Did you forget to implement or return from '; +const didNotImplement = + 'Test failed! Did you forget to implement or return from '; const oneSecond = Duration(seconds: 1); Future fetchUsername() => Future.delayed(oneSecond, () => 'Jean'); -String failOnce () { +String failOnce() { if (logoutSucceeds) { return 'Success!'; } else { @@ -894,72 +912,80 @@ String failOnce () { } } -logoutUser() => Future.delayed(oneSecond, failOnce); +Future logoutUser() => Future.delayed(oneSecond, failOnce); void main() async { try { - // ignore: cascade_invocations messages ..add(makeReadable( - - testLabel: 'Part 1', - testResult: await asyncEquals( - expected: 'Hello Jerry', - actual: addHello('Jerry'), - typoKeyword: 'Jerry' - ), - readableErrors: { - typoMessage: typoMessage, - 'null': '$didNotImplement addHello?', - 'Hello Instance of \'Future\'': 'Looks like you forgot to use the \'await\' keyword!', - 'Hello Instance of \'_Future\'': 'Looks like you forgot to use the \'await\' keyword!', - })) + testLabel: 'Part 1', + testResult: await asyncEquals( + expected: 'Hello Jerry', + actual: addHello('Jerry'), + typoKeyword: 'Jerry'), + readableErrors: { + typoMessage: typoMessage, + 'null': '$didNotImplement addHello?', + 'Hello Instance of \'Future\'': + 'Looks like you forgot to use the \'await\' keyword!', + 'Hello Instance of \'_Future\'': + 'Looks like you forgot to use the \'await\' keyword!', + })) ..add(makeReadable( - - testLabel: 'Part 2', - testResult: await asyncEquals( - expected: 'Hello Jean', - actual: await greetUser(), - typoKeyword: 'Jean' - ), - readableErrors: { - typoMessage: typoMessage, - 'null': '$didNotImplement greetUser?', - 'HelloJean' : 'Looks like you forgot the space between \'Hello\' and \'Jean\'', - 'Hello Instance of \'Future\'': 'Looks like you forgot to use the \'await\' keyword!', - 'Hello Instance of \'_Future\'': 'Looks like you forgot to use the \'await\' keyword!', - '{Closure: (String) => dynamic from Function \'addHello\': static.(await fetchUsername())}': 'Did you place the \'\$\' character correctly?', - '{Closure \'addHello\'(await fetchUsername())}': 'Did you place the \'\$\' character correctly?', - })) + testLabel: 'Part 2', + testResult: await asyncEquals( + expected: 'Hello Jean', + actual: await greetUser(), + typoKeyword: 'Jean'), + readableErrors: { + typoMessage: typoMessage, + 'null': '$didNotImplement greetUser?', + 'HelloJean': + 'Looks like you forgot the space between \'Hello\' and \'Jean\'', + 'Hello Instance of \'Future\'': + 'Looks like you forgot to use the \'await\' keyword!', + 'Hello Instance of \'_Future\'': + 'Looks like you forgot to use the \'await\' keyword!', + '{Closure: (String) => dynamic from Function \'addHello\': static.(await fetchUsername())}': + 'Did you place the \'\$\' character correctly?', + '{Closure \'addHello\'(await fetchUsername())}': + 'Did you place the \'\$\' character correctly?', + })) ..add(makeReadable( - testLabel: 'Part 3', - testResult: await asyncDidCatchException(sayGoodbye), - readableErrors: { - typoMessage: '$typoMessage. Did you add the text \'Thanks, see you next time\'?', - 'null': '$didNotImplement sayGoodbye?', - noCatch: 'Did you remember to call logoutUser within a try/catch block?', - 'Instance of \'Future\' Thanks, see you next time':'Did you remember to use the \'await\' keyword in the sayGoodbye function?', - 'Instance of \'_Future\' Thanks, see you next time':'Did you remember to use the \'await\' keyword in the sayGoodbye function?', - } - )) + testLabel: 'Part 3', + testResult: await asyncDidCatchException(sayGoodbye), + readableErrors: { + typoMessage: + '$typoMessage. Did you add the text \'Thanks, see you next time\'?', + 'null': '$didNotImplement sayGoodbye?', + noCatch: + 'Did you remember to call logoutUser within a try/catch block?', + 'Instance of \'Future\' Thanks, see you next time': + 'Did you remember to use the \'await\' keyword in the sayGoodbye function?', + 'Instance of \'_Future\' Thanks, see you next time': + 'Did you remember to use the \'await\' keyword in the sayGoodbye function?', + })) ..add(makeReadable( - testLabel: 'Part 3', - testResult: await asyncEquals( - expected: 'Success! Thanks, see you next time', - actual: await sayGoodbye(), - typoKeyword: 'Success' - ), - readableErrors: { - typoMessage: '$typoMessage. Did you add the text \'Thanks, see you next time\'?', - 'null': '$didNotImplement sayGoodbye?', - noCatch: 'Did you remember to call logoutUser within a try/catch block?', - 'Instance of \'Future\' Thanks, see you next time':'Did you remember to use the \'await\' keyword in the sayGoodbye function?', - 'Instance of \'_Future\' Thanks, see you next time':'Did you remember to use the \'await\' keyword in the sayGoodbye function?', - 'Instance of \'_Exception\'': 'CAUGHT Did you remember to return a string?', - } - )) - ..removeWhere((m) => m.contains(passed)) - ..toList(); + testLabel: 'Part 3', + testResult: await asyncEquals( + expected: 'Success! Thanks, see you next time', + actual: await sayGoodbye(), + typoKeyword: 'Success'), + readableErrors: { + typoMessage: + '$typoMessage. Did you add the text \'Thanks, see you next time\'?', + 'null': '$didNotImplement sayGoodbye?', + noCatch: + 'Did you remember to call logoutUser within a try/catch block?', + 'Instance of \'Future\' Thanks, see you next time': + 'Did you remember to use the \'await\' keyword in the sayGoodbye function?', + 'Instance of \'_Future\' Thanks, see you next time': + 'Did you remember to use the \'await\' keyword in the sayGoodbye function?', + 'Instance of \'_Exception\'': + 'CAUGHT Did you remember to return a string?', + })) + ..removeWhere((m) => m.contains(passed)) + ..toList(); if (messages.isEmpty) { _result(true); @@ -991,13 +1017,11 @@ String makeReadable({ } } -void passIfNoMessages(List messages, Map readable){ +void passIfNoMessages(List messages, Map readable) { if (messages.isEmpty) { _result(true); } else { - - // ignore: omit_local_variable_types - List userMessages = messages + final userMessages = messages .where((message) => readable.containsKey(message)) .map((message) => readable[message]!) .toList(); @@ -1006,12 +1030,13 @@ void passIfNoMessages(List messages, Map readable){ _result(false, userMessages); } } + /////////////////////////////////////// //////////// Assertions /////////////// /////////////////////////////////////// Future asyncEquals({ - expected, - actual, + required String expected, + required dynamic actual, required String typoKeyword, }) async { var strActual = actual is String ? actual : actual.toString(); @@ -1023,7 +1048,7 @@ Future asyncEquals({ } else { return strActual; } - } catch(e) { + } catch (e) { return e.toString(); } } @@ -1032,7 +1057,7 @@ Future asyncDidCatchException(Function fn) async { var caught = true; try { await fn(); - } on Exception catch(_) { + } on Exception catch (_) { caught = false; } @@ -1042,11 +1067,11 @@ Future asyncDidCatchException(Function fn) async { return noCatch; } } -{$ end test.dart $} -{$ begin hint.txt $} +``` + +```text:end-dartpad:file-hint.txt The greetUser() and sayGoodbye() functions are asynchronous; addHello() isn't. -{$ end hint.txt $} ``` ## What's next? diff --git a/src/codelabs/dart-cheatsheet.md b/src/codelabs/dart-cheatsheet.md index 78e6dc28b..72a2ede02 100644 --- a/src/codelabs/dart-cheatsheet.md +++ b/src/codelabs/dart-cheatsheet.md @@ -2488,7 +2488,7 @@ To make the constructor const, you'll need to make all the properties final. {$ end hint.txt $} ``` -## What next? +## What's next? ## 下一步是什么? diff --git a/src/null-safety/index.md b/src/null-safety/index.md index d298a6262..d7bb5b52a 100644 --- a/src/null-safety/index.md +++ b/src/null-safety/index.md @@ -5,20 +5,72 @@ description: Information about Dart's null safety feature description: Dart 空安全的有关内容 --- -The Dart language now supports sound null safety! +The Dart language comes with sound null safety. + +Dart 语言包含了健全的空安全特性。 + +Null safety prevents errors that result from unintentional access +of variables set to `null`. +For example, if a method expects an integer but receives `null`, +your app causes a runtime error. This type of error, a null dereference error, +can be difficult to debug. + +空安全会在编译期防止意外访问 `null` 变量的错误的产生。 +例如,如果一个方法需要整型结果,却接收到了 `null`,你的应用会出现运行时错误。 +这类空引用错误在以前调试是非常困难的。 + +With sound null safety variables are 'non-nullable' by default: +They can be assigned only values of the declared type +(e.g. `int i=42`), and never be assigned `null`. +You can specify that a type of a variable is nullable +(e.g. `int? i`), +and only then can they contain either a `null` *or* +a value of the defined type. + +有了健全的空安全体系,变量默认是「非空」的: +它们可以赋予与定义的类型相同类型的任意值(例如 `int i = 42`), +且永远不能被设置为 `null`。 +你可以指定一个类型为可空(例如 `int? i`), +这类变量只能包含对应类型的值或者 `null`。 + +Sound null safety changes potential **runtime errors** +into **edit-time** analysis errors, by flagging when +any non-nullable variable hasn't been initialized with a +non-null value or is being assigned a `null`. +This allows you to fix these errors before deploying your app. + +健全的空安全通过对非空变量的未被初始化或以 `null` 初始化的情况进行标记, +把潜在的 **运行时错误** 转变成了 **编辑时** 的分析错误。 +这样的特性让你在开发应用的过程中就可以修复这类错误。 + +{{site.alert.warn}} + + In Dart 2.x SDKs, you can enable or disable sound null safety + through configuration of the project SDK constraint. + To learn more, see [Enabling/disabling null safety](#enable-null-safety). + + 在 Dart 2.x 版本的 SDK 里,你可以通过设置项目的 SDK 限制来控制是否启用空安全。 + 你可以通过 [启用/禁用空安全](#enable-null-safety) 小节来了解更多。 + + Dart 3--planned for a mid-2023 release-- + will require sound null safety. It will prevent code from running without it. + All existing code must be [migrated](#migrate) to sound null safety + to be compatible with Dart 3. + To learn more, see the [Dart 3 sound null safety tracking issue][]. + + 预计在 2023 年中发布的 Dart 3 计划强制使用空安全。 + 该版本不允许未使用空安全的代码运行。 + 所有的代码都需要 [迁移](#migrate) 至健全的空安全,以适配 Dart 3。 + 你可以通过 [Dart 3 空安全问题跟踪][Dart 3 sound null safety tracking issue] + 来了解更多。 -Dart 语言已支持健全的空安全机制! +{{site.alert.end}} + +[Dart 3 sound null safety tracking issue]: https://github.com/dart-lang/sdk/issues/49530 -When you opt into null safety, -types in your code are non-nullable by default, meaning that -variables can’t contain `null` _unless you say they can._ -With null safety, your **runtime** null-dereference errors -turn into **edit-time** analysis errors. +## Introduction through examples -当你选择使用空安全时,代码中的类型将默认是非空的, -意味着 **除非你声明它们可空**,它们的值都不能为空。 -有了空安全,原本处于你的 **运行时** 的空值引用错误 -将变为 **编辑时** 的分析错误。 +## 通过示例代码介绍空安全 With null safety, all of the variables in the following code are non-nullable: @@ -42,22 +94,15 @@ just add `?` to its type declaration: int? aNullableInt = null; ``` -You can -[use null safety](#enable-null-safety) in your normal development environment, -[migrate existing code][migration guide] to use null safety, -or try null safety in [DartPad]({{site.dartpad}}). +- To try an interactive example, + see the [null safety codelab][Null safety codelab]. -你可以在你的普通开发环境中 [使用空安全](#enable-null-safety), -也建议 [迁移你项目中的代码][migration guide] 至空安全, -或者通过 [支持空安全的 DartPad]({{site.dartpad}}) 进行空安全特性实验。 + 你可以通过 [空安全 Codelab][Null safety codelab] 交互示例快速了解空安全。 -For an interactive, example-driven introduction to null safety language features, -see the [null safety codelab][Null safety codelab]. -For an in-depth discussion, see -[Understanding null safety](/null-safety/understanding-null-safety). +- To learn more about this topic, see + [Understanding null safety](/null-safety/understanding-null-safety). -若你想通过一个交互示例快速了解空安全,可以查看 [空安全 Codelab][Null safety codelab]。 -更深入的探讨,可以阅读 [深入理解空安全](/null-safety/understanding-null-safety)。 + 更深入的探讨,可以阅读文档 [深入理解空安全](/null-safety/understanding-null-safety)。 ## Null safety principles @@ -96,58 +141,56 @@ Dart 的空安全支持基于以下三条核心原则: 你会享有健全性带来的所有优势—— 更少的 BUG、更小的二进制文件以及更快的执行速度。 -## Enabling null safety {#enable-null-safety} - -## 启用空安全 {#enable-null-safety} +## Enabling/disabling null safety {#enable-null-safety} -Sound null safety is available in Dart 2.12 and Flutter 2. +## 启用和禁用空安全 {#enable-null-safety} -健全的空安全已在 Dart 2.12 和 Flutter 2 中可用。 +You can use sound null safety in Dart 2.12 and Flutter 2.0 or later. +Dart 3 and later will [_only_ support sound null safety][Dart 3 sound null safety tracking issue]. -### Migrating an existing package or app {#migrate} - -### 迁移已有 package 或应用 {#migrate} +健全的空安全已在 Dart 2.12 和 Flutter 2.0 及更高版本中可用。 +[Dart 3 及以后的版本将 **只支持** 健全的空安全][Dart 3 sound null safety tracking issue]。 -For instructions on how to migrate your code to null safety, -see the [migration guide][]. + +To enable sound null safety, set the +[SDK constraint lower-bound](/tools/pub/pubspec#sdk-constraints) +to a [language version][] of 2.12 or later. +For example, your `pubspec.yaml` file might have the following constraints: -若你需要代码迁移的指导,请查看 [迁移至空安全][migration guide]。 +想要启用健全空安全,你需要将 [SDK 的最低版本约束](/tools/pub/pubspec#sdk-constraints) +设定为 2.12 或者更高的 [语言版本][language version]。 +例如,你的 `pubspec.yaml` 可以设置为如下的限制: -{{site.alert.version-note}} +```yaml +environment: + sdk: '>=2.12.0 <3.0.0' +``` - Before Dart 2.13, the templates used by the [`dart create`][] command - and IDEs aren't null safe, so you need to migrate the code they create. - For example: +[language version]: /guides/language/evolution#language-versioning - 使用 Dart 2.13 以前版本的 [`dart create`][] 命令或 IDE 创建的模板, - 尚未迁移至空安全。在创建后你还需要对它们进行迁移。举个例子: +### Migrating an existing package or app {#migrate} - ```terminal - $ dart create -t console my_cli - $ cd my_cli - $ dart migrate --apply-changes - ``` -{{site.alert.end}} +### 迁移现有的 package 或应用 {#migrate} -### Behind the scenes: SDK constraints {#constraints} +The Dart SDK includes the `dart migrate` tool. +This tool helps you migrate code that supports sound null safety. +Use `dart migrate` if you wrote Dart code with Dart 2.12 or earlier. -### 幕后工作:SDK 限制 {#constraints} +Dart SDK 内置了 `dart migrate` 工具。 +该工具会帮助你迁移你的代码至健全的空安全。 +如果你的代码是在 Dart 2.12 或更早前编写的(未完全支持空安全), +你就可以使用这个工具进行迁移。 -To make Dart treat your code as null safe, -the [SDK constraints](/tools/pub/pubspec#sdk-constraints) -must require a [language version][] that has null safety support. -For example, your `pubspec.yaml` file might have the following constraints: +```terminal +$ cd my_app +$ dart migrate +``` -将 [SDK 版本约束](/tools/pub/pubspec#sdk-constraints) -设定为一个支持空安全的 SDK 版本。 -例如,你的 `pubspec.yaml` 可以设置为如下的限制: +To learn how to migrate your code to null safety, +see the [migration guide][]. -```yaml -environment: - sdk: '>=2.12.0 <3.0.0' -``` +你可以阅读 [迁移指南][migration guide] 来学习如何迁移你的代码至空安全。 -[language version]: /guides/language/evolution#language-versioning ## Where to learn more diff --git a/src/tools/diagnostic-messages.md b/src/tools/diagnostic-messages.md index 8631dcd6d..028dd8634 100644 --- a/src/tools/diagnostic-messages.md +++ b/src/tools/diagnostic-messages.md @@ -349,7 +349,7 @@ class [!C!] extends AbiSpecificInteger { } {% endprettify %} -The following code produces this diagnostic because the class `C` defineS +The following code produces this diagnostic because the class `C` defines a field: {% prettify dart tag=pre+code %} @@ -468,8 +468,8 @@ class C extends AbiSpecificInteger { ### abi_specific_integer_mapping_unsupported -_Only mappings to 'Int8', 'Int16', 'Int32', 'Int64', 'Uint8', 'Uint16', -'UInt32', and 'Uint64' are supported._ +_Invalid mapping to '{0}'; only mappings to 'Int8', 'Int16', 'Int32', 'Int64', +'Uint8', 'Uint16', 'UInt32', and 'Uint64' are supported._ #### Description @@ -493,7 +493,7 @@ entry is `Array`, which isn't a valid integer type: {% prettify dart tag=pre+code %} import 'dart:ffi'; -@[!AbiSpecificIntegerMapping!]({Abi.macosX64 : Array(4)}) +@AbiSpecificIntegerMapping({Abi.macosX64 : [!Array(4)!]}) class C extends AbiSpecificInteger { const C(); } @@ -4158,7 +4158,7 @@ class C { {% endprettify %} If there are multiple named constructors and all except one of them are -unneeded, then remove the constructorsthat aren't needed: +unneeded, then remove the constructors that aren't needed: {% prettify dart tag=pre+code %} class C { @@ -7310,8 +7310,6 @@ Given a [part file][] named `part.dart` containing the following: {% prettify dart tag=pre+code %} part of lib; - -class C{} {% endprettify %} The following code produces this diagnostic because imported files can't @@ -7321,8 +7319,6 @@ have a part-of directive: library lib; import [!'part.dart'!]; - -C c = C(); {% endprettify %} #### Common fixes @@ -12996,7 +12992,7 @@ class C { ### non_type_as_type_argument -_The name '{0}' isn't a type so it can't be used as a type argument._ +_The name '{0}' isn't a type, so it can't be used as a type argument._ #### Description @@ -14117,88 +14113,6 @@ class C extends Struct { } {% endprettify %} -### packed_nesting_non_packed - -_Nesting the non-packed or less tightly packed struct '{0}' in a packed struct -'{1}' isn't supported._ - -#### Description - -The analyzer produces this diagnostic when a subclass of `Struct` that is -annotated as being `Packed` declares a field whose type is also a subclass -of `Struct` and the field's type is either not packed or is packed less -tightly. - -For more information about FFI, see [C interop using dart:ffi][ffi]. - -#### Example - -The following code produces this diagnostic because the class `Outer`, -which is a subclass of `Struct` and is packed on 1-byte boundaries, -declared a field whose type (`Inner`) is packed on 8-byte boundaries: - -{% prettify dart tag=pre+code %} -import 'dart:ffi'; - -@Packed(8) -class Inner extends Struct { - external Pointer notEmpty; -} - -@Packed(1) -class Outer extends Struct { - external Pointer notEmpty; - - external [!Inner!] nestedLooselyPacked; -} -{% endprettify %} - -#### Common fixes - -If the inner struct should be packed more tightly, then change the -argument to the inner struct's `Packed` annotation: - -{% prettify dart tag=pre+code %} -import 'dart:ffi'; - -@Packed(1) -class Inner extends Struct { - external Pointer notEmpty; -} - -@Packed(1) -class Outer extends Struct { - external Pointer notEmpty; - - external Inner nestedLooselyPacked; -} -{% endprettify %} - -If the outer struct should be packed less tightly, then change the -argument to the outer struct's `Packed` annotation: - -{% prettify dart tag=pre+code %} -import 'dart:ffi'; - -@Packed(8) -class Inner extends Struct { - external Pointer notEmpty; -} - -@Packed(8) -class Outer extends Struct { - external Pointer notEmpty; - - external Inner nestedLooselyPacked; -} -{% endprettify %} - -If the inner struct doesn't have an annotation and should be packed, then -add an annotation. - -If the inner struct doesn't have an annotation and the outer struct -shouldn't be packed, then remove its annotation. - ### part_of_different_library _Expected this library to be part of '{0}', not '{1}'._ @@ -16538,7 +16452,7 @@ class B extends a.A {} ### subtype_of_disallowed_type -_''{0}' can't be used as a superclass constraint._ +_'{0}' can't be used as a superclass constraint._ _Classes and mixins can't implement '{0}'._ @@ -17123,7 +17037,7 @@ _Invalid context for 'super' invocation._ #### Description The analyzer produces this diagnostic when the keyword `super` is used -outside of a instance method. +outside of an instance method. #### Example @@ -19484,11 +19398,19 @@ _The declaration '{0}' isn't referenced._ The analyzer produces this diagnostic when a private declaration isn't referenced in the library that contains the declaration. The following kinds of declarations are analyzed: -- Private top-level declarations, such as classes, enums, mixins, typedefs, - top-level variables, and top-level functions -- Private static and instance methods +- Private top-level declarations and all of their members +- Private members of public declarations - Optional parameters of private functions for which a value is never - passed, even when the parameter doesn't have a private name + passed + +Not all references to an element will mark it as "used": +- Assigning a value to a top-level variable (with a standard `=` + assignment, or a null-aware `??=` assignment) does not count as using + it. +- Referring to an element in a doc comment reference does not count as + using it. +- Referring to a class, mixin, or enum on the right side of an `is` + expression does not count as using it. #### Example @@ -20371,5 +20293,3 @@ Iterable get zero sync* { yield '0'; } {% endprettify %} - -[C interop using dart:ffi]: /guides/libraries/c-interop diff --git a/tool/analyze-and-test-examples.sh b/tool/analyze-and-test-examples.sh index 1ca015787..8b27bb8e4 100755 --- a/tool/analyze-and-test-examples.sh +++ b/tool/analyze-and-test-examples.sh @@ -60,7 +60,7 @@ function toggle_in_file_analyzer_flags() { fi printf "\n$(blue "Toggling in-file flags: '$action'")\n" find $dir -name "*.dart" ! -path "**/.*" -exec perl \ - -i -pe "s{//$mark(ignore(_for_file)?: .*?\b(stable|beta|dev)\b)}{//$toggle\$dir}g" {} \; + -i -pe "s{//$mark(ignore(_for_file)?: .*?\b(stable|beta|dev)\b)}{//$toggle\$1}g" {} \; } # Analyze and test code for arg $1