From f072c825542c8e4163e4e385d5197581221d1093 Mon Sep 17 00:00:00 2001 From: Dhruv Maradiya Date: Fri, 31 Jan 2025 00:16:46 +0530 Subject: [PATCH 01/67] feat: added toJson functionality --- pkgs/pubspec_parse/lib/src/dependency.dart | 36 +++++++++++++++++++++- pkgs/pubspec_parse/lib/src/pubspec.dart | 31 ++++++++++++++----- pkgs/pubspec_parse/lib/src/pubspec.g.dart | 25 +++++++++++++++ pkgs/pubspec_parse/lib/src/screenshot.dart | 8 +++++ 4 files changed, 91 insertions(+), 9 deletions(-) diff --git a/pkgs/pubspec_parse/lib/src/dependency.dart b/pkgs/pubspec_parse/lib/src/dependency.dart index 24c65eac1..8e0d0c0ed 100644 --- a/pkgs/pubspec_parse/lib/src/dependency.dart +++ b/pkgs/pubspec_parse/lib/src/dependency.dart @@ -90,7 +90,9 @@ Dependency? _fromJson(Object? data, String name) { return null; } -sealed class Dependency {} +sealed class Dependency { + Map toJson(); +} @JsonSerializable() class SdkDependency extends Dependency { @@ -110,6 +112,12 @@ class SdkDependency extends Dependency { @override String toString() => 'SdkDependency: $sdk'; + + @override + Map toJson() => { + 'sdk': sdk, + 'version': version.toString(), + }; } @JsonSerializable() @@ -145,6 +153,15 @@ class GitDependency extends Dependency { @override String toString() => 'GitDependency: url@$url'; + + @override + Map toJson() => { + 'git': { + 'url': url.toString(), + if (ref != null) 'ref': ref, + if (path != null) 'path': path, + }, + }; } Uri? parseGitUriOrNull(String? value) => @@ -204,6 +221,9 @@ class PathDependency extends Dependency { @override String toString() => 'PathDependency: path@$path'; + + @override + Map toJson() => {'path': path}; } @JsonSerializable(disallowUnrecognizedKeys: true) @@ -228,6 +248,12 @@ class HostedDependency extends Dependency { @override String toString() => 'HostedDependency: $version'; + + @override + Map toJson() => { + 'version': version.toString(), + if (hosted != null) 'hosted': hosted!.toJson(), + }; } @JsonSerializable(disallowUnrecognizedKeys: true) @@ -271,7 +297,15 @@ class HostedDetails { @override int get hashCode => Object.hash(name, url); + + Map toJson() => { + if (declaredName != null) 'name': declaredName, + 'url': url.toString(), + }; } VersionConstraint _constraintFromString(String? input) => input == null ? VersionConstraint.any : VersionConstraint.parse(input); + +Map serializeDeps(Map input) => + input.map((k, v) => MapEntry(k, v.toJson())); diff --git a/pkgs/pubspec_parse/lib/src/pubspec.dart b/pkgs/pubspec_parse/lib/src/pubspec.dart index eb779089a..01a1a0d39 100644 --- a/pkgs/pubspec_parse/lib/src/pubspec.dart +++ b/pkgs/pubspec_parse/lib/src/pubspec.dart @@ -11,13 +11,13 @@ import 'screenshot.dart'; part 'pubspec.g.dart'; -@JsonSerializable() +@JsonSerializable(createToJson: true) class Pubspec { // TODO: executables final String name; - @JsonKey(fromJson: _versionFromString) + @JsonKey(fromJson: _versionFromString, toJson: _versionToString) final Version? version; final String? description; @@ -51,7 +51,7 @@ class Pubspec { final List? ignoredAdvisories; /// Optional field for specifying included screenshot files. - @JsonKey(fromJson: parseScreenshots) + @JsonKey(fromJson: parseScreenshots, toJson: serializeScreenshots) final List? screenshots; /// If there is exactly 1 value in [authors], returns it. @@ -73,16 +73,16 @@ class Pubspec { final List authors; final String? documentation; - @JsonKey(fromJson: _environmentMap) + @JsonKey(fromJson: _environmentMap, toJson: _serializeEnvironment) final Map environment; - @JsonKey(fromJson: parseDeps) + @JsonKey(fromJson: parseDeps, toJson: serializeDeps) final Map dependencies; - @JsonKey(fromJson: parseDeps) + @JsonKey(fromJson: parseDeps, toJson: serializeDeps) final Map devDependencies; - @JsonKey(fromJson: parseDeps) + @JsonKey(fromJson: parseDeps, toJson: serializeDeps) final Map dependencyOverrides; /// Optional configuration specific to [Flutter](https://flutter.io/) @@ -94,7 +94,7 @@ class Pubspec { final Map? flutter; /// Optional field to specify executables - @JsonKey(fromJson: _executablesMap) + @JsonKey(fromJson: _executablesMap, toJson: _serializeExecutables) final Map executables; /// If this package is a Pub Workspace, this field lists the sub-packages. @@ -177,6 +177,9 @@ class Pubspec { return _$PubspecFromJson(json); } + // Returns a JSON-serializable map for this instance. + Map toJson() => _$PubspecToJson(this); + /// Parses source [yaml] into [Pubspec]. /// /// When [lenient] is set, top-level property-parsing or type cast errors are @@ -256,3 +259,15 @@ Map _executablesMap(Map? source) => } }) ?? {}; + +Map _serializeEnvironment( + Map map, +) => + map.map( + (key, value) => MapEntry(key, value?.toString()), + ); + +String? _versionToString(Version? version) => version?.toString(); + +Map _serializeExecutables(Map? map) => + map?.map(MapEntry.new) ?? {}; diff --git a/pkgs/pubspec_parse/lib/src/pubspec.g.dart b/pkgs/pubspec_parse/lib/src/pubspec.g.dart index 58e015a5e..96b1af203 100644 --- a/pkgs/pubspec_parse/lib/src/pubspec.g.dart +++ b/pkgs/pubspec_parse/lib/src/pubspec.g.dart @@ -67,3 +67,28 @@ Pubspec _$PubspecFromJson(Map json) => $checkedCreate( 'dependencyOverrides': 'dependency_overrides' }, ); + +Map _$PubspecToJson(Pubspec instance) => { + 'name': instance.name, + 'version': _versionToString(instance.version), + 'description': instance.description, + 'homepage': instance.homepage, + 'publish_to': instance.publishTo, + 'repository': instance.repository?.toString(), + 'issue_tracker': instance.issueTracker?.toString(), + 'funding': instance.funding?.map((e) => e.toString()).toList(), + 'topics': instance.topics, + 'ignored_advisories': instance.ignoredAdvisories, + 'screenshots': serializeScreenshots(instance.screenshots), + 'author': instance.author, + 'authors': instance.authors, + 'documentation': instance.documentation, + 'environment': _serializeEnvironment(instance.environment), + 'dependencies': serializeDeps(instance.dependencies), + 'dev_dependencies': serializeDeps(instance.devDependencies), + 'dependency_overrides': serializeDeps(instance.dependencyOverrides), + 'flutter': instance.flutter, + 'executables': _serializeExecutables(instance.executables), + 'workspace': instance.workspace, + 'resolution': instance.resolution, + }; diff --git a/pkgs/pubspec_parse/lib/src/screenshot.dart b/pkgs/pubspec_parse/lib/src/screenshot.dart index f5f0be2ea..1ca861367 100644 --- a/pkgs/pubspec_parse/lib/src/screenshot.dart +++ b/pkgs/pubspec_parse/lib/src/screenshot.dart @@ -63,3 +63,11 @@ List parseScreenshots(List? input) { } return res; } + +List> serializeScreenshots(List? input) => + input + ?.map( + (e) => {'description': e.description, 'path': e.path}, + ) + .toList() ?? + []; From 5170f1d3525778582b2031d819424fb33bcb4905 Mon Sep 17 00:00:00 2001 From: Dhruv Maradiya Date: Fri, 31 Jan 2025 00:16:54 +0530 Subject: [PATCH 02/67] test: add unit tests for Pubspec toJson functionality --- pkgs/pubspec_parse/test/tojson_test.dart | 308 +++++++++++++++++++++++ 1 file changed, 308 insertions(+) create mode 100644 pkgs/pubspec_parse/test/tojson_test.dart diff --git a/pkgs/pubspec_parse/test/tojson_test.dart b/pkgs/pubspec_parse/test/tojson_test.dart new file mode 100644 index 000000000..eb9161f15 --- /dev/null +++ b/pkgs/pubspec_parse/test/tojson_test.dart @@ -0,0 +1,308 @@ +// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +// ignore_for_file: deprecated_member_use_from_same_package + +import 'package:pub_semver/pub_semver.dart'; +import 'package:pubspec_parse/pubspec_parse.dart'; +import 'package:test/test.dart'; + +import 'test_utils.dart'; + +void main() { + group('Pubspec toJson tests', () { + test('minimal set values', () async { + final value = await parse(defaultPubspec); + final jsonValue = value.toJson(); + + expect(jsonValue['name'], 'sample'); + expect(jsonValue['version'], isNull); + expect(jsonValue['publishTo'], isNull); + expect(jsonValue['description'], isNull); + expect(jsonValue['homepage'], isNull); + expect(jsonValue['author'], isNull); + expect(jsonValue['authors'], isEmpty); + expect(jsonValue['environment'], {'sdk': '>=2.12.0 <3.0.0'}); + expect(jsonValue['documentation'], isNull); + expect(jsonValue['dependencies'], isEmpty); + expect(jsonValue['dev_dependencies'], isEmpty); + expect(jsonValue['dependency_overrides'], isEmpty); + expect(jsonValue['flutter'], isNull); + expect(jsonValue['repository'], isNull); + expect(jsonValue['issue_tracker'], isNull); + expect(jsonValue['screenshots'], isEmpty); + expect(jsonValue['workspace'], isNull); + expect(jsonValue['resolution'], isNull); + expect(jsonValue['executables'], isEmpty); + }); + + test('all fields set', () async { + final version = Version.parse('1.2.3'); + final sdkConstraint = VersionConstraint.parse('>=3.6.0 <4.0.0'); + + final value = await parse( + { + 'name': 'sample', + 'version': version.toString(), + 'publish_to': 'none', + 'author': 'name@example.com', + 'environment': {'sdk': sdkConstraint.toString()}, + 'description': 'description', + 'homepage': 'homepage', + 'documentation': 'documentation', + 'repository': 'https://github.com/example/repo', + 'issue_tracker': 'https://github.com/example/repo/issues', + 'funding': ['https://patreon.com/example'], + 'topics': ['widget', 'button'], + 'ignored_advisories': ['111', '222'], + 'screenshots': [ + {'description': 'my screenshot', 'path': 'path/to/screenshot'}, + ], + 'workspace': ['pkg1', 'pkg2'], + 'resolution': 'workspace', + 'executables': { + 'my_script': 'bin/my_script.dart', + 'my_script2': 'bin/my_script2.dart', + }, + 'dependencies': {'foo': '1.0.0'}, + 'dev_dependencies': {'bar': '2.0.0'}, + 'dependency_overrides': {'baz': '3.0.0'}, + }, + skipTryPub: true, + ); + + final jsonValue = value.toJson(); + + expect(jsonValue['name'], 'sample'); + expect(jsonValue['version'], version.toString()); + expect(jsonValue['publish_to'], 'none'); + expect(jsonValue['description'], 'description'); + expect(jsonValue['homepage'], 'homepage'); + expect(jsonValue['author'], 'name@example.com'); + expect(jsonValue['authors'], ['name@example.com']); + expect( + jsonValue['environment'], + containsPair('sdk', sdkConstraint.toString()), + ); + expect(jsonValue['documentation'], 'documentation'); + expect(jsonValue['dependencies'], hasLength(1)); + expect( + jsonValue['dependencies'], + containsPair('foo', {'version': '1.0.0'}), + ); + expect(jsonValue['dev_dependencies'], hasLength(1)); + expect( + jsonValue['dev_dependencies'], + containsPair('bar', { + 'version': '2.0.0', + }), + ); + expect(jsonValue['dependency_overrides'], hasLength(1)); + expect( + jsonValue['dependency_overrides'], + containsPair('baz', { + 'version': '3.0.0', + }), + ); + expect(jsonValue['repository'], 'https://github.com/example/repo'); + expect( + jsonValue['issue_tracker'], + 'https://github.com/example/repo/issues', + ); + expect(jsonValue['funding'], ['https://patreon.com/example']); + expect(jsonValue['topics'], ['widget', 'button']); + expect(jsonValue['ignored_advisories'], ['111', '222']); + expect(jsonValue['screenshots'], [ + {'description': 'my screenshot', 'path': 'path/to/screenshot'}, + ]); + expect(jsonValue['workspace'], ['pkg1', 'pkg2']); + expect(jsonValue['resolution'], 'workspace'); + expect(jsonValue['executables'], { + 'my_script': 'bin/my_script.dart', + 'my_script2': 'bin/my_script2.dart', + }); + }); + }); + + group('Pubspec round trip tests', () { + test('minimal set values', () async { + final value = await parse(defaultPubspec); + final jsonValue = value.toJson(); + final newValue = Pubspec.fromJson(jsonValue); + + expect(newValue.name, value.name); + expect(newValue.version, value.version); + expect(newValue.publishTo, value.publishTo); + expect(newValue.description, value.description); + expect(newValue.homepage, value.homepage); + expect(newValue.author, value.author); + expect(newValue.authors, value.authors); + expect(newValue.environment, value.environment); + expect(newValue.documentation, value.documentation); + expect(newValue.dependencies, value.dependencies); + expect(newValue.devDependencies, value.devDependencies); + expect(newValue.dependencyOverrides, value.dependencyOverrides); + expect(newValue.flutter, value.flutter); + expect(newValue.repository, value.repository); + expect(newValue.issueTracker, value.issueTracker); + expect(newValue.screenshots, value.screenshots); + expect(newValue.workspace, value.workspace); + expect(newValue.resolution, value.resolution); + expect(newValue.executables, value.executables); + }); + + test('all fields set', () async { + final version = Version.parse('1.2.3'); + final sdkConstraint = VersionConstraint.parse('>=3.6.0 <4.0.0'); + final value = await parse( + { + 'name': 'sample', + 'version': version.toString(), + 'publish_to': 'none', + 'author': 'name@example.com', + 'environment': {'sdk': sdkConstraint.toString()}, + 'description': 'description', + 'homepage': 'homepage', + 'documentation': 'documentation', + 'repository': 'https://github.com/example/repo', + 'issue_tracker': 'https://github.com/example/repo/issues', + 'funding': ['https://patreon.com/example'], + 'topics': ['widget', 'button'], + 'ignored_advisories': ['111', '222'], + 'screenshots': [ + {'description': 'my screenshot', 'path': 'path/to/screenshot'}, + ], + 'workspace': ['pkg1', 'pkg2'], + 'resolution': 'workspace', + 'executables': { + 'my_script': 'bin/my_script.dart', + 'my_script2': 'bin/my_script2.dart', + }, + 'dependencies': {'foo': '1.0.0'}, + 'dev_dependencies': {'bar': '2.0.0'}, + 'dependency_overrides': {'baz': '3.0.0'}, + }, + skipTryPub: true, + ); + + final jsonValue = value.toJson(); + + final newValue = Pubspec.fromJson(jsonValue); + + expect(newValue.name, value.name); + expect(newValue.version, value.version); + expect(newValue.publishTo, value.publishTo); + expect(newValue.description, value.description); + expect(newValue.homepage, value.homepage); + expect(newValue.author, value.author); + expect(newValue.authors, value.authors); + expect(newValue.environment, value.environment); + expect(newValue.documentation, value.documentation); + expect(newValue.dependencies, value.dependencies); + expect(newValue.devDependencies, value.devDependencies); + expect(newValue.dependencyOverrides, value.dependencyOverrides); + expect(newValue.flutter, value.flutter); + expect(newValue.repository, value.repository); + expect(newValue.issueTracker, value.issueTracker); + expect(newValue.screenshots?.length, value.screenshots?.length); + expect( + newValue.screenshots?.first.description, + value.screenshots?.first.description, + ); + expect(newValue.screenshots?.first.path, value.screenshots?.first.path); + expect(newValue.workspace, value.workspace); + expect(newValue.resolution, value.resolution); + expect(newValue.executables, value.executables); + }); + + test('dependencies', () async { + final value = await parse( + { + ...defaultPubspec, + 'dependencies': { + 'flutter': { + 'sdk': 'flutter', + }, + 'http': '^1.1.0', + 'provider': { + 'version': '^6.0.5', + }, + 'firebase_core': { + 'hosted': { + 'name': 'firebase_core', + 'url': 'https://pub.dev', + }, + 'version': '^2.13.0', + }, + 'google_fonts': { + 'sdk': 'flutter', + 'version': '^4.0.3', + }, + 'flutter_bloc': { + 'git': 'https://github.com/felangel/bloc.git', + }, + 'shared_preferences': { + 'git': { + 'url': 'https://github.com/flutter/plugins.git', + 'ref': 'main', + 'path': 'packages/shared_preferences/shared_preferences', + }, + }, + 'local_utils': { + 'path': '../local_utils', + }, + }, + }, + skipTryPub: true, + ); + + final jsonValue = value.toJson(); + + final newValue = await parse(jsonValue, skipTryPub: true); + + expect(value.dependencies, hasLength(8)); + expect(value.dependencies['flutter'], isA()); + expect(value.dependencies['http'], isA()); + expect(value.dependencies['provider'], isA()); + expect(value.dependencies['firebase_core'], isA()); + expect(value.dependencies['google_fonts'], isA()); + expect(value.dependencies['flutter_bloc'], isA()); + expect(value.dependencies['shared_preferences'], isA()); + expect(value.dependencies['local_utils'], isA()); + + expect( + value.dependencies['flutter']?.toJson(), + newValue.dependencies['flutter']?.toJson(), + ); + expect( + value.dependencies['http']?.toJson(), + newValue.dependencies['http']?.toJson(), + ); + expect( + value.dependencies['provider']?.toJson(), + newValue.dependencies['provider']?.toJson(), + ); + expect( + value.dependencies['firebase_core']?.toJson(), + newValue.dependencies['firebase_core']?.toJson(), + ); + expect( + value.dependencies['google_fonts']?.toJson(), + newValue.dependencies['google_fonts']?.toJson(), + ); + expect( + value.dependencies['flutter_bloc']?.toJson(), + newValue.dependencies['flutter_bloc']?.toJson(), + ); + expect( + value.dependencies['shared_preferences']?.toJson(), + newValue.dependencies['shared_preferences']?.toJson(), + ); + expect( + value.dependencies['local_utils']?.toJson(), + newValue.dependencies['local_utils']?.toJson(), + ); + }); + }); +} From 68a12a639da8f5731af2ebc0f246d7284c33c190 Mon Sep 17 00:00:00 2001 From: Dhruv Maradiya Date: Fri, 31 Jan 2025 00:25:14 +0530 Subject: [PATCH 03/67] chore: update changelog and version to 1.5.1 --- pkgs/pubspec_parse/CHANGELOG.md | 4 ++++ pkgs/pubspec_parse/pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/pkgs/pubspec_parse/CHANGELOG.md b/pkgs/pubspec_parse/CHANGELOG.md index 5aeb49802..a83135808 100644 --- a/pkgs/pubspec_parse/CHANGELOG.md +++ b/pkgs/pubspec_parse/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.5.1 + +- Added `toJson` method to `Pubspec` to serialize the object back to a `Map`. + ## 1.5.0 - Added fields to `Pubspec`: `executables`, `resolution`, `workspace`. diff --git a/pkgs/pubspec_parse/pubspec.yaml b/pkgs/pubspec_parse/pubspec.yaml index 90741efff..eb9b35853 100644 --- a/pkgs/pubspec_parse/pubspec.yaml +++ b/pkgs/pubspec_parse/pubspec.yaml @@ -1,5 +1,5 @@ name: pubspec_parse -version: 1.5.0 +version: 1.5.1 description: >- Simple package for parsing pubspec.yaml files with a type-safe API and rich error reporting. From a22391e67a87a0ba67d865254d06e71411ed505b Mon Sep 17 00:00:00 2001 From: Dhruv Maradiya Date: Fri, 31 Jan 2025 10:09:53 +0530 Subject: [PATCH 04/67] update version to 1.6.0-wip in changelog and pubspec.yaml --- pkgs/pubspec_parse/CHANGELOG.md | 2 +- pkgs/pubspec_parse/pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/pubspec_parse/CHANGELOG.md b/pkgs/pubspec_parse/CHANGELOG.md index a83135808..cc5c06b16 100644 --- a/pkgs/pubspec_parse/CHANGELOG.md +++ b/pkgs/pubspec_parse/CHANGELOG.md @@ -1,4 +1,4 @@ -## 1.5.1 +## 1.6.0-wip - Added `toJson` method to `Pubspec` to serialize the object back to a `Map`. diff --git a/pkgs/pubspec_parse/pubspec.yaml b/pkgs/pubspec_parse/pubspec.yaml index eb9b35853..4a5f4568f 100644 --- a/pkgs/pubspec_parse/pubspec.yaml +++ b/pkgs/pubspec_parse/pubspec.yaml @@ -1,5 +1,5 @@ name: pubspec_parse -version: 1.5.1 +version: 1.6.0-wip description: >- Simple package for parsing pubspec.yaml files with a type-safe API and rich error reporting. From 026fc6e0e90c88456acf9c52f6af10a44bc3063b Mon Sep 17 00:00:00 2001 From: Matthew Berry Date: Thu, 17 Apr 2025 05:55:20 -0700 Subject: [PATCH 05/67] Support newInstanceNamed with empty name (#1194) Co-authored-by: Moritz --- pkgs/code_builder/CHANGELOG.md | 1 + pkgs/code_builder/lib/src/specs/expression.dart | 4 ++-- pkgs/code_builder/test/specs/code/expression_test.dart | 7 +++++++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/pkgs/code_builder/CHANGELOG.md b/pkgs/code_builder/CHANGELOG.md index 471f72f33..1d86e5cbb 100644 --- a/pkgs/code_builder/CHANGELOG.md +++ b/pkgs/code_builder/CHANGELOG.md @@ -2,6 +2,7 @@ * Upgrade `dart_style` and `source_gen` to remove `package:macros` dependency. * Require Dart `^3.6.0` due to the upgrades. +* Support `Expression.newInstanceNamed` with empty name ## 4.10.1 diff --git a/pkgs/code_builder/lib/src/specs/expression.dart b/pkgs/code_builder/lib/src/specs/expression.dart index b9193e6f3..aa06de283 100644 --- a/pkgs/code_builder/lib/src/specs/expression.dart +++ b/pkgs/code_builder/lib/src/specs/expression.dart @@ -578,10 +578,10 @@ abstract mixin class ExpressionEmitter final out = output ??= StringBuffer(); return _writeConstExpression(out, expression.isConst, () { expression.target.accept(this, out); - if (expression.name != null) { + if (expression.name case final name? when name.isNotEmpty) { out ..write('.') - ..write(expression.name); + ..write(name); } if (expression.typeArguments.isNotEmpty) { out.write('<'); diff --git a/pkgs/code_builder/test/specs/code/expression_test.dart b/pkgs/code_builder/test/specs/code/expression_test.dart index 4ce9ebaf5..7a424fd8e 100644 --- a/pkgs/code_builder/test/specs/code/expression_test.dart +++ b/pkgs/code_builder/test/specs/code/expression_test.dart @@ -251,6 +251,13 @@ void main() { ); }); + test('should emit invoking unnamed constructor when name is empty', () { + expect( + refer('Foo').newInstanceNamed('', []), + equalsDart('Foo()'), + ); + }); + test('should emit invoking const Type()', () { expect( refer('Object').constInstance([]), From c1cbc845f9c561f30be87244d8a252b6f921e2a2 Mon Sep 17 00:00:00 2001 From: Jacob MacDonald Date: Mon, 21 Apr 2025 09:02:51 -0700 Subject: [PATCH 06/67] allow custom ID generators in Clients, and support String ids (#2077) This is to support using this package to write a dart analyzer LSP client, which requires String ids. Also closes https://github.com/dart-lang/tools/issues/738 This is technically breaking because there was some code which tries to coerce string ids back into integers, but this should never have happened. Any server stringifying integer IDs is not spec compliant. Let me know if we want to release this as non-breaking instead of breaking, I think it would be fine personally, but don't feel strongly either way. --- pkgs/json_rpc_2/CHANGELOG.md | 7 + pkgs/json_rpc_2/lib/src/client.dart | 38 +- pkgs/json_rpc_2/lib/src/peer.dart | 30 +- pkgs/json_rpc_2/pubspec.yaml | 2 +- pkgs/json_rpc_2/test/client/client_test.dart | 360 ++++++++++--------- pkgs/json_rpc_2/test/client/utils.dart | 5 +- 6 files changed, 258 insertions(+), 184 deletions(-) diff --git a/pkgs/json_rpc_2/CHANGELOG.md b/pkgs/json_rpc_2/CHANGELOG.md index 1f2cf8e8e..0ab99dab8 100644 --- a/pkgs/json_rpc_2/CHANGELOG.md +++ b/pkgs/json_rpc_2/CHANGELOG.md @@ -1,3 +1,10 @@ +## 4.0.0 + +* Add custom ID generator option to clients, which allows for `String` ids. +* **Breaking**: When `String` ids are present in a response, we no longer + automatically try to parse them as integers. This behavior was never a part + of the spec, and is not compatible with allowing custom ID generators. + ## 3.0.3 * Require Dart 3.4 diff --git a/pkgs/json_rpc_2/lib/src/client.dart b/pkgs/json_rpc_2/lib/src/client.dart index 182f94584..388c6017a 100644 --- a/pkgs/json_rpc_2/lib/src/client.dart +++ b/pkgs/json_rpc_2/lib/src/client.dart @@ -18,8 +18,8 @@ import 'utils.dart'; class Client { final StreamChannel _channel; - /// The next request id. - var _id = 0; + /// A function to generate the next request id. + Object Function() _idGenerator; /// The current batch of requests to be sent together. /// @@ -27,7 +27,9 @@ class Client { List>? _batch; /// The map of request ids to pending requests. - final _pendingRequests = {}; + /// + /// Keys must be of type `int` or `String`. + final _pendingRequests = {}; final _done = Completer(); @@ -49,9 +51,14 @@ class Client { /// /// Note that the client won't begin listening to [channel] until /// [Client.listen] is called. - Client(StreamChannel channel) + /// + /// If [idGenerator] is passed, it will be called to generate an ID for each + /// request. Defaults to an auto-incrementing `int`. The value returned must + /// be either an `int` or `String`. + Client(StreamChannel channel, {Object Function()? idGenerator}) : this.withoutJson( - jsonDocument.bind(channel).transformStream(ignoreFormatExceptions)); + jsonDocument.bind(channel).transformStream(ignoreFormatExceptions), + idGenerator: idGenerator); /// Creates a [Client] that communicates using decoded messages over /// [_channel]. @@ -61,7 +68,12 @@ class Client { /// /// Note that the client won't begin listening to [_channel] until /// [Client.listen] is called. - Client.withoutJson(this._channel) { + /// + /// If [_idGenerator] is passed, it will be called to generate an ID for each + /// request. Defaults to an auto-incrementing `int`. The value returned must + /// be either an `int` or `String`. + Client.withoutJson(this._channel, {Object Function()? idGenerator}) + : _idGenerator = idGenerator ?? _createIncrementingIdGenerator() { done.whenComplete(() { for (var request in _pendingRequests.values) { request.completer.completeError(StateError( @@ -115,7 +127,7 @@ class Client { /// Throws a [StateError] if the client is closed while the request is in /// flight, or if the client is closed when this method is called. Future sendRequest(String method, [Object? parameters]) { - var id = _id++; + var id = _idGenerator(); _send(method, parameters, id); var completer = Completer.sync(); @@ -142,7 +154,7 @@ class Client { /// /// Sends a request to invoke [method] with [parameters]. If [id] is given, /// the request uses that id. - void _send(String method, Object? parameters, [int? id]) { + void _send(String method, Object? parameters, [Object? id]) { if (parameters is Iterable) parameters = parameters.toList(); if (parameters is! Map && parameters is! List && parameters != null) { throw ArgumentError('Only maps and lists may be used as JSON-RPC ' @@ -201,7 +213,6 @@ class Client { if (!_isResponseValid(response_)) return; final response = response_ as Map; var id = response['id']; - id = (id is String) ? int.parse(id) : id; var request = _pendingRequests.remove(id)!; if (response.containsKey('result')) { request.completer.complete(response['result']); @@ -218,7 +229,6 @@ class Client { if (response is! Map) return false; if (response['jsonrpc'] != '2.0') return false; var id = response['id']; - id = (id is String) ? int.parse(id) : id; if (!_pendingRequests.containsKey(id)) return false; if (response.containsKey('result')) return true; @@ -244,3 +254,11 @@ class _Request { _Request(this.method, this.completer, this.chain); } + +/// The default ID generator, uses an auto incrementing integer. +/// +/// Each call returns a new function which starts back a `0`. +int Function() _createIncrementingIdGenerator() { + var nextId = 0; + return () => nextId++; +} diff --git a/pkgs/json_rpc_2/lib/src/peer.dart b/pkgs/json_rpc_2/lib/src/peer.dart index 677b6e15f..71d9093df 100644 --- a/pkgs/json_rpc_2/lib/src/peer.dart +++ b/pkgs/json_rpc_2/lib/src/peer.dart @@ -59,12 +59,20 @@ class Peer implements Client, Server { /// some requests which are not conformant with the JSON-RPC 2.0 /// specification. In particular, requests missing the `jsonrpc` parameter /// will be accepted. - Peer(StreamChannel channel, - {ErrorCallback? onUnhandledError, bool strictProtocolChecks = true}) - : this.withoutJson( + /// + /// If [idGenerator] is passed, it will be called to generate an ID for each + /// request. Defaults to an auto-incrementing `int`. The value returned must + /// be either an `int` or `String`. + Peer( + StreamChannel channel, { + ErrorCallback? onUnhandledError, + bool strictProtocolChecks = true, + Object Function()? idGenerator, + }) : this.withoutJson( jsonDocument.bind(channel).transform(respondToFormatExceptions), onUnhandledError: onUnhandledError, - strictProtocolChecks: strictProtocolChecks); + strictProtocolChecks: strictProtocolChecks, + idGenerator: idGenerator); /// Creates a [Peer] that communicates using decoded messages over [_channel]. /// @@ -81,14 +89,24 @@ class Peer implements Client, Server { /// some requests which are not conformant with the JSON-RPC 2.0 /// specification. In particular, requests missing the `jsonrpc` parameter /// will be accepted. + /// + /// If [idGenerator] is passed, it will be called to generate an ID for each + /// request. Defaults to an auto-incrementing `int`. The value returned must + /// be either an `int` or `String`. Peer.withoutJson(this._channel, - {ErrorCallback? onUnhandledError, bool strictProtocolChecks = true}) { + {ErrorCallback? onUnhandledError, + bool strictProtocolChecks = true, + Object Function()? idGenerator}) { _server = Server.withoutJson( StreamChannel(_serverIncomingForwarder.stream, _channel.sink), onUnhandledError: onUnhandledError, strictProtocolChecks: strictProtocolChecks); _client = Client.withoutJson( - StreamChannel(_clientIncomingForwarder.stream, _channel.sink)); + StreamChannel( + _clientIncomingForwarder.stream, + _channel.sink, + ), + idGenerator: idGenerator); } // Client methods. diff --git a/pkgs/json_rpc_2/pubspec.yaml b/pkgs/json_rpc_2/pubspec.yaml index 7b4227876..ad4e839c5 100644 --- a/pkgs/json_rpc_2/pubspec.yaml +++ b/pkgs/json_rpc_2/pubspec.yaml @@ -1,5 +1,5 @@ name: json_rpc_2 -version: 3.0.3 +version: 4.0.0 description: >- Utilities to write a client or server using the JSON-RPC 2.0 spec. repository: https://github.com/dart-lang/tools/tree/main/pkgs/json_rpc_2 diff --git a/pkgs/json_rpc_2/test/client/client_test.dart b/pkgs/json_rpc_2/test/client/client_test.dart index 1a4f65d08..d907305c6 100644 --- a/pkgs/json_rpc_2/test/client/client_test.dart +++ b/pkgs/json_rpc_2/test/client/client_test.dart @@ -11,208 +11,238 @@ import 'utils.dart'; void main() { late ClientController controller; - setUp(() => controller = ClientController()); - - test('sends a message and returns the response', () { - controller.expectRequest((request) { - expect( - request, - allOf([ - containsPair('jsonrpc', '2.0'), - containsPair('method', 'foo'), - containsPair('params', {'param': 'value'}) - ])); + group('Default options', () { + setUp(() => controller = ClientController()); + + test('sends a message and returns the response', () { + controller.expectRequest((request) { + expect( + request, + allOf([ + containsPair('jsonrpc', '2.0'), + containsPair('method', 'foo'), + containsPair('params', {'param': 'value'}) + ])); + + return {'jsonrpc': '2.0', 'result': 'bar', 'id': request['id']}; + }); - return {'jsonrpc': '2.0', 'result': 'bar', 'id': request['id']}; + expect(controller.client.sendRequest('foo', {'param': 'value'}), + completion(equals('bar'))); }); - expect(controller.client.sendRequest('foo', {'param': 'value'}), - completion(equals('bar'))); - }); + test('sends a notification and expects no response', () { + controller.expectRequest((request) { + expect( + request, + equals({ + 'jsonrpc': '2.0', + 'method': 'foo', + 'params': {'param': 'value'} + })); + }); - test('sends a message and returns the response with String id', () { - controller.expectRequest((request) { - expect( - request, - allOf([ - containsPair('jsonrpc', '2.0'), - containsPair('method', 'foo'), - containsPair('params', {'param': 'value'}) - ])); + controller.client.sendNotification('foo', {'param': 'value'}); + }); - return { - 'jsonrpc': '2.0', - 'result': 'bar', - 'id': request['id'].toString() - }; + test('sends a notification with positional parameters', () { + controller.expectRequest((request) { + expect( + request, + equals({ + 'jsonrpc': '2.0', + 'method': 'foo', + 'params': ['value1', 'value2'] + })); + }); + + controller.client.sendNotification('foo', ['value1', 'value2']); }); - expect(controller.client.sendRequest('foo', {'param': 'value'}), - completion(equals('bar'))); - }); + test('sends a notification with no parameters', () { + controller.expectRequest((request) { + expect(request, equals({'jsonrpc': '2.0', 'method': 'foo'})); + }); - test('sends a notification and expects no response', () { - controller.expectRequest((request) { - expect( - request, - equals({ - 'jsonrpc': '2.0', - 'method': 'foo', - 'params': {'param': 'value'} - })); + controller.client.sendNotification('foo'); }); - controller.client.sendNotification('foo', {'param': 'value'}); - }); + test('sends a synchronous batch of requests', () { + controller.expectRequest((request) { + expect(request, isA()); + expect(request, hasLength(3)); + expect(request[0], equals({'jsonrpc': '2.0', 'method': 'foo'})); + expect( + request[1], + allOf([ + containsPair('jsonrpc', '2.0'), + containsPair('method', 'bar'), + containsPair('params', {'param': 'value'}) + ])); + expect( + request[2], + allOf([ + containsPair('jsonrpc', '2.0'), + containsPair('method', 'baz') + ])); + + return [ + {'jsonrpc': '2.0', 'result': 'baz response', 'id': request[2]['id']}, + {'jsonrpc': '2.0', 'result': 'bar response', 'id': request[1]['id']} + ]; + }); - test('sends a notification with positional parameters', () { - controller.expectRequest((request) { - expect( - request, - equals({ - 'jsonrpc': '2.0', - 'method': 'foo', - 'params': ['value1', 'value2'] - })); + controller.client.withBatch(() { + controller.client.sendNotification('foo'); + expect(controller.client.sendRequest('bar', {'param': 'value'}), + completion(equals('bar response'))); + expect(controller.client.sendRequest('baz'), + completion(equals('baz response'))); + }); }); - controller.client.sendNotification('foo', ['value1', 'value2']); - }); + test('sends an asynchronous batch of requests', () { + controller.expectRequest((request) { + expect(request, isA()); + expect(request, hasLength(3)); + expect(request[0], equals({'jsonrpc': '2.0', 'method': 'foo'})); + expect( + request[1], + allOf([ + containsPair('jsonrpc', '2.0'), + containsPair('method', 'bar'), + containsPair('params', {'param': 'value'}) + ])); + expect( + request[2], + allOf([ + containsPair('jsonrpc', '2.0'), + containsPair('method', 'baz') + ])); + + return [ + {'jsonrpc': '2.0', 'result': 'baz response', 'id': request[2]['id']}, + {'jsonrpc': '2.0', 'result': 'bar response', 'id': request[1]['id']} + ]; + }); - test('sends a notification with no parameters', () { - controller.expectRequest((request) { - expect(request, equals({'jsonrpc': '2.0', 'method': 'foo'})); + controller.client.withBatch(() { + return Future.value().then((_) { + controller.client.sendNotification('foo'); + }).then((_) { + expect(controller.client.sendRequest('bar', {'param': 'value'}), + completion(equals('bar response'))); + }).then((_) { + expect(controller.client.sendRequest('baz'), + completion(equals('baz response'))); + }); + }); }); - controller.client.sendNotification('foo'); - }); + test('reports an error from the server', () { + controller.expectRequest((request) { + expect( + request, + allOf([ + containsPair('jsonrpc', '2.0'), + containsPair('method', 'foo') + ])); + + return { + 'jsonrpc': '2.0', + 'error': { + 'code': error_code.SERVER_ERROR, + 'message': 'you are bad at requests', + 'data': 'some junk' + }, + 'id': request['id'] + }; + }); - test('sends a synchronous batch of requests', () { - controller.expectRequest((request) { - expect(request, isA()); - expect(request, hasLength(3)); - expect(request[0], equals({'jsonrpc': '2.0', 'method': 'foo'})); expect( - request[1], - allOf([ - containsPair('jsonrpc', '2.0'), - containsPair('method', 'bar'), - containsPair('params', {'param': 'value'}) - ])); - expect( - request[2], - allOf( - [containsPair('jsonrpc', '2.0'), containsPair('method', 'baz')])); - - return [ - {'jsonrpc': '2.0', 'result': 'baz response', 'id': request[2]['id']}, - {'jsonrpc': '2.0', 'result': 'bar response', 'id': request[1]['id']} - ]; + controller.client.sendRequest('foo', {'param': 'value'}), + throwsA(isA() + .having((e) => e.code, 'code', error_code.SERVER_ERROR) + .having((e) => e.message, 'message', 'you are bad at requests') + .having((e) => e.data, 'data', 'some junk'))); }); - controller.client.withBatch(() { - controller.client.sendNotification('foo'); - expect(controller.client.sendRequest('bar', {'param': 'value'}), - completion(equals('bar response'))); - expect(controller.client.sendRequest('baz'), - completion(equals('baz response'))); + test('requests throw StateErrors if the client is closed', () { + controller.client.close(); + expect(() => controller.client.sendRequest('foo'), throwsStateError); + expect(() => controller.client.sendNotification('foo'), throwsStateError); + }); + + test('ignores bogus responses', () { + // Make a request so we have something to respond to. + controller.expectRequest((request) { + controller.sendJsonResponse('{invalid'); + controller.sendResponse('not a map'); + controller.sendResponse({ + 'jsonrpc': 'wrong version', + 'result': 'wrong', + 'id': request['id'] + }); + controller.sendResponse({'jsonrpc': '2.0', 'result': 'wrong'}); + controller.sendResponse({'jsonrpc': '2.0', 'id': request['id']}); + controller.sendResponse( + {'jsonrpc': '2.0', 'error': 'not a map', 'id': request['id']}); + controller.sendResponse({ + 'jsonrpc': '2.0', + 'error': {'code': 'not an int', 'message': 'dang yo'}, + 'id': request['id'] + }); + controller.sendResponse({ + 'jsonrpc': '2.0', + 'error': {'code': 123, 'message': 0xDEADBEEF}, + 'id': request['id'] + }); + + return pumpEventQueue().then( + (_) => {'jsonrpc': '2.0', 'result': 'right', 'id': request['id']}); + }); + + expect(controller.client.sendRequest('foo'), completion(equals('right'))); }); }); - test('sends an asynchronous batch of requests', () { + test('with custom String ids', () { + var id = 0; + controller = ClientController(idGenerator: () => 'ID-${id++}'); controller.expectRequest((request) { - expect(request, isA()); - expect(request, hasLength(3)); - expect(request[0], equals({'jsonrpc': '2.0', 'method': 'foo'})); expect( - request[1], + request, allOf([ containsPair('jsonrpc', '2.0'), - containsPair('method', 'bar'), - containsPair('params', {'param': 'value'}) + containsPair('method', 'foo'), + containsPair('params', {'param': 'value'}), + containsPair('id', 'ID-0'), ])); - expect( - request[2], - allOf( - [containsPair('jsonrpc', '2.0'), containsPair('method', 'baz')])); - - return [ - {'jsonrpc': '2.0', 'result': 'baz response', 'id': request[2]['id']}, - {'jsonrpc': '2.0', 'result': 'bar response', 'id': request[1]['id']} - ]; - }); - controller.client.withBatch(() { - return Future.value().then((_) { - controller.client.sendNotification('foo'); - }).then((_) { - expect(controller.client.sendRequest('bar', {'param': 'value'}), - completion(equals('bar response'))); - }).then((_) { - expect(controller.client.sendRequest('baz'), - completion(equals('baz response'))); - }); + return {'jsonrpc': '2.0', 'result': 'bar', 'id': request['id']}; }); + + expect(controller.client.sendRequest('foo', {'param': 'value'}), + completion(equals('bar'))); }); - test('reports an error from the server', () { + test('String ids are not parsed as ints', () { + var id = 0; + controller = ClientController(idGenerator: () => '${id++}'); controller.expectRequest((request) { expect( request, - allOf( - [containsPair('jsonrpc', '2.0'), containsPair('method', 'foo')])); - - return { - 'jsonrpc': '2.0', - 'error': { - 'code': error_code.SERVER_ERROR, - 'message': 'you are bad at requests', - 'data': 'some junk' - }, - 'id': request['id'] - }; - }); - - expect( - controller.client.sendRequest('foo', {'param': 'value'}), - throwsA(isA() - .having((e) => e.code, 'code', error_code.SERVER_ERROR) - .having((e) => e.message, 'message', 'you are bad at requests') - .having((e) => e.data, 'data', 'some junk'))); - }); - - test('requests throw StateErrors if the client is closed', () { - controller.client.close(); - expect(() => controller.client.sendRequest('foo'), throwsStateError); - expect(() => controller.client.sendNotification('foo'), throwsStateError); - }); - - test('ignores bogus responses', () { - // Make a request so we have something to respond to. - controller.expectRequest((request) { - controller.sendJsonResponse('{invalid'); - controller.sendResponse('not a map'); - controller.sendResponse( - {'jsonrpc': 'wrong version', 'result': 'wrong', 'id': request['id']}); - controller.sendResponse({'jsonrpc': '2.0', 'result': 'wrong'}); - controller.sendResponse({'jsonrpc': '2.0', 'id': request['id']}); - controller.sendResponse( - {'jsonrpc': '2.0', 'error': 'not a map', 'id': request['id']}); - controller.sendResponse({ - 'jsonrpc': '2.0', - 'error': {'code': 'not an int', 'message': 'dang yo'}, - 'id': request['id'] - }); - controller.sendResponse({ - 'jsonrpc': '2.0', - 'error': {'code': 123, 'message': 0xDEADBEEF}, - 'id': request['id'] - }); + allOf([ + containsPair('jsonrpc', '2.0'), + containsPair('method', 'foo'), + containsPair('params', {'param': 'value'}), + containsPair('id', '0'), + ])); - return pumpEventQueue().then( - (_) => {'jsonrpc': '2.0', 'result': 'right', 'id': request['id']}); + return {'jsonrpc': '2.0', 'result': 'bar', 'id': request['id']}; }); - expect(controller.client.sendRequest('foo'), completion(equals('right'))); + expect(controller.client.sendRequest('foo', {'param': 'value'}), + completion(equals('bar'))); }); } diff --git a/pkgs/json_rpc_2/test/client/utils.dart b/pkgs/json_rpc_2/test/client/utils.dart index 38e187f28..ed308e561 100644 --- a/pkgs/json_rpc_2/test/client/utils.dart +++ b/pkgs/json_rpc_2/test/client/utils.dart @@ -20,9 +20,10 @@ class ClientController { /// The client. late final json_rpc.Client client; - ClientController() { + ClientController({Object Function()? idGenerator}) { client = json_rpc.Client( - StreamChannel(_responseController.stream, _requestController.sink)); + StreamChannel(_responseController.stream, _requestController.sink), + idGenerator: idGenerator); client.listen(); } From bef28acbdf9aa107b35eb05435eff006a23a606b Mon Sep 17 00:00:00 2001 From: Simone Stasi <62812903+sstasi95@users.noreply.github.com> Date: Tue, 22 Apr 2025 15:33:58 +0200 Subject: [PATCH 07/67] [html] fix TypeError in nth-child query selector (#2015) --- pkgs/html/CHANGELOG.md | 4 ++++ pkgs/html/lib/src/query_selector.dart | 11 +++++++++-- pkgs/html/pubspec.yaml | 2 +- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/pkgs/html/CHANGELOG.md b/pkgs/html/CHANGELOG.md index eaa1f8c00..aa68aed2d 100644 --- a/pkgs/html/CHANGELOG.md +++ b/pkgs/html/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.15.6-wip + +- Fixed a TypeError in nth-child with non numeric value (e.g. `nth-child(even)`) + ## 0.15.5+1 - Support "ambiguous ampersand" in attribute values. diff --git a/pkgs/html/lib/src/query_selector.dart b/pkgs/html/lib/src/query_selector.dart index b57b81145..0c48db24e 100644 --- a/pkgs/html/lib/src/query_selector.dart +++ b/pkgs/html/lib/src/query_selector.dart @@ -218,10 +218,17 @@ class SelectorEvaluator extends Visitor { final exprs = node.expression.expressions; if (exprs.length == 1 && exprs[0] is LiteralTerm) { final literal = exprs[0] as LiteralTerm; + + if (literal.value is! num) { + // non numeric values (e.g. `nth-child(even)`) are not supported + return false; + } + + final numericLiteral = literal.value as num; final parent = _element!.parentNode; return parent != null && - (literal.value as num) > 0 && - parent.nodes.indexOf(_element) == literal.value; + numericLiteral > 0 && + parent.nodes.indexOf(_element) == numericLiteral; } break; diff --git a/pkgs/html/pubspec.yaml b/pkgs/html/pubspec.yaml index 7508588ad..cdc502f0a 100644 --- a/pkgs/html/pubspec.yaml +++ b/pkgs/html/pubspec.yaml @@ -1,5 +1,5 @@ name: html -version: 0.15.5+1 +version: 0.15.6-wip description: APIs for parsing and manipulating HTML content outside the browser. repository: https://github.com/dart-lang/tools/tree/main/pkgs/html issue_tracker: https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Ahtml From 34e421b2ab8e3828cc59528559f509676ee5b681 Mon Sep 17 00:00:00 2001 From: Callum Moffat Date: Thu, 24 Apr 2025 03:42:39 -0400 Subject: [PATCH 08/67] [html] Various performance optimizations (#2019) --- pkgs/html/CHANGELOG.md | 4 +- pkgs/html/lib/parser.dart | 7 +- pkgs/html/lib/src/constants.dart | 402 +- pkgs/html/lib/src/html_input_stream.dart | 136 +- pkgs/html/lib/src/tokenizer.dart | 76 +- pkgs/html/lib/src/treebuilder.dart | 22 +- pkgs/html/lib/src/trie.dart | 13973 +++++++++++++++++++++ pkgs/html/pubspec.yaml | 3 +- pkgs/html/test/parser_feature_test.dart | 13 +- pkgs/html/test/trie_test.dart | 49 + pkgs/html/tool/generate_trie.dart | 24 + 11 files changed, 14576 insertions(+), 133 deletions(-) create mode 100644 pkgs/html/lib/src/trie.dart create mode 100644 pkgs/html/test/trie_test.dart create mode 100644 pkgs/html/tool/generate_trie.dart diff --git a/pkgs/html/CHANGELOG.md b/pkgs/html/CHANGELOG.md index aa68aed2d..c0e48769e 100644 --- a/pkgs/html/CHANGELOG.md +++ b/pkgs/html/CHANGELOG.md @@ -1,5 +1,7 @@ -## 0.15.6-wip +## 0.15.6 +- Performance improvements. +- No longer generate any error spans if generateSpans is false. - Fixed a TypeError in nth-child with non numeric value (e.g. `nth-child(even)`) ## 0.15.5+1 diff --git a/pkgs/html/lib/parser.dart b/pkgs/html/lib/parser.dart index 0e89e663a..65cc263c1 100644 --- a/pkgs/html/lib/parser.dart +++ b/pkgs/html/lib/parser.dart @@ -2026,7 +2026,7 @@ class InBodyPhase extends Phase { } void endTagHeading(EndTagToken token) { - for (var item in headingElements) { + for (var item in headingElementsList) { if (tree.elementInScope(item)) { tree.generateImpliedEndTags(); break; @@ -2036,7 +2036,7 @@ class InBodyPhase extends Phase { parser.parseError(token.span, 'end-tag-too-early', {'name': token.name}); } - for (var item in headingElements) { + for (var item in headingElementsList) { if (tree.elementInScope(item)) { var node = tree.openElements.removeLast(); while (!headingElements.contains(node.localName)) { @@ -3972,6 +3972,9 @@ class ParseError implements SourceSpanException { @override String toString({dynamic color}) { + if (span == null) { + return message; + } final res = span!.message(message, color: color); return span!.sourceUrl == null ? 'ParserError on $res' : 'On $res'; } diff --git a/pkgs/html/lib/src/constants.dart b/pkgs/html/lib/src/constants.dart index e70b1e263..34418199a 100644 --- a/pkgs/html/lib/src/constants.dart +++ b/pkgs/html/lib/src/constants.dart @@ -258,7 +258,7 @@ class Namespaces { } } -const List<(String, String)> scopingElements = [ +const scopingElements = { (Namespaces.html, 'applet'), (Namespaces.html, 'caption'), (Namespaces.html, 'html'), @@ -276,9 +276,9 @@ const List<(String, String)> scopingElements = [ (Namespaces.svg, 'foreignObject'), (Namespaces.svg, 'desc'), (Namespaces.svg, 'title') -]; +}; -const formattingElements = [ +const formattingElements = { (Namespaces.html, 'a'), (Namespaces.html, 'b'), (Namespaces.html, 'big'), @@ -293,9 +293,9 @@ const formattingElements = [ (Namespaces.html, 'strong'), (Namespaces.html, 'tt'), (Namespaces.html, '') -]; +}; -const specialElements = [ +const specialElements = { (Namespaces.html, 'address'), (Namespaces.html, 'applet'), (Namespaces.html, 'area'), @@ -376,27 +376,86 @@ const specialElements = [ (Namespaces.html, 'wbr'), (Namespaces.html, 'xmp'), (Namespaces.svg, 'foreignObject') -]; +}; -const htmlIntegrationPointElements = [ +const htmlIntegrationPointElements = { (Namespaces.mathml, 'annotaion-xml'), (Namespaces.svg, 'foreignObject'), (Namespaces.svg, 'desc'), (Namespaces.svg, 'title') -]; +}; -const mathmlTextIntegrationPointElements = [ +const mathmlTextIntegrationPointElements = { (Namespaces.mathml, 'mi'), (Namespaces.mathml, 'mo'), (Namespaces.mathml, 'mn'), (Namespaces.mathml, 'ms'), (Namespaces.mathml, 'mtext') -]; +}; + +abstract final class Charcode { + static const int nul = 0x00; + + /// '\t' + static const int tab = 0x09; + + /// '\n' + static const int lineFeed = 0x0A; + static const int formFeed = 0x0C; + + /// '\r' + static const int carriageReturn = 0x0D; + + /// ' ' + static const int space = 0x20; + + /// '"' + static const int doubleQuote = 0x22; + + /// '&' + static const int ampersand = 0x26; + + /// "'" + static const int singleQuote = 0x27; + + /// '-' + static const int hyphen = 0x2D; -const spaceCharacters = ' \n\r\t\u000C'; + /// 0 + static const int zero = 0x30; -const int newLine = 10; -const int returnCode = 13; + /// '<' + static const int lessThan = 0x3C; + + /// '=' + static const int equals = 0x3D; + + /// '>' + static const int greaterThan = 0x3E; + + /// A + static const int upperA = 0x41; + + /// Z + static const int upperZ = 0x5A; + + /// '`' + static const int graveAccent = 0x60; + + /// a + static const int lowerA = 0x61; + + /// z + static const int lowerZ = 0x7A; +} + +const spaceCharacters = { + Charcode.space, + Charcode.lineFeed, + Charcode.carriageReturn, + Charcode.tab, + Charcode.formFeed +}; bool isWhitespace(String? char) { if (char == null) return false; @@ -405,32 +464,17 @@ bool isWhitespace(String? char) { bool isWhitespaceCC(int charCode) { switch (charCode) { - case 9: // '\t' - case newLine: // '\n' - case 12: // '\f' - case returnCode: // '\r' - case 32: // ' ' + case Charcode.tab: + case Charcode.lineFeed: + case Charcode.formFeed: + case Charcode.carriageReturn: + case Charcode.space: return true; } return false; } -const List tableInsertModeElements = [ - 'table', - 'tbody', - 'tfoot', - 'thead', - 'tr' -]; - -// TODO(jmesserly): remove these in favor of the test functions -const asciiLetters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; - -const _zeroCode = 48; -const _lowerACode = 97; -const _lowerZCode = 122; -const _upperACode = 65; -const _upperZCode = 90; +const tableInsertModeElements = {'table', 'tbody', 'tfoot', 'thead', 'tr'}; bool isLetterOrDigit(String? char) => isLetter(char) || isDigit(char); @@ -438,14 +482,14 @@ bool isLetterOrDigit(String? char) => isLetter(char) || isDigit(char); bool isLetter(String? char) { if (char == null) return false; final cc = char.codeUnitAt(0); - return cc >= _lowerACode && cc <= _lowerZCode || - cc >= _upperACode && cc <= _upperZCode; + return cc >= Charcode.lowerA && cc <= Charcode.lowerZ || + cc >= Charcode.upperA && cc <= Charcode.upperZ; } bool isDigit(String? char) { if (char == null) return false; final cc = char.codeUnitAt(0); - return cc >= _zeroCode && cc < _zeroCode + 10; + return cc >= Charcode.zero && cc < Charcode.zero + 10; } bool isHexDigit(String? char) { @@ -482,20 +526,27 @@ extension AsciiUpperToLower on String { /// Converts ASCII characters to lowercase. /// /// Unlike [String.toLowerCase] does not touch non-ASCII characters. - String toAsciiLowerCase() => - String.fromCharCodes(codeUnits.map(_asciiToLower)); + String toAsciiLowerCase() { + if (codeUnits.any(_isUpperCaseCode)) { + return String.fromCharCodes(codeUnits.map(_asciiToLower)); + } + return this; + } + + static bool _isUpperCaseCode(int c) => + c >= Charcode.upperA && c <= Charcode.upperZ; - static int _asciiToLower(int c) => (c >= _upperACode && c <= _upperZCode) - ? c + _lowerACode - _upperACode - : c; + static int _asciiToLower(int c) => + _isUpperCaseCode(c) ? c + Charcode.lowerA - Charcode.upperA : c; } // Heading elements need to be ordered -const headingElements = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6']; +const headingElementsList = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6']; +const headingElements = {'h1', 'h2', 'h3', 'h4', 'h5', 'h6'}; -const cdataElements = ['title', 'textarea']; +const cdataElements = {'title', 'textarea'}; -const rcdataElements = [ +const rcdataElements = { 'style', 'script', 'xmp', @@ -503,7 +554,7 @@ const rcdataElements = [ 'noembed', 'noframes', 'noscript' -]; +}; // entitiesWindows1252 has to be _ordered_ and needs to have an index. It // therefore can't be a frozenset. @@ -3044,3 +3095,262 @@ const Map encodings = { 'windows936': 'gbk', 'x-x-big5': 'big5' }; + +const asciiCharacters = [ + '\u0000', + '\u0001', + '\u0002', + '\u0003', + '\u0004', + '\u0005', + '\u0006', + '\u0007', + '\u0008', + '\u0009', + '\u000a', + '\u000b', + '\u000c', + '\u000d', + '\u000e', + '\u000f', + '\u0010', + '\u0011', + '\u0012', + '\u0013', + '\u0014', + '\u0015', + '\u0016', + '\u0017', + '\u0018', + '\u0019', + '\u001a', + '\u001b', + '\u001c', + '\u001d', + '\u001e', + '\u001f', + '\u0020', + '\u0021', + '\u0022', + '\u0023', + '\u0024', + '\u0025', + '\u0026', + '\u0027', + '\u0028', + '\u0029', + '\u002a', + '\u002b', + '\u002c', + '\u002d', + '\u002e', + '\u002f', + '\u0030', + '\u0031', + '\u0032', + '\u0033', + '\u0034', + '\u0035', + '\u0036', + '\u0037', + '\u0038', + '\u0039', + '\u003a', + '\u003b', + '\u003c', + '\u003d', + '\u003e', + '\u003f', + '\u0040', + '\u0041', + '\u0042', + '\u0043', + '\u0044', + '\u0045', + '\u0046', + '\u0047', + '\u0048', + '\u0049', + '\u004a', + '\u004b', + '\u004c', + '\u004d', + '\u004e', + '\u004f', + '\u0050', + '\u0051', + '\u0052', + '\u0053', + '\u0054', + '\u0055', + '\u0056', + '\u0057', + '\u0058', + '\u0059', + '\u005a', + '\u005b', + '\u005c', + '\u005d', + '\u005e', + '\u005f', + '\u0060', + '\u0061', + '\u0062', + '\u0063', + '\u0064', + '\u0065', + '\u0066', + '\u0067', + '\u0068', + '\u0069', + '\u006a', + '\u006b', + '\u006c', + '\u006d', + '\u006e', + '\u006f', + '\u0070', + '\u0071', + '\u0072', + '\u0073', + '\u0074', + '\u0075', + '\u0076', + '\u0077', + '\u0078', + '\u0079', + '\u007a', + '\u007b', + '\u007c', + '\u007d', + '\u007e', + '\u007f', + '\u0080', + '\u0081', + '\u0082', + '\u0083', + '\u0084', + '\u0085', + '\u0086', + '\u0087', + '\u0088', + '\u0089', + '\u008a', + '\u008b', + '\u008c', + '\u008d', + '\u008e', + '\u008f', + '\u0090', + '\u0091', + '\u0092', + '\u0093', + '\u0094', + '\u0095', + '\u0096', + '\u0097', + '\u0098', + '\u0099', + '\u009a', + '\u009b', + '\u009c', + '\u009d', + '\u009e', + '\u009f', + '\u00a0', + '\u00a1', + '\u00a2', + '\u00a3', + '\u00a4', + '\u00a5', + '\u00a6', + '\u00a7', + '\u00a8', + '\u00a9', + '\u00aa', + '\u00ab', + '\u00ac', + '\u00ad', + '\u00ae', + '\u00af', + '\u00b0', + '\u00b1', + '\u00b2', + '\u00b3', + '\u00b4', + '\u00b5', + '\u00b6', + '\u00b7', + '\u00b8', + '\u00b9', + '\u00ba', + '\u00bb', + '\u00bc', + '\u00bd', + '\u00be', + '\u00bf', + '\u00c0', + '\u00c1', + '\u00c2', + '\u00c3', + '\u00c4', + '\u00c5', + '\u00c6', + '\u00c7', + '\u00c8', + '\u00c9', + '\u00ca', + '\u00cb', + '\u00cc', + '\u00cd', + '\u00ce', + '\u00cf', + '\u00d0', + '\u00d1', + '\u00d2', + '\u00d3', + '\u00d4', + '\u00d5', + '\u00d6', + '\u00d7', + '\u00d8', + '\u00d9', + '\u00da', + '\u00db', + '\u00dc', + '\u00dd', + '\u00de', + '\u00df', + '\u00e0', + '\u00e1', + '\u00e2', + '\u00e3', + '\u00e4', + '\u00e5', + '\u00e6', + '\u00e7', + '\u00e8', + '\u00e9', + '\u00ea', + '\u00eb', + '\u00ec', + '\u00ed', + '\u00ee', + '\u00ef', + '\u00f0', + '\u00f1', + '\u00f2', + '\u00f3', + '\u00f4', + '\u00f5', + '\u00f6', + '\u00f7', + '\u00f8', + '\u00f9', + '\u00fa', + '\u00fb', + '\u00fc', + '\u00fd', + '\u00fe', + '\u00ff' +]; diff --git a/pkgs/html/lib/src/html_input_stream.dart b/pkgs/html/lib/src/html_input_stream.dart index b093d3ce3..163992596 100644 --- a/pkgs/html/lib/src/html_input_stream.dart +++ b/pkgs/html/lib/src/html_input_stream.dart @@ -86,20 +86,30 @@ class HtmlInputStream { errors = Queue(); _offset = 0; - _chars = []; final rawChars = _rawChars ??= _decodeBytes(charEncodingName!, _rawBytes!); + // Optimistically allocate array, trim it later if there are changes + _chars = List.filled(rawChars.length, 0, growable: true); var skipNewline = false; var wasSurrogatePair = false; - for (var i = 0; i < rawChars.length; i++) { + var deletedChars = 0; + + /// CodeUnits.length is not free + final charsLength = rawChars.length; + for (var i = 0; i < charsLength; i++) { var c = rawChars[i]; if (skipNewline) { skipNewline = false; - if (c == newLine) continue; + if (c == Charcode.lineFeed) { + deletedChars++; + continue; + } } - final isSurrogatePair = _isSurrogatePair(rawChars, i); + final isSurrogatePair = _isLeadSurrogate(c) && + i + 1 < charsLength && + _isTrailSurrogate(rawChars[i + 1]); if (!isSurrogatePair && !wasSurrogatePair) { if (_invalidUnicode(c)) { errors.add('invalid-codepoint'); @@ -111,20 +121,24 @@ class HtmlInputStream { } wasSurrogatePair = isSurrogatePair; - if (c == returnCode) { + if (c == Charcode.carriageReturn) { skipNewline = true; - c = newLine; + c = Charcode.lineFeed; } - _chars.add(c); + _chars[i - deletedChars] = c; + } + if (deletedChars > 0) { + // Remove the null bytes from the end + _chars.removeRange(_chars.length - deletedChars, _chars.length); } // Free decoded characters if they aren't needed anymore. if (_rawBytes != null) _rawChars = null; - // TODO(sigmund): Don't parse the file at all if spans aren't being - // generated. - fileInfo = SourceFile.decoded(_chars, url: sourceUrl); + if (generateSpans) { + fileInfo = SourceFile.decoded(_chars, url: sourceUrl); + } } void detectEncoding([bool parseMeta = true]) { @@ -207,16 +221,31 @@ class HtmlInputStream { /// EOF when EOF is reached. String? char() { if (_offset >= _chars.length) return eof; - return _isSurrogatePair(_chars, _offset) - ? String.fromCharCodes([_chars[_offset++], _chars[_offset++]]) - : String.fromCharCode(_chars[_offset++]); + final firstCharCode = _chars[_offset++]; + if (firstCharCode < 256) { + return asciiCharacters[firstCharCode]; + } + if (_isSurrogatePair(_chars, _offset - 1)) { + return String.fromCharCodes([firstCharCode, _chars[_offset++]]); + } + return String.fromCharCode(firstCharCode); + } + + int? peekCodeUnit() { + if (_offset >= _chars.length) return null; + return _chars[_offset]; } String? peekChar() { if (_offset >= _chars.length) return eof; - return _isSurrogatePair(_chars, _offset) - ? String.fromCharCodes([_chars[_offset], _chars[_offset + 1]]) - : String.fromCharCode(_chars[_offset]); + final firstCharCode = _chars[_offset]; + if (firstCharCode < 256) { + return asciiCharacters[firstCharCode]; + } + if (_isSurrogatePair(_chars, _offset)) { + return String.fromCharCodes([firstCharCode, _chars[_offset + 1]]); + } + return String.fromCharCode(firstCharCode); } // Whether the current and next chars indicate a surrogate pair. @@ -233,12 +262,75 @@ class HtmlInputStream { bool _isTrailSurrogate(int code) => (code & 0xFC00) == 0xDC00; /// Returns a string of characters from the stream up to but not - /// including any character in 'characters' or EOF. - String charsUntil(String characters, [bool opposite = false]) { + /// including any character in 'characters' or EOF. These functions rely + /// on the charCode(s) being single-codepoint. + String charsUntil(Set charCodes, [bool opposite = false]) { + final start = _offset; + int? c; + while ((c = peekCodeUnit()) != null && charCodes.contains(c!) == opposite) { + _offset += 1; + } + + return String.fromCharCodes(_chars.sublist(start, _offset)); + } + + String charsUntil1(int charCode, [bool opposite = false]) { + final start = _offset; + int? c; + while ((c = peekCodeUnit()) != null && (charCode == c!) == opposite) { + _offset += 1; + } + + return String.fromCharCodes(_chars.sublist(start, _offset)); + } + + String charsUntil2(int charCode1, int charCode2, [bool opposite = false]) { + final start = _offset; + int? c; + while ((c = peekCodeUnit()) != null && + (charCode1 == c! || charCode2 == c) == opposite) { + _offset += 1; + } + + return String.fromCharCodes(_chars.sublist(start, _offset)); + } + + String charsUntil3(int charCode1, int charCode2, int charCode3, + [bool opposite = false]) { + final start = _offset; + int? c; + while ((c = peekCodeUnit()) != null && + (charCode1 == c! || charCode2 == c || charCode3 == c) == opposite) { + _offset += 1; + } + + return String.fromCharCodes(_chars.sublist(start, _offset)); + } + + String charsUntilAsciiLetter([bool opposite = false]) { + final start = _offset; + int? c; + while ((c = peekCodeUnit()) != null && + ((c! >= Charcode.upperA && c <= Charcode.upperZ) || + (c >= Charcode.lowerA && c <= Charcode.lowerZ)) == + opposite) { + _offset += 1; + } + return String.fromCharCodes(_chars.sublist(start, _offset)); + } + + static bool _isSpaceCharacter(int c) => + c == Charcode.space || + c == Charcode.lineFeed || + c == Charcode.carriageReturn || + c == Charcode.tab || + c == Charcode.formFeed; + + String charsUntilSpace([bool opposite = false]) { final start = _offset; - String? c; - while ((c = peekChar()) != null && characters.contains(c!) == opposite) { - _offset += c.codeUnits.length; + int? c; + while ((c = peekCodeUnit()) != null && _isSpaceCharacter(c!) == opposite) { + _offset += 1; } return String.fromCharCodes(_chars.sublist(start, _offset)); @@ -257,6 +349,8 @@ class HtmlInputStream { // TODO(jmesserly): the Python code used a regex to check for this. But // Dart doesn't let you create a regexp with invalid characters. bool _invalidUnicode(int c) { + // Fast return for common ASCII characters + if (0x0020 <= c && c <= 0x007E) return false; if (0x0001 <= c && c <= 0x0008) return true; if (0x000E <= c && c <= 0x001F) return true; if (0x007F <= c && c <= 0x009F) return true; diff --git a/pkgs/html/lib/src/tokenizer.dart b/pkgs/html/lib/src/tokenizer.dart index 228087c3d..22676c413 100644 --- a/pkgs/html/lib/src/tokenizer.dart +++ b/pkgs/html/lib/src/tokenizer.dart @@ -5,22 +5,12 @@ import '../parser.dart' show HtmlParser; import 'constants.dart'; import 'html_input_stream.dart'; import 'token.dart'; +import 'trie.dart'; import 'utils.dart'; // Group entities by their first character, for faster lookups -// TODO(jmesserly): we could use a better data structure here like a trie, if -// we had it implemented in Dart. -Map> entitiesByFirstChar = (() { - final result = >{}; - for (var k in entities.keys) { - result.putIfAbsent(k[0], () => []).add(k); - } - return result; -})(); - // TODO(jmesserly): lots of ways to make this faster: -// - use char codes everywhere instead of 1-char strings // - use switch instead of contains, indexOf // - use switch instead of the sequential if tests // - avoid string concat @@ -293,18 +283,11 @@ class HtmlTokenizer implements Iterator { // // Consume characters and compare to these to a substring of the // entity names in the list until the substring no longer matches. - var filteredEntityList = entitiesByFirstChar[charStack[0]!] ?? const []; - - while (charStack.last != eof) { - final name = charStack.join(); - filteredEntityList = filteredEntityList - .where((e) => e.startsWith(name)) - .toList(growable: false); + dynamic node = entitiesTrieRoot[charStack.last?.codeUnitAt(0)]; - if (filteredEntityList.isEmpty) { - break; - } + while (node != null && charStack.last != eof) { charStack.add(stream.char()); + node = (node as Map)[charStack.last?.codeUnitAt(0)]; } // At this point we have a string that starts with some characters @@ -419,13 +402,13 @@ class HtmlTokenizer implements Iterator { // Directly after emitting a token you switch back to the "data // state". At that point spaceCharacters are important so they are // emitted separately. - _addToken(SpaceCharactersToken( - '$data${stream.charsUntil(spaceCharacters, true)}')); + _addToken(SpaceCharactersToken('$data${stream.charsUntilSpace(true)}')); // No need to update lastFourChars here, since the first space will // have already been appended to lastFourChars and will have broken // any sequences } else { - final chars = stream.charsUntil('&<\u0000'); + final chars = stream.charsUntil3( + Charcode.ampersand, Charcode.lessThan, Charcode.nul); _addToken(CharactersToken('$data$chars')); } return true; @@ -453,10 +436,9 @@ class HtmlTokenizer implements Iterator { // Directly after emitting a token you switch back to the "data // state". At that point spaceCharacters are important so they are // emitted separately. - _addToken(SpaceCharactersToken( - '$data${stream.charsUntil(spaceCharacters, true)}')); + _addToken(SpaceCharactersToken('$data${stream.charsUntilSpace(true)}')); } else { - final chars = stream.charsUntil('&<'); + final chars = stream.charsUntil2(Charcode.ampersand, Charcode.lessThan); _addToken(CharactersToken('$data$chars')); } return true; @@ -479,7 +461,7 @@ class HtmlTokenizer implements Iterator { // Tokenization ends. return false; } else { - final chars = stream.charsUntil('<\u0000'); + final chars = stream.charsUntil2(Charcode.lessThan, Charcode.nul); _addToken(CharactersToken('$data$chars')); } return true; @@ -496,7 +478,7 @@ class HtmlTokenizer implements Iterator { // Tokenization ends. return false; } else { - final chars = stream.charsUntil('<\u0000'); + final chars = stream.charsUntil2(Charcode.lessThan, Charcode.nul); _addToken(CharactersToken('$data$chars')); } return true; @@ -511,7 +493,7 @@ class HtmlTokenizer implements Iterator { _addToken(ParseErrorToken('invalid-codepoint')); _addToken(CharactersToken('\uFFFD')); } else { - _addToken(CharactersToken('$data${stream.charsUntil("\u0000")}')); + _addToken(CharactersToken('$data${stream.charsUntil1(Charcode.nul)}')); } return true; } @@ -784,7 +766,8 @@ class HtmlTokenizer implements Iterator { } else if (data == eof) { state = dataState; } else { - final chars = stream.charsUntil('<-\u0000'); + final chars = + stream.charsUntil3(Charcode.lessThan, Charcode.hyphen, Charcode.nul); _addToken(CharactersToken('$data$chars')); } return true; @@ -1009,7 +992,7 @@ class HtmlTokenizer implements Iterator { bool beforeAttributeNameState() { final data = stream.char(); if (isWhitespace(data)) { - stream.charsUntil(spaceCharacters, true); + stream.charsUntilSpace(true); } else if (data != null && isLetter(data)) { _addAttribute(data); state = attributeNameState; @@ -1043,7 +1026,7 @@ class HtmlTokenizer implements Iterator { state = beforeAttributeValueState; } else if (isLetter(data)) { _attributeName.write(data); - _attributeName.write(stream.charsUntil(asciiLetters, true)); + _attributeName.write(stream.charsUntilAsciiLetter(true)); leavingThisState = false; } else if (data == '>') { // XXX If we emit here the attributes are converted to a dict @@ -1098,7 +1081,7 @@ class HtmlTokenizer implements Iterator { bool afterAttributeNameState() { final data = stream.char(); if (isWhitespace(data)) { - stream.charsUntil(spaceCharacters, true); + stream.charsUntilSpace(true); } else if (data == '=') { state = beforeAttributeValueState; } else if (data == '>') { @@ -1129,7 +1112,7 @@ class HtmlTokenizer implements Iterator { bool beforeAttributeValueState() { final data = stream.char(); if (isWhitespace(data)) { - stream.charsUntil(spaceCharacters, true); + stream.charsUntilSpace(true); } else if (data == '"') { _markAttributeValueStart(0); state = attributeValueDoubleQuotedState; @@ -1182,7 +1165,8 @@ class HtmlTokenizer implements Iterator { state = dataState; } else { _attributeValue.write(data); - _attributeValue.write(stream.charsUntil('"&')); + _attributeValue + .write(stream.charsUntil2(Charcode.doubleQuote, Charcode.ampersand)); } return true; } @@ -1204,7 +1188,8 @@ class HtmlTokenizer implements Iterator { state = dataState; } else { _attributeValue.write(data); - _attributeValue.write(stream.charsUntil("'&")); + _attributeValue + .write(stream.charsUntil2(Charcode.singleQuote, Charcode.ampersand)); } return true; } @@ -1232,7 +1217,16 @@ class HtmlTokenizer implements Iterator { _attributeValue.write('\uFFFD'); } else { _attributeValue.write(data); - _attributeValue.write(stream.charsUntil("&>\"'=<`$spaceCharacters")); + _attributeValue.write(stream.charsUntil(const { + Charcode.ampersand, + Charcode.greaterThan, + Charcode.doubleQuote, + Charcode.singleQuote, + Charcode.equals, + Charcode.lessThan, + Charcode.graveAccent, + ...spaceCharacters + })); } return true; } @@ -1278,7 +1272,7 @@ class HtmlTokenizer implements Iterator { // Make a new comment token and give it as value all the characters // until the first > or EOF (charsUntil checks for EOF automatically) // and emit it. - var data = stream.charsUntil('>'); + var data = stream.charsUntil1(Charcode.greaterThan); data = data.replaceAll('\u0000', '\uFFFD'); _addToken(CommentToken(data)); @@ -1397,7 +1391,9 @@ class HtmlTokenizer implements Iterator { _addToken(currentToken!); state = dataState; } else { - currentStringToken.add(data!).add(stream.charsUntil('-\u0000')); + currentStringToken + .add(data!) + .add(stream.charsUntil2(Charcode.hyphen, Charcode.nul)); } return true; } diff --git a/pkgs/html/lib/src/treebuilder.dart b/pkgs/html/lib/src/treebuilder.dart index 781f3f680..e374c7f38 100644 --- a/pkgs/html/lib/src/treebuilder.dart +++ b/pkgs/html/lib/src/treebuilder.dart @@ -111,31 +111,31 @@ class TreeBuilder { // with that name. final exactNode = target is Node; - var listElements1 = scopingElements; - var listElements2 = const <(String, String)>[]; + var setElements1 = scopingElements; + var setElements2 = const <(String, String)>{}; var invert = false; if (variant != null) { switch (variant) { case 'button': - listElements2 = const [(Namespaces.html, 'button')]; + setElements2 = const {(Namespaces.html, 'button')}; break; case 'list': - listElements2 = const [ + setElements2 = const { (Namespaces.html, 'ol'), (Namespaces.html, 'ul') - ]; + }; break; case 'table': - listElements1 = const [ + setElements1 = const { (Namespaces.html, 'html'), (Namespaces.html, 'table') - ]; + }; break; case 'select': - listElements1 = const [ + setElements1 = const { (Namespaces.html, 'optgroup'), (Namespaces.html, 'option') - ]; + }; invert = true; break; default: @@ -148,8 +148,8 @@ class TreeBuilder { exactNode && node == target) { return true; } else if (invert != - (listElements1.contains(getElementNameTuple(node)) || - listElements2.contains(getElementNameTuple(node)))) { + (setElements1.contains(getElementNameTuple(node)) || + setElements2.contains(getElementNameTuple(node)))) { return false; } } diff --git a/pkgs/html/lib/src/trie.dart b/pkgs/html/lib/src/trie.dart new file mode 100644 index 000000000..3a8b81038 --- /dev/null +++ b/pkgs/html/lib/src/trie.dart @@ -0,0 +1,13973 @@ +// AUTO GENERATED by 'tool/generate_trie.dart'. DO NOT EDIT! +const entitiesTrieRoot = { + 65: { + 69: { + 108: { + 105: { + 103: {59: {}} + } + } + }, + 77: { + 80: {59: {}} + }, + 97: { + 99: { + 117: { + 116: { + 101: {59: {}} + } + } + } + }, + 98: { + 114: { + 101: { + 118: { + 101: {59: {}} + } + } + } + }, + 99: { + 105: { + 114: { + 99: {59: {}} + } + }, + 121: {59: {}} + }, + 102: { + 114: {59: {}} + }, + 103: { + 114: { + 97: { + 118: { + 101: {59: {}} + } + } + } + }, + 108: { + 112: { + 104: { + 97: {59: {}} + } + } + }, + 109: { + 97: { + 99: { + 114: {59: {}} + } + } + }, + 110: { + 100: {59: {}} + }, + 111: { + 103: { + 111: { + 110: {59: {}} + } + }, + 112: { + 102: {59: {}} + } + }, + 112: { + 112: { + 108: { + 121: { + 70: { + 117: { + 110: { + 99: { + 116: { + 105: { + 111: { + 110: {59: {}} + } + } + } + } + } + } + } + } + } + } + }, + 114: { + 105: { + 110: { + 103: {59: {}} + } + } + }, + 115: { + 99: { + 114: {59: {}} + }, + 115: { + 105: { + 103: { + 110: {59: {}} + } + } + } + }, + 116: { + 105: { + 108: { + 100: { + 101: {59: {}} + } + } + } + }, + 117: { + 109: { + 108: {59: {}} + } + } + }, + 66: { + 97: { + 99: { + 107: { + 115: { + 108: { + 97: { + 115: { + 104: {59: {}} + } + } + } + } + } + }, + 114: { + 118: {59: {}}, + 119: { + 101: { + 100: {59: {}} + } + } + } + }, + 99: { + 121: {59: {}} + }, + 101: { + 99: { + 97: { + 117: { + 115: { + 101: {59: {}} + } + } + } + }, + 114: { + 110: { + 111: { + 117: { + 108: { + 108: { + 105: { + 115: {59: {}} + } + } + } + } + } + } + }, + 116: { + 97: {59: {}} + } + }, + 102: { + 114: {59: {}} + }, + 111: { + 112: { + 102: {59: {}} + } + }, + 114: { + 101: { + 118: { + 101: {59: {}} + } + } + }, + 115: { + 99: { + 114: {59: {}} + } + }, + 117: { + 109: { + 112: { + 101: { + 113: {59: {}} + } + } + } + } + }, + 67: { + 72: { + 99: { + 121: {59: {}} + } + }, + 79: { + 80: { + 89: {59: {}} + } + }, + 97: { + 99: { + 117: { + 116: { + 101: {59: {}} + } + } + }, + 112: { + 59: {}, + 105: { + 116: { + 97: { + 108: { + 68: { + 105: { + 102: { + 102: { + 101: { + 114: { + 101: { + 110: { + 116: { + 105: { + 97: { + 108: { + 68: {59: {}} + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + }, + 121: { + 108: { + 101: { + 121: { + 115: {59: {}} + } + } + } + } + }, + 99: { + 97: { + 114: { + 111: { + 110: {59: {}} + } + } + }, + 101: { + 100: { + 105: { + 108: {59: {}} + } + } + }, + 105: { + 114: { + 99: {59: {}} + } + }, + 111: { + 110: { + 105: { + 110: { + 116: {59: {}} + } + } + } + } + }, + 100: { + 111: { + 116: {59: {}} + } + }, + 101: { + 100: { + 105: { + 108: { + 108: { + 97: {59: {}} + } + } + } + }, + 110: { + 116: { + 101: { + 114: { + 68: { + 111: { + 116: {59: {}} + } + } + } + } + } + } + }, + 102: { + 114: {59: {}} + }, + 104: { + 105: {59: {}} + }, + 105: { + 114: { + 99: { + 108: { + 101: { + 68: { + 111: { + 116: {59: {}} + } + }, + 77: { + 105: { + 110: { + 117: { + 115: {59: {}} + } + } + } + }, + 80: { + 108: { + 117: { + 115: {59: {}} + } + } + }, + 84: { + 105: { + 109: { + 101: { + 115: {59: {}} + } + } + } + } + } + } + } + } + }, + 108: { + 111: { + 99: { + 107: { + 119: { + 105: { + 115: { + 101: { + 67: { + 111: { + 110: { + 116: { + 111: { + 117: { + 114: { + 73: { + 110: { + 116: { + 101: { + 103: { + 114: { + 97: { + 108: {59: {}} + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + }, + 115: { + 101: { + 67: { + 117: { + 114: { + 108: { + 121: { + 68: { + 111: { + 117: { + 98: { + 108: { + 101: { + 81: { + 117: { + 111: { + 116: { + 101: {59: {}} + } + } + } + } + } + } + } + } + } + }, + 81: { + 117: { + 111: { + 116: { + 101: {59: {}} + } + } + } + } + } + } + } + } + } + } + } + } + }, + 111: { + 108: { + 111: { + 110: { + 59: {}, + 101: {59: {}} + } + } + }, + 110: { + 103: { + 114: { + 117: { + 101: { + 110: { + 116: {59: {}} + } + } + } + } + }, + 105: { + 110: { + 116: {59: {}} + } + }, + 116: { + 111: { + 117: { + 114: { + 73: { + 110: { + 116: { + 101: { + 103: { + 114: { + 97: { + 108: {59: {}} + } + } + } + } + } + } + } + } + } + } + } + }, + 112: { + 102: {59: {}}, + 114: { + 111: { + 100: { + 117: { + 99: { + 116: {59: {}} + } + } + } + } + } + }, + 117: { + 110: { + 116: { + 101: { + 114: { + 67: { + 108: { + 111: { + 99: { + 107: { + 119: { + 105: { + 115: { + 101: { + 67: { + 111: { + 110: { + 116: { + 111: { + 117: { + 114: { + 73: { + 110: { + 116: { + 101: { + 103: { + 114: { + 97: { + 108: { + 59: {} + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + }, + 114: { + 111: { + 115: { + 115: {59: {}} + } + } + }, + 115: { + 99: { + 114: {59: {}} + } + }, + 117: { + 112: { + 59: {}, + 67: { + 97: { + 112: {59: {}} + } + } + } + } + }, + 68: { + 68: { + 59: {}, + 111: { + 116: { + 114: { + 97: { + 104: { + 100: {59: {}} + } + } + } + } + } + }, + 74: { + 99: { + 121: {59: {}} + } + }, + 83: { + 99: { + 121: {59: {}} + } + }, + 90: { + 99: { + 121: {59: {}} + } + }, + 97: { + 103: { + 103: { + 101: { + 114: {59: {}} + } + } + }, + 114: { + 114: {59: {}} + }, + 115: { + 104: { + 118: {59: {}} + } + } + }, + 99: { + 97: { + 114: { + 111: { + 110: {59: {}} + } + } + }, + 121: {59: {}} + }, + 101: { + 108: { + 59: {}, + 116: { + 97: {59: {}} + } + } + }, + 102: { + 114: {59: {}} + }, + 105: { + 97: { + 99: { + 114: { + 105: { + 116: { + 105: { + 99: { + 97: { + 108: { + 65: { + 99: { + 117: { + 116: { + 101: {59: {}} + } + } + } + }, + 68: { + 111: { + 116: {59: {}}, + 117: { + 98: { + 108: { + 101: { + 65: { + 99: { + 117: { + 116: { + 101: {59: {}} + } + } + } + } + } + } + } + } + } + }, + 71: { + 114: { + 97: { + 118: { + 101: {59: {}} + } + } + } + }, + 84: { + 105: { + 108: { + 100: { + 101: {59: {}} + } + } + } + } + } + } + } + } + } + } + } + }, + 109: { + 111: { + 110: { + 100: {59: {}} + } + } + } + }, + 102: { + 102: { + 101: { + 114: { + 101: { + 110: { + 116: { + 105: { + 97: { + 108: { + 68: {59: {}} + } + } + } + } + } + } + } + } + } + } + }, + 111: { + 112: { + 102: {59: {}} + }, + 116: { + 59: {}, + 68: { + 111: { + 116: {59: {}} + } + }, + 69: { + 113: { + 117: { + 97: { + 108: {59: {}} + } + } + } + } + }, + 117: { + 98: { + 108: { + 101: { + 67: { + 111: { + 110: { + 116: { + 111: { + 117: { + 114: { + 73: { + 110: { + 116: { + 101: { + 103: { + 114: { + 97: { + 108: {59: {}} + } + } + } + } + } + } + } + } + } + } + } + } + } + }, + 68: { + 111: { + 116: {59: {}}, + 119: { + 110: { + 65: { + 114: { + 114: { + 111: { + 119: {59: {}} + } + } + } + } + } + } + } + }, + 76: { + 101: { + 102: { + 116: { + 65: { + 114: { + 114: { + 111: { + 119: {59: {}} + } + } + } + }, + 82: { + 105: { + 103: { + 104: { + 116: { + 65: { + 114: { + 114: { + 111: { + 119: {59: {}} + } + } + } + } + } + } + } + } + }, + 84: { + 101: { + 101: {59: {}} + } + } + } + } + }, + 111: { + 110: { + 103: { + 76: { + 101: { + 102: { + 116: { + 65: { + 114: { + 114: { + 111: { + 119: {59: {}} + } + } + } + }, + 82: { + 105: { + 103: { + 104: { + 116: { + 65: { + 114: { + 114: { + 111: { + 119: {59: {}} + } + } + } + } + } + } + } + } + } + } + } + } + }, + 82: { + 105: { + 103: { + 104: { + 116: { + 65: { + 114: { + 114: { + 111: { + 119: {59: {}} + } + } + } + } + } + } + } + } + } + } + } + } + }, + 82: { + 105: { + 103: { + 104: { + 116: { + 65: { + 114: { + 114: { + 111: { + 119: {59: {}} + } + } + } + }, + 84: { + 101: { + 101: {59: {}} + } + } + } + } + } + } + }, + 85: { + 112: { + 65: { + 114: { + 114: { + 111: { + 119: {59: {}} + } + } + } + }, + 68: { + 111: { + 119: { + 110: { + 65: { + 114: { + 114: { + 111: { + 119: {59: {}} + } + } + } + } + } + } + } + } + } + }, + 86: { + 101: { + 114: { + 116: { + 105: { + 99: { + 97: { + 108: { + 66: { + 97: { + 114: {59: {}} + } + } + } + } + } + } + } + } + } + } + } + } + } + }, + 119: { + 110: { + 65: { + 114: { + 114: { + 111: { + 119: { + 59: {}, + 66: { + 97: { + 114: {59: {}} + } + }, + 85: { + 112: { + 65: { + 114: { + 114: { + 111: { + 119: {59: {}} + } + } + } + } + } + } + } + } + } + } + }, + 66: { + 114: { + 101: { + 118: { + 101: {59: {}} + } + } + } + }, + 76: { + 101: { + 102: { + 116: { + 82: { + 105: { + 103: { + 104: { + 116: { + 86: { + 101: { + 99: { + 116: { + 111: { + 114: {59: {}} + } + } + } + } + } + } + } + } + } + }, + 84: { + 101: { + 101: { + 86: { + 101: { + 99: { + 116: { + 111: { + 114: {59: {}} + } + } + } + } + } + } + } + }, + 86: { + 101: { + 99: { + 116: { + 111: { + 114: { + 59: {}, + 66: { + 97: { + 114: {59: {}} + } + } + } + } + } + } + } + } + } + } + } + }, + 82: { + 105: { + 103: { + 104: { + 116: { + 84: { + 101: { + 101: { + 86: { + 101: { + 99: { + 116: { + 111: { + 114: {59: {}} + } + } + } + } + } + } + } + }, + 86: { + 101: { + 99: { + 116: { + 111: { + 114: { + 59: {}, + 66: { + 97: { + 114: {59: {}} + } + } + } + } + } + } + } + } + } + } + } + } + }, + 84: { + 101: { + 101: { + 59: {}, + 65: { + 114: { + 114: { + 111: { + 119: {59: {}} + } + } + } + } + } + } + }, + 97: { + 114: { + 114: { + 111: { + 119: {59: {}} + } + } + } + } + } + } + }, + 115: { + 99: { + 114: {59: {}} + }, + 116: { + 114: { + 111: { + 107: {59: {}} + } + } + } + } + }, + 69: { + 78: { + 71: {59: {}} + }, + 84: { + 72: {59: {}} + }, + 97: { + 99: { + 117: { + 116: { + 101: {59: {}} + } + } + } + }, + 99: { + 97: { + 114: { + 111: { + 110: {59: {}} + } + } + }, + 105: { + 114: { + 99: {59: {}} + } + }, + 121: {59: {}} + }, + 100: { + 111: { + 116: {59: {}} + } + }, + 102: { + 114: {59: {}} + }, + 103: { + 114: { + 97: { + 118: { + 101: {59: {}} + } + } + } + }, + 108: { + 101: { + 109: { + 101: { + 110: { + 116: {59: {}} + } + } + } + } + }, + 109: { + 97: { + 99: { + 114: {59: {}} + } + }, + 112: { + 116: { + 121: { + 83: { + 109: { + 97: { + 108: { + 108: { + 83: { + 113: { + 117: { + 97: { + 114: { + 101: {59: {}} + } + } + } + } + } + } + } + } + } + }, + 86: { + 101: { + 114: { + 121: { + 83: { + 109: { + 97: { + 108: { + 108: { + 83: { + 113: { + 117: { + 97: { + 114: { + 101: {59: {}} + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + }, + 111: { + 103: { + 111: { + 110: {59: {}} + } + }, + 112: { + 102: {59: {}} + } + }, + 112: { + 115: { + 105: { + 108: { + 111: { + 110: {59: {}} + } + } + } + } + }, + 113: { + 117: { + 97: { + 108: { + 59: {}, + 84: { + 105: { + 108: { + 100: { + 101: {59: {}} + } + } + } + } + } + }, + 105: { + 108: { + 105: { + 98: { + 114: { + 105: { + 117: { + 109: {59: {}} + } + } + } + } + } + } + } + } + }, + 115: { + 99: { + 114: {59: {}} + }, + 105: { + 109: {59: {}} + } + }, + 116: { + 97: {59: {}} + }, + 117: { + 109: { + 108: {59: {}} + } + }, + 120: { + 105: { + 115: { + 116: { + 115: {59: {}} + } + } + }, + 112: { + 111: { + 110: { + 101: { + 110: { + 116: { + 105: { + 97: { + 108: { + 69: {59: {}} + } + } + } + } + } + } + } + } + } + } + }, + 70: { + 99: { + 121: {59: {}} + }, + 102: { + 114: {59: {}} + }, + 105: { + 108: { + 108: { + 101: { + 100: { + 83: { + 109: { + 97: { + 108: { + 108: { + 83: { + 113: { + 117: { + 97: { + 114: { + 101: {59: {}} + } + } + } + } + } + } + } + } + } + }, + 86: { + 101: { + 114: { + 121: { + 83: { + 109: { + 97: { + 108: { + 108: { + 83: { + 113: { + 117: { + 97: { + 114: { + 101: {59: {}} + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + }, + 111: { + 112: { + 102: {59: {}} + }, + 114: { + 65: { + 108: { + 108: {59: {}} + } + } + }, + 117: { + 114: { + 105: { + 101: { + 114: { + 116: { + 114: { + 102: {59: {}} + } + } + } + } + } + } + } + }, + 115: { + 99: { + 114: {59: {}} + } + } + }, + 71: { + 74: { + 99: { + 121: {59: {}} + } + }, + 84: {59: {}}, + 97: { + 109: { + 109: { + 97: { + 59: {}, + 100: {59: {}} + } + } + } + }, + 98: { + 114: { + 101: { + 118: { + 101: {59: {}} + } + } + } + }, + 99: { + 101: { + 100: { + 105: { + 108: {59: {}} + } + } + }, + 105: { + 114: { + 99: {59: {}} + } + }, + 121: {59: {}} + }, + 100: { + 111: { + 116: {59: {}} + } + }, + 102: { + 114: {59: {}} + }, + 103: {59: {}}, + 111: { + 112: { + 102: {59: {}} + } + }, + 114: { + 101: { + 97: { + 116: { + 101: { + 114: { + 69: { + 113: { + 117: { + 97: { + 108: { + 59: {}, + 76: { + 101: { + 115: { + 115: {59: {}} + } + } + } + } + } + } + } + }, + 70: { + 117: { + 108: { + 108: { + 69: { + 113: { + 117: { + 97: { + 108: {59: {}} + } + } + } + } + } + } + } + }, + 71: { + 114: { + 101: { + 97: { + 116: { + 101: { + 114: {59: {}} + } + } + } + } + } + }, + 76: { + 101: { + 115: { + 115: {59: {}} + } + } + }, + 83: { + 108: { + 97: { + 110: { + 116: { + 69: { + 113: { + 117: { + 97: { + 108: {59: {}} + } + } + } + } + } + } + } + } + }, + 84: { + 105: { + 108: { + 100: { + 101: {59: {}} + } + } + } + } + } + } + } + } + } + }, + 115: { + 99: { + 114: {59: {}} + } + }, + 116: {59: {}} + }, + 72: { + 65: { + 82: { + 68: { + 99: { + 121: {59: {}} + } + } + } + }, + 97: { + 99: { + 101: { + 107: {59: {}} + } + }, + 116: {59: {}} + }, + 99: { + 105: { + 114: { + 99: {59: {}} + } + } + }, + 102: { + 114: {59: {}} + }, + 105: { + 108: { + 98: { + 101: { + 114: { + 116: { + 83: { + 112: { + 97: { + 99: { + 101: {59: {}} + } + } + } + } + } + } + } + } + } + }, + 111: { + 112: { + 102: {59: {}} + }, + 114: { + 105: { + 122: { + 111: { + 110: { + 116: { + 97: { + 108: { + 76: { + 105: { + 110: { + 101: {59: {}} + } + } + } + } + } + } + } + } + } + } + } + }, + 115: { + 99: { + 114: {59: {}} + }, + 116: { + 114: { + 111: { + 107: {59: {}} + } + } + } + }, + 117: { + 109: { + 112: { + 68: { + 111: { + 119: { + 110: { + 72: { + 117: { + 109: { + 112: {59: {}} + } + } + } + } + } + } + }, + 69: { + 113: { + 117: { + 97: { + 108: {59: {}} + } + } + } + } + } + } + } + }, + 73: { + 69: { + 99: { + 121: {59: {}} + } + }, + 74: { + 108: { + 105: { + 103: {59: {}} + } + } + }, + 79: { + 99: { + 121: {59: {}} + } + }, + 97: { + 99: { + 117: { + 116: { + 101: {59: {}} + } + } + } + }, + 99: { + 105: { + 114: { + 99: {59: {}} + } + }, + 121: {59: {}} + }, + 100: { + 111: { + 116: {59: {}} + } + }, + 102: { + 114: {59: {}} + }, + 103: { + 114: { + 97: { + 118: { + 101: {59: {}} + } + } + } + }, + 109: { + 59: {}, + 97: { + 99: { + 114: {59: {}} + }, + 103: { + 105: { + 110: { + 97: { + 114: { + 121: { + 73: {59: {}} + } + } + } + } + } + } + }, + 112: { + 108: { + 105: { + 101: { + 115: {59: {}} + } + } + } + } + }, + 110: { + 116: { + 59: {}, + 101: { + 103: { + 114: { + 97: { + 108: {59: {}} + } + } + }, + 114: { + 115: { + 101: { + 99: { + 116: { + 105: { + 111: { + 110: {59: {}} + } + } + } + } + } + } + } + } + }, + 118: { + 105: { + 115: { + 105: { + 98: { + 108: { + 101: { + 67: { + 111: { + 109: { + 109: { + 97: {59: {}} + } + } + } + }, + 84: { + 105: { + 109: { + 101: { + 115: {59: {}} + } + } + } + } + } + } + } + } + } + } + } + }, + 111: { + 103: { + 111: { + 110: {59: {}} + } + }, + 112: { + 102: {59: {}} + }, + 116: { + 97: {59: {}} + } + }, + 115: { + 99: { + 114: {59: {}} + } + }, + 116: { + 105: { + 108: { + 100: { + 101: {59: {}} + } + } + } + }, + 117: { + 107: { + 99: { + 121: {59: {}} + } + }, + 109: { + 108: {59: {}} + } + } + }, + 74: { + 99: { + 105: { + 114: { + 99: {59: {}} + } + }, + 121: {59: {}} + }, + 102: { + 114: {59: {}} + }, + 111: { + 112: { + 102: {59: {}} + } + }, + 115: { + 99: { + 114: {59: {}} + }, + 101: { + 114: { + 99: { + 121: {59: {}} + } + } + } + }, + 117: { + 107: { + 99: { + 121: {59: {}} + } + } + } + }, + 75: { + 72: { + 99: { + 121: {59: {}} + } + }, + 74: { + 99: { + 121: {59: {}} + } + }, + 97: { + 112: { + 112: { + 97: {59: {}} + } + } + }, + 99: { + 101: { + 100: { + 105: { + 108: {59: {}} + } + } + }, + 121: {59: {}} + }, + 102: { + 114: {59: {}} + }, + 111: { + 112: { + 102: {59: {}} + } + }, + 115: { + 99: { + 114: {59: {}} + } + } + }, + 76: { + 74: { + 99: { + 121: {59: {}} + } + }, + 84: {59: {}}, + 97: { + 99: { + 117: { + 116: { + 101: {59: {}} + } + } + }, + 109: { + 98: { + 100: { + 97: {59: {}} + } + } + }, + 110: { + 103: {59: {}} + }, + 112: { + 108: { + 97: { + 99: { + 101: { + 116: { + 114: { + 102: {59: {}} + } + } + } + } + } + } + }, + 114: { + 114: {59: {}} + } + }, + 99: { + 97: { + 114: { + 111: { + 110: {59: {}} + } + } + }, + 101: { + 100: { + 105: { + 108: {59: {}} + } + } + }, + 121: {59: {}} + }, + 101: { + 102: { + 116: { + 65: { + 110: { + 103: { + 108: { + 101: { + 66: { + 114: { + 97: { + 99: { + 107: { + 101: { + 116: {59: {}} + } + } + } + } + } + } + } + } + } + }, + 114: { + 114: { + 111: { + 119: { + 59: {}, + 66: { + 97: { + 114: {59: {}} + } + }, + 82: { + 105: { + 103: { + 104: { + 116: { + 65: { + 114: { + 114: { + 111: { + 119: {59: {}} + } + } + } + } + } + } + } + } + } + } + } + } + } + }, + 67: { + 101: { + 105: { + 108: { + 105: { + 110: { + 103: {59: {}} + } + } + } + } + } + }, + 68: { + 111: { + 117: { + 98: { + 108: { + 101: { + 66: { + 114: { + 97: { + 99: { + 107: { + 101: { + 116: {59: {}} + } + } + } + } + } + } + } + } + } + }, + 119: { + 110: { + 84: { + 101: { + 101: { + 86: { + 101: { + 99: { + 116: { + 111: { + 114: {59: {}} + } + } + } + } + } + } + } + }, + 86: { + 101: { + 99: { + 116: { + 111: { + 114: { + 59: {}, + 66: { + 97: { + 114: {59: {}} + } + } + } + } + } + } + } + } + } + } + } + }, + 70: { + 108: { + 111: { + 111: { + 114: {59: {}} + } + } + } + }, + 82: { + 105: { + 103: { + 104: { + 116: { + 65: { + 114: { + 114: { + 111: { + 119: {59: {}} + } + } + } + }, + 86: { + 101: { + 99: { + 116: { + 111: { + 114: {59: {}} + } + } + } + } + } + } + } + } + } + }, + 84: { + 101: { + 101: { + 59: {}, + 65: { + 114: { + 114: { + 111: { + 119: {59: {}} + } + } + } + }, + 86: { + 101: { + 99: { + 116: { + 111: { + 114: {59: {}} + } + } + } + } + } + } + }, + 114: { + 105: { + 97: { + 110: { + 103: { + 108: { + 101: { + 59: {}, + 66: { + 97: { + 114: {59: {}} + } + }, + 69: { + 113: { + 117: { + 97: { + 108: {59: {}} + } + } + } + } + } + } + } + } + } + } + } + }, + 85: { + 112: { + 68: { + 111: { + 119: { + 110: { + 86: { + 101: { + 99: { + 116: { + 111: { + 114: {59: {}} + } + } + } + } + } + } + } + } + }, + 84: { + 101: { + 101: { + 86: { + 101: { + 99: { + 116: { + 111: { + 114: {59: {}} + } + } + } + } + } + } + } + }, + 86: { + 101: { + 99: { + 116: { + 111: { + 114: { + 59: {}, + 66: { + 97: { + 114: {59: {}} + } + } + } + } + } + } + } + } + } + }, + 86: { + 101: { + 99: { + 116: { + 111: { + 114: { + 59: {}, + 66: { + 97: { + 114: {59: {}} + } + } + } + } + } + } + } + }, + 97: { + 114: { + 114: { + 111: { + 119: {59: {}} + } + } + } + }, + 114: { + 105: { + 103: { + 104: { + 116: { + 97: { + 114: { + 114: { + 111: { + 119: {59: {}} + } + } + } + } + } + } + } + } + } + } + }, + 115: { + 115: { + 69: { + 113: { + 117: { + 97: { + 108: { + 71: { + 114: { + 101: { + 97: { + 116: { + 101: { + 114: {59: {}} + } + } + } + } + } + } + } + } + } + } + }, + 70: { + 117: { + 108: { + 108: { + 69: { + 113: { + 117: { + 97: { + 108: {59: {}} + } + } + } + } + } + } + } + }, + 71: { + 114: { + 101: { + 97: { + 116: { + 101: { + 114: {59: {}} + } + } + } + } + } + }, + 76: { + 101: { + 115: { + 115: {59: {}} + } + } + }, + 83: { + 108: { + 97: { + 110: { + 116: { + 69: { + 113: { + 117: { + 97: { + 108: {59: {}} + } + } + } + } + } + } + } + } + }, + 84: { + 105: { + 108: { + 100: { + 101: {59: {}} + } + } + } + } + } + } + }, + 102: { + 114: {59: {}} + }, + 108: { + 59: {}, + 101: { + 102: { + 116: { + 97: { + 114: { + 114: { + 111: { + 119: {59: {}} + } + } + } + } + } + } + } + }, + 109: { + 105: { + 100: { + 111: { + 116: {59: {}} + } + } + } + }, + 111: { + 110: { + 103: { + 76: { + 101: { + 102: { + 116: { + 65: { + 114: { + 114: { + 111: { + 119: {59: {}} + } + } + } + }, + 82: { + 105: { + 103: { + 104: { + 116: { + 65: { + 114: { + 114: { + 111: { + 119: {59: {}} + } + } + } + } + } + } + } + } + } + } + } + } + }, + 82: { + 105: { + 103: { + 104: { + 116: { + 65: { + 114: { + 114: { + 111: { + 119: {59: {}} + } + } + } + } + } + } + } + } + }, + 108: { + 101: { + 102: { + 116: { + 97: { + 114: { + 114: { + 111: { + 119: {59: {}} + } + } + } + }, + 114: { + 105: { + 103: { + 104: { + 116: { + 97: { + 114: { + 114: { + 111: { + 119: {59: {}} + } + } + } + } + } + } + } + } + } + } + } + } + }, + 114: { + 105: { + 103: { + 104: { + 116: { + 97: { + 114: { + 114: { + 111: { + 119: {59: {}} + } + } + } + } + } + } + } + } + } + } + }, + 112: { + 102: {59: {}} + }, + 119: { + 101: { + 114: { + 76: { + 101: { + 102: { + 116: { + 65: { + 114: { + 114: { + 111: { + 119: {59: {}} + } + } + } + } + } + } + } + }, + 82: { + 105: { + 103: { + 104: { + 116: { + 65: { + 114: { + 114: { + 111: { + 119: {59: {}} + } + } + } + } + } + } + } + } + } + } + } + } + }, + 115: { + 99: { + 114: {59: {}} + }, + 104: {59: {}}, + 116: { + 114: { + 111: { + 107: {59: {}} + } + } + } + }, + 116: {59: {}} + }, + 77: { + 97: { + 112: {59: {}} + }, + 99: { + 121: {59: {}} + }, + 101: { + 100: { + 105: { + 117: { + 109: { + 83: { + 112: { + 97: { + 99: { + 101: {59: {}} + } + } + } + } + } + } + } + }, + 108: { + 108: { + 105: { + 110: { + 116: { + 114: { + 102: {59: {}} + } + } + } + } + } + } + }, + 102: { + 114: {59: {}} + }, + 105: { + 110: { + 117: { + 115: { + 80: { + 108: { + 117: { + 115: {59: {}} + } + } + } + } + } + } + }, + 111: { + 112: { + 102: {59: {}} + } + }, + 115: { + 99: { + 114: {59: {}} + } + }, + 117: {59: {}} + }, + 78: { + 74: { + 99: { + 121: {59: {}} + } + }, + 97: { + 99: { + 117: { + 116: { + 101: {59: {}} + } + } + } + }, + 99: { + 97: { + 114: { + 111: { + 110: {59: {}} + } + } + }, + 101: { + 100: { + 105: { + 108: {59: {}} + } + } + }, + 121: {59: {}} + }, + 101: { + 103: { + 97: { + 116: { + 105: { + 118: { + 101: { + 77: { + 101: { + 100: { + 105: { + 117: { + 109: { + 83: { + 112: { + 97: { + 99: { + 101: {59: {}} + } + } + } + } + } + } + } + } + } + }, + 84: { + 104: { + 105: { + 99: { + 107: { + 83: { + 112: { + 97: { + 99: { + 101: {59: {}} + } + } + } + } + } + }, + 110: { + 83: { + 112: { + 97: { + 99: { + 101: {59: {}} + } + } + } + } + } + } + } + }, + 86: { + 101: { + 114: { + 121: { + 84: { + 104: { + 105: { + 110: { + 83: { + 112: { + 97: { + 99: { + 101: {59: {}} + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + }, + 115: { + 116: { + 101: { + 100: { + 71: { + 114: { + 101: { + 97: { + 116: { + 101: { + 114: { + 71: { + 114: { + 101: { + 97: { + 116: { + 101: { + 114: {59: {}} + } + } + } + } + } + } + } + } + } + } + } + } + }, + 76: { + 101: { + 115: { + 115: { + 76: { + 101: { + 115: { + 115: {59: {}} + } + } + } + } + } + } + } + } + } + } + }, + 119: { + 76: { + 105: { + 110: { + 101: {59: {}} + } + } + } + } + }, + 102: { + 114: {59: {}} + }, + 111: { + 66: { + 114: { + 101: { + 97: { + 107: {59: {}} + } + } + } + }, + 110: { + 66: { + 114: { + 101: { + 97: { + 107: { + 105: { + 110: { + 103: { + 83: { + 112: { + 97: { + 99: { + 101: {59: {}} + } + } + } + } + } + } + } + } + } + } + } + } + }, + 112: { + 102: {59: {}} + }, + 116: { + 59: {}, + 67: { + 111: { + 110: { + 103: { + 114: { + 117: { + 101: { + 110: { + 116: {59: {}} + } + } + } + } + } + } + }, + 117: { + 112: { + 67: { + 97: { + 112: {59: {}} + } + } + } + } + }, + 68: { + 111: { + 117: { + 98: { + 108: { + 101: { + 86: { + 101: { + 114: { + 116: { + 105: { + 99: { + 97: { + 108: { + 66: { + 97: { + 114: {59: {}} + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + }, + 69: { + 108: { + 101: { + 109: { + 101: { + 110: { + 116: {59: {}} + } + } + } + } + }, + 113: { + 117: { + 97: { + 108: { + 59: {}, + 84: { + 105: { + 108: { + 100: { + 101: {59: {}} + } + } + } + } + } + } + } + }, + 120: { + 105: { + 115: { + 116: { + 115: {59: {}} + } + } + } + } + }, + 71: { + 114: { + 101: { + 97: { + 116: { + 101: { + 114: { + 59: {}, + 69: { + 113: { + 117: { + 97: { + 108: {59: {}} + } + } + } + }, + 70: { + 117: { + 108: { + 108: { + 69: { + 113: { + 117: { + 97: { + 108: {59: {}} + } + } + } + } + } + } + } + }, + 71: { + 114: { + 101: { + 97: { + 116: { + 101: { + 114: {59: {}} + } + } + } + } + } + }, + 76: { + 101: { + 115: { + 115: {59: {}} + } + } + }, + 83: { + 108: { + 97: { + 110: { + 116: { + 69: { + 113: { + 117: { + 97: { + 108: {59: {}} + } + } + } + } + } + } + } + } + }, + 84: { + 105: { + 108: { + 100: { + 101: {59: {}} + } + } + } + } + } + } + } + } + } + } + }, + 72: { + 117: { + 109: { + 112: { + 68: { + 111: { + 119: { + 110: { + 72: { + 117: { + 109: { + 112: {59: {}} + } + } + } + } + } + } + }, + 69: { + 113: { + 117: { + 97: { + 108: {59: {}} + } + } + } + } + } + } + } + }, + 76: { + 101: { + 102: { + 116: { + 84: { + 114: { + 105: { + 97: { + 110: { + 103: { + 108: { + 101: { + 59: {}, + 66: { + 97: { + 114: {59: {}} + } + }, + 69: { + 113: { + 117: { + 97: { + 108: {59: {}} + } + } + } + } + } + } + } + } + } + } + } + } + } + }, + 115: { + 115: { + 59: {}, + 69: { + 113: { + 117: { + 97: { + 108: {59: {}} + } + } + } + }, + 71: { + 114: { + 101: { + 97: { + 116: { + 101: { + 114: {59: {}} + } + } + } + } + } + }, + 76: { + 101: { + 115: { + 115: {59: {}} + } + } + }, + 83: { + 108: { + 97: { + 110: { + 116: { + 69: { + 113: { + 117: { + 97: { + 108: {59: {}} + } + } + } + } + } + } + } + } + }, + 84: { + 105: { + 108: { + 100: { + 101: {59: {}} + } + } + } + } + } + } + } + }, + 78: { + 101: { + 115: { + 116: { + 101: { + 100: { + 71: { + 114: { + 101: { + 97: { + 116: { + 101: { + 114: { + 71: { + 114: { + 101: { + 97: { + 116: { + 101: { + 114: {59: {}} + } + } + } + } + } + } + } + } + } + } + } + } + }, + 76: { + 101: { + 115: { + 115: { + 76: { + 101: { + 115: { + 115: {59: {}} + } + } + } + } + } + } + } + } + } + } + } + } + }, + 80: { + 114: { + 101: { + 99: { + 101: { + 100: { + 101: { + 115: { + 59: {}, + 69: { + 113: { + 117: { + 97: { + 108: {59: {}} + } + } + } + }, + 83: { + 108: { + 97: { + 110: { + 116: { + 69: { + 113: { + 117: { + 97: { + 108: {59: {}} + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + }, + 82: { + 101: { + 118: { + 101: { + 114: { + 115: { + 101: { + 69: { + 108: { + 101: { + 109: { + 101: { + 110: { + 116: {59: {}} + } + } + } + } + } + } + } + } + } + } + } + }, + 105: { + 103: { + 104: { + 116: { + 84: { + 114: { + 105: { + 97: { + 110: { + 103: { + 108: { + 101: { + 59: {}, + 66: { + 97: { + 114: {59: {}} + } + }, + 69: { + 113: { + 117: { + 97: { + 108: {59: {}} + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + }, + 83: { + 113: { + 117: { + 97: { + 114: { + 101: { + 83: { + 117: { + 98: { + 115: { + 101: { + 116: { + 59: {}, + 69: { + 113: { + 117: { + 97: { + 108: {59: {}} + } + } + } + } + } + } + } + }, + 112: { + 101: { + 114: { + 115: { + 101: { + 116: { + 59: {}, + 69: { + 113: { + 117: { + 97: { + 108: {59: {}} + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + }, + 117: { + 98: { + 115: { + 101: { + 116: { + 59: {}, + 69: { + 113: { + 117: { + 97: { + 108: {59: {}} + } + } + } + } + } + } + } + }, + 99: { + 99: { + 101: { + 101: { + 100: { + 115: { + 59: {}, + 69: { + 113: { + 117: { + 97: { + 108: {59: {}} + } + } + } + }, + 83: { + 108: { + 97: { + 110: { + 116: { + 69: { + 113: { + 117: { + 97: { + 108: {59: {}} + } + } + } + } + } + } + } + } + }, + 84: { + 105: { + 108: { + 100: { + 101: {59: {}} + } + } + } + } + } + } + } + } + } + }, + 112: { + 101: { + 114: { + 115: { + 101: { + 116: { + 59: {}, + 69: { + 113: { + 117: { + 97: { + 108: {59: {}} + } + } + } + } + } + } + } + } + } + } + } + }, + 84: { + 105: { + 108: { + 100: { + 101: { + 59: {}, + 69: { + 113: { + 117: { + 97: { + 108: {59: {}} + } + } + } + }, + 70: { + 117: { + 108: { + 108: { + 69: { + 113: { + 117: { + 97: { + 108: {59: {}} + } + } + } + } + } + } + } + }, + 84: { + 105: { + 108: { + 100: { + 101: {59: {}} + } + } + } + } + } + } + } + } + }, + 86: { + 101: { + 114: { + 116: { + 105: { + 99: { + 97: { + 108: { + 66: { + 97: { + 114: {59: {}} + } + } + } + } + } + } + } + } + } + } + } + }, + 115: { + 99: { + 114: {59: {}} + } + }, + 116: { + 105: { + 108: { + 100: { + 101: {59: {}} + } + } + } + }, + 117: {59: {}} + }, + 79: { + 69: { + 108: { + 105: { + 103: {59: {}} + } + } + }, + 97: { + 99: { + 117: { + 116: { + 101: {59: {}} + } + } + } + }, + 99: { + 105: { + 114: { + 99: {59: {}} + } + }, + 121: {59: {}} + }, + 100: { + 98: { + 108: { + 97: { + 99: {59: {}} + } + } + } + }, + 102: { + 114: {59: {}} + }, + 103: { + 114: { + 97: { + 118: { + 101: {59: {}} + } + } + } + }, + 109: { + 97: { + 99: { + 114: {59: {}} + } + }, + 101: { + 103: { + 97: {59: {}} + } + }, + 105: { + 99: { + 114: { + 111: { + 110: {59: {}} + } + } + } + } + }, + 111: { + 112: { + 102: {59: {}} + } + }, + 112: { + 101: { + 110: { + 67: { + 117: { + 114: { + 108: { + 121: { + 68: { + 111: { + 117: { + 98: { + 108: { + 101: { + 81: { + 117: { + 111: { + 116: { + 101: {59: {}} + } + } + } + } + } + } + } + } + } + }, + 81: { + 117: { + 111: { + 116: { + 101: {59: {}} + } + } + } + } + } + } + } + } + } + } + } + }, + 114: {59: {}}, + 115: { + 99: { + 114: {59: {}} + }, + 108: { + 97: { + 115: { + 104: {59: {}} + } + } + } + }, + 116: { + 105: { + 108: { + 100: { + 101: {59: {}} + } + }, + 109: { + 101: { + 115: {59: {}} + } + } + } + }, + 117: { + 109: { + 108: {59: {}} + } + }, + 118: { + 101: { + 114: { + 66: { + 97: { + 114: {59: {}} + }, + 114: { + 97: { + 99: { + 101: {59: {}}, + 107: { + 101: { + 116: {59: {}} + } + } + } + } + } + }, + 80: { + 97: { + 114: { + 101: { + 110: { + 116: { + 104: { + 101: { + 115: { + 105: { + 115: {59: {}} + } + } + } + } + } + } + } + } + } + } + } + } + } + }, + 80: { + 97: { + 114: { + 116: { + 105: { + 97: { + 108: { + 68: {59: {}} + } + } + } + } + } + }, + 99: { + 121: {59: {}} + }, + 102: { + 114: {59: {}} + }, + 104: { + 105: {59: {}} + }, + 105: {59: {}}, + 108: { + 117: { + 115: { + 77: { + 105: { + 110: { + 117: { + 115: {59: {}} + } + } + } + } + } + } + }, + 111: { + 105: { + 110: { + 99: { + 97: { + 114: { + 101: { + 112: { + 108: { + 97: { + 110: { + 101: {59: {}} + } + } + } + } + } + } + } + } + } + }, + 112: { + 102: {59: {}} + } + }, + 114: { + 59: {}, + 101: { + 99: { + 101: { + 100: { + 101: { + 115: { + 59: {}, + 69: { + 113: { + 117: { + 97: { + 108: {59: {}} + } + } + } + }, + 83: { + 108: { + 97: { + 110: { + 116: { + 69: { + 113: { + 117: { + 97: { + 108: {59: {}} + } + } + } + } + } + } + } + } + }, + 84: { + 105: { + 108: { + 100: { + 101: {59: {}} + } + } + } + } + } + } + } + } + } + }, + 105: { + 109: { + 101: {59: {}} + } + }, + 111: { + 100: { + 117: { + 99: { + 116: {59: {}} + } + } + }, + 112: { + 111: { + 114: { + 116: { + 105: { + 111: { + 110: { + 59: {}, + 97: { + 108: {59: {}} + } + } + } + } + } + } + } + } + } + }, + 115: { + 99: { + 114: {59: {}} + }, + 105: {59: {}} + } + }, + 81: { + 85: { + 79: { + 84: {59: {}} + } + }, + 102: { + 114: {59: {}} + }, + 111: { + 112: { + 102: {59: {}} + } + }, + 115: { + 99: { + 114: {59: {}} + } + } + }, + 82: { + 66: { + 97: { + 114: { + 114: {59: {}} + } + } + }, + 69: { + 71: {59: {}} + }, + 97: { + 99: { + 117: { + 116: { + 101: {59: {}} + } + } + }, + 110: { + 103: {59: {}} + }, + 114: { + 114: { + 59: {}, + 116: { + 108: {59: {}} + } + } + } + }, + 99: { + 97: { + 114: { + 111: { + 110: {59: {}} + } + } + }, + 101: { + 100: { + 105: { + 108: {59: {}} + } + } + }, + 121: {59: {}} + }, + 101: { + 59: {}, + 118: { + 101: { + 114: { + 115: { + 101: { + 69: { + 108: { + 101: { + 109: { + 101: { + 110: { + 116: {59: {}} + } + } + } + } + }, + 113: { + 117: { + 105: { + 108: { + 105: { + 98: { + 114: { + 105: { + 117: { + 109: {59: {}} + } + } + } + } + } + } + } + } + } + }, + 85: { + 112: { + 69: { + 113: { + 117: { + 105: { + 108: { + 105: { + 98: { + 114: { + 105: { + 117: { + 109: {59: {}} + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + }, + 102: { + 114: {59: {}} + }, + 104: { + 111: {59: {}} + }, + 105: { + 103: { + 104: { + 116: { + 65: { + 110: { + 103: { + 108: { + 101: { + 66: { + 114: { + 97: { + 99: { + 107: { + 101: { + 116: {59: {}} + } + } + } + } + } + } + } + } + } + }, + 114: { + 114: { + 111: { + 119: { + 59: {}, + 66: { + 97: { + 114: {59: {}} + } + }, + 76: { + 101: { + 102: { + 116: { + 65: { + 114: { + 114: { + 111: { + 119: {59: {}} + } + } + } + } + } + } + } + } + } + } + } + } + }, + 67: { + 101: { + 105: { + 108: { + 105: { + 110: { + 103: {59: {}} + } + } + } + } + } + }, + 68: { + 111: { + 117: { + 98: { + 108: { + 101: { + 66: { + 114: { + 97: { + 99: { + 107: { + 101: { + 116: {59: {}} + } + } + } + } + } + } + } + } + } + }, + 119: { + 110: { + 84: { + 101: { + 101: { + 86: { + 101: { + 99: { + 116: { + 111: { + 114: {59: {}} + } + } + } + } + } + } + } + }, + 86: { + 101: { + 99: { + 116: { + 111: { + 114: { + 59: {}, + 66: { + 97: { + 114: {59: {}} + } + } + } + } + } + } + } + } + } + } + } + }, + 70: { + 108: { + 111: { + 111: { + 114: {59: {}} + } + } + } + }, + 84: { + 101: { + 101: { + 59: {}, + 65: { + 114: { + 114: { + 111: { + 119: {59: {}} + } + } + } + }, + 86: { + 101: { + 99: { + 116: { + 111: { + 114: {59: {}} + } + } + } + } + } + } + }, + 114: { + 105: { + 97: { + 110: { + 103: { + 108: { + 101: { + 59: {}, + 66: { + 97: { + 114: {59: {}} + } + }, + 69: { + 113: { + 117: { + 97: { + 108: {59: {}} + } + } + } + } + } + } + } + } + } + } + } + }, + 85: { + 112: { + 68: { + 111: { + 119: { + 110: { + 86: { + 101: { + 99: { + 116: { + 111: { + 114: {59: {}} + } + } + } + } + } + } + } + } + }, + 84: { + 101: { + 101: { + 86: { + 101: { + 99: { + 116: { + 111: { + 114: {59: {}} + } + } + } + } + } + } + } + }, + 86: { + 101: { + 99: { + 116: { + 111: { + 114: { + 59: {}, + 66: { + 97: { + 114: {59: {}} + } + } + } + } + } + } + } + } + } + }, + 86: { + 101: { + 99: { + 116: { + 111: { + 114: { + 59: {}, + 66: { + 97: { + 114: {59: {}} + } + } + } + } + } + } + } + }, + 97: { + 114: { + 114: { + 111: { + 119: {59: {}} + } + } + } + } + } + } + } + }, + 111: { + 112: { + 102: {59: {}} + }, + 117: { + 110: { + 100: { + 73: { + 109: { + 112: { + 108: { + 105: { + 101: { + 115: {59: {}} + } + } + } + } + } + } + } + } + } + }, + 114: { + 105: { + 103: { + 104: { + 116: { + 97: { + 114: { + 114: { + 111: { + 119: {59: {}} + } + } + } + } + } + } + } + } + }, + 115: { + 99: { + 114: {59: {}} + }, + 104: {59: {}} + }, + 117: { + 108: { + 101: { + 68: { + 101: { + 108: { + 97: { + 121: { + 101: { + 100: {59: {}} + } + } + } + } + } + } + } + } + } + }, + 83: { + 72: { + 67: { + 72: { + 99: { + 121: {59: {}} + } + } + }, + 99: { + 121: {59: {}} + } + }, + 79: { + 70: { + 84: { + 99: { + 121: {59: {}} + } + } + } + }, + 97: { + 99: { + 117: { + 116: { + 101: {59: {}} + } + } + } + }, + 99: { + 59: {}, + 97: { + 114: { + 111: { + 110: {59: {}} + } + } + }, + 101: { + 100: { + 105: { + 108: {59: {}} + } + } + }, + 105: { + 114: { + 99: {59: {}} + } + }, + 121: {59: {}} + }, + 102: { + 114: {59: {}} + }, + 104: { + 111: { + 114: { + 116: { + 68: { + 111: { + 119: { + 110: { + 65: { + 114: { + 114: { + 111: { + 119: {59: {}} + } + } + } + } + } + } + } + }, + 76: { + 101: { + 102: { + 116: { + 65: { + 114: { + 114: { + 111: { + 119: {59: {}} + } + } + } + } + } + } + } + }, + 82: { + 105: { + 103: { + 104: { + 116: { + 65: { + 114: { + 114: { + 111: { + 119: {59: {}} + } + } + } + } + } + } + } + } + }, + 85: { + 112: { + 65: { + 114: { + 114: { + 111: { + 119: {59: {}} + } + } + } + } + } + } + } + } + } + }, + 105: { + 103: { + 109: { + 97: {59: {}} + } + } + }, + 109: { + 97: { + 108: { + 108: { + 67: { + 105: { + 114: { + 99: { + 108: { + 101: {59: {}} + } + } + } + } + } + } + } + } + }, + 111: { + 112: { + 102: {59: {}} + } + }, + 113: { + 114: { + 116: {59: {}} + }, + 117: { + 97: { + 114: { + 101: { + 59: {}, + 73: { + 110: { + 116: { + 101: { + 114: { + 115: { + 101: { + 99: { + 116: { + 105: { + 111: { + 110: {59: {}} + } + } + } + } + } + } + } + } + } + } + }, + 83: { + 117: { + 98: { + 115: { + 101: { + 116: { + 59: {}, + 69: { + 113: { + 117: { + 97: { + 108: {59: {}} + } + } + } + } + } + } + } + }, + 112: { + 101: { + 114: { + 115: { + 101: { + 116: { + 59: {}, + 69: { + 113: { + 117: { + 97: { + 108: {59: {}} + } + } + } + } + } + } + } + } + } + } + } + }, + 85: { + 110: { + 105: { + 111: { + 110: {59: {}} + } + } + } + } + } + } + } + } + }, + 115: { + 99: { + 114: {59: {}} + } + }, + 116: { + 97: { + 114: {59: {}} + } + }, + 117: { + 98: { + 59: {}, + 115: { + 101: { + 116: { + 59: {}, + 69: { + 113: { + 117: { + 97: { + 108: {59: {}} + } + } + } + } + } + } + } + }, + 99: { + 99: { + 101: { + 101: { + 100: { + 115: { + 59: {}, + 69: { + 113: { + 117: { + 97: { + 108: {59: {}} + } + } + } + }, + 83: { + 108: { + 97: { + 110: { + 116: { + 69: { + 113: { + 117: { + 97: { + 108: {59: {}} + } + } + } + } + } + } + } + } + }, + 84: { + 105: { + 108: { + 100: { + 101: {59: {}} + } + } + } + } + } + } + } + } + }, + 104: { + 84: { + 104: { + 97: { + 116: {59: {}} + } + } + } + } + }, + 109: {59: {}}, + 112: { + 59: {}, + 101: { + 114: { + 115: { + 101: { + 116: { + 59: {}, + 69: { + 113: { + 117: { + 97: { + 108: {59: {}} + } + } + } + } + } + } + } + } + }, + 115: { + 101: { + 116: {59: {}} + } + } + } + } + }, + 84: { + 72: { + 79: { + 82: { + 78: {59: {}} + } + } + }, + 82: { + 65: { + 68: { + 69: {59: {}} + } + } + }, + 83: { + 72: { + 99: { + 121: {59: {}} + } + }, + 99: { + 121: {59: {}} + } + }, + 97: { + 98: {59: {}}, + 117: {59: {}} + }, + 99: { + 97: { + 114: { + 111: { + 110: {59: {}} + } + } + }, + 101: { + 100: { + 105: { + 108: {59: {}} + } + } + }, + 121: {59: {}} + }, + 102: { + 114: {59: {}} + }, + 104: { + 101: { + 114: { + 101: { + 102: { + 111: { + 114: { + 101: {59: {}} + } + } + } + } + }, + 116: { + 97: {59: {}} + } + }, + 105: { + 99: { + 107: { + 83: { + 112: { + 97: { + 99: { + 101: {59: {}} + } + } + } + } + } + }, + 110: { + 83: { + 112: { + 97: { + 99: { + 101: {59: {}} + } + } + } + } + } + } + }, + 105: { + 108: { + 100: { + 101: { + 59: {}, + 69: { + 113: { + 117: { + 97: { + 108: {59: {}} + } + } + } + }, + 70: { + 117: { + 108: { + 108: { + 69: { + 113: { + 117: { + 97: { + 108: {59: {}} + } + } + } + } + } + } + } + }, + 84: { + 105: { + 108: { + 100: { + 101: {59: {}} + } + } + } + } + } + } + } + }, + 111: { + 112: { + 102: {59: {}} + } + }, + 114: { + 105: { + 112: { + 108: { + 101: { + 68: { + 111: { + 116: {59: {}} + } + } + } + } + } + } + }, + 115: { + 99: { + 114: {59: {}} + }, + 116: { + 114: { + 111: { + 107: {59: {}} + } + } + } + } + }, + 85: { + 97: { + 99: { + 117: { + 116: { + 101: {59: {}} + } + } + }, + 114: { + 114: { + 59: {}, + 111: { + 99: { + 105: { + 114: {59: {}} + } + } + } + } + } + }, + 98: { + 114: { + 99: { + 121: {59: {}} + }, + 101: { + 118: { + 101: {59: {}} + } + } + } + }, + 99: { + 105: { + 114: { + 99: {59: {}} + } + }, + 121: {59: {}} + }, + 100: { + 98: { + 108: { + 97: { + 99: {59: {}} + } + } + } + }, + 102: { + 114: {59: {}} + }, + 103: { + 114: { + 97: { + 118: { + 101: {59: {}} + } + } + } + }, + 109: { + 97: { + 99: { + 114: {59: {}} + } + } + }, + 110: { + 100: { + 101: { + 114: { + 66: { + 97: { + 114: {59: {}} + }, + 114: { + 97: { + 99: { + 101: {59: {}}, + 107: { + 101: { + 116: {59: {}} + } + } + } + } + } + }, + 80: { + 97: { + 114: { + 101: { + 110: { + 116: { + 104: { + 101: { + 115: { + 105: { + 115: {59: {}} + } + } + } + } + } + } + } + } + } + } + } + } + }, + 105: { + 111: { + 110: { + 59: {}, + 80: { + 108: { + 117: { + 115: {59: {}} + } + } + } + } + } + } + }, + 111: { + 103: { + 111: { + 110: {59: {}} + } + }, + 112: { + 102: {59: {}} + } + }, + 112: { + 65: { + 114: { + 114: { + 111: { + 119: { + 59: {}, + 66: { + 97: { + 114: {59: {}} + } + }, + 68: { + 111: { + 119: { + 110: { + 65: { + 114: { + 114: { + 111: { + 119: {59: {}} + } + } + } + } + } + } + } + } + } + } + } + } + }, + 68: { + 111: { + 119: { + 110: { + 65: { + 114: { + 114: { + 111: { + 119: {59: {}} + } + } + } + } + } + } + } + }, + 69: { + 113: { + 117: { + 105: { + 108: { + 105: { + 98: { + 114: { + 105: { + 117: { + 109: {59: {}} + } + } + } + } + } + } + } + } + } + }, + 84: { + 101: { + 101: { + 59: {}, + 65: { + 114: { + 114: { + 111: { + 119: {59: {}} + } + } + } + } + } + } + }, + 97: { + 114: { + 114: { + 111: { + 119: {59: {}} + } + } + } + }, + 100: { + 111: { + 119: { + 110: { + 97: { + 114: { + 114: { + 111: { + 119: {59: {}} + } + } + } + } + } + } + } + }, + 112: { + 101: { + 114: { + 76: { + 101: { + 102: { + 116: { + 65: { + 114: { + 114: { + 111: { + 119: {59: {}} + } + } + } + } + } + } + } + }, + 82: { + 105: { + 103: { + 104: { + 116: { + 65: { + 114: { + 114: { + 111: { + 119: {59: {}} + } + } + } + } + } + } + } + } + } + } + } + }, + 115: { + 105: { + 59: {}, + 108: { + 111: { + 110: {59: {}} + } + } + } + } + }, + 114: { + 105: { + 110: { + 103: {59: {}} + } + } + }, + 115: { + 99: { + 114: {59: {}} + } + }, + 116: { + 105: { + 108: { + 100: { + 101: {59: {}} + } + } + } + }, + 117: { + 109: { + 108: {59: {}} + } + } + }, + 86: { + 68: { + 97: { + 115: { + 104: {59: {}} + } + } + }, + 98: { + 97: { + 114: {59: {}} + } + }, + 99: { + 121: {59: {}} + }, + 100: { + 97: { + 115: { + 104: { + 59: {}, + 108: {59: {}} + } + } + } + }, + 101: { + 101: {59: {}}, + 114: { + 98: { + 97: { + 114: {59: {}} + } + }, + 116: { + 59: {}, + 105: { + 99: { + 97: { + 108: { + 66: { + 97: { + 114: {59: {}} + } + }, + 76: { + 105: { + 110: { + 101: {59: {}} + } + } + }, + 83: { + 101: { + 112: { + 97: { + 114: { + 97: { + 116: { + 111: { + 114: {59: {}} + } + } + } + } + } + } + } + }, + 84: { + 105: { + 108: { + 100: { + 101: {59: {}} + } + } + } + } + } + } + } + } + }, + 121: { + 84: { + 104: { + 105: { + 110: { + 83: { + 112: { + 97: { + 99: { + 101: {59: {}} + } + } + } + } + } + } + } + } + } + } + }, + 102: { + 114: {59: {}} + }, + 111: { + 112: { + 102: {59: {}} + } + }, + 115: { + 99: { + 114: {59: {}} + } + }, + 118: { + 100: { + 97: { + 115: { + 104: {59: {}} + } + } + } + } + }, + 87: { + 99: { + 105: { + 114: { + 99: {59: {}} + } + } + }, + 101: { + 100: { + 103: { + 101: {59: {}} + } + } + }, + 102: { + 114: {59: {}} + }, + 111: { + 112: { + 102: {59: {}} + } + }, + 115: { + 99: { + 114: {59: {}} + } + } + }, + 88: { + 102: { + 114: {59: {}} + }, + 105: {59: {}}, + 111: { + 112: { + 102: {59: {}} + } + }, + 115: { + 99: { + 114: {59: {}} + } + } + }, + 89: { + 65: { + 99: { + 121: {59: {}} + } + }, + 73: { + 99: { + 121: {59: {}} + } + }, + 85: { + 99: { + 121: {59: {}} + } + }, + 97: { + 99: { + 117: { + 116: { + 101: {59: {}} + } + } + } + }, + 99: { + 105: { + 114: { + 99: {59: {}} + } + }, + 121: {59: {}} + }, + 102: { + 114: {59: {}} + }, + 111: { + 112: { + 102: {59: {}} + } + }, + 115: { + 99: { + 114: {59: {}} + } + }, + 117: { + 109: { + 108: {59: {}} + } + } + }, + 90: { + 72: { + 99: { + 121: {59: {}} + } + }, + 97: { + 99: { + 117: { + 116: { + 101: {59: {}} + } + } + } + }, + 99: { + 97: { + 114: { + 111: { + 110: {59: {}} + } + } + }, + 121: {59: {}} + }, + 100: { + 111: { + 116: {59: {}} + } + }, + 101: { + 114: { + 111: { + 87: { + 105: { + 100: { + 116: { + 104: { + 83: { + 112: { + 97: { + 99: { + 101: {59: {}} + } + } + } + } + } + } + } + } + } + } + }, + 116: { + 97: {59: {}} + } + }, + 102: { + 114: {59: {}} + }, + 111: { + 112: { + 102: {59: {}} + } + }, + 115: { + 99: { + 114: {59: {}} + } + } + }, + 97: { + 97: { + 99: { + 117: { + 116: { + 101: {59: {}} + } + } + } + }, + 98: { + 114: { + 101: { + 118: { + 101: {59: {}} + } + } + } + }, + 99: { + 59: {}, + 69: {59: {}}, + 100: {59: {}}, + 105: { + 114: { + 99: {59: {}} + } + }, + 117: { + 116: { + 101: {59: {}} + } + }, + 121: {59: {}} + }, + 101: { + 108: { + 105: { + 103: {59: {}} + } + } + }, + 102: { + 59: {}, + 114: {59: {}} + }, + 103: { + 114: { + 97: { + 118: { + 101: {59: {}} + } + } + } + }, + 108: { + 101: { + 102: { + 115: { + 121: { + 109: {59: {}} + } + } + }, + 112: { + 104: {59: {}} + } + }, + 112: { + 104: { + 97: {59: {}} + } + } + }, + 109: { + 97: { + 99: { + 114: {59: {}} + }, + 108: { + 103: {59: {}} + } + }, + 112: {59: {}} + }, + 110: { + 100: { + 59: {}, + 97: { + 110: { + 100: {59: {}} + } + }, + 100: {59: {}}, + 115: { + 108: { + 111: { + 112: { + 101: {59: {}} + } + } + } + }, + 118: {59: {}} + }, + 103: { + 59: {}, + 101: {59: {}}, + 108: { + 101: {59: {}} + }, + 109: { + 115: { + 100: { + 59: {}, + 97: { + 97: {59: {}}, + 98: {59: {}}, + 99: {59: {}}, + 100: {59: {}}, + 101: {59: {}}, + 102: {59: {}}, + 103: {59: {}}, + 104: {59: {}} + } + } + } + }, + 114: { + 116: { + 59: {}, + 118: { + 98: { + 59: {}, + 100: {59: {}} + } + } + } + }, + 115: { + 112: { + 104: {59: {}} + }, + 116: {59: {}} + }, + 122: { + 97: { + 114: { + 114: {59: {}} + } + } + } + } + }, + 111: { + 103: { + 111: { + 110: {59: {}} + } + }, + 112: { + 102: {59: {}} + } + }, + 112: { + 59: {}, + 69: {59: {}}, + 97: { + 99: { + 105: { + 114: {59: {}} + } + } + }, + 101: {59: {}}, + 105: { + 100: {59: {}} + }, + 111: { + 115: {59: {}} + }, + 112: { + 114: { + 111: { + 120: { + 59: {}, + 101: { + 113: {59: {}} + } + } + } + } + } + }, + 114: { + 105: { + 110: { + 103: {59: {}} + } + } + }, + 115: { + 99: { + 114: {59: {}} + }, + 116: {59: {}}, + 121: { + 109: { + 112: { + 59: {}, + 101: { + 113: {59: {}} + } + } + } + } + }, + 116: { + 105: { + 108: { + 100: { + 101: {59: {}} + } + } + } + }, + 117: { + 109: { + 108: {59: {}} + } + }, + 119: { + 99: { + 111: { + 110: { + 105: { + 110: { + 116: {59: {}} + } + } + } + } + }, + 105: { + 110: { + 116: {59: {}} + } + } + } + }, + 98: { + 78: { + 111: { + 116: {59: {}} + } + }, + 97: { + 99: { + 107: { + 99: { + 111: { + 110: { + 103: {59: {}} + } + } + }, + 101: { + 112: { + 115: { + 105: { + 108: { + 111: { + 110: {59: {}} + } + } + } + } + } + }, + 112: { + 114: { + 105: { + 109: { + 101: {59: {}} + } + } + } + }, + 115: { + 105: { + 109: { + 59: {}, + 101: { + 113: {59: {}} + } + } + } + } + } + }, + 114: { + 118: { + 101: { + 101: {59: {}} + } + }, + 119: { + 101: { + 100: { + 59: {}, + 103: { + 101: {59: {}} + } + } + } + } + } + }, + 98: { + 114: { + 107: { + 59: {}, + 116: { + 98: { + 114: { + 107: {59: {}} + } + } + } + } + } + }, + 99: { + 111: { + 110: { + 103: {59: {}} + } + }, + 121: {59: {}} + }, + 100: { + 113: { + 117: { + 111: {59: {}} + } + } + }, + 101: { + 99: { + 97: { + 117: { + 115: { + 59: {}, + 101: {59: {}} + } + } + } + }, + 109: { + 112: { + 116: { + 121: { + 118: {59: {}} + } + } + } + }, + 112: { + 115: { + 105: {59: {}} + } + }, + 114: { + 110: { + 111: { + 117: {59: {}} + } + } + }, + 116: { + 97: {59: {}}, + 104: {59: {}}, + 119: { + 101: { + 101: { + 110: {59: {}} + } + } + } + } + }, + 102: { + 114: {59: {}} + }, + 105: { + 103: { + 99: { + 97: { + 112: {59: {}} + }, + 105: { + 114: { + 99: {59: {}} + } + }, + 117: { + 112: {59: {}} + } + }, + 111: { + 100: { + 111: { + 116: {59: {}} + } + }, + 112: { + 108: { + 117: { + 115: {59: {}} + } + } + }, + 116: { + 105: { + 109: { + 101: { + 115: {59: {}} + } + } + } + } + }, + 115: { + 113: { + 99: { + 117: { + 112: {59: {}} + } + } + }, + 116: { + 97: { + 114: {59: {}} + } + } + }, + 116: { + 114: { + 105: { + 97: { + 110: { + 103: { + 108: { + 101: { + 100: { + 111: { + 119: { + 110: {59: {}} + } + } + }, + 117: { + 112: {59: {}} + } + } + } + } + } + } + } + } + }, + 117: { + 112: { + 108: { + 117: { + 115: {59: {}} + } + } + } + }, + 118: { + 101: { + 101: {59: {}} + } + }, + 119: { + 101: { + 100: { + 103: { + 101: {59: {}} + } + } + } + } + } + }, + 107: { + 97: { + 114: { + 111: { + 119: {59: {}} + } + } + } + }, + 108: { + 97: { + 99: { + 107: { + 108: { + 111: { + 122: { + 101: { + 110: { + 103: { + 101: {59: {}} + } + } + } + } + } + }, + 115: { + 113: { + 117: { + 97: { + 114: { + 101: {59: {}} + } + } + } + } + }, + 116: { + 114: { + 105: { + 97: { + 110: { + 103: { + 108: { + 101: { + 59: {}, + 100: { + 111: { + 119: { + 110: {59: {}} + } + } + }, + 108: { + 101: { + 102: { + 116: {59: {}} + } + } + }, + 114: { + 105: { + 103: { + 104: { + 116: {59: {}} + } + } + } + } + } + } + } + } + } + } + } + } + } + }, + 110: { + 107: {59: {}} + } + }, + 107: { + 49: { + 50: {59: {}}, + 52: {59: {}} + }, + 51: { + 52: {59: {}} + } + }, + 111: { + 99: { + 107: {59: {}} + } + } + }, + 110: { + 101: { + 59: {}, + 113: { + 117: { + 105: { + 118: {59: {}} + } + } + } + }, + 111: { + 116: {59: {}} + } + }, + 111: { + 112: { + 102: {59: {}} + }, + 116: { + 59: {}, + 116: { + 111: { + 109: {59: {}} + } + } + }, + 119: { + 116: { + 105: { + 101: {59: {}} + } + } + }, + 120: { + 68: { + 76: {59: {}}, + 82: {59: {}}, + 108: {59: {}}, + 114: {59: {}} + }, + 72: { + 59: {}, + 68: {59: {}}, + 85: {59: {}}, + 100: {59: {}}, + 117: {59: {}} + }, + 85: { + 76: {59: {}}, + 82: {59: {}}, + 108: {59: {}}, + 114: {59: {}} + }, + 86: { + 59: {}, + 72: {59: {}}, + 76: {59: {}}, + 82: {59: {}}, + 104: {59: {}}, + 108: {59: {}}, + 114: {59: {}} + }, + 98: { + 111: { + 120: {59: {}} + } + }, + 100: { + 76: {59: {}}, + 82: {59: {}}, + 108: {59: {}}, + 114: {59: {}} + }, + 104: { + 59: {}, + 68: {59: {}}, + 85: {59: {}}, + 100: {59: {}}, + 117: {59: {}} + }, + 109: { + 105: { + 110: { + 117: { + 115: {59: {}} + } + } + } + }, + 112: { + 108: { + 117: { + 115: {59: {}} + } + } + }, + 116: { + 105: { + 109: { + 101: { + 115: {59: {}} + } + } + } + }, + 117: { + 76: {59: {}}, + 82: {59: {}}, + 108: {59: {}}, + 114: {59: {}} + }, + 118: { + 59: {}, + 72: {59: {}}, + 76: {59: {}}, + 82: {59: {}}, + 104: {59: {}}, + 108: {59: {}}, + 114: {59: {}} + } + } + }, + 112: { + 114: { + 105: { + 109: { + 101: {59: {}} + } + } + } + }, + 114: { + 101: { + 118: { + 101: {59: {}} + } + }, + 118: { + 98: { + 97: { + 114: {59: {}} + } + } + } + }, + 115: { + 99: { + 114: {59: {}} + }, + 101: { + 109: { + 105: {59: {}} + } + }, + 105: { + 109: { + 59: {}, + 101: {59: {}} + } + }, + 111: { + 108: { + 59: {}, + 98: {59: {}}, + 104: { + 115: { + 117: { + 98: {59: {}} + } + } + } + } + } + }, + 117: { + 108: { + 108: { + 59: {}, + 101: { + 116: {59: {}} + } + } + }, + 109: { + 112: { + 59: {}, + 69: {59: {}}, + 101: { + 59: {}, + 113: {59: {}} + } + } + } + } + }, + 99: { + 97: { + 99: { + 117: { + 116: { + 101: {59: {}} + } + } + }, + 112: { + 59: {}, + 97: { + 110: { + 100: {59: {}} + } + }, + 98: { + 114: { + 99: { + 117: { + 112: {59: {}} + } + } + } + }, + 99: { + 97: { + 112: {59: {}} + }, + 117: { + 112: {59: {}} + } + }, + 100: { + 111: { + 116: {59: {}} + } + }, + 115: {59: {}} + }, + 114: { + 101: { + 116: {59: {}} + }, + 111: { + 110: {59: {}} + } + } + }, + 99: { + 97: { + 112: { + 115: {59: {}} + }, + 114: { + 111: { + 110: {59: {}} + } + } + }, + 101: { + 100: { + 105: { + 108: {59: {}} + } + } + }, + 105: { + 114: { + 99: {59: {}} + } + }, + 117: { + 112: { + 115: { + 59: {}, + 115: { + 109: {59: {}} + } + } + } + } + }, + 100: { + 111: { + 116: {59: {}} + } + }, + 101: { + 100: { + 105: { + 108: {59: {}} + } + }, + 109: { + 112: { + 116: { + 121: { + 118: {59: {}} + } + } + } + }, + 110: { + 116: { + 59: {}, + 101: { + 114: { + 100: { + 111: { + 116: {59: {}} + } + } + } + } + } + } + }, + 102: { + 114: {59: {}} + }, + 104: { + 99: { + 121: {59: {}} + }, + 101: { + 99: { + 107: { + 59: {}, + 109: { + 97: { + 114: { + 107: {59: {}} + } + } + } + } + } + }, + 105: {59: {}} + }, + 105: { + 114: { + 59: {}, + 69: {59: {}}, + 99: { + 59: {}, + 101: { + 113: {59: {}} + }, + 108: { + 101: { + 97: { + 114: { + 114: { + 111: { + 119: { + 108: { + 101: { + 102: { + 116: {59: {}} + } + } + }, + 114: { + 105: { + 103: { + 104: { + 116: {59: {}} + } + } + } + } + } + } + } + } + }, + 100: { + 82: {59: {}}, + 83: {59: {}}, + 97: { + 115: { + 116: {59: {}} + } + }, + 99: { + 105: { + 114: { + 99: {59: {}} + } + } + }, + 100: { + 97: { + 115: { + 104: {59: {}} + } + } + } + } + } + } + }, + 101: {59: {}}, + 102: { + 110: { + 105: { + 110: { + 116: {59: {}} + } + } + } + }, + 109: { + 105: { + 100: {59: {}} + } + }, + 115: { + 99: { + 105: { + 114: {59: {}} + } + } + } + } + }, + 108: { + 117: { + 98: { + 115: { + 59: {}, + 117: { + 105: { + 116: {59: {}} + } + } + } + } + } + }, + 111: { + 108: { + 111: { + 110: { + 59: {}, + 101: { + 59: {}, + 113: {59: {}} + } + } + } + }, + 109: { + 109: { + 97: { + 59: {}, + 116: {59: {}} + } + }, + 112: { + 59: {}, + 102: { + 110: {59: {}} + }, + 108: { + 101: { + 109: { + 101: { + 110: { + 116: {59: {}} + } + } + }, + 120: { + 101: { + 115: {59: {}} + } + } + } + } + } + }, + 110: { + 103: { + 59: {}, + 100: { + 111: { + 116: {59: {}} + } + } + }, + 105: { + 110: { + 116: {59: {}} + } + } + }, + 112: { + 102: {59: {}}, + 114: { + 111: { + 100: {59: {}} + } + }, + 121: { + 59: {}, + 115: { + 114: {59: {}} + } + } + } + }, + 114: { + 97: { + 114: { + 114: {59: {}} + } + }, + 111: { + 115: { + 115: {59: {}} + } + } + }, + 115: { + 99: { + 114: {59: {}} + }, + 117: { + 98: { + 59: {}, + 101: {59: {}} + }, + 112: { + 59: {}, + 101: {59: {}} + } + } + }, + 116: { + 100: { + 111: { + 116: {59: {}} + } + } + }, + 117: { + 100: { + 97: { + 114: { + 114: { + 108: {59: {}}, + 114: {59: {}} + } + } + } + }, + 101: { + 112: { + 114: {59: {}} + }, + 115: { + 99: {59: {}} + } + }, + 108: { + 97: { + 114: { + 114: { + 59: {}, + 112: {59: {}} + } + } + } + }, + 112: { + 59: {}, + 98: { + 114: { + 99: { + 97: { + 112: {59: {}} + } + } + } + }, + 99: { + 97: { + 112: {59: {}} + }, + 117: { + 112: {59: {}} + } + }, + 100: { + 111: { + 116: {59: {}} + } + }, + 111: { + 114: {59: {}} + }, + 115: {59: {}} + }, + 114: { + 97: { + 114: { + 114: { + 59: {}, + 109: {59: {}} + } + } + }, + 108: { + 121: { + 101: { + 113: { + 112: { + 114: { + 101: { + 99: {59: {}} + } + } + }, + 115: { + 117: { + 99: { + 99: {59: {}} + } + } + } + } + }, + 118: { + 101: { + 101: {59: {}} + } + }, + 119: { + 101: { + 100: { + 103: { + 101: {59: {}} + } + } + } + } + } + }, + 114: { + 101: { + 110: {59: {}} + } + }, + 118: { + 101: { + 97: { + 114: { + 114: { + 111: { + 119: { + 108: { + 101: { + 102: { + 116: {59: {}} + } + } + }, + 114: { + 105: { + 103: { + 104: { + 116: {59: {}} + } + } + } + } + } + } + } + } + } + } + } + }, + 118: { + 101: { + 101: {59: {}} + } + }, + 119: { + 101: { + 100: {59: {}} + } + } + }, + 119: { + 99: { + 111: { + 110: { + 105: { + 110: { + 116: {59: {}} + } + } + } + } + }, + 105: { + 110: { + 116: {59: {}} + } + } + }, + 121: { + 108: { + 99: { + 116: { + 121: {59: {}} + } + } + } + } + }, + 100: { + 65: { + 114: { + 114: {59: {}} + } + }, + 72: { + 97: { + 114: {59: {}} + } + }, + 97: { + 103: { + 103: { + 101: { + 114: {59: {}} + } + } + }, + 108: { + 101: { + 116: { + 104: {59: {}} + } + } + }, + 114: { + 114: {59: {}} + }, + 115: { + 104: { + 59: {}, + 118: {59: {}} + } + } + }, + 98: { + 107: { + 97: { + 114: { + 111: { + 119: {59: {}} + } + } + } + }, + 108: { + 97: { + 99: {59: {}} + } + } + }, + 99: { + 97: { + 114: { + 111: { + 110: {59: {}} + } + } + }, + 121: {59: {}} + }, + 100: { + 59: {}, + 97: { + 103: { + 103: { + 101: { + 114: {59: {}} + } + } + }, + 114: { + 114: {59: {}} + } + }, + 111: { + 116: { + 115: { + 101: { + 113: {59: {}} + } + } + } + } + }, + 101: { + 103: {59: {}}, + 108: { + 116: { + 97: {59: {}} + } + }, + 109: { + 112: { + 116: { + 121: { + 118: {59: {}} + } + } + } + } + }, + 102: { + 105: { + 115: { + 104: { + 116: {59: {}} + } + } + }, + 114: {59: {}} + }, + 104: { + 97: { + 114: { + 108: {59: {}}, + 114: {59: {}} + } + } + }, + 105: { + 97: { + 109: { + 59: {}, + 111: { + 110: { + 100: { + 59: {}, + 115: { + 117: { + 105: { + 116: {59: {}} + } + } + } + } + } + }, + 115: {59: {}} + } + }, + 101: {59: {}}, + 103: { + 97: { + 109: { + 109: { + 97: {59: {}} + } + } + } + }, + 115: { + 105: { + 110: {59: {}} + } + }, + 118: { + 59: {}, + 105: { + 100: { + 101: { + 59: {}, + 111: { + 110: { + 116: { + 105: { + 109: { + 101: { + 115: {59: {}} + } + } + } + } + } + } + } + } + }, + 111: { + 110: { + 120: {59: {}} + } + } + } + }, + 106: { + 99: { + 121: {59: {}} + } + }, + 108: { + 99: { + 111: { + 114: { + 110: {59: {}} + } + }, + 114: { + 111: { + 112: {59: {}} + } + } + } + }, + 111: { + 108: { + 108: { + 97: { + 114: {59: {}} + } + } + }, + 112: { + 102: {59: {}} + }, + 116: { + 59: {}, + 101: { + 113: { + 59: {}, + 100: { + 111: { + 116: {59: {}} + } + } + } + }, + 109: { + 105: { + 110: { + 117: { + 115: {59: {}} + } + } + } + }, + 112: { + 108: { + 117: { + 115: {59: {}} + } + } + }, + 115: { + 113: { + 117: { + 97: { + 114: { + 101: {59: {}} + } + } + } + } + } + }, + 117: { + 98: { + 108: { + 101: { + 98: { + 97: { + 114: { + 119: { + 101: { + 100: { + 103: { + 101: {59: {}} + } + } + } + } + } + } + } + } + } + } + }, + 119: { + 110: { + 97: { + 114: { + 114: { + 111: { + 119: {59: {}} + } + } + } + }, + 100: { + 111: { + 119: { + 110: { + 97: { + 114: { + 114: { + 111: { + 119: { + 115: {59: {}} + } + } + } + } + } + } + } + } + }, + 104: { + 97: { + 114: { + 112: { + 111: { + 111: { + 110: { + 108: { + 101: { + 102: { + 116: {59: {}} + } + } + }, + 114: { + 105: { + 103: { + 104: { + 116: {59: {}} + } + } + } + } + } + } + } + } + } + } + } + } + } + }, + 114: { + 98: { + 107: { + 97: { + 114: { + 111: { + 119: {59: {}} + } + } + } + } + }, + 99: { + 111: { + 114: { + 110: {59: {}} + } + }, + 114: { + 111: { + 112: {59: {}} + } + } + } + }, + 115: { + 99: { + 114: {59: {}}, + 121: {59: {}} + }, + 111: { + 108: {59: {}} + }, + 116: { + 114: { + 111: { + 107: {59: {}} + } + } + } + }, + 116: { + 100: { + 111: { + 116: {59: {}} + } + }, + 114: { + 105: { + 59: {}, + 102: {59: {}} + } + } + }, + 117: { + 97: { + 114: { + 114: {59: {}} + } + }, + 104: { + 97: { + 114: {59: {}} + } + } + }, + 119: { + 97: { + 110: { + 103: { + 108: { + 101: {59: {}} + } + } + } + } + }, + 122: { + 99: { + 121: {59: {}} + }, + 105: { + 103: { + 114: { + 97: { + 114: { + 114: {59: {}} + } + } + } + } + } + } + }, + 101: { + 68: { + 68: { + 111: { + 116: {59: {}} + } + }, + 111: { + 116: {59: {}} + } + }, + 97: { + 99: { + 117: { + 116: { + 101: {59: {}} + } + } + }, + 115: { + 116: { + 101: { + 114: {59: {}} + } + } + } + }, + 99: { + 97: { + 114: { + 111: { + 110: {59: {}} + } + } + }, + 105: { + 114: { + 59: {}, + 99: {59: {}} + } + }, + 111: { + 108: { + 111: { + 110: {59: {}} + } + } + }, + 121: {59: {}} + }, + 100: { + 111: { + 116: {59: {}} + } + }, + 101: {59: {}}, + 102: { + 68: { + 111: { + 116: {59: {}} + } + }, + 114: {59: {}} + }, + 103: { + 59: {}, + 114: { + 97: { + 118: { + 101: {59: {}} + } + } + }, + 115: { + 59: {}, + 100: { + 111: { + 116: {59: {}} + } + } + } + }, + 108: { + 59: {}, + 105: { + 110: { + 116: { + 101: { + 114: { + 115: {59: {}} + } + } + } + } + }, + 108: {59: {}}, + 115: { + 59: {}, + 100: { + 111: { + 116: {59: {}} + } + } + } + }, + 109: { + 97: { + 99: { + 114: {59: {}} + } + }, + 112: { + 116: { + 121: { + 59: {}, + 115: { + 101: { + 116: {59: {}} + } + }, + 118: {59: {}} + } + } + }, + 115: { + 112: { + 49: { + 51: {59: {}}, + 52: {59: {}} + }, + 59: {} + } + } + }, + 110: { + 103: {59: {}}, + 115: { + 112: {59: {}} + } + }, + 111: { + 103: { + 111: { + 110: {59: {}} + } + }, + 112: { + 102: {59: {}} + } + }, + 112: { + 97: { + 114: { + 59: {}, + 115: { + 108: {59: {}} + } + } + }, + 108: { + 117: { + 115: {59: {}} + } + }, + 115: { + 105: { + 59: {}, + 108: { + 111: { + 110: {59: {}} + } + }, + 118: {59: {}} + } + } + }, + 113: { + 99: { + 105: { + 114: { + 99: {59: {}} + } + }, + 111: { + 108: { + 111: { + 110: {59: {}} + } + } + } + }, + 115: { + 105: { + 109: {59: {}} + }, + 108: { + 97: { + 110: { + 116: { + 103: { + 116: { + 114: {59: {}} + } + }, + 108: { + 101: { + 115: { + 115: {59: {}} + } + } + } + } + } + } + } + }, + 117: { + 97: { + 108: { + 115: {59: {}} + } + }, + 101: { + 115: { + 116: {59: {}} + } + }, + 105: { + 118: { + 59: {}, + 68: { + 68: {59: {}} + } + } + } + }, + 118: { + 112: { + 97: { + 114: { + 115: { + 108: {59: {}} + } + } + } + } + } + }, + 114: { + 68: { + 111: { + 116: {59: {}} + } + }, + 97: { + 114: { + 114: {59: {}} + } + } + }, + 115: { + 99: { + 114: {59: {}} + }, + 100: { + 111: { + 116: {59: {}} + } + }, + 105: { + 109: {59: {}} + } + }, + 116: { + 97: {59: {}}, + 104: {59: {}} + }, + 117: { + 109: { + 108: {59: {}} + }, + 114: { + 111: {59: {}} + } + }, + 120: { + 99: { + 108: {59: {}} + }, + 105: { + 115: { + 116: {59: {}} + } + }, + 112: { + 101: { + 99: { + 116: { + 97: { + 116: { + 105: { + 111: { + 110: {59: {}} + } + } + } + } + } + } + }, + 111: { + 110: { + 101: { + 110: { + 116: { + 105: { + 97: { + 108: { + 101: {59: {}} + } + } + } + } + } + } + } + } + } + } + }, + 102: { + 97: { + 108: { + 108: { + 105: { + 110: { + 103: { + 100: { + 111: { + 116: { + 115: { + 101: { + 113: {59: {}} + } + } + } + } + } + } + } + } + } + } + }, + 99: { + 121: {59: {}} + }, + 101: { + 109: { + 97: { + 108: { + 101: {59: {}} + } + } + } + }, + 102: { + 105: { + 108: { + 105: { + 103: {59: {}} + } + } + }, + 108: { + 105: { + 103: {59: {}} + }, + 108: { + 105: { + 103: {59: {}} + } + } + }, + 114: {59: {}} + }, + 105: { + 108: { + 105: { + 103: {59: {}} + } + } + }, + 106: { + 108: { + 105: { + 103: {59: {}} + } + } + }, + 108: { + 97: { + 116: {59: {}} + }, + 108: { + 105: { + 103: {59: {}} + } + }, + 116: { + 110: { + 115: {59: {}} + } + } + }, + 110: { + 111: { + 102: {59: {}} + } + }, + 111: { + 112: { + 102: {59: {}} + }, + 114: { + 97: { + 108: { + 108: {59: {}} + } + }, + 107: { + 59: {}, + 118: {59: {}} + } + } + }, + 112: { + 97: { + 114: { + 116: { + 105: { + 110: { + 116: {59: {}} + } + } + } + } + } + }, + 114: { + 97: { + 99: { + 49: { + 50: {59: {}}, + 51: {59: {}}, + 52: {59: {}}, + 53: {59: {}}, + 54: {59: {}}, + 56: {59: {}} + }, + 50: { + 51: {59: {}}, + 53: {59: {}} + }, + 51: { + 52: {59: {}}, + 53: {59: {}}, + 56: {59: {}} + }, + 52: { + 53: {59: {}} + }, + 53: { + 54: {59: {}}, + 56: {59: {}} + }, + 55: { + 56: {59: {}} + } + }, + 115: { + 108: {59: {}} + } + }, + 111: { + 119: { + 110: {59: {}} + } + } + }, + 115: { + 99: { + 114: {59: {}} + } + } + }, + 103: { + 69: { + 59: {}, + 108: {59: {}} + }, + 97: { + 99: { + 117: { + 116: { + 101: {59: {}} + } + } + }, + 109: { + 109: { + 97: { + 59: {}, + 100: {59: {}} + } + } + }, + 112: {59: {}} + }, + 98: { + 114: { + 101: { + 118: { + 101: {59: {}} + } + } + } + }, + 99: { + 105: { + 114: { + 99: {59: {}} + } + }, + 121: {59: {}} + }, + 100: { + 111: { + 116: {59: {}} + } + }, + 101: { + 59: {}, + 108: {59: {}}, + 113: { + 59: {}, + 113: {59: {}}, + 115: { + 108: { + 97: { + 110: { + 116: {59: {}} + } + } + } + } + }, + 115: { + 59: {}, + 99: { + 99: {59: {}} + }, + 100: { + 111: { + 116: { + 59: {}, + 111: { + 59: {}, + 108: {59: {}} + } + } + } + }, + 108: { + 59: {}, + 101: { + 115: {59: {}} + } + } + } + }, + 102: { + 114: {59: {}} + }, + 103: { + 59: {}, + 103: {59: {}} + }, + 105: { + 109: { + 101: { + 108: {59: {}} + } + } + }, + 106: { + 99: { + 121: {59: {}} + } + }, + 108: { + 59: {}, + 69: {59: {}}, + 97: {59: {}}, + 106: {59: {}} + }, + 110: { + 69: {59: {}}, + 97: { + 112: { + 59: {}, + 112: { + 114: { + 111: { + 120: {59: {}} + } + } + } + } + }, + 101: { + 59: {}, + 113: { + 59: {}, + 113: {59: {}} + } + }, + 115: { + 105: { + 109: {59: {}} + } + } + }, + 111: { + 112: { + 102: {59: {}} + } + }, + 114: { + 97: { + 118: { + 101: {59: {}} + } + } + }, + 115: { + 99: { + 114: {59: {}} + }, + 105: { + 109: { + 59: {}, + 101: {59: {}}, + 108: {59: {}} + } + } + }, + 116: { + 59: {}, + 99: { + 99: {59: {}}, + 105: { + 114: {59: {}} + } + }, + 100: { + 111: { + 116: {59: {}} + } + }, + 108: { + 80: { + 97: { + 114: {59: {}} + } + } + }, + 113: { + 117: { + 101: { + 115: { + 116: {59: {}} + } + } + } + }, + 114: { + 97: { + 112: { + 112: { + 114: { + 111: { + 120: {59: {}} + } + } + } + }, + 114: { + 114: {59: {}} + } + }, + 100: { + 111: { + 116: {59: {}} + } + }, + 101: { + 113: { + 108: { + 101: { + 115: { + 115: {59: {}} + } + } + }, + 113: { + 108: { + 101: { + 115: { + 115: {59: {}} + } + } + } + } + } + }, + 108: { + 101: { + 115: { + 115: {59: {}} + } + } + }, + 115: { + 105: { + 109: {59: {}} + } + } + } + }, + 118: { + 101: { + 114: { + 116: { + 110: { + 101: { + 113: { + 113: {59: {}} + } + } + } + } + } + }, + 110: { + 69: {59: {}} + } + } + }, + 104: { + 65: { + 114: { + 114: {59: {}} + } + }, + 97: { + 105: { + 114: { + 115: { + 112: {59: {}} + } + } + }, + 108: { + 102: {59: {}} + }, + 109: { + 105: { + 108: { + 116: {59: {}} + } + } + }, + 114: { + 100: { + 99: { + 121: {59: {}} + } + }, + 114: { + 59: {}, + 99: { + 105: { + 114: {59: {}} + } + }, + 119: {59: {}} + } + } + }, + 98: { + 97: { + 114: {59: {}} + } + }, + 99: { + 105: { + 114: { + 99: {59: {}} + } + } + }, + 101: { + 97: { + 114: { + 116: { + 115: { + 59: {}, + 117: { + 105: { + 116: {59: {}} + } + } + } + } + } + }, + 108: { + 108: { + 105: { + 112: {59: {}} + } + } + }, + 114: { + 99: { + 111: { + 110: {59: {}} + } + } + } + }, + 102: { + 114: {59: {}} + }, + 107: { + 115: { + 101: { + 97: { + 114: { + 111: { + 119: {59: {}} + } + } + } + }, + 119: { + 97: { + 114: { + 111: { + 119: {59: {}} + } + } + } + } + } + }, + 111: { + 97: { + 114: { + 114: {59: {}} + } + }, + 109: { + 116: { + 104: { + 116: {59: {}} + } + } + }, + 111: { + 107: { + 108: { + 101: { + 102: { + 116: { + 97: { + 114: { + 114: { + 111: { + 119: {59: {}} + } + } + } + } + } + } + } + }, + 114: { + 105: { + 103: { + 104: { + 116: { + 97: { + 114: { + 114: { + 111: { + 119: {59: {}} + } + } + } + } + } + } + } + } + } + } + }, + 112: { + 102: {59: {}} + }, + 114: { + 98: { + 97: { + 114: {59: {}} + } + } + } + }, + 115: { + 99: { + 114: {59: {}} + }, + 108: { + 97: { + 115: { + 104: {59: {}} + } + } + }, + 116: { + 114: { + 111: { + 107: {59: {}} + } + } + } + }, + 121: { + 98: { + 117: { + 108: { + 108: {59: {}} + } + } + }, + 112: { + 104: { + 101: { + 110: {59: {}} + } + } + } + } + }, + 105: { + 97: { + 99: { + 117: { + 116: { + 101: {59: {}} + } + } + } + }, + 99: { + 59: {}, + 105: { + 114: { + 99: {59: {}} + } + }, + 121: {59: {}} + }, + 101: { + 99: { + 121: {59: {}} + }, + 120: { + 99: { + 108: {59: {}} + } + } + }, + 102: { + 102: {59: {}}, + 114: {59: {}} + }, + 103: { + 114: { + 97: { + 118: { + 101: {59: {}} + } + } + } + }, + 105: { + 59: {}, + 105: { + 105: { + 110: { + 116: {59: {}} + } + }, + 110: { + 116: {59: {}} + } + }, + 110: { + 102: { + 105: { + 110: {59: {}} + } + } + }, + 111: { + 116: { + 97: {59: {}} + } + } + }, + 106: { + 108: { + 105: { + 103: {59: {}} + } + } + }, + 109: { + 97: { + 99: { + 114: {59: {}} + }, + 103: { + 101: {59: {}}, + 108: { + 105: { + 110: { + 101: {59: {}} + } + } + }, + 112: { + 97: { + 114: { + 116: {59: {}} + } + } + } + }, + 116: { + 104: {59: {}} + } + }, + 111: { + 102: {59: {}} + }, + 112: { + 101: { + 100: {59: {}} + } + } + }, + 110: { + 59: {}, + 99: { + 97: { + 114: { + 101: {59: {}} + } + } + }, + 102: { + 105: { + 110: { + 59: {}, + 116: { + 105: { + 101: {59: {}} + } + } + } + } + }, + 111: { + 100: { + 111: { + 116: {59: {}} + } + } + }, + 116: { + 59: {}, + 99: { + 97: { + 108: {59: {}} + } + }, + 101: { + 103: { + 101: { + 114: { + 115: {59: {}} + } + } + }, + 114: { + 99: { + 97: { + 108: {59: {}} + } + } + } + }, + 108: { + 97: { + 114: { + 104: { + 107: {59: {}} + } + } + } + }, + 112: { + 114: { + 111: { + 100: {59: {}} + } + } + } + } + }, + 111: { + 99: { + 121: {59: {}} + }, + 103: { + 111: { + 110: {59: {}} + } + }, + 112: { + 102: {59: {}} + }, + 116: { + 97: {59: {}} + } + }, + 112: { + 114: { + 111: { + 100: {59: {}} + } + } + }, + 113: { + 117: { + 101: { + 115: { + 116: {59: {}} + } + } + } + }, + 115: { + 99: { + 114: {59: {}} + }, + 105: { + 110: { + 59: {}, + 69: {59: {}}, + 100: { + 111: { + 116: {59: {}} + } + }, + 115: { + 59: {}, + 118: {59: {}} + }, + 118: {59: {}} + } + } + }, + 116: { + 59: {}, + 105: { + 108: { + 100: { + 101: {59: {}} + } + } + } + }, + 117: { + 107: { + 99: { + 121: {59: {}} + } + }, + 109: { + 108: {59: {}} + } + } + }, + 106: { + 99: { + 105: { + 114: { + 99: {59: {}} + } + }, + 121: {59: {}} + }, + 102: { + 114: {59: {}} + }, + 109: { + 97: { + 116: { + 104: {59: {}} + } + } + }, + 111: { + 112: { + 102: {59: {}} + } + }, + 115: { + 99: { + 114: {59: {}} + }, + 101: { + 114: { + 99: { + 121: {59: {}} + } + } + } + }, + 117: { + 107: { + 99: { + 121: {59: {}} + } + } + } + }, + 107: { + 97: { + 112: { + 112: { + 97: { + 59: {}, + 118: {59: {}} + } + } + } + }, + 99: { + 101: { + 100: { + 105: { + 108: {59: {}} + } + } + }, + 121: {59: {}} + }, + 102: { + 114: {59: {}} + }, + 103: { + 114: { + 101: { + 101: { + 110: {59: {}} + } + } + } + }, + 104: { + 99: { + 121: {59: {}} + } + }, + 106: { + 99: { + 121: {59: {}} + } + }, + 111: { + 112: { + 102: {59: {}} + } + }, + 115: { + 99: { + 114: {59: {}} + } + } + }, + 108: { + 65: { + 97: { + 114: { + 114: {59: {}} + } + }, + 114: { + 114: {59: {}} + }, + 116: { + 97: { + 105: { + 108: {59: {}} + } + } + } + }, + 66: { + 97: { + 114: { + 114: {59: {}} + } + } + }, + 69: { + 59: {}, + 103: {59: {}} + }, + 72: { + 97: { + 114: {59: {}} + } + }, + 97: { + 99: { + 117: { + 116: { + 101: {59: {}} + } + } + }, + 101: { + 109: { + 112: { + 116: { + 121: { + 118: {59: {}} + } + } + } + } + }, + 103: { + 114: { + 97: { + 110: {59: {}} + } + } + }, + 109: { + 98: { + 100: { + 97: {59: {}} + } + } + }, + 110: { + 103: { + 59: {}, + 100: {59: {}}, + 108: { + 101: {59: {}} + } + } + }, + 112: {59: {}}, + 113: { + 117: { + 111: {59: {}} + } + }, + 114: { + 114: { + 59: {}, + 98: { + 59: {}, + 102: { + 115: {59: {}} + } + }, + 102: { + 115: {59: {}} + }, + 104: { + 107: {59: {}} + }, + 108: { + 112: {59: {}} + }, + 112: { + 108: {59: {}} + }, + 115: { + 105: { + 109: {59: {}} + } + }, + 116: { + 108: {59: {}} + } + } + }, + 116: { + 59: {}, + 97: { + 105: { + 108: {59: {}} + } + }, + 101: { + 59: {}, + 115: {59: {}} + } + } + }, + 98: { + 97: { + 114: { + 114: {59: {}} + } + }, + 98: { + 114: { + 107: {59: {}} + } + }, + 114: { + 97: { + 99: { + 101: {59: {}}, + 107: {59: {}} + } + }, + 107: { + 101: {59: {}}, + 115: { + 108: { + 100: {59: {}}, + 117: {59: {}} + } + } + } + } + }, + 99: { + 97: { + 114: { + 111: { + 110: {59: {}} + } + } + }, + 101: { + 100: { + 105: { + 108: {59: {}} + } + }, + 105: { + 108: {59: {}} + } + }, + 117: { + 98: {59: {}} + }, + 121: {59: {}} + }, + 100: { + 99: { + 97: {59: {}} + }, + 113: { + 117: { + 111: { + 59: {}, + 114: {59: {}} + } + } + }, + 114: { + 100: { + 104: { + 97: { + 114: {59: {}} + } + } + }, + 117: { + 115: { + 104: { + 97: { + 114: {59: {}} + } + } + } + } + }, + 115: { + 104: {59: {}} + } + }, + 101: { + 59: {}, + 102: { + 116: { + 97: { + 114: { + 114: { + 111: { + 119: { + 59: {}, + 116: { + 97: { + 105: { + 108: {59: {}} + } + } + } + } + } + } + } + }, + 104: { + 97: { + 114: { + 112: { + 111: { + 111: { + 110: { + 100: { + 111: { + 119: { + 110: {59: {}} + } + } + }, + 117: { + 112: {59: {}} + } + } + } + } + } + } + } + }, + 108: { + 101: { + 102: { + 116: { + 97: { + 114: { + 114: { + 111: { + 119: { + 115: {59: {}} + } + } + } + } + } + } + } + } + }, + 114: { + 105: { + 103: { + 104: { + 116: { + 97: { + 114: { + 114: { + 111: { + 119: { + 59: {}, + 115: {59: {}} + } + } + } + } + }, + 104: { + 97: { + 114: { + 112: { + 111: { + 111: { + 110: { + 115: {59: {}} + } + } + } + } + } + } + }, + 115: { + 113: { + 117: { + 105: { + 103: { + 97: { + 114: { + 114: { + 111: { + 119: {59: {}} + } + } + } + } + } + } + } + } + } + } + } + } + } + }, + 116: { + 104: { + 114: { + 101: { + 101: { + 116: { + 105: { + 109: { + 101: { + 115: {59: {}} + } + } + } + } + } + } + } + } + } + } + }, + 103: {59: {}}, + 113: { + 59: {}, + 113: {59: {}}, + 115: { + 108: { + 97: { + 110: { + 116: {59: {}} + } + } + } + } + }, + 115: { + 59: {}, + 99: { + 99: {59: {}} + }, + 100: { + 111: { + 116: { + 59: {}, + 111: { + 59: {}, + 114: {59: {}} + } + } + } + }, + 103: { + 59: {}, + 101: { + 115: {59: {}} + } + }, + 115: { + 97: { + 112: { + 112: { + 114: { + 111: { + 120: {59: {}} + } + } + } + } + }, + 100: { + 111: { + 116: {59: {}} + } + }, + 101: { + 113: { + 103: { + 116: { + 114: {59: {}} + } + }, + 113: { + 103: { + 116: { + 114: {59: {}} + } + } + } + } + }, + 103: { + 116: { + 114: {59: {}} + } + }, + 115: { + 105: { + 109: {59: {}} + } + } + } + } + }, + 102: { + 105: { + 115: { + 104: { + 116: {59: {}} + } + } + }, + 108: { + 111: { + 111: { + 114: {59: {}} + } + } + }, + 114: {59: {}} + }, + 103: { + 59: {}, + 69: {59: {}} + }, + 104: { + 97: { + 114: { + 100: {59: {}}, + 117: { + 59: {}, + 108: {59: {}} + } + } + }, + 98: { + 108: { + 107: {59: {}} + } + } + }, + 106: { + 99: { + 121: {59: {}} + } + }, + 108: { + 59: {}, + 97: { + 114: { + 114: {59: {}} + } + }, + 99: { + 111: { + 114: { + 110: { + 101: { + 114: {59: {}} + } + } + } + } + }, + 104: { + 97: { + 114: { + 100: {59: {}} + } + } + }, + 116: { + 114: { + 105: {59: {}} + } + } + }, + 109: { + 105: { + 100: { + 111: { + 116: {59: {}} + } + } + }, + 111: { + 117: { + 115: { + 116: { + 59: {}, + 97: { + 99: { + 104: { + 101: {59: {}} + } + } + } + } + } + } + } + }, + 110: { + 69: {59: {}}, + 97: { + 112: { + 59: {}, + 112: { + 114: { + 111: { + 120: {59: {}} + } + } + } + } + }, + 101: { + 59: {}, + 113: { + 59: {}, + 113: {59: {}} + } + }, + 115: { + 105: { + 109: {59: {}} + } + } + }, + 111: { + 97: { + 110: { + 103: {59: {}} + }, + 114: { + 114: {59: {}} + } + }, + 98: { + 114: { + 107: {59: {}} + } + }, + 110: { + 103: { + 108: { + 101: { + 102: { + 116: { + 97: { + 114: { + 114: { + 111: { + 119: {59: {}} + } + } + } + }, + 114: { + 105: { + 103: { + 104: { + 116: { + 97: { + 114: { + 114: { + 111: { + 119: {59: {}} + } + } + } + } + } + } + } + } + } + } + } + } + }, + 109: { + 97: { + 112: { + 115: { + 116: { + 111: {59: {}} + } + } + } + } + }, + 114: { + 105: { + 103: { + 104: { + 116: { + 97: { + 114: { + 114: { + 111: { + 119: {59: {}} + } + } + } + } + } + } + } + } + } + } + }, + 111: { + 112: { + 97: { + 114: { + 114: { + 111: { + 119: { + 108: { + 101: { + 102: { + 116: {59: {}} + } + } + }, + 114: { + 105: { + 103: { + 104: { + 116: {59: {}} + } + } + } + } + } + } + } + } + } + } + }, + 112: { + 97: { + 114: {59: {}} + }, + 102: {59: {}}, + 108: { + 117: { + 115: {59: {}} + } + } + }, + 116: { + 105: { + 109: { + 101: { + 115: {59: {}} + } + } + } + }, + 119: { + 97: { + 115: { + 116: {59: {}} + } + }, + 98: { + 97: { + 114: {59: {}} + } + } + }, + 122: { + 59: {}, + 101: { + 110: { + 103: { + 101: {59: {}} + } + } + }, + 102: {59: {}} + } + }, + 112: { + 97: { + 114: { + 59: {}, + 108: { + 116: {59: {}} + } + } + } + }, + 114: { + 97: { + 114: { + 114: {59: {}} + } + }, + 99: { + 111: { + 114: { + 110: { + 101: { + 114: {59: {}} + } + } + } + } + }, + 104: { + 97: { + 114: { + 59: {}, + 100: {59: {}} + } + } + }, + 109: {59: {}}, + 116: { + 114: { + 105: {59: {}} + } + } + }, + 115: { + 97: { + 113: { + 117: { + 111: {59: {}} + } + } + }, + 99: { + 114: {59: {}} + }, + 104: {59: {}}, + 105: { + 109: { + 59: {}, + 101: {59: {}}, + 103: {59: {}} + } + }, + 113: { + 98: {59: {}}, + 117: { + 111: { + 59: {}, + 114: {59: {}} + } + } + }, + 116: { + 114: { + 111: { + 107: {59: {}} + } + } + } + }, + 116: { + 59: {}, + 99: { + 99: {59: {}}, + 105: { + 114: {59: {}} + } + }, + 100: { + 111: { + 116: {59: {}} + } + }, + 104: { + 114: { + 101: { + 101: {59: {}} + } + } + }, + 105: { + 109: { + 101: { + 115: {59: {}} + } + } + }, + 108: { + 97: { + 114: { + 114: {59: {}} + } + } + }, + 113: { + 117: { + 101: { + 115: { + 116: {59: {}} + } + } + } + }, + 114: { + 80: { + 97: { + 114: {59: {}} + } + }, + 105: { + 59: {}, + 101: {59: {}}, + 102: {59: {}} + } + } + }, + 117: { + 114: { + 100: { + 115: { + 104: { + 97: { + 114: {59: {}} + } + } + } + }, + 117: { + 104: { + 97: { + 114: {59: {}} + } + } + } + } + }, + 118: { + 101: { + 114: { + 116: { + 110: { + 101: { + 113: { + 113: {59: {}} + } + } + } + } + } + }, + 110: { + 69: {59: {}} + } + } + }, + 109: { + 68: { + 68: { + 111: { + 116: {59: {}} + } + } + }, + 97: { + 99: { + 114: {59: {}} + }, + 108: { + 101: {59: {}}, + 116: { + 59: {}, + 101: { + 115: { + 101: {59: {}} + } + } + } + }, + 112: { + 59: {}, + 115: { + 116: { + 111: { + 59: {}, + 100: { + 111: { + 119: { + 110: {59: {}} + } + } + }, + 108: { + 101: { + 102: { + 116: {59: {}} + } + } + }, + 117: { + 112: {59: {}} + } + } + } + } + }, + 114: { + 107: { + 101: { + 114: {59: {}} + } + } + } + }, + 99: { + 111: { + 109: { + 109: { + 97: {59: {}} + } + } + }, + 121: {59: {}} + }, + 100: { + 97: { + 115: { + 104: {59: {}} + } + } + }, + 101: { + 97: { + 115: { + 117: { + 114: { + 101: { + 100: { + 97: { + 110: { + 103: { + 108: { + 101: {59: {}} + } + } + } + } + } + } + } + } + } + } + }, + 102: { + 114: {59: {}} + }, + 104: { + 111: {59: {}} + }, + 105: { + 99: { + 114: { + 111: {59: {}} + } + }, + 100: { + 59: {}, + 97: { + 115: { + 116: {59: {}} + } + }, + 99: { + 105: { + 114: {59: {}} + } + }, + 100: { + 111: { + 116: {59: {}} + } + } + }, + 110: { + 117: { + 115: { + 59: {}, + 98: {59: {}}, + 100: { + 59: {}, + 117: {59: {}} + } + } + } + } + }, + 108: { + 99: { + 112: {59: {}} + }, + 100: { + 114: {59: {}} + } + }, + 110: { + 112: { + 108: { + 117: { + 115: {59: {}} + } + } + } + }, + 111: { + 100: { + 101: { + 108: { + 115: {59: {}} + } + } + }, + 112: { + 102: {59: {}} + } + }, + 112: {59: {}}, + 115: { + 99: { + 114: {59: {}} + }, + 116: { + 112: { + 111: { + 115: {59: {}} + } + } + } + }, + 117: { + 59: {}, + 108: { + 116: { + 105: { + 109: { + 97: { + 112: {59: {}} + } + } + } + } + }, + 109: { + 97: { + 112: {59: {}} + } + } + } + }, + 110: { + 71: { + 103: {59: {}}, + 116: { + 59: {}, + 118: {59: {}} + } + }, + 76: { + 101: { + 102: { + 116: { + 97: { + 114: { + 114: { + 111: { + 119: {59: {}} + } + } + } + }, + 114: { + 105: { + 103: { + 104: { + 116: { + 97: { + 114: { + 114: { + 111: { + 119: {59: {}} + } + } + } + } + } + } + } + } + } + } + } + }, + 108: {59: {}}, + 116: { + 59: {}, + 118: {59: {}} + } + }, + 82: { + 105: { + 103: { + 104: { + 116: { + 97: { + 114: { + 114: { + 111: { + 119: {59: {}} + } + } + } + } + } + } + } + } + }, + 86: { + 68: { + 97: { + 115: { + 104: {59: {}} + } + } + }, + 100: { + 97: { + 115: { + 104: {59: {}} + } + } + } + }, + 97: { + 98: { + 108: { + 97: {59: {}} + } + }, + 99: { + 117: { + 116: { + 101: {59: {}} + } + } + }, + 110: { + 103: {59: {}} + }, + 112: { + 59: {}, + 69: {59: {}}, + 105: { + 100: {59: {}} + }, + 111: { + 115: {59: {}} + }, + 112: { + 114: { + 111: { + 120: {59: {}} + } + } + } + }, + 116: { + 117: { + 114: { + 59: {}, + 97: { + 108: { + 59: {}, + 115: {59: {}} + } + } + } + } + } + }, + 98: { + 115: { + 112: {59: {}} + }, + 117: { + 109: { + 112: { + 59: {}, + 101: {59: {}} + } + } + } + }, + 99: { + 97: { + 112: {59: {}}, + 114: { + 111: { + 110: {59: {}} + } + } + }, + 101: { + 100: { + 105: { + 108: {59: {}} + } + } + }, + 111: { + 110: { + 103: { + 59: {}, + 100: { + 111: { + 116: {59: {}} + } + } + } + } + }, + 117: { + 112: {59: {}} + }, + 121: {59: {}} + }, + 100: { + 97: { + 115: { + 104: {59: {}} + } + } + }, + 101: { + 59: {}, + 65: { + 114: { + 114: {59: {}} + } + }, + 97: { + 114: { + 104: { + 107: {59: {}} + }, + 114: { + 59: {}, + 111: { + 119: {59: {}} + } + } + } + }, + 100: { + 111: { + 116: {59: {}} + } + }, + 113: { + 117: { + 105: { + 118: {59: {}} + } + } + }, + 115: { + 101: { + 97: { + 114: {59: {}} + } + }, + 105: { + 109: {59: {}} + } + }, + 120: { + 105: { + 115: { + 116: { + 59: {}, + 115: {59: {}} + } + } + } + } + }, + 102: { + 114: {59: {}} + }, + 103: { + 69: {59: {}}, + 101: { + 59: {}, + 113: { + 59: {}, + 113: {59: {}}, + 115: { + 108: { + 97: { + 110: { + 116: {59: {}} + } + } + } + } + }, + 115: {59: {}} + }, + 115: { + 105: { + 109: {59: {}} + } + }, + 116: { + 59: {}, + 114: {59: {}} + } + }, + 104: { + 65: { + 114: { + 114: {59: {}} + } + }, + 97: { + 114: { + 114: {59: {}} + } + }, + 112: { + 97: { + 114: {59: {}} + } + } + }, + 105: { + 59: {}, + 115: { + 59: {}, + 100: {59: {}} + }, + 118: {59: {}} + }, + 106: { + 99: { + 121: {59: {}} + } + }, + 108: { + 65: { + 114: { + 114: {59: {}} + } + }, + 69: {59: {}}, + 97: { + 114: { + 114: {59: {}} + } + }, + 100: { + 114: {59: {}} + }, + 101: { + 59: {}, + 102: { + 116: { + 97: { + 114: { + 114: { + 111: { + 119: {59: {}} + } + } + } + }, + 114: { + 105: { + 103: { + 104: { + 116: { + 97: { + 114: { + 114: { + 111: { + 119: {59: {}} + } + } + } + } + } + } + } + } + } + } + }, + 113: { + 59: {}, + 113: {59: {}}, + 115: { + 108: { + 97: { + 110: { + 116: {59: {}} + } + } + } + } + }, + 115: { + 59: {}, + 115: {59: {}} + } + }, + 115: { + 105: { + 109: {59: {}} + } + }, + 116: { + 59: {}, + 114: { + 105: { + 59: {}, + 101: {59: {}} + } + } + } + }, + 109: { + 105: { + 100: {59: {}} + } + }, + 111: { + 112: { + 102: {59: {}} + }, + 116: { + 59: {}, + 105: { + 110: { + 59: {}, + 69: {59: {}}, + 100: { + 111: { + 116: {59: {}} + } + }, + 118: { + 97: {59: {}}, + 98: {59: {}}, + 99: {59: {}} + } + } + }, + 110: { + 105: { + 59: {}, + 118: { + 97: {59: {}}, + 98: {59: {}}, + 99: {59: {}} + } + } + } + } + }, + 112: { + 97: { + 114: { + 59: {}, + 97: { + 108: { + 108: { + 101: { + 108: {59: {}} + } + } + } + }, + 115: { + 108: {59: {}} + }, + 116: {59: {}} + } + }, + 111: { + 108: { + 105: { + 110: { + 116: {59: {}} + } + } + } + }, + 114: { + 59: {}, + 99: { + 117: { + 101: {59: {}} + } + }, + 101: { + 59: {}, + 99: { + 59: {}, + 101: { + 113: {59: {}} + } + } + } + } + }, + 114: { + 65: { + 114: { + 114: {59: {}} + } + }, + 97: { + 114: { + 114: { + 59: {}, + 99: {59: {}}, + 119: {59: {}} + } + } + }, + 105: { + 103: { + 104: { + 116: { + 97: { + 114: { + 114: { + 111: { + 119: {59: {}} + } + } + } + } + } + } + } + }, + 116: { + 114: { + 105: { + 59: {}, + 101: {59: {}} + } + } + } + }, + 115: { + 99: { + 59: {}, + 99: { + 117: { + 101: {59: {}} + } + }, + 101: {59: {}}, + 114: {59: {}} + }, + 104: { + 111: { + 114: { + 116: { + 109: { + 105: { + 100: {59: {}} + } + }, + 112: { + 97: { + 114: { + 97: { + 108: { + 108: { + 101: { + 108: {59: {}} + } + } + } + } + } + } + } + } + } + } + }, + 105: { + 109: { + 59: {}, + 101: { + 59: {}, + 113: {59: {}} + } + } + }, + 109: { + 105: { + 100: {59: {}} + } + }, + 112: { + 97: { + 114: {59: {}} + } + }, + 113: { + 115: { + 117: { + 98: { + 101: {59: {}} + }, + 112: { + 101: {59: {}} + } + } + } + }, + 117: { + 98: { + 59: {}, + 69: {59: {}}, + 101: {59: {}}, + 115: { + 101: { + 116: { + 59: {}, + 101: { + 113: { + 59: {}, + 113: {59: {}} + } + } + } + } + } + }, + 99: { + 99: { + 59: {}, + 101: { + 113: {59: {}} + } + } + }, + 112: { + 59: {}, + 69: {59: {}}, + 101: {59: {}}, + 115: { + 101: { + 116: { + 59: {}, + 101: { + 113: { + 59: {}, + 113: {59: {}} + } + } + } + } + } + } + } + }, + 116: { + 103: { + 108: {59: {}} + }, + 105: { + 108: { + 100: { + 101: {59: {}} + } + } + }, + 108: { + 103: {59: {}} + }, + 114: { + 105: { + 97: { + 110: { + 103: { + 108: { + 101: { + 108: { + 101: { + 102: { + 116: { + 59: {}, + 101: { + 113: {59: {}} + } + } + } + } + }, + 114: { + 105: { + 103: { + 104: { + 116: { + 59: {}, + 101: { + 113: {59: {}} + } + } + } + } + } + } + } + } + } + } + } + } + } + }, + 117: { + 59: {}, + 109: { + 59: {}, + 101: { + 114: { + 111: {59: {}} + } + }, + 115: { + 112: {59: {}} + } + } + }, + 118: { + 68: { + 97: { + 115: { + 104: {59: {}} + } + } + }, + 72: { + 97: { + 114: { + 114: {59: {}} + } + } + }, + 97: { + 112: {59: {}} + }, + 100: { + 97: { + 115: { + 104: {59: {}} + } + } + }, + 103: { + 101: {59: {}}, + 116: {59: {}} + }, + 105: { + 110: { + 102: { + 105: { + 110: {59: {}} + } + } + } + }, + 108: { + 65: { + 114: { + 114: {59: {}} + } + }, + 101: {59: {}}, + 116: { + 59: {}, + 114: { + 105: { + 101: {59: {}} + } + } + } + }, + 114: { + 65: { + 114: { + 114: {59: {}} + } + }, + 116: { + 114: { + 105: { + 101: {59: {}} + } + } + } + }, + 115: { + 105: { + 109: {59: {}} + } + } + }, + 119: { + 65: { + 114: { + 114: {59: {}} + } + }, + 97: { + 114: { + 104: { + 107: {59: {}} + }, + 114: { + 59: {}, + 111: { + 119: {59: {}} + } + } + } + }, + 110: { + 101: { + 97: { + 114: {59: {}} + } + } + } + } + }, + 111: { + 83: {59: {}}, + 97: { + 99: { + 117: { + 116: { + 101: {59: {}} + } + } + }, + 115: { + 116: {59: {}} + } + }, + 99: { + 105: { + 114: { + 59: {}, + 99: {59: {}} + } + }, + 121: {59: {}} + }, + 100: { + 97: { + 115: { + 104: {59: {}} + } + }, + 98: { + 108: { + 97: { + 99: {59: {}} + } + } + }, + 105: { + 118: {59: {}} + }, + 111: { + 116: {59: {}} + }, + 115: { + 111: { + 108: { + 100: {59: {}} + } + } + } + }, + 101: { + 108: { + 105: { + 103: {59: {}} + } + } + }, + 102: { + 99: { + 105: { + 114: {59: {}} + } + }, + 114: {59: {}} + }, + 103: { + 111: { + 110: {59: {}} + }, + 114: { + 97: { + 118: { + 101: {59: {}} + } + } + }, + 116: {59: {}} + }, + 104: { + 98: { + 97: { + 114: {59: {}} + } + }, + 109: {59: {}} + }, + 105: { + 110: { + 116: {59: {}} + } + }, + 108: { + 97: { + 114: { + 114: {59: {}} + } + }, + 99: { + 105: { + 114: {59: {}} + }, + 114: { + 111: { + 115: { + 115: {59: {}} + } + } + } + }, + 105: { + 110: { + 101: {59: {}} + } + }, + 116: {59: {}} + }, + 109: { + 97: { + 99: { + 114: {59: {}} + } + }, + 101: { + 103: { + 97: {59: {}} + } + }, + 105: { + 99: { + 114: { + 111: { + 110: {59: {}} + } + } + }, + 100: {59: {}}, + 110: { + 117: { + 115: {59: {}} + } + } + } + }, + 111: { + 112: { + 102: {59: {}} + } + }, + 112: { + 97: { + 114: {59: {}} + }, + 101: { + 114: { + 112: {59: {}} + } + }, + 108: { + 117: { + 115: {59: {}} + } + } + }, + 114: { + 59: {}, + 97: { + 114: { + 114: {59: {}} + } + }, + 100: { + 59: {}, + 101: { + 114: { + 59: {}, + 111: { + 102: {59: {}} + } + } + }, + 102: {59: {}}, + 109: {59: {}} + }, + 105: { + 103: { + 111: { + 102: {59: {}} + } + } + }, + 111: { + 114: {59: {}} + }, + 115: { + 108: { + 111: { + 112: { + 101: {59: {}} + } + } + } + }, + 118: {59: {}} + }, + 115: { + 99: { + 114: {59: {}} + }, + 108: { + 97: { + 115: { + 104: {59: {}} + } + } + }, + 111: { + 108: {59: {}} + } + }, + 116: { + 105: { + 108: { + 100: { + 101: {59: {}} + } + }, + 109: { + 101: { + 115: { + 59: {}, + 97: { + 115: {59: {}} + } + } + } + } + } + }, + 117: { + 109: { + 108: {59: {}} + } + }, + 118: { + 98: { + 97: { + 114: {59: {}} + } + } + } + }, + 112: { + 97: { + 114: { + 59: {}, + 97: { + 59: {}, + 108: { + 108: { + 101: { + 108: {59: {}} + } + } + } + }, + 115: { + 105: { + 109: {59: {}} + }, + 108: {59: {}} + }, + 116: {59: {}} + } + }, + 99: { + 121: {59: {}} + }, + 101: { + 114: { + 99: { + 110: { + 116: {59: {}} + } + }, + 105: { + 111: { + 100: {59: {}} + } + }, + 109: { + 105: { + 108: {59: {}} + } + }, + 112: {59: {}}, + 116: { + 101: { + 110: { + 107: {59: {}} + } + } + } + } + }, + 102: { + 114: {59: {}} + }, + 104: { + 105: { + 59: {}, + 118: {59: {}} + }, + 109: { + 109: { + 97: { + 116: {59: {}} + } + } + }, + 111: { + 110: { + 101: {59: {}} + } + } + }, + 105: { + 59: {}, + 116: { + 99: { + 104: { + 102: { + 111: { + 114: { + 107: {59: {}} + } + } + } + } + } + }, + 118: {59: {}} + }, + 108: { + 97: { + 110: { + 99: { + 107: { + 59: {}, + 104: {59: {}} + } + }, + 107: { + 118: {59: {}} + } + } + }, + 117: { + 115: { + 59: {}, + 97: { + 99: { + 105: { + 114: {59: {}} + } + } + }, + 98: {59: {}}, + 99: { + 105: { + 114: {59: {}} + } + }, + 100: { + 111: {59: {}}, + 117: {59: {}} + }, + 101: {59: {}}, + 109: { + 110: {59: {}} + }, + 115: { + 105: { + 109: {59: {}} + } + }, + 116: { + 119: { + 111: {59: {}} + } + } + } + } + }, + 109: {59: {}}, + 111: { + 105: { + 110: { + 116: { + 105: { + 110: { + 116: {59: {}} + } + } + } + } + }, + 112: { + 102: {59: {}} + }, + 117: { + 110: { + 100: {59: {}} + } + } + }, + 114: { + 59: {}, + 69: {59: {}}, + 97: { + 112: {59: {}} + }, + 99: { + 117: { + 101: {59: {}} + } + }, + 101: { + 59: {}, + 99: { + 59: {}, + 97: { + 112: { + 112: { + 114: { + 111: { + 120: {59: {}} + } + } + } + } + }, + 99: { + 117: { + 114: { + 108: { + 121: { + 101: { + 113: {59: {}} + } + } + } + } + } + }, + 101: { + 113: {59: {}} + }, + 110: { + 97: { + 112: { + 112: { + 114: { + 111: { + 120: {59: {}} + } + } + } + } + }, + 101: { + 113: { + 113: {59: {}} + } + }, + 115: { + 105: { + 109: {59: {}} + } + } + }, + 115: { + 105: { + 109: {59: {}} + } + } + } + }, + 105: { + 109: { + 101: { + 59: {}, + 115: {59: {}} + } + } + }, + 110: { + 69: {59: {}}, + 97: { + 112: {59: {}} + }, + 115: { + 105: { + 109: {59: {}} + } + } + }, + 111: { + 100: {59: {}}, + 102: { + 97: { + 108: { + 97: { + 114: {59: {}} + } + } + }, + 108: { + 105: { + 110: { + 101: {59: {}} + } + } + }, + 115: { + 117: { + 114: { + 102: {59: {}} + } + } + } + }, + 112: { + 59: {}, + 116: { + 111: {59: {}} + } + } + }, + 115: { + 105: { + 109: {59: {}} + } + }, + 117: { + 114: { + 101: { + 108: {59: {}} + } + } + } + }, + 115: { + 99: { + 114: {59: {}} + }, + 105: {59: {}} + }, + 117: { + 110: { + 99: { + 115: { + 112: {59: {}} + } + } + } + } + }, + 113: { + 102: { + 114: {59: {}} + }, + 105: { + 110: { + 116: {59: {}} + } + }, + 111: { + 112: { + 102: {59: {}} + } + }, + 112: { + 114: { + 105: { + 109: { + 101: {59: {}} + } + } + } + }, + 115: { + 99: { + 114: {59: {}} + } + }, + 117: { + 97: { + 116: { + 101: { + 114: { + 110: { + 105: { + 111: { + 110: { + 115: {59: {}} + } + } + } + } + } + }, + 105: { + 110: { + 116: {59: {}} + } + } + } + }, + 101: { + 115: { + 116: { + 59: {}, + 101: { + 113: {59: {}} + } + } + } + }, + 111: { + 116: {59: {}} + } + } + }, + 114: { + 65: { + 97: { + 114: { + 114: {59: {}} + } + }, + 114: { + 114: {59: {}} + }, + 116: { + 97: { + 105: { + 108: {59: {}} + } + } + } + }, + 66: { + 97: { + 114: { + 114: {59: {}} + } + } + }, + 72: { + 97: { + 114: {59: {}} + } + }, + 97: { + 99: { + 101: {59: {}}, + 117: { + 116: { + 101: {59: {}} + } + } + }, + 100: { + 105: { + 99: {59: {}} + } + }, + 101: { + 109: { + 112: { + 116: { + 121: { + 118: {59: {}} + } + } + } + } + }, + 110: { + 103: { + 59: {}, + 100: {59: {}}, + 101: {59: {}}, + 108: { + 101: {59: {}} + } + } + }, + 113: { + 117: { + 111: {59: {}} + } + }, + 114: { + 114: { + 59: {}, + 97: { + 112: {59: {}} + }, + 98: { + 59: {}, + 102: { + 115: {59: {}} + } + }, + 99: {59: {}}, + 102: { + 115: {59: {}} + }, + 104: { + 107: {59: {}} + }, + 108: { + 112: {59: {}} + }, + 112: { + 108: {59: {}} + }, + 115: { + 105: { + 109: {59: {}} + } + }, + 116: { + 108: {59: {}} + }, + 119: {59: {}} + } + }, + 116: { + 97: { + 105: { + 108: {59: {}} + } + }, + 105: { + 111: { + 59: {}, + 110: { + 97: { + 108: { + 115: {59: {}} + } + } + } + } + } + } + }, + 98: { + 97: { + 114: { + 114: {59: {}} + } + }, + 98: { + 114: { + 107: {59: {}} + } + }, + 114: { + 97: { + 99: { + 101: {59: {}}, + 107: {59: {}} + } + }, + 107: { + 101: {59: {}}, + 115: { + 108: { + 100: {59: {}}, + 117: {59: {}} + } + } + } + } + }, + 99: { + 97: { + 114: { + 111: { + 110: {59: {}} + } + } + }, + 101: { + 100: { + 105: { + 108: {59: {}} + } + }, + 105: { + 108: {59: {}} + } + }, + 117: { + 98: {59: {}} + }, + 121: {59: {}} + }, + 100: { + 99: { + 97: {59: {}} + }, + 108: { + 100: { + 104: { + 97: { + 114: {59: {}} + } + } + } + }, + 113: { + 117: { + 111: { + 59: {}, + 114: {59: {}} + } + } + }, + 115: { + 104: {59: {}} + } + }, + 101: { + 97: { + 108: { + 59: {}, + 105: { + 110: { + 101: {59: {}} + } + }, + 112: { + 97: { + 114: { + 116: {59: {}} + } + } + }, + 115: {59: {}} + } + }, + 99: { + 116: {59: {}} + }, + 103: {59: {}} + }, + 102: { + 105: { + 115: { + 104: { + 116: {59: {}} + } + } + }, + 108: { + 111: { + 111: { + 114: {59: {}} + } + } + }, + 114: {59: {}} + }, + 104: { + 97: { + 114: { + 100: {59: {}}, + 117: { + 59: {}, + 108: {59: {}} + } + } + }, + 111: { + 59: {}, + 118: {59: {}} + } + }, + 105: { + 103: { + 104: { + 116: { + 97: { + 114: { + 114: { + 111: { + 119: { + 59: {}, + 116: { + 97: { + 105: { + 108: {59: {}} + } + } + } + } + } + } + } + }, + 104: { + 97: { + 114: { + 112: { + 111: { + 111: { + 110: { + 100: { + 111: { + 119: { + 110: {59: {}} + } + } + }, + 117: { + 112: {59: {}} + } + } + } + } + } + } + } + }, + 108: { + 101: { + 102: { + 116: { + 97: { + 114: { + 114: { + 111: { + 119: { + 115: {59: {}} + } + } + } + } + }, + 104: { + 97: { + 114: { + 112: { + 111: { + 111: { + 110: { + 115: {59: {}} + } + } + } + } + } + } + } + } + } + } + }, + 114: { + 105: { + 103: { + 104: { + 116: { + 97: { + 114: { + 114: { + 111: { + 119: { + 115: {59: {}} + } + } + } + } + } + } + } + } + } + }, + 115: { + 113: { + 117: { + 105: { + 103: { + 97: { + 114: { + 114: { + 111: { + 119: {59: {}} + } + } + } + } + } + } + } + } + }, + 116: { + 104: { + 114: { + 101: { + 101: { + 116: { + 105: { + 109: { + 101: { + 115: {59: {}} + } + } + } + } + } + } + } + } + } + } + } + }, + 110: { + 103: {59: {}} + }, + 115: { + 105: { + 110: { + 103: { + 100: { + 111: { + 116: { + 115: { + 101: { + 113: {59: {}} + } + } + } + } + } + } + } + } + } + }, + 108: { + 97: { + 114: { + 114: {59: {}} + } + }, + 104: { + 97: { + 114: {59: {}} + } + }, + 109: {59: {}} + }, + 109: { + 111: { + 117: { + 115: { + 116: { + 59: {}, + 97: { + 99: { + 104: { + 101: {59: {}} + } + } + } + } + } + } + } + }, + 110: { + 109: { + 105: { + 100: {59: {}} + } + } + }, + 111: { + 97: { + 110: { + 103: {59: {}} + }, + 114: { + 114: {59: {}} + } + }, + 98: { + 114: { + 107: {59: {}} + } + }, + 112: { + 97: { + 114: {59: {}} + }, + 102: {59: {}}, + 108: { + 117: { + 115: {59: {}} + } + } + }, + 116: { + 105: { + 109: { + 101: { + 115: {59: {}} + } + } + } + } + }, + 112: { + 97: { + 114: { + 59: {}, + 103: { + 116: {59: {}} + } + } + }, + 112: { + 111: { + 108: { + 105: { + 110: { + 116: {59: {}} + } + } + } + } + } + }, + 114: { + 97: { + 114: { + 114: {59: {}} + } + } + }, + 115: { + 97: { + 113: { + 117: { + 111: {59: {}} + } + } + }, + 99: { + 114: {59: {}} + }, + 104: {59: {}}, + 113: { + 98: {59: {}}, + 117: { + 111: { + 59: {}, + 114: {59: {}} + } + } + } + }, + 116: { + 104: { + 114: { + 101: { + 101: {59: {}} + } + } + }, + 105: { + 109: { + 101: { + 115: {59: {}} + } + } + }, + 114: { + 105: { + 59: {}, + 101: {59: {}}, + 102: {59: {}}, + 108: { + 116: { + 114: { + 105: {59: {}} + } + } + } + } + } + }, + 117: { + 108: { + 117: { + 104: { + 97: { + 114: {59: {}} + } + } + } + } + }, + 120: {59: {}} + }, + 115: { + 97: { + 99: { + 117: { + 116: { + 101: {59: {}} + } + } + } + }, + 98: { + 113: { + 117: { + 111: {59: {}} + } + } + }, + 99: { + 59: {}, + 69: {59: {}}, + 97: { + 112: {59: {}}, + 114: { + 111: { + 110: {59: {}} + } + } + }, + 99: { + 117: { + 101: {59: {}} + } + }, + 101: { + 59: {}, + 100: { + 105: { + 108: {59: {}} + } + } + }, + 105: { + 114: { + 99: {59: {}} + } + }, + 110: { + 69: {59: {}}, + 97: { + 112: {59: {}} + }, + 115: { + 105: { + 109: {59: {}} + } + } + }, + 112: { + 111: { + 108: { + 105: { + 110: { + 116: {59: {}} + } + } + } + } + }, + 115: { + 105: { + 109: {59: {}} + } + }, + 121: {59: {}} + }, + 100: { + 111: { + 116: { + 59: {}, + 98: {59: {}}, + 101: {59: {}} + } + } + }, + 101: { + 65: { + 114: { + 114: {59: {}} + } + }, + 97: { + 114: { + 104: { + 107: {59: {}} + }, + 114: { + 59: {}, + 111: { + 119: {59: {}} + } + } + } + }, + 99: { + 116: {59: {}} + }, + 109: { + 105: {59: {}} + }, + 115: { + 119: { + 97: { + 114: {59: {}} + } + } + }, + 116: { + 109: { + 105: { + 110: { + 117: { + 115: {59: {}} + } + } + }, + 110: {59: {}} + } + }, + 120: { + 116: {59: {}} + } + }, + 102: { + 114: { + 59: {}, + 111: { + 119: { + 110: {59: {}} + } + } + } + }, + 104: { + 97: { + 114: { + 112: {59: {}} + } + }, + 99: { + 104: { + 99: { + 121: {59: {}} + } + }, + 121: {59: {}} + }, + 111: { + 114: { + 116: { + 109: { + 105: { + 100: {59: {}} + } + }, + 112: { + 97: { + 114: { + 97: { + 108: { + 108: { + 101: { + 108: {59: {}} + } + } + } + } + } + } + } + } + } + }, + 121: {59: {}} + }, + 105: { + 103: { + 109: { + 97: { + 59: {}, + 102: {59: {}}, + 118: {59: {}} + } + } + }, + 109: { + 59: {}, + 100: { + 111: { + 116: {59: {}} + } + }, + 101: { + 59: {}, + 113: {59: {}} + }, + 103: { + 59: {}, + 69: {59: {}} + }, + 108: { + 59: {}, + 69: {59: {}} + }, + 110: { + 101: {59: {}} + }, + 112: { + 108: { + 117: { + 115: {59: {}} + } + } + }, + 114: { + 97: { + 114: { + 114: {59: {}} + } + } + } + } + }, + 108: { + 97: { + 114: { + 114: {59: {}} + } + } + }, + 109: { + 97: { + 108: { + 108: { + 115: { + 101: { + 116: { + 109: { + 105: { + 110: { + 117: { + 115: {59: {}} + } + } + } + } + } + } + } + } + }, + 115: { + 104: { + 112: {59: {}} + } + } + }, + 101: { + 112: { + 97: { + 114: { + 115: { + 108: {59: {}} + } + } + } + } + }, + 105: { + 100: {59: {}}, + 108: { + 101: {59: {}} + } + }, + 116: { + 59: {}, + 101: { + 59: {}, + 115: {59: {}} + } + } + }, + 111: { + 102: { + 116: { + 99: { + 121: {59: {}} + } + } + }, + 108: { + 59: {}, + 98: { + 59: {}, + 97: { + 114: {59: {}} + } + } + }, + 112: { + 102: {59: {}} + } + }, + 112: { + 97: { + 100: { + 101: { + 115: { + 59: {}, + 117: { + 105: { + 116: {59: {}} + } + } + } + } + }, + 114: {59: {}} + } + }, + 113: { + 99: { + 97: { + 112: { + 59: {}, + 115: {59: {}} + } + }, + 117: { + 112: { + 59: {}, + 115: {59: {}} + } + } + }, + 115: { + 117: { + 98: { + 59: {}, + 101: {59: {}}, + 115: { + 101: { + 116: { + 59: {}, + 101: { + 113: {59: {}} + } + } + } + } + }, + 112: { + 59: {}, + 101: {59: {}}, + 115: { + 101: { + 116: { + 59: {}, + 101: { + 113: {59: {}} + } + } + } + } + } + } + }, + 117: { + 59: {}, + 97: { + 114: { + 101: {59: {}}, + 102: {59: {}} + } + }, + 102: {59: {}} + } + }, + 114: { + 97: { + 114: { + 114: {59: {}} + } + } + }, + 115: { + 99: { + 114: {59: {}} + }, + 101: { + 116: { + 109: { + 110: {59: {}} + } + } + }, + 109: { + 105: { + 108: { + 101: {59: {}} + } + } + }, + 116: { + 97: { + 114: { + 102: {59: {}} + } + } + } + }, + 116: { + 97: { + 114: { + 59: {}, + 102: {59: {}} + } + }, + 114: { + 97: { + 105: { + 103: { + 104: { + 116: { + 101: { + 112: { + 115: { + 105: { + 108: { + 111: { + 110: {59: {}} + } + } + } + } + } + }, + 112: { + 104: { + 105: {59: {}} + } + } + } + } + } + } + }, + 110: { + 115: {59: {}} + } + } + }, + 117: { + 98: { + 59: {}, + 69: {59: {}}, + 100: { + 111: { + 116: {59: {}} + } + }, + 101: { + 59: {}, + 100: { + 111: { + 116: {59: {}} + } + } + }, + 109: { + 117: { + 108: { + 116: {59: {}} + } + } + }, + 110: { + 69: {59: {}}, + 101: {59: {}} + }, + 112: { + 108: { + 117: { + 115: {59: {}} + } + } + }, + 114: { + 97: { + 114: { + 114: {59: {}} + } + } + }, + 115: { + 101: { + 116: { + 59: {}, + 101: { + 113: { + 59: {}, + 113: {59: {}} + } + }, + 110: { + 101: { + 113: { + 59: {}, + 113: {59: {}} + } + } + } + } + }, + 105: { + 109: {59: {}} + }, + 117: { + 98: {59: {}}, + 112: {59: {}} + } + } + }, + 99: { + 99: { + 59: {}, + 97: { + 112: { + 112: { + 114: { + 111: { + 120: {59: {}} + } + } + } + } + }, + 99: { + 117: { + 114: { + 108: { + 121: { + 101: { + 113: {59: {}} + } + } + } + } + } + }, + 101: { + 113: {59: {}} + }, + 110: { + 97: { + 112: { + 112: { + 114: { + 111: { + 120: {59: {}} + } + } + } + } + }, + 101: { + 113: { + 113: {59: {}} + } + }, + 115: { + 105: { + 109: {59: {}} + } + } + }, + 115: { + 105: { + 109: {59: {}} + } + } + } + }, + 109: {59: {}}, + 110: { + 103: {59: {}} + }, + 112: { + 49: {59: {}}, + 50: {59: {}}, + 51: {59: {}}, + 59: {}, + 69: {59: {}}, + 100: { + 111: { + 116: {59: {}} + }, + 115: { + 117: { + 98: {59: {}} + } + } + }, + 101: { + 59: {}, + 100: { + 111: { + 116: {59: {}} + } + } + }, + 104: { + 115: { + 111: { + 108: {59: {}} + }, + 117: { + 98: {59: {}} + } + } + }, + 108: { + 97: { + 114: { + 114: {59: {}} + } + } + }, + 109: { + 117: { + 108: { + 116: {59: {}} + } + } + }, + 110: { + 69: {59: {}}, + 101: {59: {}} + }, + 112: { + 108: { + 117: { + 115: {59: {}} + } + } + }, + 115: { + 101: { + 116: { + 59: {}, + 101: { + 113: { + 59: {}, + 113: {59: {}} + } + }, + 110: { + 101: { + 113: { + 59: {}, + 113: {59: {}} + } + } + } + } + }, + 105: { + 109: {59: {}} + }, + 117: { + 98: {59: {}}, + 112: {59: {}} + } + } + } + }, + 119: { + 65: { + 114: { + 114: {59: {}} + } + }, + 97: { + 114: { + 104: { + 107: {59: {}} + }, + 114: { + 59: {}, + 111: { + 119: {59: {}} + } + } + } + }, + 110: { + 119: { + 97: { + 114: {59: {}} + } + } + } + }, + 122: { + 108: { + 105: { + 103: {59: {}} + } + } + } + }, + 116: { + 97: { + 114: { + 103: { + 101: { + 116: {59: {}} + } + } + }, + 117: {59: {}} + }, + 98: { + 114: { + 107: {59: {}} + } + }, + 99: { + 97: { + 114: { + 111: { + 110: {59: {}} + } + } + }, + 101: { + 100: { + 105: { + 108: {59: {}} + } + } + }, + 121: {59: {}} + }, + 100: { + 111: { + 116: {59: {}} + } + }, + 101: { + 108: { + 114: { + 101: { + 99: {59: {}} + } + } + } + }, + 102: { + 114: {59: {}} + }, + 104: { + 101: { + 114: { + 101: { + 52: {59: {}}, + 102: { + 111: { + 114: { + 101: {59: {}} + } + } + } + } + }, + 116: { + 97: { + 59: {}, + 115: { + 121: { + 109: {59: {}} + } + }, + 118: {59: {}} + } + } + }, + 105: { + 99: { + 107: { + 97: { + 112: { + 112: { + 114: { + 111: { + 120: {59: {}} + } + } + } + } + }, + 115: { + 105: { + 109: {59: {}} + } + } + } + }, + 110: { + 115: { + 112: {59: {}} + } + } + }, + 107: { + 97: { + 112: {59: {}} + }, + 115: { + 105: { + 109: {59: {}} + } + } + }, + 111: { + 114: { + 110: {59: {}} + } + } + }, + 105: { + 108: { + 100: { + 101: {59: {}} + } + }, + 109: { + 101: { + 115: { + 59: {}, + 98: { + 59: {}, + 97: { + 114: {59: {}} + } + }, + 100: {59: {}} + } + } + }, + 110: { + 116: {59: {}} + } + }, + 111: { + 101: { + 97: {59: {}} + }, + 112: { + 59: {}, + 98: { + 111: { + 116: {59: {}} + } + }, + 99: { + 105: { + 114: {59: {}} + } + }, + 102: { + 59: {}, + 111: { + 114: { + 107: {59: {}} + } + } + } + }, + 115: { + 97: {59: {}} + } + }, + 112: { + 114: { + 105: { + 109: { + 101: {59: {}} + } + } + } + }, + 114: { + 97: { + 100: { + 101: {59: {}} + } + }, + 105: { + 97: { + 110: { + 103: { + 108: { + 101: { + 59: {}, + 100: { + 111: { + 119: { + 110: {59: {}} + } + } + }, + 108: { + 101: { + 102: { + 116: { + 59: {}, + 101: { + 113: {59: {}} + } + } + } + } + }, + 113: {59: {}}, + 114: { + 105: { + 103: { + 104: { + 116: { + 59: {}, + 101: { + 113: {59: {}} + } + } + } + } + } + } + } + } + } + } + }, + 100: { + 111: { + 116: {59: {}} + } + }, + 101: {59: {}}, + 109: { + 105: { + 110: { + 117: { + 115: {59: {}} + } + } + } + }, + 112: { + 108: { + 117: { + 115: {59: {}} + } + } + }, + 115: { + 98: {59: {}} + }, + 116: { + 105: { + 109: { + 101: {59: {}} + } + } + } + }, + 112: { + 101: { + 122: { + 105: { + 117: { + 109: {59: {}} + } + } + } + } + } + }, + 115: { + 99: { + 114: {59: {}}, + 121: {59: {}} + }, + 104: { + 99: { + 121: {59: {}} + } + }, + 116: { + 114: { + 111: { + 107: {59: {}} + } + } + } + }, + 119: { + 105: { + 120: { + 116: {59: {}} + } + }, + 111: { + 104: { + 101: { + 97: { + 100: { + 108: { + 101: { + 102: { + 116: { + 97: { + 114: { + 114: { + 111: { + 119: {59: {}} + } + } + } + } + } + } + } + }, + 114: { + 105: { + 103: { + 104: { + 116: { + 97: { + 114: { + 114: { + 111: { + 119: {59: {}} + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + }, + 117: { + 65: { + 114: { + 114: {59: {}} + } + }, + 72: { + 97: { + 114: {59: {}} + } + }, + 97: { + 99: { + 117: { + 116: { + 101: {59: {}} + } + } + }, + 114: { + 114: {59: {}} + } + }, + 98: { + 114: { + 99: { + 121: {59: {}} + }, + 101: { + 118: { + 101: {59: {}} + } + } + } + }, + 99: { + 105: { + 114: { + 99: {59: {}} + } + }, + 121: {59: {}} + }, + 100: { + 97: { + 114: { + 114: {59: {}} + } + }, + 98: { + 108: { + 97: { + 99: {59: {}} + } + } + }, + 104: { + 97: { + 114: {59: {}} + } + } + }, + 102: { + 105: { + 115: { + 104: { + 116: {59: {}} + } + } + }, + 114: {59: {}} + }, + 103: { + 114: { + 97: { + 118: { + 101: {59: {}} + } + } + } + }, + 104: { + 97: { + 114: { + 108: {59: {}}, + 114: {59: {}} + } + }, + 98: { + 108: { + 107: {59: {}} + } + } + }, + 108: { + 99: { + 111: { + 114: { + 110: { + 59: {}, + 101: { + 114: {59: {}} + } + } + } + }, + 114: { + 111: { + 112: {59: {}} + } + } + }, + 116: { + 114: { + 105: {59: {}} + } + } + }, + 109: { + 97: { + 99: { + 114: {59: {}} + } + }, + 108: {59: {}} + }, + 111: { + 103: { + 111: { + 110: {59: {}} + } + }, + 112: { + 102: {59: {}} + } + }, + 112: { + 97: { + 114: { + 114: { + 111: { + 119: {59: {}} + } + } + } + }, + 100: { + 111: { + 119: { + 110: { + 97: { + 114: { + 114: { + 111: { + 119: {59: {}} + } + } + } + } + } + } + } + }, + 104: { + 97: { + 114: { + 112: { + 111: { + 111: { + 110: { + 108: { + 101: { + 102: { + 116: {59: {}} + } + } + }, + 114: { + 105: { + 103: { + 104: { + 116: {59: {}} + } + } + } + } + } + } + } + } + } + } + }, + 108: { + 117: { + 115: {59: {}} + } + }, + 115: { + 105: { + 59: {}, + 104: {59: {}}, + 108: { + 111: { + 110: {59: {}} + } + } + } + }, + 117: { + 112: { + 97: { + 114: { + 114: { + 111: { + 119: { + 115: {59: {}} + } + } + } + } + } + } + } + }, + 114: { + 99: { + 111: { + 114: { + 110: { + 59: {}, + 101: { + 114: {59: {}} + } + } + } + }, + 114: { + 111: { + 112: {59: {}} + } + } + }, + 105: { + 110: { + 103: {59: {}} + } + }, + 116: { + 114: { + 105: {59: {}} + } + } + }, + 115: { + 99: { + 114: {59: {}} + } + }, + 116: { + 100: { + 111: { + 116: {59: {}} + } + }, + 105: { + 108: { + 100: { + 101: {59: {}} + } + } + }, + 114: { + 105: { + 59: {}, + 102: {59: {}} + } + } + }, + 117: { + 97: { + 114: { + 114: {59: {}} + } + }, + 109: { + 108: {59: {}} + } + }, + 119: { + 97: { + 110: { + 103: { + 108: { + 101: {59: {}} + } + } + } + } + } + }, + 118: { + 65: { + 114: { + 114: {59: {}} + } + }, + 66: { + 97: { + 114: { + 59: {}, + 118: {59: {}} + } + } + }, + 68: { + 97: { + 115: { + 104: {59: {}} + } + } + }, + 97: { + 110: { + 103: { + 114: { + 116: {59: {}} + } + } + }, + 114: { + 101: { + 112: { + 115: { + 105: { + 108: { + 111: { + 110: {59: {}} + } + } + } + } + } + }, + 107: { + 97: { + 112: { + 112: { + 97: {59: {}} + } + } + } + }, + 110: { + 111: { + 116: { + 104: { + 105: { + 110: { + 103: {59: {}} + } + } + } + } + } + }, + 112: { + 104: { + 105: {59: {}} + }, + 105: {59: {}}, + 114: { + 111: { + 112: { + 116: { + 111: {59: {}} + } + } + } + } + }, + 114: { + 59: {}, + 104: { + 111: {59: {}} + } + }, + 115: { + 105: { + 103: { + 109: { + 97: {59: {}} + } + } + }, + 117: { + 98: { + 115: { + 101: { + 116: { + 110: { + 101: { + 113: { + 59: {}, + 113: {59: {}} + } + } + } + } + } + } + }, + 112: { + 115: { + 101: { + 116: { + 110: { + 101: { + 113: { + 59: {}, + 113: {59: {}} + } + } + } + } + } + } + } + } + }, + 116: { + 104: { + 101: { + 116: { + 97: {59: {}} + } + } + }, + 114: { + 105: { + 97: { + 110: { + 103: { + 108: { + 101: { + 108: { + 101: { + 102: { + 116: {59: {}} + } + } + }, + 114: { + 105: { + 103: { + 104: { + 116: {59: {}} + } + } + } + } + } + } + } + } + } + } + } + } + } + }, + 99: { + 121: {59: {}} + }, + 100: { + 97: { + 115: { + 104: {59: {}} + } + } + }, + 101: { + 101: { + 59: {}, + 98: { + 97: { + 114: {59: {}} + } + }, + 101: { + 113: {59: {}} + } + }, + 108: { + 108: { + 105: { + 112: {59: {}} + } + } + }, + 114: { + 98: { + 97: { + 114: {59: {}} + } + }, + 116: {59: {}} + } + }, + 102: { + 114: {59: {}} + }, + 108: { + 116: { + 114: { + 105: {59: {}} + } + } + }, + 110: { + 115: { + 117: { + 98: {59: {}}, + 112: {59: {}} + } + } + }, + 111: { + 112: { + 102: {59: {}} + } + }, + 112: { + 114: { + 111: { + 112: {59: {}} + } + } + }, + 114: { + 116: { + 114: { + 105: {59: {}} + } + } + }, + 115: { + 99: { + 114: {59: {}} + }, + 117: { + 98: { + 110: { + 69: {59: {}}, + 101: {59: {}} + } + }, + 112: { + 110: { + 69: {59: {}}, + 101: {59: {}} + } + } + } + }, + 122: { + 105: { + 103: { + 122: { + 97: { + 103: {59: {}} + } + } + } + } + } + }, + 119: { + 99: { + 105: { + 114: { + 99: {59: {}} + } + } + }, + 101: { + 100: { + 98: { + 97: { + 114: {59: {}} + } + }, + 103: { + 101: { + 59: {}, + 113: {59: {}} + } + } + }, + 105: { + 101: { + 114: { + 112: {59: {}} + } + } + } + }, + 102: { + 114: {59: {}} + }, + 111: { + 112: { + 102: {59: {}} + } + }, + 112: {59: {}}, + 114: { + 59: {}, + 101: { + 97: { + 116: { + 104: {59: {}} + } + } + } + }, + 115: { + 99: { + 114: {59: {}} + } + } + }, + 120: { + 99: { + 97: { + 112: {59: {}} + }, + 105: { + 114: { + 99: {59: {}} + } + }, + 117: { + 112: {59: {}} + } + }, + 100: { + 116: { + 114: { + 105: {59: {}} + } + } + }, + 102: { + 114: {59: {}} + }, + 104: { + 65: { + 114: { + 114: {59: {}} + } + }, + 97: { + 114: { + 114: {59: {}} + } + } + }, + 105: {59: {}}, + 108: { + 65: { + 114: { + 114: {59: {}} + } + }, + 97: { + 114: { + 114: {59: {}} + } + } + }, + 109: { + 97: { + 112: {59: {}} + } + }, + 110: { + 105: { + 115: {59: {}} + } + }, + 111: { + 100: { + 111: { + 116: {59: {}} + } + }, + 112: { + 102: {59: {}}, + 108: { + 117: { + 115: {59: {}} + } + } + }, + 116: { + 105: { + 109: { + 101: {59: {}} + } + } + } + }, + 114: { + 65: { + 114: { + 114: {59: {}} + } + }, + 97: { + 114: { + 114: {59: {}} + } + } + }, + 115: { + 99: { + 114: {59: {}} + }, + 113: { + 99: { + 117: { + 112: {59: {}} + } + } + } + }, + 117: { + 112: { + 108: { + 117: { + 115: {59: {}} + } + } + }, + 116: { + 114: { + 105: {59: {}} + } + } + }, + 118: { + 101: { + 101: {59: {}} + } + }, + 119: { + 101: { + 100: { + 103: { + 101: {59: {}} + } + } + } + } + }, + 121: { + 97: { + 99: { + 117: { + 116: { + 101: {59: {}} + } + }, + 121: {59: {}} + } + }, + 99: { + 105: { + 114: { + 99: {59: {}} + } + }, + 121: {59: {}} + }, + 101: { + 110: {59: {}} + }, + 102: { + 114: {59: {}} + }, + 105: { + 99: { + 121: {59: {}} + } + }, + 111: { + 112: { + 102: {59: {}} + } + }, + 115: { + 99: { + 114: {59: {}} + } + }, + 117: { + 99: { + 121: {59: {}} + }, + 109: { + 108: {59: {}} + } + } + }, + 122: { + 97: { + 99: { + 117: { + 116: { + 101: {59: {}} + } + } + } + }, + 99: { + 97: { + 114: { + 111: { + 110: {59: {}} + } + } + }, + 121: {59: {}} + }, + 100: { + 111: { + 116: {59: {}} + } + }, + 101: { + 101: { + 116: { + 114: { + 102: {59: {}} + } + } + }, + 116: { + 97: {59: {}} + } + }, + 102: { + 114: {59: {}} + }, + 104: { + 99: { + 121: {59: {}} + } + }, + 105: { + 103: { + 114: { + 97: { + 114: { + 114: {59: {}} + } + } + } + } + }, + 111: { + 112: { + 102: {59: {}} + } + }, + 115: { + 99: { + 114: {59: {}} + } + }, + 119: { + 106: {59: {}}, + 110: { + 106: {59: {}} + } + } + } +}; diff --git a/pkgs/html/pubspec.yaml b/pkgs/html/pubspec.yaml index cdc502f0a..75327df30 100644 --- a/pkgs/html/pubspec.yaml +++ b/pkgs/html/pubspec.yaml @@ -1,5 +1,5 @@ name: html -version: 0.15.6-wip +version: 0.15.6 description: APIs for parsing and manipulating HTML content outside the browser. repository: https://github.com/dart-lang/tools/tree/main/pkgs/html issue_tracker: https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Ahtml @@ -17,5 +17,6 @@ dependencies: dev_dependencies: dart_flutter_team_lints: ^3.0.0 + dart_style: ^2.3.6 path: ^1.8.0 test: ^1.16.6 diff --git a/pkgs/html/test/parser_feature_test.dart b/pkgs/html/test/parser_feature_test.dart index df9172ee7..1faaeea98 100644 --- a/pkgs/html/test/parser_feature_test.dart +++ b/pkgs/html/test/parser_feature_test.dart @@ -77,9 +77,6 @@ On line 4, column 3 of ParseError: Unexpected DOCTYPE. Ignored. expect(parser.errors.length, 1); final error = parser.errors[0]; expect(error.errorCode, 'unexpected-doctype'); - expect(error.span!.start.line, 3); - // Note: error position is at the end, not the beginning - expect(error.span!.start.column, 17); }); test('text spans should have the correct length', () { @@ -266,14 +263,8 @@ On line 4, column 3 of ParseError: Unexpected DOCTYPE. Ignored. expect(parser.errors[0].errorCode, 'expected-doctype-but-got-chars'); expect(parser.errors[0].message, 'Unexpected non-space characters. Expected DOCTYPE.'); - expect( - parser.errors[0].toString(), - 'ParserError on line 1, column 4: Unexpected non-space characters. ' - 'Expected DOCTYPE.\n' - ' ╷\n' - '1 │ foo\n' - ' │ ^\n' - ' ╵'); + expect(parser.errors[0].toString(), + 'Unexpected non-space characters. Expected DOCTYPE.'); }); test('Element.text', () { diff --git a/pkgs/html/test/trie_test.dart b/pkgs/html/test/trie_test.dart new file mode 100644 index 000000000..e23ae376c --- /dev/null +++ b/pkgs/html/test/trie_test.dart @@ -0,0 +1,49 @@ +import 'package:html/src/constants.dart'; +import 'package:html/src/trie.dart'; +import 'package:test/test.dart'; + +/// Some tests to ensure the [entities] and [entitiesTrieRoot] are in sync. +void main() { + test('All entities are in trie', () { + for (final entity in entities.keys) { + Map? node = entitiesTrieRoot; + for (final codeUnit in entity.codeUnits) { + node = node?[codeUnit] as Map?; + } + expect(node, isNotNull, reason: 'trie should contain $entity'); + } + }); + + test('Trie does not contain non-entity paths', () { + Map deepCopyMapOfMaps(Map src) { + return { + for (final pair in src.entries) + pair.key: deepCopyMapOfMaps(pair.value as Map) + }; + } + + final root = deepCopyMapOfMaps(entitiesTrieRoot); + // Iterate from longest to shortest to clean up trie as we go + outer: + for (final entityString in entities.keys.toList() + ..sort((a, b) => b.length.compareTo(a.length))) { + final codeUnits = entityString.codeUnits; + var node = root; + final path = [root]; + for (final codeUnit in codeUnits) { + final newNode = node[codeUnit] as Map?; + if (newNode == null) { + // This path was already cleaned up, this entityString must be a prefix of a longer one. + continue outer; + } + path.add(node = newNode); + } + for (var i = codeUnits.length - 1; i >= 0; i--) { + if (path[i + 1].isEmpty) { + path[i].remove(codeUnits[i]); + } + } + } + expect(root, isEmpty, reason: 'trie root contains some dead paths'); + }); +} diff --git a/pkgs/html/tool/generate_trie.dart b/pkgs/html/tool/generate_trie.dart new file mode 100644 index 000000000..e7fc99619 --- /dev/null +++ b/pkgs/html/tool/generate_trie.dart @@ -0,0 +1,24 @@ +import 'dart:io'; + +import 'package:dart_style/dart_style.dart'; +import 'package:html/src/constants.dart'; +import 'package:path/path.dart'; + +/// Run this file to generate package:html/src/trie.dart +void main() { + final root = {}; + for (final entity in entities.keys) { + var node = root; + for (final charCode in entity.codeUnits) { + node = (node[charCode] ??= {}) as Map; + } + } + final source = + '''// AUTO GENERATED by 'tool/generate_trie.dart'. DO NOT EDIT!\n''' + 'const entitiesTrieRoot = $root;' + .replaceAll('{}', '{}'); + final formatted = DartFormatter().format(source); + final htmlDir = File(Platform.script.path).parent.parent; + File(join(htmlDir.path, 'lib', 'src', 'trie.dart')) + .writeAsStringSync(formatted); +} From d083ea030913c0eb7157b7de349cef6d4951c2e7 Mon Sep 17 00:00:00 2001 From: Viral Verma <102751195+vvs-personalstash@users.noreply.github.com> Date: Tue, 29 Apr 2025 11:31:09 +0530 Subject: [PATCH 09/67] Add --fail-under flag for minimum coverage threshold (#2075) --- pkgs/coverage/CHANGELOG.md | 5 + pkgs/coverage/bin/format_coverage.dart | 80 ++++++++++++---- pkgs/coverage/bin/test_with_coverage.dart | 10 +- .../coverage/lib/src/coverage_percentage.dart | 53 +++++++++++ pkgs/coverage/pubspec.yaml | 2 +- .../test/coverage_percentage_test.dart | 91 +++++++++++++++++++ .../lib/validate_lib.dart | 14 +++ .../test/evaluate_test.dart | 24 +++++ .../test/test_with_coverage_test.dart | 62 +++++++++++++ 9 files changed, 322 insertions(+), 19 deletions(-) create mode 100644 pkgs/coverage/lib/src/coverage_percentage.dart create mode 100644 pkgs/coverage/test/coverage_percentage_test.dart create mode 100644 pkgs/coverage/test/test_with_coverage_package/test/evaluate_test.dart diff --git a/pkgs/coverage/CHANGELOG.md b/pkgs/coverage/CHANGELOG.md index b6c93f4f9..26f242d03 100644 --- a/pkgs/coverage/CHANGELOG.md +++ b/pkgs/coverage/CHANGELOG.md @@ -1,3 +1,8 @@ +## 1.13.0-wip + +- Introduced support for minimum coverage thresholds using --fail-under flag in + format_coverage. + ## 1.12.0 - Introduced support for specifying coverage flags through a YAML file. diff --git a/pkgs/coverage/bin/format_coverage.dart b/pkgs/coverage/bin/format_coverage.dart index c2d8283ab..9490ed8d5 100644 --- a/pkgs/coverage/bin/format_coverage.dart +++ b/pkgs/coverage/bin/format_coverage.dart @@ -7,6 +7,7 @@ import 'dart:io'; import 'package:args/args.dart'; import 'package:coverage/coverage.dart'; import 'package:coverage/src/coverage_options.dart'; +import 'package:coverage/src/coverage_percentage.dart'; import 'package:glob/glob.dart'; import 'package:path/path.dart' as p; @@ -30,6 +31,7 @@ class Environment { required this.sdkRoot, required this.verbose, required this.workers, + required this.failUnder, }); String? baseDirectory; @@ -49,6 +51,7 @@ class Environment { String? sdkRoot; bool verbose; int workers; + double? failUnder; } Future main(List arguments) async { @@ -130,6 +133,29 @@ Future main(List arguments) async { } } await outputSink.close(); + + // Check coverage against the fail-under threshold if specified. + final failUnder = env.failUnder; + if (failUnder != null) { + // Calculate the overall coverage percentage using the utility function. + final result = calculateCoveragePercentage( + hitmap, + ); + + if (env.verbose) { + print('Coverage: ${result.percentage.toStringAsFixed(2)}% ' + '(${result.coveredLines} of ${result.totalLines} lines)'); + } + + if (result.percentage < failUnder) { + print('Error: Coverage ${result.percentage.toStringAsFixed(2)}% ' + 'is less than required ${failUnder.toStringAsFixed(2)}%'); + exit(1); + } else if (env.verbose) { + print('Coverage ${result.percentage.toStringAsFixed(2)}% meets or exceeds' + 'the required ${failUnder.toStringAsFixed(2)}%'); + } + } } /// Checks the validity of the provided arguments. Does not initialize actual @@ -143,6 +169,10 @@ Environment parseArgs(List arguments, CoverageOptions defaultOptions) { abbr: 's', help: 'path to the SDK root', ) + ..addOption( + 'fail-under', + help: 'Fail if coverage is less than the given percentage (0-100)', + ) ..addOption( 'packages', help: '[DEPRECATED] path to the package spec file', @@ -308,24 +338,40 @@ Environment parseArgs(List arguments, CoverageOptions defaultOptions) { final checkIgnore = args['check-ignore'] as bool; final ignoredGlobs = args['ignore-files'] as List; final verbose = args['verbose'] as bool; + + double? failUnder; + final failUnderStr = args['fail-under'] as String?; + if (failUnderStr != null) { + try { + failUnder = double.parse(failUnderStr); + if (failUnder < 0 || failUnder > 100) { + fail('--fail-under must be a percentage between 0 and 100'); + } + } catch (e) { + fail('Invalid --fail-under value: $e'); + } + } + return Environment( - baseDirectory: baseDirectory, - bazel: bazel, - bazelWorkspace: bazelWorkspace, - checkIgnore: checkIgnore, - input: input, - lcov: lcov, - output: output, - packagesPath: packagesPath, - packagePath: packagePath, - prettyPrint: prettyPrint, - prettyPrintFunc: prettyPrintFunc, - prettyPrintBranch: prettyPrintBranch, - reportOn: reportOn, - ignoreFiles: ignoredGlobs, - sdkRoot: sdkRoot, - verbose: verbose, - workers: workers); + baseDirectory: baseDirectory, + bazel: bazel, + bazelWorkspace: bazelWorkspace, + checkIgnore: checkIgnore, + input: input, + lcov: lcov, + output: output, + packagesPath: packagesPath, + packagePath: packagePath, + prettyPrint: prettyPrint, + prettyPrintFunc: prettyPrintFunc, + prettyPrintBranch: prettyPrintBranch, + reportOn: reportOn, + ignoreFiles: ignoredGlobs, + sdkRoot: sdkRoot, + verbose: verbose, + workers: workers, + failUnder: failUnder, + ); } /// Given an absolute path absPath, this function returns a [List] of files diff --git a/pkgs/coverage/bin/test_with_coverage.dart b/pkgs/coverage/bin/test_with_coverage.dart index 91e9b5f06..c22e9b8fb 100644 --- a/pkgs/coverage/bin/test_with_coverage.dart +++ b/pkgs/coverage/bin/test_with_coverage.dart @@ -85,6 +85,10 @@ ArgParser _createArgParser(CoverageOptions defaultOptions) => ArgParser() defaultsTo: defaultOptions.branchCoverage, help: 'Collect branch coverage info.', ) + ..addOption( + 'fail-under', + help: 'Fail if coverage is less than the given percentage (0-100)', + ) ..addMultiOption('scope-output', defaultsTo: defaultOptions.scopeOutput, help: 'restrict coverage results so that only scripts that start with ' @@ -101,7 +105,8 @@ class Flags { this.testScript, this.functionCoverage, this.branchCoverage, - this.scopeOutput, { + this.scopeOutput, + this.failUnder, { required this.rest, }); @@ -113,6 +118,7 @@ class Flags { final bool functionCoverage; final bool branchCoverage; final List scopeOutput; + final String? failUnder; final List rest; } @@ -169,6 +175,7 @@ ${parser.usage} args['function-coverage'] as bool, args['branch-coverage'] as bool, args['scope-output'] as List, + args['fail-under'] as String?, rest: args.rest, ); } @@ -233,6 +240,7 @@ Future main(List arguments) async { outJson, '-o', outLcov, + if (flags.failUnder != null) '--fail-under=${flags.failUnder}', ]); exit(0); } diff --git a/pkgs/coverage/lib/src/coverage_percentage.dart b/pkgs/coverage/lib/src/coverage_percentage.dart new file mode 100644 index 000000000..3171aa906 --- /dev/null +++ b/pkgs/coverage/lib/src/coverage_percentage.dart @@ -0,0 +1,53 @@ +// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'hitmap.dart'; + +/// Calculates the coverage percentage from a hitmap. +/// +/// [hitmap] is the map of file paths to hit maps. +/// +/// Returns a [CoverageResult] containing the coverage percentage and line +/// counts. +CoverageResult calculateCoveragePercentage(Map hitmap) { + var totalLines = 0; + var coveredLines = 0; + for (final entry in hitmap.entries) { + final lineHits = entry.value.lineHits; + final branchHits = entry.value.branchHits; + totalLines += lineHits.length; + if (branchHits != null) { + totalLines += branchHits.length; + coveredLines += branchHits.values.where((v) => v > 0).length; + } + coveredLines += lineHits.values.where((v) => v > 0).length; + } + final coveragePercentage = + totalLines > 0 ? coveredLines * 100 / totalLines : 0.0; + + return CoverageResult( + percentage: coveragePercentage, + coveredLines: coveredLines, + totalLines: totalLines, + ); +} + +/// The result of a coverage calculation. +class CoverageResult { + /// Creates a new [CoverageResult]. + const CoverageResult({ + required this.percentage, + required this.coveredLines, + required this.totalLines, + }); + + /// The coverage percentage. + final double percentage; + + /// The number of covered lines. + final int coveredLines; + + /// The total number of lines. + final int totalLines; +} diff --git a/pkgs/coverage/pubspec.yaml b/pkgs/coverage/pubspec.yaml index 6bd452470..2de3f074d 100644 --- a/pkgs/coverage/pubspec.yaml +++ b/pkgs/coverage/pubspec.yaml @@ -1,5 +1,5 @@ name: coverage -version: 1.12.0 +version: 1.13.0-wip description: Coverage data manipulation and formatting repository: https://github.com/dart-lang/tools/tree/main/pkgs/coverage issue_tracker: https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Acoverage diff --git a/pkgs/coverage/test/coverage_percentage_test.dart b/pkgs/coverage/test/coverage_percentage_test.dart new file mode 100644 index 000000000..e71269f5b --- /dev/null +++ b/pkgs/coverage/test/coverage_percentage_test.dart @@ -0,0 +1,91 @@ +// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file +// for details. 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:coverage/src/coverage_percentage.dart'; +import 'package:coverage/src/hitmap.dart'; +import 'package:test/test.dart'; + +void main() { + group('calculateCoveragePercentage', () { + test('calculates correct percentage', () { + final hitmap = { + 'file1.dart': HitMap({ + 1: 1, // covered + 2: 1, // covered + 3: 0, // not covered + 4: 1, // covered + }), + 'file2.dart': HitMap({ + 1: 1, // covered + 2: 0, // not covered + 3: 0, // not covered + }), + }; + + final result = calculateCoveragePercentage(hitmap); + + // 4 covered lines out of 7 total lines = 57.14% + expect(result.percentage.toStringAsFixed(2), equals('57.14')); + expect(result.coveredLines, equals(4)); + expect(result.totalLines, equals(7)); + }); + test('handles empty hitmap', () { + final hitmap = {}; + + final result = calculateCoveragePercentage(hitmap); + + expect(result.percentage, equals(0)); + expect(result.coveredLines, equals(0)); + expect(result.totalLines, equals(0)); + }); + + test('handles hitmap with no covered lines', () { + final hitmap = { + 'file1.dart': HitMap({ + 1: 0, // not covered + 2: 0, // not covered + 3: 0, // not covered + }), + }; + + final result = calculateCoveragePercentage(hitmap); + + expect(result.percentage, equals(0)); + expect(result.coveredLines, equals(0)); + expect(result.totalLines, equals(3)); + }); + + test('handles hitmap with all lines covered', () { + final hitmap = { + 'file1.dart': HitMap({ + 1: 1, // covered + 2: 1, // covered + 3: 1, // covered + }), + }; + + final result = calculateCoveragePercentage(hitmap); + + expect(result.percentage, equals(100)); + expect(result.coveredLines, equals(3)); + expect(result.totalLines, equals(3)); + }); + test('includes branch hits in coverage percentage', () { + // 2 lines (1 covered) + 3 branches (2 covered) = 5 total, 3 covered + final hitmap = { + 'file.dart': HitMap( + {1: 1, 2: 0}, // lineHits + null, + null, + {10: 1, 11: 0, 12: 2}, // branchHits + ), + }; + final result = calculateCoveragePercentage(hitmap); + + expect(result.totalLines, equals(5)); + expect(result.coveredLines, equals(3)); + expect(result.percentage.toStringAsFixed(2), equals('60.00')); + }); + }); +} diff --git a/pkgs/coverage/test/test_with_coverage_package/lib/validate_lib.dart b/pkgs/coverage/test/test_with_coverage_package/lib/validate_lib.dart index 771c0400e..a19d46a52 100644 --- a/pkgs/coverage/test/test_with_coverage_package/lib/validate_lib.dart +++ b/pkgs/coverage/test/test_with_coverage_package/lib/validate_lib.dart @@ -17,3 +17,17 @@ int product(Iterable values) { } return val; } + +String evaluateScore(int score) { + if (score < 0) { + return 'Invalid'; + } else if (score < 50) { + return 'Fail'; + } else if (score < 70) { + return 'Pass'; + } else if (score <= 100) { + return 'Excellent'; + } else { + return 'Overflow'; + } +} diff --git a/pkgs/coverage/test/test_with_coverage_package/test/evaluate_test.dart b/pkgs/coverage/test/test_with_coverage_package/test/evaluate_test.dart new file mode 100644 index 000000000..65caf4974 --- /dev/null +++ b/pkgs/coverage/test/test_with_coverage_package/test/evaluate_test.dart @@ -0,0 +1,24 @@ +// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file +// for details. 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:test/test.dart'; + +// ignore: avoid_relative_lib_imports +import '../lib/validate_lib.dart'; + +void main() { + group('evaluateScore', () { + test('returns Invalid for negative score', () { + expect(evaluateScore(-10), equals('Invalid')); + }); + + test('returns Fail for score < 50', () { + expect(evaluateScore(30), equals('Fail')); + }); + + test('returns Excellent for score 85', () { + expect(evaluateScore(85), equals('Excellent')); + }); + }); +} diff --git a/pkgs/coverage/test/test_with_coverage_test.dart b/pkgs/coverage/test/test_with_coverage_test.dart index 689452d2d..53e225315 100644 --- a/pkgs/coverage/test/test_with_coverage_test.dart +++ b/pkgs/coverage/test/test_with_coverage_test.dart @@ -86,6 +86,7 @@ dependency_overrides: { 'product': 1, 'sum': 1, + 'evaluateScore': 1, }, ); }); @@ -104,6 +105,7 @@ dependency_overrides: { 'product': 0, 'sum': 1, + 'evaluateScore': 0, }, reason: 'only `sum` tests should be run', ); @@ -122,6 +124,66 @@ dependency_overrides: ['pub', 'global', 'run', 'coverage:test_with_coverage'], ); }); + + test( + 'dart run bin/test_with_coverage.dart --fail-under succeeds' + 'when coverage meets threshold', () async { + // This should pass as coverage=100% when all tests run. + final process = await _run([ + 'run', + _testWithCoveragePath, + '--fail-under=100', + '--port', + '${_port++}', + ]); + await process.shouldExit(0); + }); + test( + 'dart run bin/test_with_coverage.dart --fail-under fails' + 'when coverage is below threshold', () async { + // This should throw an exit(1) as coverage =27.27% when only the `sum` test + // is run i.e. out of 11 lines only 3 lines i.e. [5,7,8]will have hits>0. + final process = await _run([ + 'run', + _testWithCoveragePath, + '--fail-under=90', + '--port', + '${_port++}', + '--', + '-N', + 'sum', + ]); + await process.shouldExit(1); + }); + test( + 'dart run bin/test_with_coverage.dart -b --fail-under succeeds' + 'when coverage meets threshold', () async { + // This should pass as total lines+branches covered=20 and total lines(11)+ + // branches covered(10)=21 => percentage_covered=95.23. + final process = await _run([ + 'run', + _testWithCoveragePath, + '-b', + '--fail-under=90', + '--port', + '${_port++}', + ]); + await process.shouldExit(0); + }); + test( + 'dart run bin/test_with_coverage.dart -b --fail-under fails' + 'when coverage is below threshold', () async { + // This should throw an exit(1) as percentage_covered=95.23 . + final process = await _run([ + 'run', + _testWithCoveragePath, + '-b', + '--fail-under=99', + '--port', + '${_port++}', + ]); + await process.shouldExit(1); + }); } Future _run(List args) => TestProcess.start( From 1935f400173f221bc89c8965c5da782ce73e133d Mon Sep 17 00:00:00 2001 From: Liam Appelbe Date: Wed, 30 Apr 2025 11:51:21 +1200 Subject: [PATCH 10/67] [coverage] Fix resume after shutdown error (#2079) --- pkgs/coverage/CHANGELOG.md | 4 +- pkgs/coverage/README.md | 2 +- .../lib/src/isolate_paused_listener.dart | 6 ++- pkgs/coverage/pubspec.yaml | 2 +- .../test/isolate_paused_listener_test.dart | 40 +++++++++++++++++++ 5 files changed, 49 insertions(+), 5 deletions(-) diff --git a/pkgs/coverage/CHANGELOG.md b/pkgs/coverage/CHANGELOG.md index 26f242d03..3c843f3f8 100644 --- a/pkgs/coverage/CHANGELOG.md +++ b/pkgs/coverage/CHANGELOG.md @@ -1,7 +1,9 @@ -## 1.13.0-wip +## 1.13.0 - Introduced support for minimum coverage thresholds using --fail-under flag in format_coverage. +- Fix a bug where we attempt to resume an isolate after the VM service has been + shut down. ## 1.12.0 diff --git a/pkgs/coverage/README.md b/pkgs/coverage/README.md index 81736a4de..2963c6a97 100644 --- a/pkgs/coverage/README.md +++ b/pkgs/coverage/README.md @@ -1,7 +1,7 @@ Coverage provides coverage data collection, manipulation, and formatting for Dart. -[![Build Status](https://github.com/dart-lang/tools/actions/workflows/coverage.yml/badge.svg)](https://github.com/dart-lang/tools/actions/workflows/coverage.yml) +[![Build Status](https://github.com/dart-lang/tools/actions/workflows/coverage.yaml/badge.svg)](https://github.com/dart-lang/tools/actions/workflows/coverage.yaml) [![Coverage Status](https://coveralls.io/repos/github/dart-lang/tools/badge.svg?branch=main)](https://coveralls.io/github/dart-lang/tools?branch=main) [![Pub](https://img.shields.io/pub/v/coverage.svg)](https://pub.dev/packages/coverage) diff --git a/pkgs/coverage/lib/src/isolate_paused_listener.dart b/pkgs/coverage/lib/src/isolate_paused_listener.dart index 28894f772..915acf50e 100644 --- a/pkgs/coverage/lib/src/isolate_paused_listener.dart +++ b/pkgs/coverage/lib/src/isolate_paused_listener.dart @@ -89,13 +89,15 @@ class IsolatePausedListener { } else { final isLastIsolateInGroup = group.noRunningIsolates; if (isLastIsolateInGroup) { - _getGroup(isolateRef).collected = true; + group.collected = true; } try { await _onIsolatePaused(isolateRef, isLastIsolateInGroup); } finally { group.exit(isolateRef); - await _service.resume(isolateRef.id!); + if (!_finishedListening) { + await _service.resume(isolateRef.id!); + } } } } diff --git a/pkgs/coverage/pubspec.yaml b/pkgs/coverage/pubspec.yaml index 2de3f074d..621832101 100644 --- a/pkgs/coverage/pubspec.yaml +++ b/pkgs/coverage/pubspec.yaml @@ -1,5 +1,5 @@ name: coverage -version: 1.13.0-wip +version: 1.13.0 description: Coverage data manipulation and formatting repository: https://github.com/dart-lang/tools/tree/main/pkgs/coverage issue_tracker: https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Acoverage diff --git a/pkgs/coverage/test/isolate_paused_listener_test.dart b/pkgs/coverage/test/isolate_paused_listener_test.dart index dc77408ef..53134db08 100644 --- a/pkgs/coverage/test/isolate_paused_listener_test.dart +++ b/pkgs/coverage/test/isolate_paused_listener_test.dart @@ -485,6 +485,7 @@ void main() { late Future allIsolatesExited; late List received; + Future Function(String)? delayTheOnPauseCallback; late bool stopped; void startEvent(String id, String groupId, [String? name]) => @@ -522,6 +523,7 @@ void main() { when(service.getVM()).thenAnswer((_) async => VM()); received = []; + delayTheOnPauseCallback = null; when(service.resume(any)).thenAnswer((invocation) async { final id = invocation.positionalArguments[0]; received.add('Resume $id'); @@ -535,6 +537,10 @@ void main() { expect(stopped, isFalse); received.add('Pause ${iso.id}. Collect group ${iso.isolateGroupId}? ' '${isLastIsolateInGroup ? 'Yes' : 'No'}'); + if (delayTheOnPauseCallback != null) { + await delayTheOnPauseCallback!(iso.id!); + received.add('Pause done ${iso.id}'); + } }, (message) => received.add(message), ).waitUntilAllExited(); @@ -849,5 +855,39 @@ void main() { 'Resume C', ]); }); + + test('main isolate paused during other isolate pause callback', () async { + final delayingB = Completer(); + delayTheOnPauseCallback = (String isoId) async { + if (isoId == 'B') await delayingB.future; + }; + + startEvent('A', '1', 'main'); + startEvent('B', '2'); + pauseEvent('B', '2'); + pauseEvent('A', '1', 'main'); + + while (received.length < 4) { + await Future.delayed(Duration.zero); + } + + expect(received, [ + 'Pause B. Collect group 2? Yes', + 'Pause A. Collect group 1? Yes', + 'Pause done A', + 'Resume A', + ]); + + delayingB.complete(); + await endTest(); + expect(received, [ + 'Pause B. Collect group 2? Yes', + 'Pause A. Collect group 1? Yes', + 'Pause done A', + 'Resume A', + 'Pause done B', + // Don't try to resume B, because the VM service is already shut down. + ]); + }); }); } From 010afe0735e46c2a2cae5a5de02f9d44b184d57c Mon Sep 17 00:00:00 2001 From: Liam Appelbe Date: Fri, 2 May 2025 10:42:46 +1200 Subject: [PATCH 11/67] [coverage] Fix another flaky lifecycle management error (#2082) --- pkgs/coverage/CHANGELOG.md | 5 +++++ pkgs/coverage/lib/src/isolate_paused_listener.dart | 9 +++++++-- pkgs/coverage/pubspec.yaml | 2 +- pkgs/coverage/test/isolate_paused_listener_test.dart | 6 +++--- 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/pkgs/coverage/CHANGELOG.md b/pkgs/coverage/CHANGELOG.md index 3c843f3f8..6dc80a537 100644 --- a/pkgs/coverage/CHANGELOG.md +++ b/pkgs/coverage/CHANGELOG.md @@ -1,3 +1,8 @@ +## 1.13.1 + +- Fix a bug where the VM service can be shut down while some coverage + collections are still happening. + ## 1.13.0 - Introduced support for minimum coverage thresholds using --fail-under flag in diff --git a/pkgs/coverage/lib/src/isolate_paused_listener.dart b/pkgs/coverage/lib/src/isolate_paused_listener.dart index 915acf50e..49f17e7a9 100644 --- a/pkgs/coverage/lib/src/isolate_paused_listener.dart +++ b/pkgs/coverage/lib/src/isolate_paused_listener.dart @@ -27,6 +27,7 @@ class IsolatePausedListener { final SyncErrorLogger _log; final _isolateGroups = {}; + final _oldCollectionTasks = >{}; int _numIsolates = 0; bool _finishedListening = false; @@ -48,7 +49,7 @@ class IsolatePausedListener { _finishedListening = true; // Collect all remaining uncollected groups. - final collectionTasks = >[]; + final collectionTasks = _oldCollectionTasks.toList(); for (final group in _isolateGroups.values) { if (!group.collected) { group.collected = true; @@ -91,9 +92,13 @@ class IsolatePausedListener { if (isLastIsolateInGroup) { group.collected = true; } + Future? collectionTask; try { - await _onIsolatePaused(isolateRef, isLastIsolateInGroup); + collectionTask = _onIsolatePaused(isolateRef, isLastIsolateInGroup); + _oldCollectionTasks.add(collectionTask); + await collectionTask; } finally { + _oldCollectionTasks.remove(collectionTask); group.exit(isolateRef); if (!_finishedListening) { await _service.resume(isolateRef.id!); diff --git a/pkgs/coverage/pubspec.yaml b/pkgs/coverage/pubspec.yaml index 621832101..46307b824 100644 --- a/pkgs/coverage/pubspec.yaml +++ b/pkgs/coverage/pubspec.yaml @@ -1,5 +1,5 @@ name: coverage -version: 1.13.0 +version: 1.13.1 description: Coverage data manipulation and formatting repository: https://github.com/dart-lang/tools/tree/main/pkgs/coverage issue_tracker: https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Acoverage diff --git a/pkgs/coverage/test/isolate_paused_listener_test.dart b/pkgs/coverage/test/isolate_paused_listener_test.dart index 53134db08..55d204528 100644 --- a/pkgs/coverage/test/isolate_paused_listener_test.dart +++ b/pkgs/coverage/test/isolate_paused_listener_test.dart @@ -867,7 +867,7 @@ void main() { pauseEvent('B', '2'); pauseEvent('A', '1', 'main'); - while (received.length < 4) { + while (received.length < 3) { await Future.delayed(Duration.zero); } @@ -875,7 +875,6 @@ void main() { 'Pause B. Collect group 2? Yes', 'Pause A. Collect group 1? Yes', 'Pause done A', - 'Resume A', ]); delayingB.complete(); @@ -884,8 +883,9 @@ void main() { 'Pause B. Collect group 2? Yes', 'Pause A. Collect group 1? Yes', 'Pause done A', - 'Resume A', 'Pause done B', + // A waits for B's pause callback to complete before resuming. + 'Resume A', // Don't try to resume B, because the VM service is already shut down. ]); }); From bf31336803169a1f56e837f43df370e58bc338f7 Mon Sep 17 00:00:00 2001 From: Goddchen Date: Mon, 5 May 2025 09:19:25 +0200 Subject: [PATCH 12/67] fix(clock): keep micros in monthsAgo, monthsFromNow and yearsAgo (#1202) Signed-off-by: Goddchen Co-authored-by: Moritz --- pkgs/clock/CHANGELOG.md | 4 ++++ pkgs/clock/lib/src/clock.dart | 6 +++--- pkgs/clock/pubspec.yaml | 2 +- pkgs/clock/test/clock_test.dart | 18 ++++++++++++++++++ 4 files changed, 26 insertions(+), 4 deletions(-) diff --git a/pkgs/clock/CHANGELOG.md b/pkgs/clock/CHANGELOG.md index f372adaa1..eb4f9ab95 100644 --- a/pkgs/clock/CHANGELOG.md +++ b/pkgs/clock/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.1.3-wip + +* Keep microseconds when using `monthsAgo`, `monthsFromNow` and `yearsAgo` + ## 1.1.2 * Require Dart 3.4 diff --git a/pkgs/clock/lib/src/clock.dart b/pkgs/clock/lib/src/clock.dart index f6f47deb1..9cad995c5 100644 --- a/pkgs/clock/lib/src/clock.dart +++ b/pkgs/clock/lib/src/clock.dart @@ -139,7 +139,7 @@ class Clock { var year = time.year - (months + 12 - time.month) ~/ 12; var day = clampDayOfMonth(year: year, month: month, day: time.day); return DateTime(year, month, day, time.hour, time.minute, time.second, - time.millisecond); + time.millisecond, time.microsecond); } /// Return the point in time [months] from now on the same date. @@ -152,7 +152,7 @@ class Clock { var year = time.year + (months + time.month - 1) ~/ 12; var day = clampDayOfMonth(year: year, month: month, day: time.day); return DateTime(year, month, day, time.hour, time.minute, time.second, - time.millisecond); + time.millisecond, time.microsecond); } /// Return the point in time [years] ago on the same date. @@ -164,7 +164,7 @@ class Clock { var year = time.year - years; var day = clampDayOfMonth(year: year, month: time.month, day: time.day); return DateTime(year, time.month, day, time.hour, time.minute, time.second, - time.millisecond); + time.millisecond, time.microsecond); } /// Return the point in time [years] from now on the same date. diff --git a/pkgs/clock/pubspec.yaml b/pkgs/clock/pubspec.yaml index 605aa0991..7d21e5871 100644 --- a/pkgs/clock/pubspec.yaml +++ b/pkgs/clock/pubspec.yaml @@ -1,5 +1,5 @@ name: clock -version: 1.1.2 +version: 1.1.3-wip description: A fakeable wrapper for dart:core clock APIs. repository: https://github.com/dart-lang/tools/tree/main/pkgs/clock issue_tracker: https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Aclock diff --git a/pkgs/clock/test/clock_test.dart b/pkgs/clock/test/clock_test.dart index c457153df..f72273946 100644 --- a/pkgs/clock/test/clock_test.dart +++ b/pkgs/clock/test/clock_test.dart @@ -207,4 +207,22 @@ void main() { expect(clock.yearsFromNow(30), date(2043, 1, 1)); expect(clock.yearsFromNow(1000), date(3013, 1, 1)); }); + + group('micros', () { + test('should keep micros for monthsAgo', () { + expect(Clock.fixed(DateTime(2024, 2, 1, 0, 0, 0, 0, 123)).monthsAgo(1), + Clock.fixed(DateTime(2024, 1, 1, 0, 0, 0, 0, 123)).now()); + }); + + test('should keep micros for monthsFromNow', () { + expect( + Clock.fixed(DateTime(2024, 2, 1, 0, 0, 0, 0, 123)).monthsFromNow(1), + Clock.fixed(DateTime(2024, 3, 1, 0, 0, 0, 0, 123)).now()); + }); + + test('should keep micros for yearsAgo', () { + expect(Clock.fixed(DateTime(2024, 2, 1, 0, 0, 0, 0, 123)).yearsAgo(1), + Clock.fixed(DateTime(2023, 2, 1, 0, 0, 0, 0, 123)).now()); + }); + }); } From 64119a3313c591b672655bfa8680a5ac93a6b7b6 Mon Sep 17 00:00:00 2001 From: Jacob MacDonald Date: Mon, 5 May 2025 07:53:40 -0700 Subject: [PATCH 13/67] broaden the publish tag regex to allow digits (#2085) Should fix https://github.com/dart-lang/tools/issues/2084 --- .github/workflows/publish.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index 9893b13be..765898ce4 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -7,7 +7,8 @@ on: pull_request: branches: [ main ] push: - tags: [ '[A-z]+-v[0-9]+.[0-9]+.[0-9]+' ] + # Match -v publish tags + tags: [ '[A-z0-9]+-v[0-9]+.[0-9]+.[0-9]+' ] jobs: publish: From 4668c93910399eca1ea8ba8d8be34175fc9ec00e Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Tue, 20 May 2025 09:22:29 -0500 Subject: [PATCH 14/67] [benchmark_harness] Move Measure into its on lib (#2092) --- pkgs/benchmark_harness/CHANGELOG.md | 2 + .../lib/src/benchmark_base.dart | 53 +---------------- .../lib/src/measurement.dart | 57 +++++++++++++++++++ .../lib/src/perf_benchmark_base.dart | 1 + pkgs/benchmark_harness/pubspec.yaml | 2 +- 5 files changed, 62 insertions(+), 53 deletions(-) create mode 100644 pkgs/benchmark_harness/lib/src/measurement.dart diff --git a/pkgs/benchmark_harness/CHANGELOG.md b/pkgs/benchmark_harness/CHANGELOG.md index fceecb830..f0e24bf7e 100644 --- a/pkgs/benchmark_harness/CHANGELOG.md +++ b/pkgs/benchmark_harness/CHANGELOG.md @@ -1,3 +1,5 @@ +## 2.3.2-wip + ## 2.3.1 - Move to `dart-lang/tools` monorepo. diff --git a/pkgs/benchmark_harness/lib/src/benchmark_base.dart b/pkgs/benchmark_harness/lib/src/benchmark_base.dart index bc874f54a..d119d4da2 100644 --- a/pkgs/benchmark_harness/lib/src/benchmark_base.dart +++ b/pkgs/benchmark_harness/lib/src/benchmark_base.dart @@ -2,8 +2,7 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. -import 'dart:math' as math; - +import 'measurement.dart'; import 'score_emitter.dart'; const int minimumMeasureDurationMillis = 2000; @@ -61,53 +60,3 @@ class BenchmarkBase { emitter.emit(name, measure()); } } - -/// Measures the score for this benchmark by executing it enough times -/// to reach [minimumMillis]. -Measurement measureForImpl(void Function() f, int minimumMillis) { - final minimumMicros = minimumMillis * 1000; - // If running a long measurement permit some amount of measurement jitter - // to avoid discarding results that are almost good, but not quite there. - final allowedJitter = - minimumMillis < 1000 ? 0 : (minimumMicros * 0.1).floor(); - var iter = 2; - var totalIterations = iter; - final watch = Stopwatch()..start(); - while (true) { - watch.reset(); - for (var i = 0; i < iter; i++) { - f(); - } - final elapsed = watch.elapsedMicroseconds; - final measurement = Measurement(elapsed, iter, totalIterations); - if (measurement.elapsedMicros >= (minimumMicros - allowedJitter)) { - return measurement; - } - - iter = measurement.estimateIterationsNeededToReach( - minimumMicros: minimumMicros); - totalIterations += iter; - } -} - -class Measurement { - final int elapsedMicros; - final int iterations; - final int totalIterations; - - Measurement(this.elapsedMicros, this.iterations, this.totalIterations); - - double get score => elapsedMicros / iterations; - - int estimateIterationsNeededToReach({required int minimumMicros}) { - final elapsed = roundDownToMillisecond(elapsedMicros); - return elapsed == 0 - ? iterations * 1000 - : (iterations * math.max(minimumMicros / elapsed, 1.5)).ceil(); - } - - static int roundDownToMillisecond(int micros) => (micros ~/ 1000) * 1000; - - @override - String toString() => '$elapsedMicros in $iterations iterations'; -} diff --git a/pkgs/benchmark_harness/lib/src/measurement.dart b/pkgs/benchmark_harness/lib/src/measurement.dart new file mode 100644 index 000000000..11119b983 --- /dev/null +++ b/pkgs/benchmark_harness/lib/src/measurement.dart @@ -0,0 +1,57 @@ +// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:math' as math; + +/// Measures the score for this benchmark by executing it enough times +/// to reach [minimumMillis]. +/// +/// [f] will be run a minimum of 2 times. +Measurement measureForImpl(void Function() f, int minimumMillis) { + final minimumMicros = minimumMillis * 1000; + // If running a long measurement permit some amount of measurement jitter + // to avoid discarding results that are almost good, but not quite there. + final allowedJitter = + minimumMillis < 1000 ? 0 : (minimumMicros * 0.1).floor(); + var iter = 2; + var totalIterations = iter; + final watch = Stopwatch()..start(); + while (true) { + watch.reset(); + for (var i = 0; i < iter; i++) { + f(); + } + final elapsed = watch.elapsedMicroseconds; + final measurement = Measurement(elapsed, iter, totalIterations); + if (measurement.elapsedMicros >= (minimumMicros - allowedJitter)) { + return measurement; + } + + iter = measurement.estimateIterationsNeededToReach( + minimumMicros: minimumMicros); + totalIterations += iter; + } +} + +class Measurement { + Measurement(this.elapsedMicros, this.iterations, this.totalIterations); + + final int elapsedMicros; + final int iterations; + final int totalIterations; + + double get score => elapsedMicros / iterations; + + int estimateIterationsNeededToReach({required int minimumMicros}) { + final elapsed = _roundDownToMillisecond(elapsedMicros); + return elapsed == 0 + ? iterations * 1000 + : (iterations * math.max(minimumMicros / elapsed, 1.5)).ceil(); + } + + @override + String toString() => '$elapsedMicros in $iterations iterations'; +} + +int _roundDownToMillisecond(int micros) => (micros ~/ 1000) * 1000; diff --git a/pkgs/benchmark_harness/lib/src/perf_benchmark_base.dart b/pkgs/benchmark_harness/lib/src/perf_benchmark_base.dart index 1b7fb92a1..a1c3de9c9 100644 --- a/pkgs/benchmark_harness/lib/src/perf_benchmark_base.dart +++ b/pkgs/benchmark_harness/lib/src/perf_benchmark_base.dart @@ -7,6 +7,7 @@ import 'dart:convert'; import 'dart:io'; import 'benchmark_base.dart'; +import 'measurement.dart'; import 'score_emitter.dart'; class PerfBenchmarkBase extends BenchmarkBase { diff --git a/pkgs/benchmark_harness/pubspec.yaml b/pkgs/benchmark_harness/pubspec.yaml index d9b82e5fb..8561c2a06 100644 --- a/pkgs/benchmark_harness/pubspec.yaml +++ b/pkgs/benchmark_harness/pubspec.yaml @@ -1,5 +1,5 @@ name: benchmark_harness -version: 2.3.1 +version: 2.3.2-wip description: The official Dart project benchmark harness. repository: https://github.com/dart-lang/tools/tree/main/pkgs/benchmark_harness issue_tracker: https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Abenchmark_harness From 9589942b70351b97f156d369b56cd2363c6d4b47 Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Tue, 20 May 2025 11:11:59 -0500 Subject: [PATCH 15/67] [benchmark_harness]: add a bench command (#2091) --- .github/workflows/benchmark_harness.yaml | 5 + pkgs/benchmark_harness/CHANGELOG.md | 4 +- pkgs/benchmark_harness/README.md | 53 +++++ pkgs/benchmark_harness/bin/bench.dart | 39 ++++ .../lib/src/bench_command/bench_options.dart | 82 ++++++++ .../src/bench_command/compile_and_run.dart | 186 ++++++++++++++++++ pkgs/benchmark_harness/pubspec.yaml | 14 +- .../test/bench_command_test.dart | 127 ++++++++++++ 8 files changed, 504 insertions(+), 6 deletions(-) create mode 100644 pkgs/benchmark_harness/bin/bench.dart create mode 100644 pkgs/benchmark_harness/lib/src/bench_command/bench_options.dart create mode 100644 pkgs/benchmark_harness/lib/src/bench_command/compile_and_run.dart create mode 100644 pkgs/benchmark_harness/test/bench_command_test.dart diff --git a/.github/workflows/benchmark_harness.yaml b/.github/workflows/benchmark_harness.yaml index 5e783a45c..4b839526c 100644 --- a/.github/workflows/benchmark_harness.yaml +++ b/.github/workflows/benchmark_harness.yaml @@ -61,6 +61,11 @@ jobs: - uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c with: sdk: ${{ matrix.sdk }} + # Node 22 has wasmGC enabled, which allows the wasm tests to run! + - name: Setup Node.js 22 + uses: actions/setup-node@v3 + with: + node-version: 22 - id: install name: Install dependencies run: dart pub get diff --git a/pkgs/benchmark_harness/CHANGELOG.md b/pkgs/benchmark_harness/CHANGELOG.md index f0e24bf7e..87864a084 100644 --- a/pkgs/benchmark_harness/CHANGELOG.md +++ b/pkgs/benchmark_harness/CHANGELOG.md @@ -1,4 +1,6 @@ -## 2.3.2-wip +## 2.4.0-wip + +- Added a `bench` command. ## 2.3.1 diff --git a/pkgs/benchmark_harness/README.md b/pkgs/benchmark_harness/README.md index f156f2336..d4778087f 100644 --- a/pkgs/benchmark_harness/README.md +++ b/pkgs/benchmark_harness/README.md @@ -114,3 +114,56 @@ Template(RunTime): 0.1568472448997197 us. This is the average amount of time it takes to run `run()` 10 times for `BenchmarkBase` and once for `AsyncBenchmarkBase`. > µs is an abbreviation for microseconds. + +## `bench` command + +A convenience command available in `package:benchmark_harness`. + +If a package depends on `benchmark_harness`, invoke the command by running + +```shell +dart run benchmark_harness:bench +``` + +If not, you can use this command by activating it. + +```shell +dart pub global activate benchmark_harness +dart pub global run benchmark_harness:bench +``` + +Output from `dart run benchmark_harness:bench --help` + +``` +Runs a dart script in a number of runtimes. + +Meant to make it easy to run a benchmark executable across runtimes to validate +performance impacts. + +-f, --flavor + [aot] Compile and run as a native binary. + [jit] Run as-is without compilation, using the just-in-time (JIT) runtime. + [js] Compile to JavaScript and run on node. + [wasm] Compile to WebAssembly and run on node. + + --target The target script to compile and run. + (defaults to "benchmark/benchmark.dart") +-h, --help Print usage information and quit. +-v, --verbose Print the full stack trace if an exception is thrown. +``` + +Example usage: + +```shell +dart run benchmark_harness:bench --flavor aot --target example/template.dart + +AOT - COMPILE +/dart_installation/dart-sdk/bin/dart compile exe example/template.dart -o /temp_dir/bench_1747680526905_GtfAeM/out.exe + +Generated: /temp_dir/bench_1747680526905_GtfAeM/out.exe + +AOT - RUN +/temp_dir/bench_1747680526905_GtfAeM/out.exe + +Template(RunTime): 0.005620051244379949 us. +``` \ No newline at end of file diff --git a/pkgs/benchmark_harness/bin/bench.dart b/pkgs/benchmark_harness/bin/bench.dart new file mode 100644 index 000000000..43e1a91e8 --- /dev/null +++ b/pkgs/benchmark_harness/bin/bench.dart @@ -0,0 +1,39 @@ +// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:io'; + +import 'package:benchmark_harness/src/bench_command/bench_options.dart'; +import 'package:benchmark_harness/src/bench_command/compile_and_run.dart'; + +Future main(List args) async { + BenchOptions? options; + + try { + options = BenchOptions.fromArgs(args); + if (options.help) { + print(''' +\nRuns a dart script in a number of runtimes. + +Meant to make it easy to run a benchmark executable across runtimes to validate +performance impacts. +'''); + print(BenchOptions.usage); + return; + } + + await compileAndRun(options); + } on FormatException catch (e) { + print(e.message); + print(BenchOptions.usage); + exitCode = 64; // command line usage error + } on BenchException catch (e, stack) { + print(e.message); + if (options?.verbose ?? true) { + print(e); + print(stack); + } + exitCode = e.exitCode; + } +} diff --git a/pkgs/benchmark_harness/lib/src/bench_command/bench_options.dart b/pkgs/benchmark_harness/lib/src/bench_command/bench_options.dart new file mode 100644 index 000000000..e1ffa1cdb --- /dev/null +++ b/pkgs/benchmark_harness/lib/src/bench_command/bench_options.dart @@ -0,0 +1,82 @@ +// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file +// for details. 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:args/args.dart'; + +enum RuntimeFlavor { + aot(help: 'Compile and run as a native binary.'), + jit( + help: 'Run as-is without compilation, ' + 'using the just-in-time (JIT) runtime.', + ), + js(help: 'Compile to JavaScript and run on node.'), + wasm(help: 'Compile to WebAssembly and run on node.'); + + const RuntimeFlavor({required this.help}); + + final String help; +} + +class BenchOptions { + BenchOptions({ + required this.flavor, + required this.target, + this.help = false, + this.verbose = false, + }) { + if (!help && flavor.isEmpty) { + // This is the wrong exception to use, except that it's caught in the + // program, so it makes implementation easy. + throw const FormatException('At least one `flavor` must be provided', 64); + } + } + + factory BenchOptions.fromArgs(List args) { + final result = _parserForBenchOptions.parse(args); + + if (result.rest.isNotEmpty) { + throw FormatException('All arguments must be provided via `--` options. ' + 'Not sure what to do with "${result.rest.join()}".'); + } + + return BenchOptions( + flavor: + result.multiOption('flavor').map(RuntimeFlavor.values.byName).toSet(), + target: result.option('target')!, + help: result.flag('help'), + verbose: result.flag('verbose'), + ); + } + + final String target; + + final Set flavor; + + final bool help; + + final bool verbose; + + static String get usage => _parserForBenchOptions.usage; + + static final _parserForBenchOptions = ArgParser() + ..addMultiOption('flavor', + abbr: 'f', + allowed: RuntimeFlavor.values.map((e) => e.name), + allowedHelp: { + for (final flavor in RuntimeFlavor.values) flavor.name: flavor.help + }) + ..addOption('target', + defaultsTo: 'benchmark/benchmark.dart', + help: 'The target script to compile and run.') + ..addFlag('help', + defaultsTo: false, + negatable: false, + help: 'Print usage information and quit.', + abbr: 'h') + ..addFlag('verbose', + defaultsTo: false, + negatable: false, + help: 'Print the full stack trace if an exception is thrown.', + abbr: 'v'); +} diff --git a/pkgs/benchmark_harness/lib/src/bench_command/compile_and_run.dart b/pkgs/benchmark_harness/lib/src/bench_command/compile_and_run.dart new file mode 100644 index 000000000..bd8f29207 --- /dev/null +++ b/pkgs/benchmark_harness/lib/src/bench_command/compile_and_run.dart @@ -0,0 +1,186 @@ +// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:io'; + +import 'bench_options.dart'; + +// TODO(kevmoo): allow the user to specify custom flags – for compile and/or run + +Future compileAndRun(BenchOptions options) async { + if (!FileSystemEntity.isFileSync(options.target)) { + throw BenchException( + 'The target Dart program `${options.target}` does not exist', + 2, // standard bash code for file doesn't exist + ); + } + + for (var mode in options.flavor) { + await _Runner(flavor: mode, target: options.target).run(); + } +} + +class BenchException implements Exception { + const BenchException(this.message, this.exitCode) : assert(exitCode > 0); + final String message; + final int exitCode; + + @override + String toString() => 'BenchException: $message ($exitCode)'; +} + +/// Base name for output files. +const _outputFileRoot = 'out'; + +/// Denote the "stage" of the compile/run step for logging. +enum _Stage { compile, run } + +/// Base class for runtime-specific runners. +abstract class _Runner { + _Runner._({required this.target, required this.flavor}) + : assert(FileSystemEntity.isFileSync(target), '$target is not a file'); + + factory _Runner({required RuntimeFlavor flavor, required String target}) { + return (switch (flavor) { + RuntimeFlavor.jit => _JITRunner.new, + RuntimeFlavor.aot => _AOTRunner.new, + RuntimeFlavor.js => _JSRunner.new, + RuntimeFlavor.wasm => _WasmRunner.new, + })(target: target); + } + + final String target; + final RuntimeFlavor flavor; + late Directory _tempDirectory; + + /// Executes the compile and run cycle. + /// + /// Takes care of creating and deleting the corresponding temp directory. + Future run() async { + _tempDirectory = Directory.systemTemp + .createTempSync('bench_${DateTime.now().millisecondsSinceEpoch}_'); + try { + await _runImpl(); + } finally { + _tempDirectory.deleteSync(recursive: true); + } + } + + /// Overridden in implementations to handle the compile and run cycle. + Future _runImpl(); + + /// Executes the specific [executable] with the provided [args]. + /// + /// Also prints out a nice message before execution denoting the [flavor] and + /// the [stage]. + Future _runProc( + _Stage stage, String executable, List args) async { + print(''' +\n${flavor.name.toUpperCase()} - ${stage.name.toUpperCase()} +$executable ${args.join(' ')} +'''); + + final proc = await Process.start(executable, args, + mode: ProcessStartMode.inheritStdio); + + final exitCode = await proc.exitCode; + + if (exitCode != 0) { + throw ProcessException(executable, args, 'Process errored', exitCode); + } + } + + String _outputFile(String ext) => + _tempDirectory.uri.resolve('$_outputFileRoot.$ext').toFilePath(); +} + +class _JITRunner extends _Runner { + _JITRunner({required super.target}) : super._(flavor: RuntimeFlavor.jit); + + @override + Future _runImpl() async { + await _runProc(_Stage.run, Platform.executable, [target]); + } +} + +class _AOTRunner extends _Runner { + _AOTRunner({required super.target}) : super._(flavor: RuntimeFlavor.aot); + + @override + Future _runImpl() async { + final outFile = _outputFile('exe'); + await _runProc(_Stage.compile, Platform.executable, [ + 'compile', + 'exe', + target, + '-o', + outFile, + ]); + + await _runProc(_Stage.run, outFile, []); + } +} + +class _JSRunner extends _Runner { + _JSRunner({required super.target}) : super._(flavor: RuntimeFlavor.js); + + @override + Future _runImpl() async { + final outFile = _outputFile('js'); + await _runProc(_Stage.compile, Platform.executable, [ + 'compile', + 'js', + target, + '-O4', // default for Flutter + '-o', + outFile, + ]); + + await _runProc(_Stage.run, 'node', [outFile]); + } +} + +class _WasmRunner extends _Runner { + _WasmRunner({required super.target}) : super._(flavor: RuntimeFlavor.wasm); + + @override + Future _runImpl() async { + final outFile = _outputFile('wasm'); + await _runProc(_Stage.compile, Platform.executable, [ + 'compile', + 'wasm', + target, + '-O2', // default for Flutter + '-o', + outFile, + ]); + + final jsFile = + File.fromUri(_tempDirectory.uri.resolve('$_outputFileRoot.js')); + jsFile.writeAsStringSync(_wasmInvokeScript); + + await _runProc(_Stage.run, 'node', [jsFile.path]); + } + + static const _wasmInvokeScript = ''' +import { readFile } from 'node:fs/promises'; // For async file reading +import { fileURLToPath } from 'url'; +import { dirname, join } from 'path'; + +// Get the current directory name +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const wasmFilePath = join(__dirname, '$_outputFileRoot.wasm'); +const wasmBytes = await readFile(wasmFilePath); + +const mjsFilePath = join(__dirname, '$_outputFileRoot.mjs'); +const dartModule = await import(mjsFilePath); +const {compile} = dartModule; + +const compiledApp = await compile(wasmBytes); +const instantiatedApp = await compiledApp.instantiate({}); +await instantiatedApp.invokeMain(); +'''; +} diff --git a/pkgs/benchmark_harness/pubspec.yaml b/pkgs/benchmark_harness/pubspec.yaml index 8561c2a06..b2ad9a045 100644 --- a/pkgs/benchmark_harness/pubspec.yaml +++ b/pkgs/benchmark_harness/pubspec.yaml @@ -1,5 +1,5 @@ name: benchmark_harness -version: 2.3.2-wip +version: 2.4.0-wip description: The official Dart project benchmark harness. repository: https://github.com/dart-lang/tools/tree/main/pkgs/benchmark_harness issue_tracker: https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Abenchmark_harness @@ -10,9 +10,13 @@ topics: environment: sdk: ^3.2.0 +dependencies: + args: ^2.5.0 + dev_dependencies: - build_runner: ^2.0.0 - build_web_compilers: ^4.0.0 dart_flutter_team_lints: ^3.0.0 - path: ^1.8.0 - test: ^1.16.0 + path: ^1.9.0 + test: ^1.25.7 + +executables: + bench: diff --git a/pkgs/benchmark_harness/test/bench_command_test.dart b/pkgs/benchmark_harness/test/bench_command_test.dart new file mode 100644 index 000000000..77604160c --- /dev/null +++ b/pkgs/benchmark_harness/test/bench_command_test.dart @@ -0,0 +1,127 @@ +// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +@TestOn('vm') +library; + +import 'dart:convert'; +import 'dart:io'; + +import 'package:benchmark_harness/src/bench_command/bench_options.dart'; +import 'package:benchmark_harness/src/bench_command/compile_and_run.dart'; +import 'package:test/test.dart'; + +void main() { + test('readme', () async { + final output = + await Process.run(Platform.executable, ['bin/bench.dart', '--help']); + + expect(output.exitCode, 0); + // Sadly, the help output likes to include trailing spaces that don't + // copy paste nicely or consistently into the README. + final trimmed = LineSplitter.split(output.stdout as String) + .map((e) => e.trimRight()) + .join('\n'); + + final readmeFile = File('README.md').readAsStringSync(); + + expect( + readmeFile, + contains(trimmed), + ); + }); + + group('invoke the command', () { + late final Directory tempDir; + late final String testFilePath; + + setUpAll(() { + tempDir = Directory.systemTemp.createTempSync('benchtest_'); + testFilePath = tempDir.uri.resolve('input.dart').toFilePath(); + File(testFilePath) + ..create() + ..writeAsStringSync(_testDartFile); + }); + + tearDownAll(() { + tempDir.deleteSync(recursive: true); + }); + + group('BenchOptions.fromArgs', () { + test('options parsing', () async { + final options = BenchOptions.fromArgs( + ['--flavor', 'aot,jit', '--target', testFilePath], + ); + + await expectLater( + () => compileAndRun(options), + prints( + stringContainsInOrder([ + 'AOT - COMPILE', + testFilePath, + 'AOT - RUN', + 'JIT - RUN', + testFilePath, + ]), + ), + ); + }); + + test('rest args not supported', () async { + expect( + () => BenchOptions.fromArgs( + ['--flavor', 'aot,jit', testFilePath], + ), + throwsFormatException, + ); + }); + }); + + for (var bench in RuntimeFlavor.values) { + test('$bench', () async { + await expectLater( + () => compileAndRun( + BenchOptions(flavor: {bench}, target: testFilePath)), + prints( + stringContainsInOrder( + [ + if (bench != RuntimeFlavor.jit) ...[ + '${bench.name.toUpperCase()} - COMPILE', + testFilePath, + ], + '${bench.name.toUpperCase()} - RUN' + ], + ), + ), + ); + }, skip: _skipWasm(bench)); + } + }); +} + +// Can remove this once the min tested SDK on GitHub is >= 3.7 +String? _skipWasm(RuntimeFlavor flavor) { + if (flavor != RuntimeFlavor.wasm) { + return null; + } + final versionBits = Platform.version.split('.'); + final versionValues = versionBits.take(2).map(int.parse).toList(); + + return switch ((versionValues[0], versionValues[1])) { + // If major is greater than 3, it's definitely >= 3.7 + (int m, _) when m > 3 => null, + // If major is 3, check the minor version + (3, int n) when n >= 7 => null, + // All other cases (major < 3, or major is 3 but minor < 7) + _ => 'Requires Dart >= 3.7', + }; +} + +const _testDartFile = ''' +void main() { + // outputs 0 is JS + // 8589934592 everywhere else + print(1 << 33); +} +'''; From c6a331cc4b8f2224ffe9a0afaa6acceac42f3923 Mon Sep 17 00:00:00 2001 From: Devon Carew Date: Wed, 21 May 2025 13:42:46 -0700 Subject: [PATCH 16/67] Add package:process (#2097) --- .github/CODEOWNERS | 1 + .github/labeler.yml | 4 + .github/workflows/process.yaml | 38 ++ pkgs/process/.gitignore | 3 + pkgs/process/AUTHORS | 8 + pkgs/process/CHANGELOG.md | 209 +++++++ pkgs/process/LICENSE | 27 + pkgs/process/README.md | 17 + pkgs/process/dart_test.yaml | 1 + pkgs/process/lib/process.dart | 8 + pkgs/process/lib/src/interface/common.dart | 165 +++++ .../process/lib/src/interface/exceptions.dart | 101 +++ .../src/interface/local_process_manager.dart | 152 +++++ .../lib/src/interface/process_manager.dart | 187 ++++++ .../lib/src/interface/process_wrapper.dart | 83 +++ pkgs/process/pubspec.yaml | 19 + .../test/src/interface/common_test.dart | 589 ++++++++++++++++++ .../src/interface/process_wrapper_test.dart | 116 ++++ pkgs/process/test/utils.dart | 14 + 19 files changed, 1742 insertions(+) create mode 100644 .github/workflows/process.yaml create mode 100644 pkgs/process/.gitignore create mode 100644 pkgs/process/AUTHORS create mode 100644 pkgs/process/CHANGELOG.md create mode 100644 pkgs/process/LICENSE create mode 100644 pkgs/process/README.md create mode 100644 pkgs/process/dart_test.yaml create mode 100644 pkgs/process/lib/process.dart create mode 100644 pkgs/process/lib/src/interface/common.dart create mode 100644 pkgs/process/lib/src/interface/exceptions.dart create mode 100644 pkgs/process/lib/src/interface/local_process_manager.dart create mode 100644 pkgs/process/lib/src/interface/process_manager.dart create mode 100644 pkgs/process/lib/src/interface/process_wrapper.dart create mode 100644 pkgs/process/pubspec.yaml create mode 100644 pkgs/process/test/src/interface/common_test.dart create mode 100644 pkgs/process/test/src/interface/process_wrapper_test.dart create mode 100644 pkgs/process/test/utils.dart diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 60ddae203..a50b447bc 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -28,6 +28,7 @@ pkgs/mime @lrhn pkgs/oauth2 @dart-lang/dart-pub-team pkgs/package_config @dart-lang/dart-ecosystem-team pkgs/pool @dart-lang/dart-ecosystem-team +pkgs/process @dart-lang/dart-ecosystem-team pkgs/pub_semver @dart-lang/dart-pub-team pkgs/pubspec_parse @dart-lang/dart-bat pkgs/source_maps @dart-lang/dart-ecosystem-team diff --git a/.github/labeler.yml b/.github/labeler.yml index 6bdbffd66..dbaf8e5fa 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -96,6 +96,10 @@ - changed-files: - any-glob-to-any-file: 'pkgs/pool/**' +'package:procoess': + - changed-files: + - any-glob-to-any-file: 'pkgs/procoess/**' + 'package:pub_semver': - changed-files: - any-glob-to-any-file: 'pkgs/pub_semver/**' diff --git a/.github/workflows/process.yaml b/.github/workflows/process.yaml new file mode 100644 index 000000000..98044335b --- /dev/null +++ b/.github/workflows/process.yaml @@ -0,0 +1,38 @@ +name: package:process +permissions: read-all + +on: + schedule: + # “At 00:00 (UTC) on Sunday.” + - cron: '0 0 * * 0' + push: + branches: [ main ] + paths: + - '.github/workflows/process.yaml' + - 'pkgs/process/**' + pull_request: + branches: [ main ] + paths: + - '.github/workflows/process.yaml' + - 'pkgs/process/**' + +defaults: + run: + working-directory: pkgs/process/ + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + os: [ubuntu-latest] + sdk: [stable, dev] + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c + + - run: dart pub get + - run: dart format --output=none --set-exit-if-changed . + if: ${{ matrix.sdk == 'stable' }} + - run: dart analyze --fatal-infos + - run: dart test diff --git a/pkgs/process/.gitignore b/pkgs/process/.gitignore new file mode 100644 index 000000000..1f1b0dfdf --- /dev/null +++ b/pkgs/process/.gitignore @@ -0,0 +1,3 @@ +# Don’t commit the following directories created by pub. +.dart_tool +pubspec.lock diff --git a/pkgs/process/AUTHORS b/pkgs/process/AUTHORS new file mode 100644 index 000000000..044d6170b --- /dev/null +++ b/pkgs/process/AUTHORS @@ -0,0 +1,8 @@ +# Below is a list of people and organizations that have contributed +# to the Process project. Names should be added to the list like so: +# +# Name/Organization + +Google Inc. + +Bartek Pacia / @bartekpacia diff --git a/pkgs/process/CHANGELOG.md b/pkgs/process/CHANGELOG.md new file mode 100644 index 000000000..3791f2195 --- /dev/null +++ b/pkgs/process/CHANGELOG.md @@ -0,0 +1,209 @@ +## 5.0.4 + +* Updates minimum supported SDK version to Flutter 3.22/Dart 3.4. +* Move the package into the `dart-lang/tools` repository. + +## 5.0.3 + +* Adds `missing_code_block_language_in_doc_comment` lint. +* Updates minimum supported SDK version to Flutter 3.19/Dart 3.3. + +## 5.0.2 + +* Removes mention of the removed record/replay feature from README. +* Updates minimum supported SDK version to Flutter 3.10/Dart 3.0. +* Fixes new lint warnings. + +## 5.0.1 + +* Transfers the package source from https://github.com/google/process.dart to + https://github.com/flutter/packages. + +## 5.0.0 + +* Remove the `covariant` keyword from `stderrEncoding` and `stdoutEncoding` + parameters. +* Update dependencies to work on Dart 3. +* Bumped min SDK dependency to nearest non-prerelease version (2.14.0) + +## 4.2.4 + +* Mark `stderrEncoding` and `stdoutEncoding` parameters as nullable again, + now that the upstream SDK issue has been fixed. + +## 4.2.3 + +* Rollback to version 4.2.1 (https://github.com/google/process.dart/issues/64) + +## 4.2.2 + +* Mark `stderrEncoding` and `stdoutEncoding` parameters as nullable. + +## 4.2.1 + +* Added custom exception types `ProcessPackageException` and + `ProcessPackageExecutableNotFoundException` to provide extra + information from exception conditions. + +## 4.2.0 + +* Fix the signature of `ProcessManager.canRun` to be consistent with + `LocalProcessManager`. + +## 4.1.1 + +* Fixed `getExecutablePath()` to only return path items that are + executable and readable to the user. + +## 4.1.0 + +* Fix the signatures of `ProcessManager.run`, `.runSync`, and `.start` to be + consistent with `LocalProcessManager`'s. +* Added more details to the `ArgumentError` thrown when a command cannot be resolved + to an executable. + +## 4.0.0 + +* First stable null safe release. + +## 4.0.0-nullsafety.4 + +* Update supported SDK range. + +## 4.0.0-nullsafety.3 + +* Update supported SDK range. + +## 4.0.0-nullsafety.2 + +* Update supported SDK range. + +## 4.0.0-nullsafety.1 + +* Migrate to null-safety. +* Remove record/replay functionality. +* Remove implicit casts in preparation for null-safety. +* Remove dependency on `package:intl` and `package:meta`. + +## 3.0.13 + +* Handle `currentDirectory` throwing an exception in `getExecutablePath()`. + +## 3.0.12 + +* Updated version constraint on intl. + +## 3.0.11 + +* Fix bug: don't add quotes if the file name already has quotes. + +## 3.0.10 + +* Added quoted strings to indicate where the command name ends and the arguments +begin otherwise, the file name is ambiguous on Windows. + +## 3.0.9 + +* Fixed bug in `ProcessWrapper` + +## 3.0.8 + +* Fixed bug in `ProcessWrapper` + +## 3.0.7 + +* Renamed `Process` to `ProcessWrapper` + +## 3.0.6 + +* Added class `Process`, a simple wrapper around dart:io's `Process` class. + +## 3.0.5 + +* Fixes for missing_return analysis errors with 2.10.0-dev.1.0. + +## 3.0.4 + +* Fix unit tests +* Update SDK constraint to 3. + +## 3.0.3 + +* Update dependency on `package:file` + +## 3.0.2 + +* Remove upper case constants. +* Update SDK constraint to 2.0.0-dev.54.0. +* Fix tests for Dart 2. + +## 3.0.1 + +* General cleanup + +## 3.0.0 + +* Cleanup getExecutablePath() to better respect the platform + +## 2.0.9 + +* Bumped `package:file` dependency + +### 2.0.8 + +* Fixed method getArguments to qualify the map method with the specific + String type + +### 2.0.7 + +* Remove `set exitCode` instances + +### 2.0.6 + +* Fix SDK constraint. +* rename .analysis_options file to analaysis_options.yaml. +* Use covariant in place of @checked. +* Update comment style generics. + +### 2.0.5 + +* Bumped maximum Dart SDK version to 2.0.0-dev.infinity + +### 2.0.4 + +* relax dependency requirement for `intl` + +### 2.0.3 + +* relax dependency requirement for `platform` + +## 2.0.2 + +* Fix a strong mode function expression return type inference bug with Dart + 1.23.0-dev.10.0. + +## 2.0.1 + +* Fixed bug in `ReplayProcessManager` whereby it could try to write to `stdout` + or `stderr` after the streams were closed. + +## 2.0.0 + +* Bumped `package:file` dependency to 2.0.1 + +## 1.1.0 + +* Added support to transparently find the right executable under Windows. + +## 1.0.1 + +* The `executable` and `arguments` parameters have been merged into one + `command` parameter in the `run`, `runSync`, and `start` methods of + `ProcessManager`. +* Added support for sanitization of command elements in + `RecordingProcessManager` and `ReplayProcessManager` via the `CommandElement` + class. + +## 1.0.0 + +* Initial version diff --git a/pkgs/process/LICENSE b/pkgs/process/LICENSE new file mode 100644 index 000000000..ed771e647 --- /dev/null +++ b/pkgs/process/LICENSE @@ -0,0 +1,27 @@ +Copyright 2013, the Dart project authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/pkgs/process/README.md b/pkgs/process/README.md new file mode 100644 index 000000000..9cd7dde1d --- /dev/null +++ b/pkgs/process/README.md @@ -0,0 +1,17 @@ +[![pub package](https://img.shields.io/pub/v/process.svg)](https://pub.dev/packages/process) +[![package publisher](https://img.shields.io/pub/publisher/process.svg)](https://pub.dev/packages/process/publisher) + +A pluggable, mockable process invocation abstraction for Dart. + +## What's this? + +A generic process invocation abstraction for Dart. + +Like `dart:io`, `package:process` supplies a rich, Dart-idiomatic API for +spawning OS processes. + +Unlike `dart:io`, `package:process` requires processes to be started with +[ProcessManager], which allows for easy mocking and testing of code that +spawns processes in a hermetic way. + +[ProcessManager]: https://pub.dev/documentation/process/latest/process/ProcessManager-class.html diff --git a/pkgs/process/dart_test.yaml b/pkgs/process/dart_test.yaml new file mode 100644 index 000000000..91ec220b8 --- /dev/null +++ b/pkgs/process/dart_test.yaml @@ -0,0 +1 @@ +test_on: vm diff --git a/pkgs/process/lib/process.dart b/pkgs/process/lib/process.dart new file mode 100644 index 000000000..2618e6d2a --- /dev/null +++ b/pkgs/process/lib/process.dart @@ -0,0 +1,8 @@ +// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +export 'src/interface/exceptions.dart'; +export 'src/interface/local_process_manager.dart'; +export 'src/interface/process_manager.dart'; +export 'src/interface/process_wrapper.dart'; diff --git a/pkgs/process/lib/src/interface/common.dart b/pkgs/process/lib/src/interface/common.dart new file mode 100644 index 000000000..d374631cb --- /dev/null +++ b/pkgs/process/lib/src/interface/common.dart @@ -0,0 +1,165 @@ +// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file +// for details. 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:file/file.dart'; +import 'package:file/local.dart'; +import 'package:path/path.dart' show Context; +import 'package:platform/platform.dart'; + +import 'exceptions.dart'; + +const Map _osToPathStyle = { + 'linux': 'posix', + 'macos': 'posix', + 'android': 'posix', + 'ios': 'posix', + 'fuchsia': 'posix', + 'windows': 'windows', +}; + +/// Sanitizes the executable path on Windows. +/// https://github.com/dart-lang/sdk/issues/37751 +String sanitizeExecutablePath(String executable, + {Platform platform = const LocalPlatform()}) { + if (executable.isEmpty) { + return executable; + } + if (!platform.isWindows) { + return executable; + } + if (executable.contains(' ') && !executable.contains('"')) { + // Use quoted strings to indicate where the file name ends and the arguments begin; + // otherwise, the file name is ambiguous. + return '"$executable"'; + } + return executable; +} + +/// Searches the `PATH` for the executable that [executable] is supposed to launch. +/// +/// This first builds a list of candidate paths where the executable may reside. +/// If [executable] is already an absolute path, then the `PATH` environment +/// variable will not be consulted, and the specified absolute path will be the +/// only candidate that is considered. +/// +/// Once the list of candidate paths has been constructed, this will pick the +/// first such path that represents an existent file. +/// +/// Return `null` if there were no viable candidates, meaning the executable +/// could not be found. +/// +/// If [platform] is not specified, it will default to the current platform. +String? getExecutablePath( + String executable, + String? workingDirectory, { + Platform platform = const LocalPlatform(), + FileSystem fs = const LocalFileSystem(), + bool throwOnFailure = false, +}) { + assert(_osToPathStyle[platform.operatingSystem] == fs.path.style.name); + try { + workingDirectory ??= fs.currentDirectory.path; + } on FileSystemException { + // The `currentDirectory` getter can throw a FileSystemException for example + // when the process doesn't have read/list permissions in each component of + // the cwd path. In this case, fall back on '.'. + workingDirectory ??= '.'; + } + final Context context = + Context(style: fs.path.style, current: workingDirectory); + + // TODO(goderbauer): refactor when github.com/google/platform.dart/issues/2 + // is available. + final String pathSeparator = platform.isWindows ? ';' : ':'; + + List extensions = []; + if (platform.isWindows && context.extension(executable).isEmpty) { + extensions = platform.environment['PATHEXT']!.split(pathSeparator); + } + + List candidates = []; + List searchPath; + if (executable.contains(context.separator)) { + // Deal with commands that specify a relative or absolute path differently. + searchPath = [workingDirectory]; + } else { + searchPath = platform.environment['PATH']!.split(pathSeparator); + } + candidates = _getCandidatePaths(executable, searchPath, extensions, context); + final List foundCandidates = []; + for (final String path in candidates) { + final File candidate = fs.file(path); + final FileStat stat = candidate.statSync(); + // Only return files or links that exist. + if (stat.type == FileSystemEntityType.notFound || + stat.type == FileSystemEntityType.directory) { + continue; + } + + // File exists, but we don't know if it's readable/executable yet. + foundCandidates.add(candidate.path); + + const int isExecutable = 0x40; + const int isReadable = 0x100; + const int isExecutableAndReadable = isExecutable | isReadable; + // Should only return files or links that are readable and executable by the + // user. + + // On Windows it's not actually possible to only return files that are + // readable, since Dart reports files that have had read permission removed + // as being readable, but not checking for it is the same as checking for it + // and finding it readable, so we use the same check here on all platforms, + // so that if Dart ever gets fixed, it'll just work. + if (stat.mode & isExecutableAndReadable == isExecutableAndReadable) { + return path; + } + } + if (throwOnFailure) { + if (foundCandidates.isNotEmpty) { + throw ProcessPackageExecutableNotFoundException( + executable, + message: + 'Found candidates, but lacked sufficient permissions to execute "$executable".', + workingDirectory: workingDirectory, + candidates: foundCandidates, + searchPath: searchPath, + ); + } else { + throw ProcessPackageExecutableNotFoundException( + executable, + message: 'Failed to find "$executable" in the search path.', + workingDirectory: workingDirectory, + searchPath: searchPath, + ); + } + } + return null; +} + +/// Returns all possible combinations of `$searchPath\$command.$ext` for +/// `searchPath` in [searchPaths] and `ext` in [extensions]. +/// +/// If [extensions] is empty, it will just enumerate all +/// `$searchPath\$command`. +/// If [command] is an absolute path, it will just enumerate +/// `$command.$ext`. +List _getCandidatePaths( + String command, + List searchPaths, + List extensions, + Context context, +) { + final List withExtensions = extensions.isNotEmpty + ? extensions.map((String ext) => '$command$ext').toList() + : [command]; + if (context.isAbsolute(command)) { + return withExtensions; + } + return searchPaths + .map((String path) => + withExtensions.map((String command) => context.join(path, command))) + .expand((Iterable e) => e) + .toList() + .cast(); +} diff --git a/pkgs/process/lib/src/interface/exceptions.dart b/pkgs/process/lib/src/interface/exceptions.dart new file mode 100644 index 000000000..dec80d38c --- /dev/null +++ b/pkgs/process/lib/src/interface/exceptions.dart @@ -0,0 +1,101 @@ +// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:io' show ProcessException; + +/// A specialized exception class for this package, so that it can throw +/// customized exceptions with more information. +class ProcessPackageException extends ProcessException { + /// Create a const ProcessPackageException. + /// + /// The [executable] should be the name of the executable to be run. + /// + /// The optional [workingDirectory] is the directory where the command + /// execution is attempted. + /// + /// The optional [arguments] is a list of the arguments to given to the + /// executable, already separated. + /// + /// The optional [message] is an additional message to be included in the + /// exception string when printed. + /// + /// The optional [errorCode] is the error code received when the executable + /// was run. Zero means it ran successfully, or that no error code was + /// available. + /// + /// See [ProcessException] for more information. + const ProcessPackageException( + String executable, { + List arguments = const [], + String message = '', + int errorCode = 0, + this.workingDirectory, + }) : super(executable, arguments, message, errorCode); + + /// Creates a [ProcessPackageException] from a [ProcessException]. + factory ProcessPackageException.fromProcessException( + ProcessException exception, { + String? workingDirectory, + }) { + return ProcessPackageException( + exception.executable, + arguments: exception.arguments, + message: exception.message, + errorCode: exception.errorCode, + workingDirectory: workingDirectory, + ); + } + + /// The optional working directory that the command was being executed in. + final String? workingDirectory; + + // Don't implement a toString() for this exception, since code may be + // depending upon the format of ProcessException.toString(). +} + +/// An exception for when an executable is not found that was expected to be found. +class ProcessPackageExecutableNotFoundException + extends ProcessPackageException { + /// Creates a const ProcessPackageExecutableNotFoundException + /// + /// The optional [candidates] are the files matching the expected executable + /// on the [searchPath]. + /// + /// The optional [searchPath] is the list of directories searched for the + /// expected executable. + /// + /// See [ProcessPackageException] for more information. + const ProcessPackageExecutableNotFoundException( + super.executable, { + super.arguments, + super.message, + super.errorCode, + super.workingDirectory, + this.candidates = const [], + this.searchPath = const [], + }); + + /// The list of non-viable executable candidates found. + final List candidates; + + /// The search path used to find candidates. + final List searchPath; + + @override + String toString() { + final StringBuffer buffer = + StringBuffer('ProcessPackageExecutableNotFoundException: $message\n'); + // Don't add an extra space if there are no arguments. + final String args = arguments.isNotEmpty ? ' ${arguments.join(' ')}' : ''; + buffer.writeln(' Command: $executable$args'); + if (workingDirectory != null && workingDirectory!.isNotEmpty) { + buffer.writeln(' Working Directory: $workingDirectory'); + } + if (candidates.isNotEmpty) { + buffer.writeln(' Candidates:\n ${candidates.join('\n ')}'); + } + buffer.writeln(' Search Path:\n ${searchPath.join('\n ')}'); + return buffer.toString(); + } +} diff --git a/pkgs/process/lib/src/interface/local_process_manager.dart b/pkgs/process/lib/src/interface/local_process_manager.dart new file mode 100644 index 000000000..ab7e96a31 --- /dev/null +++ b/pkgs/process/lib/src/interface/local_process_manager.dart @@ -0,0 +1,152 @@ +// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:convert'; +import 'dart:io' + show + Process, + ProcessException, + ProcessResult, + ProcessSignal, + ProcessStartMode, + systemEncoding; + +import 'common.dart'; +import 'exceptions.dart'; +import 'process_manager.dart'; + +/// Local implementation of the `ProcessManager` interface. +/// +/// This implementation delegates directly to the corresponding static methods +/// in `dart:io`. +/// +/// All methods that take a `command` will run `toString()` on the command +/// elements to derive the executable and arguments that should be passed to +/// the underlying `dart:io` methods. Thus, the degenerate case of +/// `List` will trivially work as expected. +class LocalProcessManager implements ProcessManager { + /// Creates a new `LocalProcessManager`. + const LocalProcessManager(); + + @override + Future start( + List command, { + String? workingDirectory, + Map? environment, + bool includeParentEnvironment = true, + bool runInShell = false, + ProcessStartMode mode = ProcessStartMode.normal, + }) { + try { + return Process.start( + sanitizeExecutablePath(_getExecutable( + command, + workingDirectory, + runInShell, + )), + _getArguments(command), + workingDirectory: workingDirectory, + environment: environment, + includeParentEnvironment: includeParentEnvironment, + runInShell: runInShell, + mode: mode, + ); + } on ProcessException catch (exception) { + throw ProcessPackageException.fromProcessException(exception, + workingDirectory: workingDirectory); + } + } + + @override + Future run( + List command, { + String? workingDirectory, + Map? environment, + bool includeParentEnvironment = true, + bool runInShell = false, + Encoding? stdoutEncoding = systemEncoding, + Encoding? stderrEncoding = systemEncoding, + }) { + try { + return Process.run( + sanitizeExecutablePath(_getExecutable( + command, + workingDirectory, + runInShell, + )), + _getArguments(command), + workingDirectory: workingDirectory, + environment: environment, + includeParentEnvironment: includeParentEnvironment, + runInShell: runInShell, + stdoutEncoding: stdoutEncoding, + stderrEncoding: stderrEncoding, + ); + } on ProcessException catch (exception) { + throw ProcessPackageException.fromProcessException(exception, + workingDirectory: workingDirectory); + } + } + + @override + ProcessResult runSync( + List command, { + String? workingDirectory, + Map? environment, + bool includeParentEnvironment = true, + bool runInShell = false, + Encoding? stdoutEncoding = systemEncoding, + Encoding? stderrEncoding = systemEncoding, + }) { + try { + return Process.runSync( + sanitizeExecutablePath(_getExecutable( + command, + workingDirectory, + runInShell, + )), + _getArguments(command), + workingDirectory: workingDirectory, + environment: environment, + includeParentEnvironment: includeParentEnvironment, + runInShell: runInShell, + stdoutEncoding: stdoutEncoding, + stderrEncoding: stderrEncoding, + ); + } on ProcessException catch (exception) { + throw ProcessPackageException.fromProcessException(exception, + workingDirectory: workingDirectory); + } + } + + @override + bool canRun(covariant String executable, {String? workingDirectory}) => + getExecutablePath(executable, workingDirectory) != null; + + @override + bool killPid(int pid, [ProcessSignal signal = ProcessSignal.sigterm]) { + return Process.killPid(pid, signal); + } +} + +String _getExecutable( + List command, String? workingDirectory, bool runInShell) { + final String commandName = command.first.toString(); + if (runInShell) { + return commandName; + } + return getExecutablePath( + commandName, + workingDirectory, + throwOnFailure: true, + )!; +} + +List _getArguments(List command) => + // Adding a specific type to map in order to workaround dart issue + // https://github.com/dart-lang/sdk/issues/32414 + command + .skip(1) + .map((dynamic element) => element.toString()) + .toList(); diff --git a/pkgs/process/lib/src/interface/process_manager.dart b/pkgs/process/lib/src/interface/process_manager.dart new file mode 100644 index 000000000..69f6a2a79 --- /dev/null +++ b/pkgs/process/lib/src/interface/process_manager.dart @@ -0,0 +1,187 @@ +// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:convert'; +import 'dart:io' + show + Process, + ProcessResult, + ProcessSignal, + ProcessStartMode, + systemEncoding; + +/// Manages the creation of abstract processes. +/// +/// Using instances of this class provides level of indirection from the static +/// methods in the [Process] class, which in turn allows the underlying +/// implementation to be mocked out or decorated for testing and debugging +/// purposes. +abstract class ProcessManager { + /// Starts a process by running the specified [command]. + /// + /// The first element in [command] will be treated as the executable to run, + /// with subsequent elements being passed as arguments to the executable. It + /// is left to implementations to decide what element types they support in + /// the [command] list. + /// + /// Returns a `Future` that completes with a Process instance when + /// the process has been successfully started. That [Process] object can be + /// used to interact with the process. If the process cannot be started, the + /// returned [Future] completes with an exception. + /// + /// Use [workingDirectory] to set the working directory for the process. Note + /// that the change of directory occurs before executing the process on some + /// platforms, which may have impact when using relative paths for the + /// executable and the arguments. + /// + /// Use [environment] to set the environment variables for the process. If not + /// set, the environment of the parent process is inherited. Currently, only + /// US-ASCII environment variables are supported and errors are likely to occur + /// if an environment variable with code-points outside the US-ASCII range is + /// passed in. + /// + /// If [includeParentEnvironment] is `true`, the process's environment will + /// include the parent process's environment, with [environment] taking + /// precedence. Default is `true`. + /// + /// If [runInShell] is `true`, the process will be spawned through a system + /// shell. On Linux and OS X, `/bin/sh` is used, while + /// `%WINDIR%\system32\cmd.exe` is used on Windows. + /// + /// Users must read all data coming on the `stdout` and `stderr` + /// streams of processes started with [start]. If the user + /// does not read all data on the streams the underlying system + /// resources will not be released since there is still pending data. + /// + /// The following code uses `start` to grep for `main` in the + /// file `test.dart` on Linux. + /// + /// ```dart + /// ProcessManager mgr = new LocalProcessManager(); + /// mgr.start(['grep', '-i', 'main', 'test.dart']).then((process) { + /// stdout.addStream(process.stdout); + /// stderr.addStream(process.stderr); + /// }); + /// ``` + /// + /// If [mode] is [ProcessStartMode.normal] (the default) a child + /// process will be started with `stdin`, `stdout` and `stderr` + /// connected. + /// + /// If `mode` is [ProcessStartMode.detached] a detached process will + /// be created. A detached process has no connection to its parent, + /// and can keep running on its own when the parent dies. The only + /// information available from a detached process is its `pid`. There + /// is no connection to its `stdin`, `stdout` or `stderr`, nor will + /// the process' exit code become available when it terminates. + /// + /// If `mode` is [ProcessStartMode.detachedWithStdio] a detached + /// process will be created where the `stdin`, `stdout` and `stderr` + /// are connected. The creator can communicate with the child through + /// these. The detached process will keep running even if these + /// communication channels are closed. The process' exit code will + /// not become available when it terminated. + /// + /// The default value for `mode` is `ProcessStartMode.normal`. + Future start( + List command, { + String? workingDirectory, + Map? environment, + bool includeParentEnvironment = true, + bool runInShell = false, + ProcessStartMode mode = ProcessStartMode.normal, + }); + + /// Starts a process and runs it non-interactively to completion. + /// + /// The first element in [command] will be treated as the executable to run, + /// with subsequent elements being passed as arguments to the executable. It + /// is left to implementations to decide what element types they support in + /// the [command] list. + /// + /// Use [workingDirectory] to set the working directory for the process. Note + /// that the change of directory occurs before executing the process on some + /// platforms, which may have impact when using relative paths for the + /// executable and the arguments. + /// + /// Use [environment] to set the environment variables for the process. If not + /// set the environment of the parent process is inherited. Currently, only + /// US-ASCII environment variables are supported and errors are likely to occur + /// if an environment variable with code-points outside the US-ASCII range is + /// passed in. + /// + /// If [includeParentEnvironment] is `true`, the process's environment will + /// include the parent process's environment, with [environment] taking + /// precedence. Default is `true`. + /// + /// If [runInShell] is true, the process will be spawned through a system + /// shell. On Linux and OS X, `/bin/sh` is used, while + /// `%WINDIR%\system32\cmd.exe` is used on Windows. + /// + /// The encoding used for decoding `stdout` and `stderr` into text is + /// controlled through [stdoutEncoding] and [stderrEncoding]. The + /// default encoding is [systemEncoding]. If `null` is used no + /// decoding will happen and the [ProcessResult] will hold binary + /// data. + /// + /// Returns a `Future` that completes with the + /// result of running the process, i.e., exit code, standard out and + /// standard in. + /// + /// The following code uses `run` to grep for `main` in the + /// file `test.dart` on Linux. + /// + /// ```dart + /// ProcessManager mgr = new LocalProcessManager(); + /// mgr.run('grep', ['-i', 'main', 'test.dart']).then((result) { + /// stdout.write(result.stdout); + /// stderr.write(result.stderr); + /// }); + /// ``` + Future run( + List command, { + String? workingDirectory, + Map? environment, + bool includeParentEnvironment = true, + bool runInShell = false, + Encoding? stdoutEncoding = systemEncoding, + Encoding? stderrEncoding = systemEncoding, + }); + + /// Starts a process and runs it to completion. This is a synchronous + /// call and will block until the child process terminates. + /// + /// The arguments are the same as for [run]`. + /// + /// Returns a `ProcessResult` with the result of running the process, + /// i.e., exit code, standard out and standard in. + ProcessResult runSync( + List command, { + String? workingDirectory, + Map? environment, + bool includeParentEnvironment = true, + bool runInShell = false, + Encoding? stdoutEncoding = systemEncoding, + Encoding? stderrEncoding = systemEncoding, + }); + + /// Returns `true` if the [executable] exists and if it can be executed. + bool canRun(dynamic executable, {String? workingDirectory}); + + /// Kills the process with id [pid]. + /// + /// Where possible, sends the [signal] to the process with id + /// `pid`. This includes Linux and OS X. The default signal is + /// [ProcessSignal.sigterm] which will normally terminate the + /// process. + /// + /// On platforms without signal support, including Windows, the call + /// just terminates the process with id `pid` in a platform specific + /// way, and the `signal` parameter is ignored. + /// + /// Returns `true` if the signal is successfully delivered to the + /// process. Otherwise the signal could not be sent, usually meaning + /// that the process is already dead. + bool killPid(int pid, [ProcessSignal signal = ProcessSignal.sigterm]); +} diff --git a/pkgs/process/lib/src/interface/process_wrapper.dart b/pkgs/process/lib/src/interface/process_wrapper.dart new file mode 100644 index 000000000..9e5307129 --- /dev/null +++ b/pkgs/process/lib/src/interface/process_wrapper.dart @@ -0,0 +1,83 @@ +// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:async'; +import 'dart:io' as io; + +/// A wrapper around an [io.Process] class that adds some convenience methods. +class ProcessWrapper implements io.Process { + /// Constructs a [ProcessWrapper] object that delegates to the specified + /// underlying object. + ProcessWrapper(this._delegate) + : _stdout = StreamController>(), + _stderr = StreamController>(), + _stdoutDone = Completer(), + _stderrDone = Completer() { + _monitorStdioStream(_delegate.stdout, _stdout, _stdoutDone); + _monitorStdioStream(_delegate.stderr, _stderr, _stderrDone); + } + + final io.Process _delegate; + final StreamController> _stdout; + final StreamController> _stderr; + final Completer _stdoutDone; + final Completer _stderrDone; + + /// Listens to the specified [stream], repeating events on it via + /// [controller], and completing [completer] once the stream is done. + void _monitorStdioStream( + Stream> stream, + StreamController> controller, + Completer completer, + ) { + stream.listen( + controller.add, + onError: controller.addError, + onDone: () { + controller.close(); + completer.complete(); + }, + ); + } + + @override + Future get exitCode => _delegate.exitCode; + + /// A [Future] that completes when the process has exited and its standard + /// output and error streams have closed. + /// + /// This exists as an alternative to [exitCode], which does not guarantee + /// that the stdio streams have closed (it is possible for the exit code to + /// be available before stdout and stderr have closed). + /// + /// The future returned here will complete with the exit code of the process. + Future get done async { + late int result; + await Future.wait(>[ + _stdoutDone.future, + _stderrDone.future, + _delegate.exitCode.then((int value) { + result = value; + }), + ]); + return result; + } + + @override + bool kill([io.ProcessSignal signal = io.ProcessSignal.sigterm]) { + return _delegate.kill(signal); + } + + @override + int get pid => _delegate.pid; + + @override + io.IOSink get stdin => _delegate.stdin; + + @override + Stream> get stdout => _stdout.stream; + + @override + Stream> get stderr => _stderr.stream; +} diff --git a/pkgs/process/pubspec.yaml b/pkgs/process/pubspec.yaml new file mode 100644 index 000000000..38ad51c8a --- /dev/null +++ b/pkgs/process/pubspec.yaml @@ -0,0 +1,19 @@ +name: process +description: A pluggable, mockable process invocation abstraction for Dart. +version: 5.0.4 +repository: https://github.com/dart-lang/tools/tree/main/pkgs/process +issue_tracker: https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Aprocess + +topics: + - process + +environment: + sdk: ^3.4.0 + +dependencies: + file: '>=6.0.0 <8.0.0' + path: ^1.8.0 + platform: '^3.0.0' + +dev_dependencies: + test: ^1.16.8 diff --git a/pkgs/process/test/src/interface/common_test.dart b/pkgs/process/test/src/interface/common_test.dart new file mode 100644 index 000000000..8290b5ef4 --- /dev/null +++ b/pkgs/process/test/src/interface/common_test.dart @@ -0,0 +1,589 @@ +// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:io' as io; + +import 'package:file/file.dart'; +import 'package:file/local.dart'; +import 'package:file/memory.dart'; +import 'package:platform/platform.dart'; +import 'package:process/process.dart'; +import 'package:process/src/interface/common.dart'; +import 'package:test/test.dart'; + +void main() { + group('getExecutablePath', () { + late FileSystem fs; + late Directory workingDir, dir1, dir2, dir3; + + void initialize(FileSystemStyle style) { + setUp(() { + fs = MemoryFileSystem(style: style); + workingDir = fs.systemTempDirectory.createTempSync('work_dir_'); + dir1 = fs.systemTempDirectory.createTempSync('dir1_'); + dir2 = fs.systemTempDirectory.createTempSync('dir2_'); + dir3 = fs.systemTempDirectory.createTempSync('dir3_'); + }); + } + + tearDown(() { + for (final Directory directory in [ + workingDir, + dir1, + dir2, + dir3 + ]) { + directory.deleteSync(recursive: true); + } + }); + + group('on windows', () { + late Platform platform; + + initialize(FileSystemStyle.windows); + + setUp(() { + platform = FakePlatform( + operatingSystem: 'windows', + environment: { + 'PATH': '${dir1.path};${dir2.path}', + 'PATHEXT': '.exe;.bat' + }, + ); + }); + + test('absolute', () { + String command = fs.path.join(dir3.path, 'bla.exe'); + final String expectedPath = command; + fs.file(command).createSync(); + + String? executablePath = getExecutablePath( + command, + workingDir.path, + platform: platform, + fs: fs, + ); + _expectSamePath(executablePath, expectedPath); + + command = fs.path.withoutExtension(command); + executablePath = getExecutablePath( + command, + workingDir.path, + platform: platform, + fs: fs, + ); + _expectSamePath(executablePath, expectedPath); + }); + + test('in path', () { + String command = 'bla.exe'; + final String expectedPath = fs.path.join(dir2.path, command); + fs.file(expectedPath).createSync(); + + String? executablePath = getExecutablePath( + command, + workingDir.path, + platform: platform, + fs: fs, + ); + _expectSamePath(executablePath, expectedPath); + + command = fs.path.withoutExtension(command); + executablePath = getExecutablePath( + command, + workingDir.path, + platform: platform, + fs: fs, + ); + _expectSamePath(executablePath, expectedPath); + }); + + test('in path multiple times', () { + String command = 'bla.exe'; + final String expectedPath = fs.path.join(dir1.path, command); + final String wrongPath = fs.path.join(dir2.path, command); + fs.file(expectedPath).createSync(); + fs.file(wrongPath).createSync(); + + String? executablePath = getExecutablePath( + command, + workingDir.path, + platform: platform, + fs: fs, + ); + _expectSamePath(executablePath, expectedPath); + + command = fs.path.withoutExtension(command); + executablePath = getExecutablePath( + command, + workingDir.path, + platform: platform, + fs: fs, + ); + _expectSamePath(executablePath, expectedPath); + }); + + test('in subdir of work dir', () { + String command = fs.path.join('.', 'foo', 'bla.exe'); + final String expectedPath = fs.path.join(workingDir.path, command); + fs.file(expectedPath).createSync(recursive: true); + + String? executablePath = getExecutablePath( + command, + workingDir.path, + platform: platform, + fs: fs, + ); + _expectSamePath(executablePath, expectedPath); + + command = fs.path.withoutExtension(command); + executablePath = getExecutablePath( + command, + workingDir.path, + platform: platform, + fs: fs, + ); + _expectSamePath(executablePath, expectedPath); + }); + + test('in work dir', () { + String command = fs.path.join('.', 'bla.exe'); + final String expectedPath = fs.path.join(workingDir.path, command); + final String wrongPath = fs.path.join(dir2.path, command); + fs.file(expectedPath).createSync(); + fs.file(wrongPath).createSync(); + + String? executablePath = getExecutablePath( + command, + workingDir.path, + platform: platform, + fs: fs, + ); + _expectSamePath(executablePath, expectedPath); + + command = fs.path.withoutExtension(command); + executablePath = getExecutablePath( + command, + workingDir.path, + platform: platform, + fs: fs, + ); + _expectSamePath(executablePath, expectedPath); + }); + + test('with multiple extensions', () { + const String command = 'foo'; + final String expectedPath = fs.path.join(dir1.path, '$command.exe'); + final String wrongPath1 = fs.path.join(dir1.path, '$command.bat'); + final String wrongPath2 = fs.path.join(dir2.path, '$command.exe'); + fs.file(expectedPath).createSync(); + fs.file(wrongPath1).createSync(); + fs.file(wrongPath2).createSync(); + + final String? executablePath = getExecutablePath( + command, + workingDir.path, + platform: platform, + fs: fs, + ); + _expectSamePath(executablePath, expectedPath); + }); + + test('not found', () { + const String command = 'foo.exe'; + + final String? executablePath = getExecutablePath( + command, + workingDir.path, + platform: platform, + fs: fs, + ); + expect(executablePath, isNull); + }); + + test('not found with throwOnFailure throws exception with match state', + () { + const String command = 'foo.exe'; + expect( + () => getExecutablePath( + command, + workingDir.path, + platform: platform, + fs: fs, + throwOnFailure: true, + ), + throwsA(isA() + .having( + (ProcessPackageExecutableNotFoundException + notFoundException) => + notFoundException.candidates, + 'candidates', + isEmpty) + .having( + (ProcessPackageExecutableNotFoundException + notFoundException) => + notFoundException.workingDirectory, + 'workingDirectory', + equals(workingDir.path)) + .having( + (ProcessPackageExecutableNotFoundException + notFoundException) => + notFoundException.toString(), + 'toString', + contains( + ' Working Directory: C:\\.tmp_rand0\\work_dir_rand0\n' + ' Search Path:\n' + ' C:\\.tmp_rand0\\dir1_rand0\n' + ' C:\\.tmp_rand0\\dir2_rand0\n')))); + }); + + test('when path has spaces', () { + expect( + sanitizeExecutablePath(r'Program Files\bla.exe', + platform: platform), + r'"Program Files\bla.exe"'); + expect( + sanitizeExecutablePath(r'ProgramFiles\bla.exe', platform: platform), + r'ProgramFiles\bla.exe'); + expect( + sanitizeExecutablePath(r'"Program Files\bla.exe"', + platform: platform), + r'"Program Files\bla.exe"'); + expect( + sanitizeExecutablePath(r'"Program Files\bla.exe"', + platform: platform), + r'"Program Files\bla.exe"'); + expect( + sanitizeExecutablePath(r'C:\"Program Files"\bla.exe', + platform: platform), + r'C:\"Program Files"\bla.exe'); + }); + + test('with absolute path when currentDirectory getter throws', () { + final FileSystem fsNoCwd = MemoryFileSystemNoCwd(fs); + final String command = fs.path.join(dir3.path, 'bla.exe'); + final String expectedPath = command; + fs.file(command).createSync(); + + final String? executablePath = getExecutablePath( + command, + null, + platform: platform, + fs: fsNoCwd, + ); + _expectSamePath(executablePath, expectedPath); + }); + + test('with relative path when currentDirectory getter throws', () { + final FileSystem fsNoCwd = MemoryFileSystemNoCwd(fs); + final String command = fs.path.join('.', 'bla.exe'); + + final String? executablePath = getExecutablePath( + command, + null, + platform: platform, + fs: fsNoCwd, + ); + expect(executablePath, isNull); + }); + }); + + group('on Linux', () { + late Platform platform; + + initialize(FileSystemStyle.posix); + + setUp(() { + platform = FakePlatform( + operatingSystem: 'linux', + environment: {'PATH': '${dir1.path}:${dir2.path}'}); + }); + + test('absolute', () { + final String command = fs.path.join(dir3.path, 'bla'); + final String expectedPath = command; + final String wrongPath = fs.path.join(dir3.path, 'bla.bat'); + fs.file(command).createSync(); + fs.file(wrongPath).createSync(); + + final String? executablePath = getExecutablePath( + command, + workingDir.path, + platform: platform, + fs: fs, + ); + _expectSamePath(executablePath, expectedPath); + }); + + test('in path multiple times', () { + const String command = 'xxx'; + final String expectedPath = fs.path.join(dir1.path, command); + final String wrongPath = fs.path.join(dir2.path, command); + fs.file(expectedPath).createSync(); + fs.file(wrongPath).createSync(); + + final String? executablePath = getExecutablePath( + command, + workingDir.path, + platform: platform, + fs: fs, + ); + _expectSamePath(executablePath, expectedPath); + }); + + test('not found', () { + const String command = 'foo'; + + final String? executablePath = getExecutablePath( + command, + workingDir.path, + platform: platform, + fs: fs, + ); + expect(executablePath, isNull); + }); + + test('not found with throwOnFailure throws exception with match state', + () { + const String command = 'foo'; + expect( + () => getExecutablePath( + command, + workingDir.path, + platform: platform, + fs: fs, + throwOnFailure: true, + ), + throwsA(isA() + .having( + (ProcessPackageExecutableNotFoundException + notFoundException) => + notFoundException.candidates, + 'candidates', + isEmpty) + .having( + (ProcessPackageExecutableNotFoundException + notFoundException) => + notFoundException.workingDirectory, + 'workingDirectory', + equals(workingDir.path)) + .having( + (ProcessPackageExecutableNotFoundException + notFoundException) => + notFoundException.toString(), + 'toString', + contains(' Working Directory: /.tmp_rand0/work_dir_rand0\n' + ' Search Path:\n' + ' /.tmp_rand0/dir1_rand0\n' + ' /.tmp_rand0/dir2_rand0\n')))); + }); + + test('when path has spaces', () { + expect( + sanitizeExecutablePath('/usr/local/bin/foo bar', + platform: platform), + '/usr/local/bin/foo bar'); + }); + }); + }); + group('Real Filesystem', () { + // These tests don't use the memory filesystem because Dart can't modify file + // executable permissions, so we have to create them with actual commands. + + late Platform platform; + late Directory tmpDir; + late Directory pathDir1; + late Directory pathDir2; + late Directory pathDir3; + late Directory pathDir4; + late Directory pathDir5; + late File command1; + late File command2; + late File command3; + late File command4; + late File command5; + const Platform localPlatform = LocalPlatform(); + late FileSystem fs; + + setUp(() { + fs = const LocalFileSystem(); + tmpDir = fs.systemTempDirectory.createTempSync(); + pathDir1 = tmpDir.childDirectory('path1')..createSync(); + pathDir2 = tmpDir.childDirectory('path2')..createSync(); + pathDir3 = tmpDir.childDirectory('path3')..createSync(); + pathDir4 = tmpDir.childDirectory('path4')..createSync(); + pathDir5 = tmpDir.childDirectory('path5')..createSync(); + command1 = pathDir1.childFile('command')..createSync(); + command2 = pathDir2.childFile('command')..createSync(); + command3 = pathDir3.childFile('command')..createSync(); + command4 = pathDir4.childFile('command')..createSync(); + command5 = pathDir5.childFile('command')..createSync(); + platform = FakePlatform( + operatingSystem: localPlatform.operatingSystem, + environment: { + 'PATH': [ + pathDir1, + pathDir2, + pathDir3, + pathDir4, + pathDir5, + ].map((Directory dir) => dir.absolute.path).join(':'), + }, + ); + }); + + tearDown(() { + tmpDir.deleteSync(recursive: true); + }); + + test('Only returns executables in PATH', () { + if (localPlatform.isWindows) { + // Windows doesn't check for executable-ness, and we can't run 'chmod' + // on Windows anyhow. + return; + } + + // Make the second command in the path executable, but not the first. + // No executable permissions + io.Process.runSync('chmod', ['0644', '--', command1.path]); + // Only group executable permissions + io.Process.runSync('chmod', ['0645', '--', command2.path]); + // Only other executable permissions + io.Process.runSync('chmod', ['0654', '--', command3.path]); + // All executable permissions, but not readable + io.Process.runSync('chmod', ['0311', '--', command4.path]); + // All executable permissions + io.Process.runSync('chmod', ['0755', '--', command5.path]); + + final String? executablePath = getExecutablePath( + 'command', + tmpDir.path, + platform: platform, + fs: fs, + ); + + // Make sure that the path returned is for the last command, since that + // one comes last in the PATH, but is the only one executable by the + // user. + _expectSamePath(executablePath, command5.absolute.path); + }); + + test( + 'Test that finding non-executable paths throws with proper information', + () { + if (localPlatform.isWindows) { + // Windows doesn't check for executable-ness, and we can't run 'chmod' + // on Windows anyhow. + return; + } + + // Make the second command in the path executable, but not the first. + // No executable permissions + io.Process.runSync('chmod', ['0644', '--', command1.path]); + // Only group executable permissions + io.Process.runSync('chmod', ['0645', '--', command2.path]); + // Only other executable permissions + io.Process.runSync('chmod', ['0654', '--', command3.path]); + // All executable permissions, but not readable + io.Process.runSync('chmod', ['0311', '--', command4.path]); + + expect( + () => getExecutablePath( + 'command', + tmpDir.path, + platform: platform, + fs: fs, + throwOnFailure: true, + ), + throwsA(isA() + .having( + (ProcessPackageExecutableNotFoundException + notFoundException) => + notFoundException.candidates, + 'candidates', + equals([ + '${tmpDir.path}/path1/command', + '${tmpDir.path}/path2/command', + '${tmpDir.path}/path3/command', + '${tmpDir.path}/path4/command', + '${tmpDir.path}/path5/command', + ])) + .having( + (ProcessPackageExecutableNotFoundException + notFoundException) => + notFoundException.toString(), + 'toString', + contains( + 'ProcessPackageExecutableNotFoundException: Found candidates, but lacked sufficient permissions to execute "command".\n' + ' Command: command\n' + ' Working Directory: ${tmpDir.path}\n' + ' Candidates:\n' + ' ${tmpDir.path}/path1/command\n' + ' ${tmpDir.path}/path2/command\n' + ' ${tmpDir.path}/path3/command\n' + ' ${tmpDir.path}/path4/command\n' + ' ${tmpDir.path}/path5/command\n' + ' Search Path:\n' + ' ${tmpDir.path}/path1\n' + ' ${tmpDir.path}/path2\n' + ' ${tmpDir.path}/path3\n' + ' ${tmpDir.path}/path4\n' + ' ${tmpDir.path}/path5\n')))); + }); + + test('Test that finding no executable paths throws with proper information', + () { + if (localPlatform.isWindows) { + // Windows doesn't check for executable-ness, and we can't run 'chmod' + // on Windows anyhow. + return; + } + + expect( + () => getExecutablePath( + 'non-existent-command', + tmpDir.path, + platform: platform, + fs: fs, + throwOnFailure: true, + ), + throwsA(isA() + .having( + (ProcessPackageExecutableNotFoundException + notFoundException) => + notFoundException.candidates, + 'candidates', + isEmpty) + .having( + (ProcessPackageExecutableNotFoundException + notFoundException) => + notFoundException.toString(), + 'toString', + contains( + 'ProcessPackageExecutableNotFoundException: Failed to find "non-existent-command" in the search path.\n' + ' Command: non-existent-command\n' + ' Working Directory: ${tmpDir.path}\n' + ' Search Path:\n' + ' ${tmpDir.path}/path1\n' + ' ${tmpDir.path}/path2\n' + ' ${tmpDir.path}/path3\n' + ' ${tmpDir.path}/path4\n' + ' ${tmpDir.path}/path5\n')))); + }); + }); +} + +void _expectSamePath(String? actual, String? expected) { + expect(actual, isNotNull); + expect(actual!.toLowerCase(), expected!.toLowerCase()); +} + +class MemoryFileSystemNoCwd extends ForwardingFileSystem { + MemoryFileSystemNoCwd(super.delegate); + + @override + Directory get currentDirectory { + throw const FileSystemException('Access denied'); + } +} diff --git a/pkgs/process/test/src/interface/process_wrapper_test.dart b/pkgs/process/test/src/interface/process_wrapper_test.dart new file mode 100644 index 000000000..27f8cfa2f --- /dev/null +++ b/pkgs/process/test/src/interface/process_wrapper_test.dart @@ -0,0 +1,116 @@ +// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:async'; +import 'dart:convert'; +import 'dart:io' as io; + +import 'package:process/process.dart'; +import 'package:test/test.dart'; + +void main() { + group('ProcessWrapper', () { + late TestProcess delegate; + late ProcessWrapper process; + + setUp(() { + delegate = TestProcess(); + process = ProcessWrapper(delegate); + }); + + group('done', () { + late bool done; + + setUp(() { + done = false; + // ignore: unawaited_futures + process.done.then((int result) { + done = true; + }); + }); + + test('completes only when all done', () async { + expect(done, isFalse); + delegate.exitCodeCompleter.complete(0); + await Future.value(); + expect(done, isFalse); + await delegate.stdoutController.close(); + await Future.value(); + expect(done, isFalse); + await delegate.stderrController.close(); + await Future.value(); + expect(done, isTrue); + expect(await process.exitCode, 0); + }); + + test('works in conjunction with subscribers to stdio streams', () async { + process.stdout + .transform(utf8.decoder) + .transform(const LineSplitter()) + // ignore: avoid_print + .listen(print); + delegate.exitCodeCompleter.complete(0); + await delegate.stdoutController.close(); + await delegate.stderrController.close(); + await Future.value(); + expect(done, isTrue); + }); + }); + + group('stdio', () { + test('streams properly close', () async { + Future testStream( + Stream> stream, + StreamController> controller, + String name, + ) async { + bool closed = false; + stream.listen( + (_) {}, + onDone: () { + closed = true; + }, + ); + await controller.close(); + await Future.value(); + expect(closed, isTrue, reason: 'for $name'); + } + + await testStream(process.stdout, delegate.stdoutController, 'stdout'); + await testStream(process.stderr, delegate.stderrController, 'stderr'); + }); + }); + }); +} + +class TestProcess implements io.Process { + TestProcess([this.pid = 123]) + : exitCodeCompleter = Completer(), + stdoutController = StreamController>(), + stderrController = StreamController>(); + + @override + final int pid; + final Completer exitCodeCompleter; + final StreamController> stdoutController; + final StreamController> stderrController; + + @override + Future get exitCode => exitCodeCompleter.future; + + @override + bool kill([io.ProcessSignal signal = io.ProcessSignal.sigterm]) { + exitCodeCompleter.complete(-1); + return true; + } + + @override + Stream> get stderr => stderrController.stream; + + @override + io.IOSink get stdin => throw UnsupportedError('Not supported'); + + @override + Stream> get stdout => stdoutController.stream; +} diff --git a/pkgs/process/test/utils.dart b/pkgs/process/test/utils.dart new file mode 100644 index 000000000..d35e3cf75 --- /dev/null +++ b/pkgs/process/test/utils.dart @@ -0,0 +1,14 @@ +// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:convert'; + +/// Decodes a UTF8-encoded byte array into a list of Strings, where each list +/// entry represents a line of text. +List decode(List data) => + const LineSplitter().convert(utf8.decode(data)); + +/// Consumes and returns an entire stream of bytes. +Future> consume(Stream> stream) => + stream.expand((List data) => data).toList(); From c79aa93a001b72d5103f40b2dca114baf6f21829 Mon Sep 17 00:00:00 2001 From: Devon Carew Date: Wed, 21 May 2025 14:35:48 -0700 Subject: [PATCH 17/67] have package:lints use package:lints/recommended (#2098) --- .github/labeler.yml | 4 ++-- README.md | 2 ++ pkgs/process/CHANGELOG.md | 1 + pkgs/process/analysis_options.yaml | 1 + pkgs/process/pubspec.yaml | 3 ++- 5 files changed, 8 insertions(+), 3 deletions(-) create mode 100644 pkgs/process/analysis_options.yaml diff --git a/.github/labeler.yml b/.github/labeler.yml index dbaf8e5fa..d3938dc50 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -96,9 +96,9 @@ - changed-files: - any-glob-to-any-file: 'pkgs/pool/**' -'package:procoess': +'package:process': - changed-files: - - any-glob-to-any-file: 'pkgs/procoess/**' + - any-glob-to-any-file: 'pkgs/process/**' 'package:pub_semver': - changed-files: diff --git a/README.md b/README.md index 057f24583..90c481814 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,7 @@ don't naturally belong to other topic monorepos (like | [oauth2](pkgs/oauth2/) | A client library for authenticating with a remote service via OAuth2 on behalf of a user, and making authorized HTTP requests with the user's OAuth2 credentials. | [![issues](https://img.shields.io/badge/issues-4774bc)][oauth2_issues] | [![pub package](https://img.shields.io/pub/v/oauth2.svg)](https://pub.dev/packages/oauth2) | | [package_config](pkgs/package_config/) | Support for reading and writing Dart Package Configuration files. | [![issues](https://img.shields.io/badge/issues-4774bc)][package_config_issues] | [![pub package](https://img.shields.io/pub/v/package_config.svg)](https://pub.dev/packages/package_config) | | [pool](pkgs/pool/) | Manage a finite pool of resources. Useful for controlling concurrent file system or network requests. | [![issues](https://img.shields.io/badge/issues-4774bc)][pool_issues] | [![pub package](https://img.shields.io/pub/v/pool.svg)](https://pub.dev/packages/pool) | +| [process](pkgs/process/) | A pluggable, mockable process invocation abstraction for Dart. | [![issues](https://img.shields.io/badge/issues-4774bc)][process_issues] | [![pub package](https://img.shields.io/pub/v/process.svg)](https://pub.dev/packages/process) | | [pub_semver](pkgs/pub_semver/) | Versions and version constraints implementing pub's versioning policy. This is very similar to vanilla semver, with a few corner cases. | [![issues](https://img.shields.io/badge/issues-4774bc)][pub_semver_issues] | [![pub package](https://img.shields.io/pub/v/pub_semver.svg)](https://pub.dev/packages/pub_semver) | | [pubspec_parse](pkgs/pubspec_parse/) | Simple package for parsing pubspec.yaml files with a type-safe API and rich error reporting. | [![issues](https://img.shields.io/badge/issues-4774bc)][pubspec_parse_issues] | [![pub package](https://img.shields.io/pub/v/pubspec_parse.svg)](https://pub.dev/packages/pubspec_parse) | | [source_map_stack_trace](pkgs/source_map_stack_trace/) | A package for applying source maps to stack traces. | [![issues](https://img.shields.io/badge/issues-4774bc)][source_map_stack_trace_issues] | [![pub package](https://img.shields.io/pub/v/source_map_stack_trace.svg)](https://pub.dev/packages/source_map_stack_trace) | @@ -78,6 +79,7 @@ don't naturally belong to other topic monorepos (like [oauth2_issues]: https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Aoauth2 [package_config_issues]: https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Apackage_config [pool_issues]: https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Apool +[process_issues]: https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Aprocess [pub_semver_issues]: https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Apub_semver [pubspec_parse_issues]: https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Apubspec_parse [source_map_stack_trace_issues]: https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Asource_map_stack_trace diff --git a/pkgs/process/CHANGELOG.md b/pkgs/process/CHANGELOG.md index 3791f2195..bfa1e9664 100644 --- a/pkgs/process/CHANGELOG.md +++ b/pkgs/process/CHANGELOG.md @@ -2,6 +2,7 @@ * Updates minimum supported SDK version to Flutter 3.22/Dart 3.4. * Move the package into the `dart-lang/tools` repository. +* Bumped min SDK dependency to 3.5.0. ## 5.0.3 diff --git a/pkgs/process/analysis_options.yaml b/pkgs/process/analysis_options.yaml new file mode 100644 index 000000000..572dd239d --- /dev/null +++ b/pkgs/process/analysis_options.yaml @@ -0,0 +1 @@ +include: package:lints/recommended.yaml diff --git a/pkgs/process/pubspec.yaml b/pkgs/process/pubspec.yaml index 38ad51c8a..2a4194cfc 100644 --- a/pkgs/process/pubspec.yaml +++ b/pkgs/process/pubspec.yaml @@ -8,7 +8,7 @@ topics: - process environment: - sdk: ^3.4.0 + sdk: ^3.5.0 dependencies: file: '>=6.0.0 <8.0.0' @@ -16,4 +16,5 @@ dependencies: platform: '^3.0.0' dev_dependencies: + lints: ^5.0.0 test: ^1.16.8 From 896db9f08cdbbd5571436bffd831b8db4b8f79f2 Mon Sep 17 00:00:00 2001 From: Devon Carew Date: Wed, 21 May 2025 14:47:17 -0700 Subject: [PATCH 18/67] add an issue template for package:process (#2099) --- .github/ISSUE_TEMPLATE/process.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/process.md diff --git a/.github/ISSUE_TEMPLATE/process.md b/.github/ISSUE_TEMPLATE/process.md new file mode 100644 index 000000000..39ce815f4 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/process.md @@ -0,0 +1,5 @@ +--- +name: "package:process" +about: "Create a bug or file a feature request against package:process." +labels: "package:process" +--- \ No newline at end of file From 7fe3e108bceb4fde72945bec820e236afb6b5edc Mon Sep 17 00:00:00 2001 From: Danny Tuppeny Date: Thu, 22 May 2025 16:53:40 +0100 Subject: [PATCH 19/67] [test_reflective_loader] Pass test locations to `pkg:test` to improve IDE navigation (#2090) --- .github/workflows/test_reflective_loader.yaml | 2 +- pkgs/test_reflective_loader/CHANGELOG.md | 7 ++ .../lib/test_reflective_loader.dart | 37 +++++++--- pkgs/test_reflective_loader/pubspec.yaml | 7 +- .../test/location_test.dart | 69 +++++++++++++++++++ 5 files changed, 108 insertions(+), 14 deletions(-) create mode 100644 pkgs/test_reflective_loader/test/location_test.dart diff --git a/.github/workflows/test_reflective_loader.yaml b/.github/workflows/test_reflective_loader.yaml index 7550fff5f..8e70b85a3 100644 --- a/.github/workflows/test_reflective_loader.yaml +++ b/.github/workflows/test_reflective_loader.yaml @@ -26,7 +26,7 @@ jobs: strategy: fail-fast: false matrix: - sdk: [dev, 3.1] + sdk: [dev, 3.5] steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 diff --git a/pkgs/test_reflective_loader/CHANGELOG.md b/pkgs/test_reflective_loader/CHANGELOG.md index 803eb0e0c..61ba81bde 100644 --- a/pkgs/test_reflective_loader/CHANGELOG.md +++ b/pkgs/test_reflective_loader/CHANGELOG.md @@ -1,3 +1,10 @@ +## 0.3.0 + +- Require Dart `^3.5.0`. +- Update to `package:test` 1.26.1. +- Pass locations of groups/tests to `package:test` to improve locations reported + in the JSON reporter that may be used for navigation in IDEs. + ## 0.2.3 - Require Dart `^3.1.0`. diff --git a/pkgs/test_reflective_loader/lib/test_reflective_loader.dart b/pkgs/test_reflective_loader/lib/test_reflective_loader.dart index cb69bf3ba..9c3a103ce 100644 --- a/pkgs/test_reflective_loader/lib/test_reflective_loader.dart +++ b/pkgs/test_reflective_loader/lib/test_reflective_loader.dart @@ -87,7 +87,8 @@ void defineReflectiveTests(Type type) { { var isSolo = _hasAnnotationInstance(classMirror, soloTest); var className = MirrorSystem.getName(classMirror.simpleName); - group = _Group(isSolo, _combineNames(_currentSuiteName, className)); + group = _Group(isSolo, _combineNames(_currentSuiteName, className), + classMirror.testLocation); _currentGroups.add(group); } @@ -104,7 +105,7 @@ void defineReflectiveTests(Type type) { // test_ if (memberName.startsWith('test_')) { if (_hasSkippedTestAnnotation(memberMirror)) { - group.addSkippedTest(memberName); + group.addSkippedTest(memberName, memberMirror.testLocation); } else { group.addTest(isSolo, memberName, memberMirror, () { if (_hasFailingTestAnnotation(memberMirror) || @@ -137,7 +138,7 @@ void defineReflectiveTests(Type type) { } // skip_test_ if (memberName.startsWith('skip_test_')) { - group.addSkippedTest(memberName); + group.addSkippedTest(memberName, memberMirror.testLocation); } }); @@ -154,7 +155,9 @@ void _addTestsIfTopLevelSuite() { for (var test in group.tests) { if (allTests || test.isSolo) { test_package.test(test.name, test.function, - timeout: test.timeout, skip: test.isSkipped); + timeout: test.timeout, + skip: test.isSkipped, + location: test.location); } } } @@ -304,15 +307,16 @@ class _AssertFailingTest { class _Group { final bool isSolo; final String name; + final test_package.TestLocation? location; final List<_Test> tests = <_Test>[]; - _Group(this.isSolo, this.name); + _Group(this.isSolo, this.name, this.location); bool get hasSoloTest => tests.any((test) => test.isSolo); - void addSkippedTest(String name) { + void addSkippedTest(String name, test_package.TestLocation? location) { var fullName = _combineNames(this.name, name); - tests.add(_Test.skipped(isSolo, fullName)); + tests.add(_Test.skipped(isSolo, fullName, location)); } void addTest(bool isSolo, String name, MethodMirror memberMirror, @@ -320,7 +324,8 @@ class _Group { var fullName = _combineNames(this.name, name); var timeout = _getAnnotationInstance(memberMirror, TestTimeout) as TestTimeout?; - tests.add(_Test(isSolo, fullName, function, timeout?._timeout)); + tests.add(_Test(isSolo, fullName, function, timeout?._timeout, + memberMirror.testLocation)); } } @@ -341,14 +346,26 @@ class _Test { final String name; final _TestFunction function; final test_package.Timeout? timeout; + final test_package.TestLocation? location; final bool isSkipped; - _Test(this.isSolo, this.name, this.function, this.timeout) + _Test(this.isSolo, this.name, this.function, this.timeout, this.location) : isSkipped = false; - _Test.skipped(this.isSolo, this.name) + _Test.skipped(this.isSolo, this.name, this.location) : isSkipped = true, function = (() {}), timeout = null; } + +extension on DeclarationMirror { + test_package.TestLocation? get testLocation { + if (location case var location?) { + return test_package.TestLocation( + location.sourceUri, location.line, location.column); + } else { + return null; + } + } +} diff --git a/pkgs/test_reflective_loader/pubspec.yaml b/pkgs/test_reflective_loader/pubspec.yaml index f63ab0140..262a3498f 100644 --- a/pkgs/test_reflective_loader/pubspec.yaml +++ b/pkgs/test_reflective_loader/pubspec.yaml @@ -1,14 +1,15 @@ name: test_reflective_loader -version: 0.2.3 +version: 0.3.0 description: Support for discovering tests and test suites using reflection. repository: https://github.com/dart-lang/tools/tree/main/pkgs/test_reflective_loader issue_tracker: https://github.com/dart-lang/tools/labels/package%3Atest_reflective_loader environment: - sdk: ^3.1.0 + sdk: ^3.5.0 dependencies: - test: ^1.16.0 + test: ^1.26.1 dev_dependencies: dart_flutter_team_lints: ^3.0.0 + path: ^1.8.0 diff --git a/pkgs/test_reflective_loader/test/location_test.dart b/pkgs/test_reflective_loader/test/location_test.dart new file mode 100644 index 000000000..14984bb83 --- /dev/null +++ b/pkgs/test_reflective_loader/test/location_test.dart @@ -0,0 +1,69 @@ +// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:convert'; +import 'dart:io'; +import 'dart:isolate'; + +import 'package:path/path.dart' as path; +import 'package:test/test.dart'; + +void main() { + test("reports correct locations in the JSON output from 'dart test'", + () async { + var testPackagePath = (await Isolate.resolvePackageUri( + Uri.parse('package:test_reflective_loader/')))! + .toFilePath(); + var testFilePath = path.normalize(path.join( + testPackagePath, '..', 'test', 'test_reflective_loader_test.dart')); + var testFileContent = File(testFilePath).readAsLinesSync(); + var result = await Process.run( + Platform.resolvedExecutable, ['test', '-r', 'json', testFilePath]); + + var error = result.stderr.toString().trim(); + var output = result.stdout.toString().trim(); + + expect(error, isEmpty); + expect(output, isNotEmpty); + + for (var event in LineSplitter.split(output).map(jsonDecode)) { + if (event case {'type': 'testStart', 'test': Map test}) { + var name = test['name'] as String; + + // Skip the "loading" test, it never has a location. + if (name.startsWith('loading')) { + continue; + } + + // Split just the method name from the combined test so we can search + // the source code to ensure the locations match up. + name = name.split('|').last.trim(); + + // Expect locations for all remaining fields. + var url = test['url'] as String; + var line = test['line'] as int; + var column = test['column'] as int; + + expect(path.equals(Uri.parse(url).toFilePath(), testFilePath), isTrue); + + // Verify the location provided matches where this test appears in the + // file. + var lineContent = testFileContent[line - 1]; + // If the line is an annotation, skip to the next line + if (lineContent.trim().startsWith('@')) { + lineContent = testFileContent[line]; + } + expect(lineContent, contains(name), + reason: 'JSON reports test $name on line $line, ' + 'but line content is "$lineContent"'); + + // Verify the column too. + var columnContent = lineContent.substring(column - 1); + expect(columnContent, contains(name), + reason: 'JSON reports test $name at column $column, ' + 'but text at column is "$columnContent"'); + } + } + }); +} From b837c1ead6079ef6c863da60171e535b7a3fe0e5 Mon Sep 17 00:00:00 2001 From: Liam Appelbe Date: Fri, 23 May 2025 09:21:16 +1000 Subject: [PATCH 20/67] [coverage] Partial workspace support (#2095) --- .github/workflows/coverage.yaml | 9 +- pkgs/coverage/CHANGELOG.md | 8 ++ pkgs/coverage/README.md | 24 +++++ pkgs/coverage/bin/test_with_coverage.dart | 90 ++++++++++--------- pkgs/coverage/dart_test.yaml | 5 ++ pkgs/coverage/lib/src/collect.dart | 19 ++-- pkgs/coverage/lib/src/coverage_options.dart | 5 -- pkgs/coverage/lib/src/formatter.dart | 28 +++--- pkgs/coverage/lib/src/hitmap.dart | 9 ++ pkgs/coverage/lib/src/util.dart | 37 ++++---- pkgs/coverage/pubspec.yaml | 3 +- .../test/collect_coverage_api_test.dart | 7 +- .../test/collect_coverage_config_test.dart | 10 +-- pkgs/coverage/test/collect_coverage_test.dart | 6 +- .../test/config_file_locator_test.dart | 4 + .../coverage/test/function_coverage_test.dart | 12 ++- pkgs/coverage/test/lcov_test.dart | 8 +- pkgs/coverage/test/run_and_collect_test.dart | 3 + .../test/test_coverage_options/pubspec.yaml | 1 + pkgs/coverage/test/test_files/pubspec.yaml | 5 ++ pkgs/coverage/test/test_util.dart | 6 +- .../test/test_with_coverage_test.dart | 2 +- pkgs/coverage/test/util_test.dart | 18 ++++ .../pkgs/bar/bar_example/pubspec.yaml | 2 + .../workspace_names/pkgs/bar/pubspec.yaml | 4 + .../pkgs/foo/foo_example/pubspec.yaml | 1 + .../workspace_names/pkgs/foo/pubspec.yaml | 2 + .../test/workspace_names/pubspec.yaml | 4 + 28 files changed, 207 insertions(+), 125 deletions(-) create mode 100644 pkgs/coverage/dart_test.yaml create mode 100644 pkgs/coverage/test/test_coverage_options/pubspec.yaml create mode 100644 pkgs/coverage/test/test_files/pubspec.yaml create mode 100644 pkgs/coverage/test/workspace_names/pkgs/bar/bar_example/pubspec.yaml create mode 100644 pkgs/coverage/test/workspace_names/pkgs/bar/pubspec.yaml create mode 100644 pkgs/coverage/test/workspace_names/pkgs/foo/foo_example/pubspec.yaml create mode 100644 pkgs/coverage/test/workspace_names/pkgs/foo/pubspec.yaml create mode 100644 pkgs/coverage/test/workspace_names/pubspec.yaml diff --git a/.github/workflows/coverage.yaml b/.github/workflows/coverage.yaml index 98e1bb47d..997e33fda 100644 --- a/.github/workflows/coverage.yaml +++ b/.github/workflows/coverage.yaml @@ -56,12 +56,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest, macos-latest, windows-latest] - sdk: [3.4, dev] - exclude: - # VM service times out on windows before Dart 3.5 - # https://github.com/dart-lang/coverage/issues/490 - - os: windows-latest - sdk: 3.4 + sdk: [3.6, dev] steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c @@ -89,7 +84,7 @@ jobs: name: Install dependencies run: dart pub get - name: Collect and report coverage - run: dart run bin/test_with_coverage.dart --port=9292 + run: dart run bin/test_with_coverage.dart - name: Upload coverage uses: coverallsapp/github-action@648a8eb78e6d50909eff900e4ec85cab4524a45b with: diff --git a/pkgs/coverage/CHANGELOG.md b/pkgs/coverage/CHANGELOG.md index 6dc80a537..2a680adbe 100644 --- a/pkgs/coverage/CHANGELOG.md +++ b/pkgs/coverage/CHANGELOG.md @@ -1,3 +1,11 @@ +## 1.14.0 + +- Require Dart ^3.6.0 +- Partial support for workspace packages in `test_wth_coverage`. +- Deprecate `test_wth_coverage`'s `--package-name` flag, because it doesn't make + sense for workspaces. +- Change the default `--port` to 0, allowing the VM to choose a free port. + ## 1.13.1 - Fix a bug where the VM service can be shut down while some coverage diff --git a/pkgs/coverage/README.md b/pkgs/coverage/README.md index 2963c6a97..7a90c8eaa 100644 --- a/pkgs/coverage/README.md +++ b/pkgs/coverage/README.md @@ -43,6 +43,30 @@ dart pub global run coverage:format_coverage --packages=.dart_tool/package_confi For more complicated use cases, where you want to control each of these stages, see the sections below. +#### Workspaces + +package:coverage has partial support for +[workspaces](https://dart.dev/tools/pub/workspaces). You can run +`test_with_coverage` from the root of the workspace to collect coverage for all +the tests in all the subpackages, but you must specify the test directories to +run. + +For example, in a workspace with subpackages `pkgs/foo` and `pkgs/bar`, you +could run the following command from the root directory of the workspace: + +``` +dart run coverage:test_with_coverage -- pkgs/foo/test pkgs/bar/test +``` + +This would output coverage to ./coverage/ as normal. An important caveat is that +the working directory of the tests will be the workspace's root directory. So +this approach won't work if your tests assume that they are being run from the +subpackage directory. + +[Full support](https://github.com/dart-lang/tools/issues/2083) for workspaces +will likely be added in a future version. This will mean you won't need to +explicitly specify the test directories: `dart run coverage:test_with_coverage` + #### Collecting coverage from the VM ``` diff --git a/pkgs/coverage/bin/test_with_coverage.dart b/pkgs/coverage/bin/test_with_coverage.dart index c22e9b8fb..9f4884d9b 100644 --- a/pkgs/coverage/bin/test_with_coverage.dart +++ b/pkgs/coverage/bin/test_with_coverage.dart @@ -7,10 +7,8 @@ import 'dart:io'; import 'package:args/args.dart'; import 'package:coverage/src/coverage_options.dart'; -import 'package:coverage/src/util.dart' - show StandardOutExtension, extractVMServiceUri; +import 'package:coverage/src/util.dart'; import 'package:meta/meta.dart'; -import 'package:package_config/package_config.dart'; import 'package:path/path.dart' as path; import 'collect_coverage.dart' as collect_coverage; @@ -19,37 +17,36 @@ import 'format_coverage.dart' as format_coverage; final _allProcesses = []; Future _dartRun(List args, - {void Function(String)? onStdout, String? workingDir}) async { - final process = await Process.start( - Platform.executable, - args, - workingDirectory: workingDir, - ); + {required void Function(String) onStdout, + required void Function(String) onStderr}) async { + final process = await Process.start(Platform.executable, args); _allProcesses.add(process); - final broadStdout = process.stdout.asBroadcastStream(); - broadStdout.listen(stdout.add); - if (onStdout != null) { - broadStdout.lines().listen(onStdout); + + void listen( + Stream> stream, IOSink sink, void Function(String) onLine) { + final broadStream = stream.asBroadcastStream(); + broadStream.listen(sink.add); + broadStream.lines().listen(onLine); } - process.stderr.listen(stderr.add); + + listen(process.stdout, stdout, onStdout); + listen(process.stderr, stderr, onStderr); + final result = await process.exitCode; if (result != 0) { throw ProcessException(Platform.executable, args, '', result); } } -Future _packageNameFromConfig(String packageDir) async { - final config = await findPackageConfig(Directory(packageDir)); - return config?.packageOf(Uri.directory(packageDir))?.name; +void _killSubprocessesAndExit(ProcessSignal signal) { + for (final process in _allProcesses) { + process.kill(signal); + } + exit(1); } void _watchExitSignal(ProcessSignal signal) { - signal.watch().listen((sig) { - for (final process in _allProcesses) { - process.kill(sig); - } - exit(1); - }); + signal.watch().listen(_killSubprocessesAndExit); } ArgParser _createArgParser(CoverageOptions defaultOptions) => ArgParser() @@ -61,10 +58,10 @@ ArgParser _createArgParser(CoverageOptions defaultOptions) => ArgParser() ..addOption( 'package-name', help: 'Name of the package to test. ' - 'Deduced from --package if not provided.', - defaultsTo: defaultOptions.packageName, + 'Deduced from --package if not provided. ' + 'DEPRECATED: use --scope-output', ) - ..addOption('port', help: 'VM service port.', defaultsTo: '8181') + ..addOption('port', help: 'VM service port. Defaults to using any free port.') ..addOption( 'out', defaultsTo: defaultOptions.outputDirectory, @@ -93,13 +90,13 @@ ArgParser _createArgParser(CoverageOptions defaultOptions) => ArgParser() defaultsTo: defaultOptions.scopeOutput, help: 'restrict coverage results so that only scripts that start with ' 'the provided package path are considered. Defaults to the name of ' - 'the package under test.') + 'the current package (including all subpackages, if this is a ' + 'workspace).') ..addFlag('help', abbr: 'h', negatable: false, help: 'Show this help.'); class Flags { Flags( this.packageDir, - this.packageName, this.outDir, this.port, this.testScript, @@ -111,7 +108,6 @@ class Flags { }); final String packageDir; - final String packageName; final String outDir; final String port; final String testScript; @@ -157,25 +153,23 @@ ${parser.usage} fail('--package is not a valid directory.'); } - final packageName = (args['package-name'] as String?) ?? - await _packageNameFromConfig(packageDir); - if (packageName == null) { + final pubspecPath = getPubspecPath(packageDir); + if (!File(pubspecPath).existsSync()) { fail( - "Couldn't figure out package name from --package. Make sure this is a " - 'package directory, or try passing --package-name explicitly.', + "Couldn't find $pubspecPath. Make sure this command is run in a " + 'package directory, or pass --package to explicitly set the directory.', ); } return Flags( packageDir, - packageName, - (args['out'] as String?) ?? path.join(packageDir, 'coverage'), - args['port'] as String, - args['test'] as String, - args['function-coverage'] as bool, - args['branch-coverage'] as bool, - args['scope-output'] as List, - args['fail-under'] as String?, + args.option('out') ?? path.join(packageDir, 'coverage'), + args.option('port') ?? '0', + args.option('test')!, + args.flag('function-coverage'), + args.flag('branch-coverage'), + args.multiOption('scope-output'), + args.option('fail-under'), rest: args.rest, ); } @@ -215,11 +209,19 @@ Future main(List arguments) async { } } }, + onStderr: (line) { + if (!serviceUriCompleter.isCompleted) { + if (line.contains('Could not start the VM service')) { + _killSubprocessesAndExit(ProcessSignal.sigkill); + } + } + }, ); final serviceUri = await serviceUriCompleter.future; - final scopes = - flags.scopeOutput.isEmpty ? [flags.packageName] : flags.scopeOutput; + final scopes = flags.scopeOutput.isEmpty + ? getAllWorkspaceNames(flags.packageDir) + : flags.scopeOutput; await collect_coverage.main([ '--wait-paused', '--resume-isolates', diff --git a/pkgs/coverage/dart_test.yaml b/pkgs/coverage/dart_test.yaml new file mode 100644 index 000000000..f9821679d --- /dev/null +++ b/pkgs/coverage/dart_test.yaml @@ -0,0 +1,5 @@ +tags: + # Tests that start subprocesses, so are slower and can be a bit flaky. + integration: + timeout: 2x + retry: 3 diff --git a/pkgs/coverage/lib/src/collect.dart b/pkgs/coverage/lib/src/collect.dart index 76227bac9..1bed28d4d 100644 --- a/pkgs/coverage/lib/src/collect.dart +++ b/pkgs/coverage/lib/src/collect.dart @@ -170,6 +170,7 @@ Future> _getAllCoverage( isolateReport, includeDart, functionCoverage, + branchCoverage, coverableLineCache, scopedOutput); allCoverage.addAll(coverage); @@ -244,6 +245,7 @@ Future>> _processSourceReport( SourceReport report, bool includeDart, bool functionCoverage, + bool branchCoverage, Map>? coverableLineCache, Set scopedOutput) async { final hitMaps = {}; @@ -262,7 +264,10 @@ Future>> _processSourceReport( return scripts[scriptRef]; } - HitMap getHitMap(Uri scriptUri) => hitMaps.putIfAbsent(scriptUri, HitMap.new); + HitMap getHitMap(Uri scriptUri) => hitMaps.putIfAbsent( + scriptUri, + () => HitMap.empty( + functionCoverage: functionCoverage, branchCoverage: branchCoverage)); Future processFunction(FuncRef funcRef) async { final func = await service.getObject(isolateRef.id!, funcRef.id!) as Func; @@ -290,8 +295,7 @@ Future>> _processSourceReport( return; } final hits = getHitMap(Uri.parse(script.uri!)); - hits.funcHits ??= {}; - (hits.funcNames ??= {})[line] = funcName; + hits.funcNames![line] = funcName; } for (var range in report.ranges!) { @@ -385,13 +389,12 @@ Future>> _processSourceReport( hits.funcHits?.putIfAbsent(line, () => 0); }); - final branchCoverage = range.branchCoverage; - if (branchCoverage != null) { - hits.branchHits ??= {}; - forEachLine(branchCoverage.hits, (line) { + final branches = range.branchCoverage; + if (branchCoverage && branches != null) { + forEachLine(branches.hits, (line) { hits.branchHits!.increment(line); }); - forEachLine(branchCoverage.misses, (line) { + forEachLine(branches.misses, (line) { hits.branchHits!.putIfAbsent(line, () => 0); }); } diff --git a/pkgs/coverage/lib/src/coverage_options.dart b/pkgs/coverage/lib/src/coverage_options.dart index a15c31dc5..87790dfc6 100644 --- a/pkgs/coverage/lib/src/coverage_options.dart +++ b/pkgs/coverage/lib/src/coverage_options.dart @@ -9,7 +9,6 @@ class CoverageOptions { required this.functionCoverage, required this.branchCoverage, required this.packageDirectory, - this.packageName, required this.testScript, }); @@ -40,8 +39,6 @@ class CoverageOptions { branchCoverage: options.optionalBool('branch_coverage') ?? defaultOptions.branchCoverage, packageDirectory: packageDirectory, - packageName: - options.optionalString('package_name') ?? defaultOptions.packageName, testScript: options.optionalString('test_script') ?? defaultOptions.testScript, ); @@ -52,7 +49,6 @@ class CoverageOptions { final bool functionCoverage; final bool branchCoverage; final String packageDirectory; - final String? packageName; final String testScript; } @@ -119,7 +115,6 @@ class CoverageOptionsProvider { functionCoverage: false, branchCoverage: false, packageDirectory: '.', - packageName: null, testScript: 'test', ); } diff --git a/pkgs/coverage/lib/src/formatter.dart b/pkgs/coverage/lib/src/formatter.dart index a37df73b7..26806075b 100644 --- a/pkgs/coverage/lib/src/formatter.dart +++ b/pkgs/coverage/lib/src/formatter.dart @@ -151,6 +151,20 @@ extension FileHitMapsFormatter on Map { ); final buf = StringBuffer(); for (final entry in entries) { + final source = resolver.resolve(entry.key); + if (source == null) { + continue; + } + + if (!pathFilter(source)) { + continue; + } + + final lines = await loader.load(source); + if (lines == null) { + continue; + } + final v = entry.value; if (reportFuncs && v.funcHits == null) { throw StateError( @@ -165,24 +179,12 @@ extension FileHitMapsFormatter on Map { 'missing branch coverage information. Did you run ' 'collect_coverage with the --branch-coverage flag?'); } + final hits = reportFuncs ? v.funcHits! : reportBranches ? v.branchHits! : v.lineHits; - final source = resolver.resolve(entry.key); - if (source == null) { - continue; - } - - if (!pathFilter(source)) { - continue; - } - - final lines = await loader.load(source); - if (lines == null) { - continue; - } buf.writeln(source); for (var line = 1; line <= lines.length; line++) { var prefix = _prefix; diff --git a/pkgs/coverage/lib/src/hitmap.dart b/pkgs/coverage/lib/src/hitmap.dart index 4c3b46886..133c24be8 100644 --- a/pkgs/coverage/lib/src/hitmap.dart +++ b/pkgs/coverage/lib/src/hitmap.dart @@ -18,6 +18,15 @@ class HitMap { this.branchHits, ]) : lineHits = lineHits ?? {}; + /// Constructs an empty hitmap, optionally with function and branch coverage + /// tables. + HitMap.empty({bool functionCoverage = false, bool branchCoverage = false}) + : this( + null, + functionCoverage ? {} : null, + functionCoverage ? {} : null, + branchCoverage ? {} : null); + /// Map from line to hit count for that line. final Map lineHits; diff --git a/pkgs/coverage/lib/src/util.dart b/pkgs/coverage/lib/src/util.dart index cc7f58425..74a1697f2 100644 --- a/pkgs/coverage/lib/src/util.dart +++ b/pkgs/coverage/lib/src/util.dart @@ -6,6 +6,8 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; +import 'package:path/path.dart' as path; +import 'package:pubspec_parse/pubspec_parse.dart'; import 'package:vm_service/vm_service.dart'; // TODO(cbracken) make generic @@ -57,25 +59,6 @@ Uri? extractVMServiceUri(String str) { return null; } -/// Returns an open port by creating a temporary Socket -Future getOpenPort() async { - ServerSocket socket; - - try { - socket = await ServerSocket.bind(InternetAddress.loopbackIPv4, 0); - } catch (_) { - // try again v/ V6 only. Slight possibility that V4 is disabled - socket = - await ServerSocket.bind(InternetAddress.loopbackIPv6, 0, v6Only: true); - } - - try { - return socket.port; - } finally { - await socket.close(); - } -} - final muliLineIgnoreStart = RegExp(r'//\s*coverage:ignore-start[\w\d\s]*$'); final muliLineIgnoreEnd = RegExp(r'//\s*coverage:ignore-end[\w\d\s]*$'); final singleLineIgnore = RegExp(r'//\s*coverage:ignore-line[\w\d\s]*$'); @@ -184,3 +167,19 @@ Future serviceUriFromProcess(Stream procStdout) { Future> getAllIsolates(VmService service) async => (await service.getVM()).isolates ?? []; + +String getPubspecPath(String root) => path.join(root, 'pubspec.yaml'); + +List getAllWorkspaceNames(String packageRoot) => + _getAllWorkspaceNames(packageRoot, []); + +List _getAllWorkspaceNames(String packageRoot, List results) { + final pubspecPath = getPubspecPath(packageRoot); + final yaml = File(pubspecPath).readAsStringSync(); + final pubspec = Pubspec.parse(yaml, sourceUrl: Uri.file(pubspecPath)); + results.add(pubspec.name); + for (final workspace in pubspec.workspace ?? []) { + _getAllWorkspaceNames(path.join(packageRoot, workspace), results); + } + return results; +} diff --git a/pkgs/coverage/pubspec.yaml b/pkgs/coverage/pubspec.yaml index 46307b824..e4a229e7d 100644 --- a/pkgs/coverage/pubspec.yaml +++ b/pkgs/coverage/pubspec.yaml @@ -1,5 +1,5 @@ name: coverage -version: 1.13.1 +version: 1.14.0 description: Coverage data manipulation and formatting repository: https://github.com/dart-lang/tools/tree/main/pkgs/coverage issue_tracker: https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Acoverage @@ -15,6 +15,7 @@ dependencies: meta: ^1.0.2 package_config: ^2.0.0 path: ^1.8.0 + pubspec_parse: ^1.5.0 source_maps: ^0.10.10 stack_trace: ^1.10.0 vm_service: '>=12.0.0 <16.0.0' diff --git a/pkgs/coverage/test/collect_coverage_api_test.dart b/pkgs/coverage/test/collect_coverage_api_test.dart index fd3de4395..5ccedd8da 100644 --- a/pkgs/coverage/test/collect_coverage_api_test.dart +++ b/pkgs/coverage/test/collect_coverage_api_test.dart @@ -2,6 +2,9 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. +@Tags(['integration']) +library; + import 'dart:async'; import 'package:coverage/coverage.dart'; @@ -140,10 +143,8 @@ Future> _collectCoverage( bool functionCoverage = false, bool branchCoverage = false, Map>? coverableLineCache}) async { - final openPort = await getOpenPort(); - // run the sample app, with the right flags - final sampleProcess = await runTestApp(openPort); + final sampleProcess = await runTestApp(); final serviceUri = await serviceUriFromProcess(sampleProcess.stdoutStream()); final isolateIdSet = isolateIds ? {} : null; diff --git a/pkgs/coverage/test/collect_coverage_config_test.dart b/pkgs/coverage/test/collect_coverage_config_test.dart index 5f6c460a8..d2b6b5884 100644 --- a/pkgs/coverage/test/collect_coverage_config_test.dart +++ b/pkgs/coverage/test/collect_coverage_config_test.dart @@ -1,3 +1,7 @@ +// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file +// for details. 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:coverage/src/coverage_options.dart'; import 'package:path/path.dart' as path; import 'package:test/test.dart'; @@ -35,7 +39,6 @@ void main() { expect(path.canonicalize(testCoverage.packageDir), path.canonicalize(defaults.packageDirectory)); - expect(testCoverage.packageName, 'coverage'); expect(path.canonicalize(testCoverage.outDir), path.canonicalize('coverage')); expect(testCoverage.testScript, defaults.testScript); @@ -73,7 +76,6 @@ void main() { // Verify test with coverage yaml values expect(path.canonicalize(testCoverage.packageDir), path.canonicalize('test/test_files')); - expect(testCoverage.packageName, 'My Dart Package'); expect(path.canonicalize(testCoverage.outDir), path.canonicalize('var/coverage_data')); expect(testCoverage.testScript, 'test1'); @@ -102,7 +104,6 @@ void main() { expect(collectedCoverage.functionCoverage, isFalse); expect(path.canonicalize(formattedCoverage.output!), path.canonicalize('var/coverage_data/custom_coverage/lcov.info')); - expect(testCoverage.packageName, 'Custom Dart Package'); expect(testCoverage.scopeOutput, ['lib', 'test']); }); @@ -133,7 +134,6 @@ void main() { path.canonicalize('test/test_coverage_options')); // Verify test with coverage yaml values - expect(testCoverage.packageName, 'coverage'); expect(path.canonicalize(testCoverage.outDir), path.canonicalize('var/coverage_data/custom_lcov')); expect(testCoverage.testScript, 'custom_test'); @@ -178,7 +178,6 @@ void main() { expect(formattedCoverage.packagePath, '../code_builder'); // Verify test with coverage command line args - expect(testCoverage.packageName, 'test'); expect(testCoverage.outDir, 'test_coverage.json'); expect(testCoverage.testScript, 'test_test.dart'); expect(testCoverage.functionCoverage, isTrue); @@ -218,7 +217,6 @@ void main() { expect(formattedCoverage.packagePath, '../cli_config'); // Verify test with coverage command line args - expect(testCoverage.packageName, 'cli_config'); expect(testCoverage.outDir, 'cli_config_coverage.json'); expect(testCoverage.testScript, 'cli_config_test.dart'); expect(testCoverage.functionCoverage, isTrue); diff --git a/pkgs/coverage/test/collect_coverage_test.dart b/pkgs/coverage/test/collect_coverage_test.dart index 476fee1c2..5758b5e99 100644 --- a/pkgs/coverage/test/collect_coverage_test.dart +++ b/pkgs/coverage/test/collect_coverage_test.dart @@ -2,7 +2,7 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. -@Retry(3) +@Tags(['integration']) library; import 'dart:async'; @@ -294,10 +294,8 @@ Future _collectCoverage( bool functionCoverage, bool branchCoverage) async { expect(FileSystemEntity.isFileSync(testAppPath), isTrue); - final openPort = await getOpenPort(); - // Run the sample app with the right flags. - final sampleProcess = await runTestApp(openPort); + final sampleProcess = await runTestApp(); // Capture the VM service URI. final serviceUri = await serviceUriFromProcess(sampleProcess.stdoutStream()); diff --git a/pkgs/coverage/test/config_file_locator_test.dart b/pkgs/coverage/test/config_file_locator_test.dart index d46fc869b..db131e8c5 100644 --- a/pkgs/coverage/test/config_file_locator_test.dart +++ b/pkgs/coverage/test/config_file_locator_test.dart @@ -1,3 +1,7 @@ +// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + import 'dart:io'; import 'package:coverage/src/coverage_options.dart'; import 'package:path/path.dart' as path; diff --git a/pkgs/coverage/test/function_coverage_test.dart b/pkgs/coverage/test/function_coverage_test.dart index 965d0d03e..d8aaa74b7 100644 --- a/pkgs/coverage/test/function_coverage_test.dart +++ b/pkgs/coverage/test/function_coverage_test.dart @@ -2,6 +2,9 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. +@Tags(['integration']) +library; + import 'dart:async'; import 'dart:convert' show json; import 'dart:io'; @@ -74,14 +77,9 @@ void main() { Future _collectCoverage() async { expect(FileSystemEntity.isFileSync(_funcCovApp), isTrue); - final openPort = await getOpenPort(); - // Run the sample app with the right flags. - final sampleProcess = await TestProcess.start(Platform.resolvedExecutable, [ - '--enable-vm-service=$openPort', - '--pause_isolates_on_exit', - _funcCovApp - ]); + final sampleProcess = await TestProcess.start(Platform.resolvedExecutable, + ['--enable-vm-service=0', '--pause_isolates_on_exit', _funcCovApp]); final serviceUri = await serviceUriFromProcess(sampleProcess.stdoutStream()); diff --git a/pkgs/coverage/test/lcov_test.dart b/pkgs/coverage/test/lcov_test.dart index ca62117cc..a03612c7e 100644 --- a/pkgs/coverage/test/lcov_test.dart +++ b/pkgs/coverage/test/lcov_test.dart @@ -2,6 +2,9 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. +@Tags(['integration']) +library; + import 'dart:async'; import 'dart:io'; @@ -337,13 +340,10 @@ void main() { Future> _getHitMap() async { expect(FileSystemEntity.isFileSync(_sampleAppPath), isTrue); - // select service port. - final port = await getOpenPort(); - // start sample app. final sampleAppArgs = [ '--pause-isolates-on-exit', - '--enable-vm-service=$port', + '--enable-vm-service=0', '--branch-coverage', _sampleAppPath ]; diff --git a/pkgs/coverage/test/run_and_collect_test.dart b/pkgs/coverage/test/run_and_collect_test.dart index a538a88a5..0a45a449b 100644 --- a/pkgs/coverage/test/run_and_collect_test.dart +++ b/pkgs/coverage/test/run_and_collect_test.dart @@ -2,6 +2,9 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. +@Tags(['integration']) +library; + import 'package:coverage/coverage.dart'; import 'package:path/path.dart' as p; import 'package:test/test.dart'; diff --git a/pkgs/coverage/test/test_coverage_options/pubspec.yaml b/pkgs/coverage/test/test_coverage_options/pubspec.yaml new file mode 100644 index 000000000..9d55a4fb7 --- /dev/null +++ b/pkgs/coverage/test/test_coverage_options/pubspec.yaml @@ -0,0 +1 @@ +name: test_coverage_options diff --git a/pkgs/coverage/test/test_files/pubspec.yaml b/pkgs/coverage/test/test_files/pubspec.yaml new file mode 100644 index 000000000..ab2a25bf7 --- /dev/null +++ b/pkgs/coverage/test/test_files/pubspec.yaml @@ -0,0 +1,5 @@ +name: coverage_test_files + +dev_dependencies: + coverage: + path: ../../ diff --git a/pkgs/coverage/test/test_util.dart b/pkgs/coverage/test/test_util.dart index e0a3edbe7..6fe89d3f5 100644 --- a/pkgs/coverage/test/test_util.dart +++ b/pkgs/coverage/test/test_util.dart @@ -11,12 +11,12 @@ import 'package:test_process/test_process.dart'; final String testAppPath = p.join('test', 'test_files', 'test_app.dart'); -const Duration timeout = Duration(seconds: 20); +const Duration timeout = Duration(seconds: 30); -Future runTestApp(int openPort) => TestProcess.start( +Future runTestApp() => TestProcess.start( Platform.resolvedExecutable, [ - '--enable-vm-service=$openPort', + '--enable-vm-service=0', '--pause_isolates_on_exit', '--branch-coverage', testAppPath diff --git a/pkgs/coverage/test/test_with_coverage_test.dart b/pkgs/coverage/test/test_with_coverage_test.dart index 53e225315..364510b4f 100644 --- a/pkgs/coverage/test/test_with_coverage_test.dart +++ b/pkgs/coverage/test/test_with_coverage_test.dart @@ -2,7 +2,7 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. -@Timeout(Duration(seconds: 60)) +@Tags(['integration']) library; import 'dart:convert'; diff --git a/pkgs/coverage/test/util_test.dart b/pkgs/coverage/test/util_test.dart index 6a4e5564a..75405bae7 100644 --- a/pkgs/coverage/test/util_test.dart +++ b/pkgs/coverage/test/util_test.dart @@ -355,4 +355,22 @@ void main() { ]); }); }); + + test('getAllWorkspaceNames', () { + // Uses the workspace_names directory: + // workspace_names + // └── pkgs + // ├── foo + // │ └── foo_example // Not part of foo's workspace. + // └── bar + // └── bar_example // Part of bar's workspace. + expect( + getAllWorkspaceNames('test/workspace_names'), + unorderedEquals([ + 'workspace_names', + 'foo', + 'bar', + 'bar_example', + ])); + }); } diff --git a/pkgs/coverage/test/workspace_names/pkgs/bar/bar_example/pubspec.yaml b/pkgs/coverage/test/workspace_names/pkgs/bar/bar_example/pubspec.yaml new file mode 100644 index 000000000..e59be7d6d --- /dev/null +++ b/pkgs/coverage/test/workspace_names/pkgs/bar/bar_example/pubspec.yaml @@ -0,0 +1,2 @@ +name: bar_example +resolution: workspace diff --git a/pkgs/coverage/test/workspace_names/pkgs/bar/pubspec.yaml b/pkgs/coverage/test/workspace_names/pkgs/bar/pubspec.yaml new file mode 100644 index 000000000..a0036812e --- /dev/null +++ b/pkgs/coverage/test/workspace_names/pkgs/bar/pubspec.yaml @@ -0,0 +1,4 @@ +name: bar +resolution: workspace +workspace: +- bar_example diff --git a/pkgs/coverage/test/workspace_names/pkgs/foo/foo_example/pubspec.yaml b/pkgs/coverage/test/workspace_names/pkgs/foo/foo_example/pubspec.yaml new file mode 100644 index 000000000..92033a9b8 --- /dev/null +++ b/pkgs/coverage/test/workspace_names/pkgs/foo/foo_example/pubspec.yaml @@ -0,0 +1 @@ +name: foo_example diff --git a/pkgs/coverage/test/workspace_names/pkgs/foo/pubspec.yaml b/pkgs/coverage/test/workspace_names/pkgs/foo/pubspec.yaml new file mode 100644 index 000000000..e6ffc83c2 --- /dev/null +++ b/pkgs/coverage/test/workspace_names/pkgs/foo/pubspec.yaml @@ -0,0 +1,2 @@ +name: foo +resolution: workspace diff --git a/pkgs/coverage/test/workspace_names/pubspec.yaml b/pkgs/coverage/test/workspace_names/pubspec.yaml new file mode 100644 index 000000000..90e1fbcf4 --- /dev/null +++ b/pkgs/coverage/test/workspace_names/pubspec.yaml @@ -0,0 +1,4 @@ +name: workspace_names +workspace: +- pkgs/bar +- pkgs/foo From 14a9767bc5a1e804cb7e15658c6690d2c009c019 Mon Sep 17 00:00:00 2001 From: Liam Appelbe Date: Wed, 28 May 2025 13:21:54 +1000 Subject: [PATCH 21/67] [coverage] Switch from package:pubspec_parse to package:yaml (#2103) --- pkgs/coverage/CHANGELOG.md | 4 ++++ pkgs/coverage/lib/src/util.dart | 18 +++++++++++------- pkgs/coverage/pubspec.yaml | 4 ++-- 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/pkgs/coverage/CHANGELOG.md b/pkgs/coverage/CHANGELOG.md index 2a680adbe..98f88523f 100644 --- a/pkgs/coverage/CHANGELOG.md +++ b/pkgs/coverage/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.14.1-wip + +- Remove dependency on `package:pubspec_parse`. + ## 1.14.0 - Require Dart ^3.6.0 diff --git a/pkgs/coverage/lib/src/util.dart b/pkgs/coverage/lib/src/util.dart index 74a1697f2..8ae1a4b79 100644 --- a/pkgs/coverage/lib/src/util.dart +++ b/pkgs/coverage/lib/src/util.dart @@ -7,8 +7,8 @@ import 'dart:convert'; import 'dart:io'; import 'package:path/path.dart' as path; -import 'package:pubspec_parse/pubspec_parse.dart'; import 'package:vm_service/vm_service.dart'; +import 'package:yaml/yaml.dart'; // TODO(cbracken) make generic /// Retries the specified function with the specified interval and returns @@ -174,12 +174,16 @@ List getAllWorkspaceNames(String packageRoot) => _getAllWorkspaceNames(packageRoot, []); List _getAllWorkspaceNames(String packageRoot, List results) { - final pubspecPath = getPubspecPath(packageRoot); - final yaml = File(pubspecPath).readAsStringSync(); - final pubspec = Pubspec.parse(yaml, sourceUrl: Uri.file(pubspecPath)); - results.add(pubspec.name); - for (final workspace in pubspec.workspace ?? []) { - _getAllWorkspaceNames(path.join(packageRoot, workspace), results); + final pubspec = _loadPubspec(packageRoot); + results.add(pubspec['name'] as String); + for (final workspace in pubspec['workspace'] as YamlList? ?? []) { + _getAllWorkspaceNames(path.join(packageRoot, workspace as String), results); } return results; } + +YamlMap _loadPubspec(String packageRoot) { + final pubspecPath = getPubspecPath(packageRoot); + final yaml = File(pubspecPath).readAsStringSync(); + return loadYaml(yaml, sourceUrl: Uri.file(pubspecPath)) as YamlMap; +} diff --git a/pkgs/coverage/pubspec.yaml b/pkgs/coverage/pubspec.yaml index e4a229e7d..3228ab310 100644 --- a/pkgs/coverage/pubspec.yaml +++ b/pkgs/coverage/pubspec.yaml @@ -1,5 +1,5 @@ name: coverage -version: 1.14.0 +version: 1.14.1-wip description: Coverage data manipulation and formatting repository: https://github.com/dart-lang/tools/tree/main/pkgs/coverage issue_tracker: https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Acoverage @@ -15,10 +15,10 @@ dependencies: meta: ^1.0.2 package_config: ^2.0.0 path: ^1.8.0 - pubspec_parse: ^1.5.0 source_maps: ^0.10.10 stack_trace: ^1.10.0 vm_service: '>=12.0.0 <16.0.0' + yaml: ^3.1.3 dev_dependencies: benchmark_harness: ^2.2.0 From d3d2ec1efc97e275281a43bfb83e9271817bc1b3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 1 Jun 2025 05:53:21 +0000 Subject: [PATCH 22/67] Bump actions/setup-node from 3 to 4 in the github-actions group (#2106) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the github-actions group with 1 update: [actions/setup-node](https://github.com/actions/setup-node). Updates `actions/setup-node` from 3 to 4
Release notes

Sourced from actions/setup-node's releases.

v4.0.0

What's Changed

In scope of this release we changed version of node runtime for action from node16 to node20 and updated dependencies in actions/setup-node#866

Besides, release contains such changes as:

New Contributors

Full Changelog: https://github.com/actions/setup-node/compare/v3...v4.0.0

v3.9.1

What's Changed

Full Changelog: https://github.com/actions/setup-node/compare/v3...v3.9.1

v3.9.0

What's Changed

  • Upgrade @​actions/cache to 4.0.3 by @​gowridurgad in actions/setup-node#1270 In scope of this release we updated actions/cache package to ensure continued support and compatibility, as older versions of the package are now deprecated. For more information please refer to the toolkit/cache.

Full Changelog: https://github.com/actions/setup-node/compare/v3...v3.9.0

v3.8.2

What's Changed

Full Changelog: https://github.com/actions/setup-node/compare/v3...v3.8.2

v3.8.1

What's Changed

In scope of this release, the filter was removed within the cache-save step by @​dmitry-shibanov in actions/setup-node#831. It is filtered and checked in the toolkit/cache library.

Full Changelog: https://github.com/actions/setup-node/compare/v3...v3.8.1

v3.8.0

What's Changed

Bug fixes:

... (truncated)

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/setup-node&package-manager=github_actions&previous-version=3&new-version=4)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
--- .github/workflows/benchmark_harness.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/benchmark_harness.yaml b/.github/workflows/benchmark_harness.yaml index 4b839526c..b3856751c 100644 --- a/.github/workflows/benchmark_harness.yaml +++ b/.github/workflows/benchmark_harness.yaml @@ -63,7 +63,7 @@ jobs: sdk: ${{ matrix.sdk }} # Node 22 has wasmGC enabled, which allows the wasm tests to run! - name: Setup Node.js 22 - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: 22 - id: install From 5d9ed7cc85283bda1087adf47a1a0afa29a450c5 Mon Sep 17 00:00:00 2001 From: Liam Appelbe Date: Wed, 4 Jun 2025 14:27:55 +1000 Subject: [PATCH 23/67] [coverage] Fix remaining ~0.1% flakiness (#2102) --- pkgs/coverage/CHANGELOG.md | 5 +- .../lib/src/isolate_paused_listener.dart | 6 +- pkgs/coverage/pubspec.yaml | 2 +- .../test/isolate_paused_listener_test.dart | 105 +++++++++++++----- pkgs/coverage/test/test_util.dart | 2 +- 5 files changed, 89 insertions(+), 31 deletions(-) diff --git a/pkgs/coverage/CHANGELOG.md b/pkgs/coverage/CHANGELOG.md index 98f88523f..a9825bca9 100644 --- a/pkgs/coverage/CHANGELOG.md +++ b/pkgs/coverage/CHANGELOG.md @@ -1,6 +1,9 @@ -## 1.14.1-wip +## 1.14.1 - Remove dependency on `package:pubspec_parse`. +- Silence a rare error that can occur when trying to resume the main isolate + because the VM service has already shut down. This was responsible for a ~0.1% + flakiness, and is safe to ignore. ## 1.14.0 diff --git a/pkgs/coverage/lib/src/isolate_paused_listener.dart b/pkgs/coverage/lib/src/isolate_paused_listener.dart index 49f17e7a9..c1b56c223 100644 --- a/pkgs/coverage/lib/src/isolate_paused_listener.dart +++ b/pkgs/coverage/lib/src/isolate_paused_listener.dart @@ -63,7 +63,11 @@ class IsolatePausedListener { // Resume the main isolate. if (_mainIsolate != null) { - await _service.resume(_mainIsolate!.id!); + try { + await _service.resume(_mainIsolate!.id!); + } on RPCError { + // The VM Service has already shut down, so there's nothing left to do. + } } } diff --git a/pkgs/coverage/pubspec.yaml b/pkgs/coverage/pubspec.yaml index 3228ab310..844914f72 100644 --- a/pkgs/coverage/pubspec.yaml +++ b/pkgs/coverage/pubspec.yaml @@ -1,5 +1,5 @@ name: coverage -version: 1.14.1-wip +version: 1.14.1 description: Coverage data manipulation and formatting repository: https://github.com/dart-lang/tools/tree/main/pkgs/coverage issue_tracker: https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Acoverage diff --git a/pkgs/coverage/test/isolate_paused_listener_test.dart b/pkgs/coverage/test/isolate_paused_listener_test.dart index 55d204528..a76998779 100644 --- a/pkgs/coverage/test/isolate_paused_listener_test.dart +++ b/pkgs/coverage/test/isolate_paused_listener_test.dart @@ -483,10 +483,12 @@ void main() { late MockVmService service; late StreamController allEvents; late Future allIsolatesExited; + Object? lastError; late List received; Future Function(String)? delayTheOnPauseCallback; late bool stopped; + late Set resumeFailures; void startEvent(String id, String groupId, [String? name]) => allEvents.add(event( @@ -516,34 +518,48 @@ void main() { } setUp(() { - (service, allEvents) = createServiceAndEventStreams(); - - // Backfill was tested above, so this test does everything using events, - // for simplicity. No need to report any isolates. - when(service.getVM()).thenAnswer((_) async => VM()); - - received = []; - delayTheOnPauseCallback = null; - when(service.resume(any)).thenAnswer((invocation) async { - final id = invocation.positionalArguments[0]; - received.add('Resume $id'); - return Success(); - }); - - stopped = false; - allIsolatesExited = IsolatePausedListener( - service, - (iso, isLastIsolateInGroup) async { - expect(stopped, isFalse); - received.add('Pause ${iso.id}. Collect group ${iso.isolateGroupId}? ' - '${isLastIsolateInGroup ? 'Yes' : 'No'}'); - if (delayTheOnPauseCallback != null) { - await delayTheOnPauseCallback!(iso.id!); - received.add('Pause done ${iso.id}'); + Zone.current.fork( + specification: ZoneSpecification( + handleUncaughtError: (Zone self, ZoneDelegate parent, Zone zone, + Object error, StackTrace stackTrace) { + lastError = error; + }, + ), + ).runGuarded(() { + (service, allEvents) = createServiceAndEventStreams(); + + // Backfill was tested above, so this test does everything using events, + // for simplicity. No need to report any isolates. + when(service.getVM()).thenAnswer((_) async => VM()); + + received = []; + delayTheOnPauseCallback = null; + resumeFailures = {}; + when(service.resume(any)).thenAnswer((invocation) async { + final id = invocation.positionalArguments[0]; + received.add('Resume $id'); + if (resumeFailures.contains(id)) { + throw RPCError('resume', -32000, id); } - }, - (message) => received.add(message), - ).waitUntilAllExited(); + return Success(); + }); + + stopped = false; + allIsolatesExited = IsolatePausedListener( + service, + (iso, isLastIsolateInGroup) async { + expect(stopped, isFalse); + received + .add('Pause ${iso.id}. Collect group ${iso.isolateGroupId}? ' + '${isLastIsolateInGroup ? 'Yes' : 'No'}'); + if (delayTheOnPauseCallback != null) { + await delayTheOnPauseCallback!(iso.id!); + received.add('Pause done ${iso.id}'); + } + }, + (message) => received.add(message), + ).waitUntilAllExited(); + }); }); test('ordinary flows', () async { @@ -889,5 +905,40 @@ void main() { // Don't try to resume B, because the VM service is already shut down. ]); }); + + test('throw when resuming main isolate is ignored', () async { + resumeFailures = {'main'}; + + startEvent('main', '1'); + startEvent('other', '2'); + pauseEvent('other', '2'); + exitEvent('other', '2'); + pauseEvent('main', '1'); + exitEvent('main', '1'); + + await endTest(); + expect(lastError, isNull); + + expect(received, [ + 'Pause other. Collect group 2? Yes', + 'Resume other', + 'Pause main. Collect group 1? Yes', + 'Resume main', + ]); + }); + + test('throw when resuming other isolate is not ignored', () async { + resumeFailures = {'other'}; + + startEvent('main', '1'); + startEvent('other', '2'); + pauseEvent('other', '2'); + exitEvent('other', '2'); + pauseEvent('main', '1'); + exitEvent('main', '1'); + + await endTest(); + expect(lastError, isA()); + }); }); } diff --git a/pkgs/coverage/test/test_util.dart b/pkgs/coverage/test/test_util.dart index 6fe89d3f5..92fb56148 100644 --- a/pkgs/coverage/test/test_util.dart +++ b/pkgs/coverage/test/test_util.dart @@ -11,7 +11,7 @@ import 'package:test_process/test_process.dart'; final String testAppPath = p.join('test', 'test_files', 'test_app.dart'); -const Duration timeout = Duration(seconds: 30); +const Duration timeout = Duration(seconds: 60); Future runTestApp() => TestProcess.start( Platform.resolvedExecutable, From 585b9b1cacbdd3c0c465afd04546717dd70071da Mon Sep 17 00:00:00 2001 From: Christophe Coevoet Date: Wed, 4 Jun 2025 20:05:21 +0200 Subject: [PATCH 24/67] [source_span] Add a test covering the highlighting of non-contiguous spans (#1666) --- .../test/multiple_highlight_test.dart | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/pkgs/source_span/test/multiple_highlight_test.dart b/pkgs/source_span/test/multiple_highlight_test.dart index 139d53c8a..92ad569fe 100644 --- a/pkgs/source_span/test/multiple_highlight_test.dart +++ b/pkgs/source_span/test/multiple_highlight_test.dart @@ -44,6 +44,22 @@ gibble bibble bop '""")); }); + test('highlights non contiguous spans', () { + expect( + file.span(17, 21).highlightMultiple( + 'one', {file.span(60, 66): 'two', file.span(4, 7): 'three'}), + equals(""" + , +1 | foo bar baz + | === three +2 | whiz bang boom + | ^^^^ one +... | +5 | argle bargle boo + | ====== two + '""")); + }); + test('highlights spans on the same line', () { expect( file.span(17, 21).highlightMultiple( From 5d46a4cea2700d3fbb4d47faafb20883a8e49497 Mon Sep 17 00:00:00 2001 From: Ben Konyi Date: Wed, 11 Jun 2025 10:46:32 -0400 Subject: [PATCH 25/67] Don't emit two WatchEvents when creating a file in a non-existent directory on Windows (#2111) --- pkgs/watcher/CHANGELOG.md | 5 +++ .../lib/src/directory_watcher/windows.dart | 2 +- pkgs/watcher/pubspec.yaml | 2 +- .../test/directory_watcher/windows_test.dart | 41 +++++++++++++++++++ 4 files changed, 48 insertions(+), 2 deletions(-) diff --git a/pkgs/watcher/CHANGELOG.md b/pkgs/watcher/CHANGELOG.md index ef3a7e2d3..6d22c5ea6 100644 --- a/pkgs/watcher/CHANGELOG.md +++ b/pkgs/watcher/CHANGELOG.md @@ -1,3 +1,8 @@ +## 1.1.2 + +- Fix a bug on Windows where a file creation event could be reported twice when creating + a file recursively in a non-existent directory. + ## 1.1.1 - Ensure `PollingFileWatcher.ready` completes for files that do not exist. diff --git a/pkgs/watcher/lib/src/directory_watcher/windows.dart b/pkgs/watcher/lib/src/directory_watcher/windows.dart index d1c98be1f..5607948dd 100644 --- a/pkgs/watcher/lib/src/directory_watcher/windows.dart +++ b/pkgs/watcher/lib/src/directory_watcher/windows.dart @@ -184,7 +184,7 @@ class _WindowsDirectoryWatcher var stream = Directory(path).list(recursive: true); var subscription = stream.listen((entity) { if (entity is Directory) return; - if (_files.contains(path)) return; + if (_files.contains(entity.path)) return; _emitEvent(ChangeType.ADD, entity.path); _files.add(entity.path); diff --git a/pkgs/watcher/pubspec.yaml b/pkgs/watcher/pubspec.yaml index 93db01dff..b86dbf11b 100644 --- a/pkgs/watcher/pubspec.yaml +++ b/pkgs/watcher/pubspec.yaml @@ -1,5 +1,5 @@ name: watcher -version: 1.1.1 +version: 1.1.2 description: >- A file system watcher. It monitors changes to contents of directories and sends notifications when files have been added, removed, or modified. diff --git a/pkgs/watcher/test/directory_watcher/windows_test.dart b/pkgs/watcher/test/directory_watcher/windows_test.dart index 499e7fb16..5be87ec32 100644 --- a/pkgs/watcher/test/directory_watcher/windows_test.dart +++ b/pkgs/watcher/test/directory_watcher/windows_test.dart @@ -5,6 +5,10 @@ @TestOn('windows') library; +import 'dart:async'; +import 'dart:io'; + +import 'package:path/path.dart' as p; import 'package:test/test.dart'; import 'package:watcher/src/directory_watcher/windows.dart'; import 'package:watcher/watcher.dart'; @@ -20,4 +24,41 @@ void main() { test('DirectoryWatcher creates a WindowsDirectoryWatcher on Windows', () { expect(DirectoryWatcher('.'), const TypeMatcher()); }); + + test('Regression test for https://github.com/dart-lang/tools/issues/2110', + () async { + late StreamSubscription sub; + try { + final temp = Directory.systemTemp.createTempSync(); + final watcher = DirectoryWatcher(temp.path); + final events = []; + sub = watcher.events.listen(events.add); + await watcher.ready; + + // Create a file in a directory that doesn't exist. This forces the + // directory to be created first before the child file. + // + // When directory creation is detected by the watcher, it calls + // `Directory.list` on the directory to determine if there's files that + // have been created or modified. It's possible that the watcher will have + // already detected the file creation event before `Directory.list` + // returns. Before https://github.com/dart-lang/tools/issues/2110 was + // resolved, the check to ensure an event hadn't already been emitted for + // the file creation was incorrect, leading to the event being emitted + // again in some circumstances. + final file = File(p.join(temp.path, 'foo', 'file.txt')) + ..createSync(recursive: true); + + // Introduce a short delay to allow for the directory watcher to detect + // the creation of foo/ and foo/file.txt. + await Future.delayed(const Duration(seconds: 1)); + + // There should only be a single file added event. + expect(events, hasLength(1)); + expect(events.first.toString(), + WatchEvent(ChangeType.ADD, file.path).toString()); + } finally { + await sub.cancel(); + } + }); } From 1174eb3920283e3ae8b045196dce6a6ff1e89b6d Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Tue, 24 Jun 2025 08:12:14 -0700 Subject: [PATCH 26/67] remove flutter_markdown integration test (#2114) --- .github/workflows/markdown_flutter.yaml | 63 ------------------------- 1 file changed, 63 deletions(-) delete mode 100644 .github/workflows/markdown_flutter.yaml diff --git a/.github/workflows/markdown_flutter.yaml b/.github/workflows/markdown_flutter.yaml deleted file mode 100644 index 2cc5b344a..000000000 --- a/.github/workflows/markdown_flutter.yaml +++ /dev/null @@ -1,63 +0,0 @@ -# Run a smoke test against package:flutter_markdown. - -name: "package:markdown: flutter" - -on: - # Run on PRs and pushes to the default branch. - push: - branches: [ main ] - paths: - - '.github/workflows/markdown_flutter.yaml' - - 'pkgs/markdown/**' - pull_request: - branches: [ main ] - paths: - - '.github/workflows/markdown_flutter.yaml' - - 'pkgs/markdown/**' - schedule: - - cron: "0 0 * * 0" - -jobs: - smoke-test: - runs-on: ubuntu-latest - - steps: - - name: clone dart-lang/tools - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - with: - repository: dart-lang/tools - path: tools_repo - - - name: clone flutter/packages - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - with: - repository: flutter/packages - path: flutter_packages - - # Install the Flutter SDK using the subosito/flutter-action GitHub action. - - name: install the flutter sdk - uses: subosito/flutter-action@e938fdf56512cc96ef2f93601a5a40bde3801046 - with: - channel: beta - - - name: flutter --version - run: flutter --version - - - name: create pubspec_overrides.yaml - working-directory: flutter_packages/packages/flutter_markdown - run: | - echo "dependency_overrides:" > pubspec_overrides.yaml - echo " markdown:" >> pubspec_overrides.yaml - echo " path: ../../../tools_repo/pkgs/markdown" >> pubspec_overrides.yaml - - - name: flutter pub get - working-directory: flutter_packages/packages/flutter_markdown - run: flutter pub get - - - name: flutter analyze package:flutter_markdown - working-directory: flutter_packages/packages/flutter_markdown - run: flutter analyze - - - name: flutter test package:flutter_markdown - working-directory: flutter_packages/packages/flutter_markdown - run: flutter test From 8c8d753cf8668642115c57e23c62ddfb65a6a6d0 Mon Sep 17 00:00:00 2001 From: Jacob MacDonald Date: Tue, 24 Jun 2025 09:42:02 -0700 Subject: [PATCH 27/67] Add events for dart mcp server tool invocations (#2112) --- pkgs/unified_analytics/CHANGELOG.md | 3 +++ pkgs/unified_analytics/lib/src/constants.dart | 2 +- pkgs/unified_analytics/lib/src/enums.dart | 5 ++++ pkgs/unified_analytics/lib/src/event.dart | 26 +++++++++++++++++++ pkgs/unified_analytics/pubspec.yaml | 2 +- pkgs/unified_analytics/test/event_test.dart | 23 +++++++++++++++- 6 files changed, 58 insertions(+), 3 deletions(-) diff --git a/pkgs/unified_analytics/CHANGELOG.md b/pkgs/unified_analytics/CHANGELOG.md index 3d4c4f278..e5c26c902 100644 --- a/pkgs/unified_analytics/CHANGELOG.md +++ b/pkgs/unified_analytics/CHANGELOG.md @@ -1,3 +1,6 @@ +## 8.0.2 +- Added `Event.dartMCPEvent` for events from the `dart mcp-server` command. + ## 8.0.1 - Added `Event.flutterInjectDarwinPlugins` event for plugins injected into an iOS/macOS project. diff --git a/pkgs/unified_analytics/lib/src/constants.dart b/pkgs/unified_analytics/lib/src/constants.dart index c6ba7496e..3d472ea0e 100644 --- a/pkgs/unified_analytics/lib/src/constants.dart +++ b/pkgs/unified_analytics/lib/src/constants.dart @@ -87,7 +87,7 @@ const int kMaxLogFileSize = 25 * (1 << 20); const String kLogFileName = 'dart-flutter-telemetry.log'; /// The current version of the package, should be in line with pubspec version. -const String kPackageVersion = '8.0.1'; +const String kPackageVersion = '8.0.2'; /// The minimum length for a session. const int kSessionDurationMinutes = 30; diff --git a/pkgs/unified_analytics/lib/src/enums.dart b/pkgs/unified_analytics/lib/src/enums.dart index eff8205a5..b051e8487 100644 --- a/pkgs/unified_analytics/lib/src/enums.dart +++ b/pkgs/unified_analytics/lib/src/enums.dart @@ -55,6 +55,11 @@ enum DashEvent { description: 'Pub package resolution details', toolOwner: DashTool.dartTool, ), + dartMCPEvent( + label: 'dart_mcp_server', + description: 'Information for a Dart MCP server event', + toolOwner: DashTool.dartTool, + ), // Events for Flutter devtools diff --git a/pkgs/unified_analytics/lib/src/event.dart b/pkgs/unified_analytics/lib/src/event.dart index 0071ebd84..7fcaad6f2 100644 --- a/pkgs/unified_analytics/lib/src/event.dart +++ b/pkgs/unified_analytics/lib/src/event.dart @@ -905,6 +905,32 @@ final class Event { }, ); + /// An event that is sent from the Dart MCP server. + /// + /// The [client] is the name of the client, as given when it connected to the + /// MCP server, and [clientVersion] is the version of the client. + /// + /// The [serverVersion] is the version of the Dart MCP server. + /// + /// The [type] identifies the kind of event this is, and [additionalData] is + /// the actual data for the event. + Event.dartMCPEvent({ + required String client, + required String clientVersion, + required String serverVersion, + required String type, + CustomMetrics? additionalData, + }) : this._( + eventName: DashEvent.dartMCPEvent, + eventData: { + 'client': client, + 'clientVersion': clientVersion, + 'serverVersion': serverVersion, + 'type': type, + ...?additionalData?.toMap(), + }, + ); + @override int get hashCode => Object.hash(eventName, jsonEncode(eventData)); diff --git a/pkgs/unified_analytics/pubspec.yaml b/pkgs/unified_analytics/pubspec.yaml index 8bba46243..94b3fe901 100644 --- a/pkgs/unified_analytics/pubspec.yaml +++ b/pkgs/unified_analytics/pubspec.yaml @@ -5,7 +5,7 @@ description: >- # LINT.IfChange # When updating this, keep the version consistent with the changelog and the # value in lib/src/constants.dart. -version: 8.0.1 +version: 8.0.2 # LINT.ThenChange(lib/src/constants.dart) repository: https://github.com/dart-lang/tools/tree/main/pkgs/unified_analytics issue_tracker: https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Aunified_analytics diff --git a/pkgs/unified_analytics/test/event_test.dart b/pkgs/unified_analytics/test/event_test.dart index e8a9f5696..9c4a99c0b 100644 --- a/pkgs/unified_analytics/test/event_test.dart +++ b/pkgs/unified_analytics/test/event_test.dart @@ -655,6 +655,27 @@ void main() { expect(constructedEvent.eventData.length, 20); }); + test('Event.dartMCPEvent constructed', () { + final event = Event.dartMCPEvent( + client: 'test client', + clientVersion: '1.0.0', + serverVersion: '1.1.0', + type: 'some_event', + additionalData: + _TestMetrics(boolField: true, stringField: 'hello', intField: 1)); + expect( + event.eventData, + equals({ + 'client': 'test client', + 'clientVersion': '1.0.0', + 'serverVersion': '1.1.0', + 'type': 'some_event', + 'boolField': true, + 'stringField': 'hello', + 'intField': 1, + })); + }); + test('Confirm all constructors were checked', () { var constructorCount = 0; for (final declaration in reflectClass(Event).declarations.keys) { @@ -667,7 +688,7 @@ void main() { // Change this integer below if your PR either adds or removes // an Event constructor - final eventsAccountedForInTests = 28; + final eventsAccountedForInTests = 29; expect(eventsAccountedForInTests, constructorCount, reason: 'If you added or removed an event constructor, ' 'ensure you have updated ' From b81556b760afe83f377bd08600cd38d16dc0a2d0 Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Tue, 24 Jun 2025 10:18:00 -0700 Subject: [PATCH 28/67] Fix status badge (#2116) --- pkgs/unified_analytics/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/unified_analytics/README.md b/pkgs/unified_analytics/README.md index 4263cfb5c..cc245ecc2 100644 --- a/pkgs/unified_analytics/README.md +++ b/pkgs/unified_analytics/README.md @@ -1,4 +1,4 @@ -[![package:unified_analytics](https://github.com/dart-lang/tools/actions/workflows/unified_analytics.yml/badge.svg)](https://github.com/dart-lang/tools/actions/workflows/unified_analytics.yml) +[![package:unified_analytics](https://github.com/dart-lang/tools/actions/workflows/unified_analytics.yaml/badge.svg)](https://github.com/dart-lang/tools/actions/workflows/unified_analytics.yaml) [![pub package](https://img.shields.io/pub/v/unified_analytics.svg)](https://pub.dev/packages/unified_analytics) [![package publisher](https://img.shields.io/pub/publisher/unified_analytics.svg)](https://pub.dev/packages/unified_analytics/publisher) From 590e8a220054d085821ad83394854de8341f3cf0 Mon Sep 17 00:00:00 2001 From: Keerti Parthasarathy Date: Tue, 24 Jun 2025 14:41:30 -0700 Subject: [PATCH 29/67] [unified_analytics]Add library cycle info to analysis server data (#2117) --- pkgs/unified_analytics/CHANGELOG.md | 4 ++ pkgs/unified_analytics/lib/src/constants.dart | 2 +- pkgs/unified_analytics/lib/src/event.dart | 50 +++++++++++-------- pkgs/unified_analytics/pubspec.yaml | 2 +- pkgs/unified_analytics/test/event_test.dart | 46 ++++++++--------- 5 files changed, 59 insertions(+), 45 deletions(-) diff --git a/pkgs/unified_analytics/CHANGELOG.md b/pkgs/unified_analytics/CHANGELOG.md index e5c26c902..bb1ba6ad4 100644 --- a/pkgs/unified_analytics/CHANGELOG.md +++ b/pkgs/unified_analytics/CHANGELOG.md @@ -1,3 +1,7 @@ +## 8.0.3 +- Changed `Event.contextStructure` to make optional data that's no longer being + collected and to add data about the size of library cycles. + ## 8.0.2 - Added `Event.dartMCPEvent` for events from the `dart mcp-server` command. diff --git a/pkgs/unified_analytics/lib/src/constants.dart b/pkgs/unified_analytics/lib/src/constants.dart index 3d472ea0e..449c591a8 100644 --- a/pkgs/unified_analytics/lib/src/constants.dart +++ b/pkgs/unified_analytics/lib/src/constants.dart @@ -87,7 +87,7 @@ const int kMaxLogFileSize = 25 * (1 << 20); const String kLogFileName = 'dart-flutter-telemetry.log'; /// The current version of the package, should be in line with pubspec version. -const String kPackageVersion = '8.0.2'; +const String kPackageVersion = '8.0.3'; /// The minimum length for a session. const int kSessionDurationMinutes = 30; diff --git a/pkgs/unified_analytics/lib/src/event.dart b/pkgs/unified_analytics/lib/src/event.dart index 7fcaad6f2..45b0ac5ff 100644 --- a/pkgs/unified_analytics/lib/src/event.dart +++ b/pkgs/unified_analytics/lib/src/event.dart @@ -309,18 +309,6 @@ final class Event { /// Event that is emitted on shutdown to report the structure of the analysis /// contexts created immediately after startup. /// - /// [contextsFromBothFiles] - the number of contexts that were created because - /// of both a package config and an analysis options file. - /// - /// [contextsFromOptionsFiles] - the number of contexts that were created - /// because of an analysis options file. - /// - /// [contextsFromPackagesFiles] - the number of contexts that were created - /// because of a package config file. - /// - /// [contextsWithoutFiles] - the number of contexts that were created because - /// of the lack of either a package config or an analysis options file. - /// /// [immediateFileCount] - the number of files in one of the analysis /// contexts. /// @@ -341,11 +329,25 @@ final class Event { /// /// [transitiveFileUniqueLineCount] - the number of lines in the unique /// transitive files. + /// + /// [libraryCycleLibraryCounts] - json encoded percentile values indicating + /// the number of libraries in a single library cycle. + /// + /// [libraryCycleLineCounts] - json encoded percentile values indicating the + /// number of lines of code in all of the files in a single library cycle. + /// + /// [contextsFromBothFiles] - the number of contexts that were created because + /// of both a package config and an analysis options file. + /// + /// [contextsFromOptionsFiles] - the number of contexts that were created + /// because of an analysis options file. + /// + /// [contextsFromPackagesFiles] - the number of contexts that were created + /// because of a package config file. + /// + /// [contextsWithoutFiles] - the number of contexts that were created because + /// of the lack of either a package config or an analysis options file. Event.contextStructure({ - required int contextsFromBothFiles, - required int contextsFromOptionsFiles, - required int contextsFromPackagesFiles, - required int contextsWithoutFiles, required int immediateFileCount, required int immediateFileLineCount, required int numberOfContexts, @@ -353,13 +355,15 @@ final class Event { required int transitiveFileLineCount, required int transitiveFileUniqueCount, required int transitiveFileUniqueLineCount, + String libraryCycleLibraryCounts = '', + String libraryCycleLineCounts = '', + int contextsFromBothFiles = 0, + int contextsFromOptionsFiles = 0, + int contextsFromPackagesFiles = 0, + int contextsWithoutFiles = 0, }) : this._( eventName: DashEvent.contextStructure, eventData: { - 'contextsFromBothFiles': contextsFromBothFiles, - 'contextsFromOptionsFiles': contextsFromOptionsFiles, - 'contextsFromPackagesFiles': contextsFromPackagesFiles, - 'contextsWithoutFiles': contextsWithoutFiles, 'immediateFileCount': immediateFileCount, 'immediateFileLineCount': immediateFileLineCount, 'numberOfContexts': numberOfContexts, @@ -367,6 +371,12 @@ final class Event { 'transitiveFileLineCount': transitiveFileLineCount, 'transitiveFileUniqueCount': transitiveFileUniqueCount, 'transitiveFileUniqueLineCount': transitiveFileUniqueLineCount, + 'libraryCycleLibraryCounts': libraryCycleLibraryCounts, + 'libraryCycleLineCounts': libraryCycleLineCounts, + 'contextsFromBothFiles': contextsFromBothFiles, + 'contextsFromOptionsFiles': contextsFromOptionsFiles, + 'contextsFromPackagesFiles': contextsFromPackagesFiles, + 'contextsWithoutFiles': contextsWithoutFiles, }, ); diff --git a/pkgs/unified_analytics/pubspec.yaml b/pkgs/unified_analytics/pubspec.yaml index 94b3fe901..b3517d69a 100644 --- a/pkgs/unified_analytics/pubspec.yaml +++ b/pkgs/unified_analytics/pubspec.yaml @@ -5,7 +5,7 @@ description: >- # LINT.IfChange # When updating this, keep the version consistent with the changelog and the # value in lib/src/constants.dart. -version: 8.0.2 +version: 8.0.3 # LINT.ThenChange(lib/src/constants.dart) repository: https://github.com/dart-lang/tools/tree/main/pkgs/unified_analytics issue_tracker: https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Aunified_analytics diff --git a/pkgs/unified_analytics/test/event_test.dart b/pkgs/unified_analytics/test/event_test.dart index 9c4a99c0b..98924fbb9 100644 --- a/pkgs/unified_analytics/test/event_test.dart +++ b/pkgs/unified_analytics/test/event_test.dart @@ -85,35 +85,35 @@ void main() { test('Event.contextStructure constructed', () { Event generateEvent() => Event.contextStructure( - contextsFromBothFiles: 1, - contextsFromOptionsFiles: 2, - contextsFromPackagesFiles: 3, - contextsWithoutFiles: 4, - immediateFileCount: 5, - immediateFileLineCount: 6, - numberOfContexts: 7, - transitiveFileCount: 8, - transitiveFileLineCount: 9, - transitiveFileUniqueCount: 10, - transitiveFileUniqueLineCount: 11, + immediateFileCount: 1, + immediateFileLineCount: 2, + numberOfContexts: 3, + transitiveFileCount: 4, + transitiveFileLineCount: 5, + transitiveFileUniqueCount: 6, + transitiveFileUniqueLineCount: 7, + libraryCycleLibraryCounts: 'a', + libraryCycleLineCounts: 'b', ); final constructedEvent = generateEvent(); expect(generateEvent, returnsNormally); expect(constructedEvent.eventName, DashEvent.contextStructure); - expect(constructedEvent.eventData['contextsFromBothFiles'], 1); - expect(constructedEvent.eventData['contextsFromOptionsFiles'], 2); - expect(constructedEvent.eventData['contextsFromPackagesFiles'], 3); - expect(constructedEvent.eventData['contextsWithoutFiles'], 4); - expect(constructedEvent.eventData['immediateFileCount'], 5); - expect(constructedEvent.eventData['immediateFileLineCount'], 6); - expect(constructedEvent.eventData['numberOfContexts'], 7); - expect(constructedEvent.eventData['transitiveFileCount'], 8); - expect(constructedEvent.eventData['transitiveFileLineCount'], 9); - expect(constructedEvent.eventData['transitiveFileUniqueCount'], 10); - expect(constructedEvent.eventData['transitiveFileUniqueLineCount'], 11); - expect(constructedEvent.eventData.length, 11); + expect(constructedEvent.eventData['immediateFileCount'], 1); + expect(constructedEvent.eventData['immediateFileLineCount'], 2); + expect(constructedEvent.eventData['numberOfContexts'], 3); + expect(constructedEvent.eventData['transitiveFileCount'], 4); + expect(constructedEvent.eventData['transitiveFileLineCount'], 5); + expect(constructedEvent.eventData['transitiveFileUniqueCount'], 6); + expect(constructedEvent.eventData['transitiveFileUniqueLineCount'], 7); + expect(constructedEvent.eventData['libraryCycleLibraryCounts'], 'a'); + expect(constructedEvent.eventData['libraryCycleLineCounts'], 'b'); + expect(constructedEvent.eventData['contextsFromBothFiles'], 0); + expect(constructedEvent.eventData['contextsFromOptionsFiles'], 0); + expect(constructedEvent.eventData['contextsFromPackagesFiles'], 0); + expect(constructedEvent.eventData['contextsWithoutFiles'], 0); + expect(constructedEvent.eventData.length, 13); }); test('Event.dartCliCommandExecuted constructed', () { From 0f8807adfa4b404c71cc1e3b45ebb9e25b2d7b4a Mon Sep 17 00:00:00 2001 From: "Lasse R.H. Nielsen" Date: Thu, 3 Jul 2025 10:55:27 +0200 Subject: [PATCH 30/67] [Markdown] Fix HTML comment parser. (#2121) Fix performance and correctness of HTML comment parser. Fixes #2119. RegExp had catastrophic backtracking. Also didn't match the spec it linked to. (Which is still CM 30, not 31.2. They differ, we may want to upgrade support eventually.) Fixed a few other incorrect parsings. - The content of ``, `` and `` can contain newlines. Changed `.` to `[^]`. - The `]+-[^-<>]+)+|[^-<>]+)-->' + r'` is needed. + // The number of lines increase time exponentially. + // The length of lines affect the base of the exponentiation. + // Locally, three "Lorem-ipsum" lines ran in ~6 seconds, two in < 200 ms. + // Adding a fourth line should ensure it cannot possibly finish in ten + // seconds if the bug isn't fixed. + const input = ''' +a +'''; + + final time = Stopwatch()..start(); + final html = markdownToHtml(input); // Should not hang. + expect(html, isNotNull); // To use the output. + final elapsed = time.elapsedMilliseconds; + expect(elapsed, lessThan(10000)); + }); + + test('HTML comment with lt/gt', () { + // Incorrect parsing found as part of fixing #2119. + // Now matches `` where text + // does not start with `>` or `->`, does not end with `-`, + // and does not contain `--`. + const input = 'a '; + final html = markdownToHtml(input); + expect(html, '

$input

\n'); + }); +} From de96517387e026518d31a4e5e75f24c7f43942d5 Mon Sep 17 00:00:00 2001 From: Nate Biggs Date: Tue, 8 Jul 2025 17:28:06 -0400 Subject: [PATCH 31/67] Add wasm dry run event to unified analytics. (#2125) Co-authored-by: Nate Biggs --- pkgs/unified_analytics/CHANGELOG.md | 3 ++ pkgs/unified_analytics/lib/src/enums.dart | 5 +++ pkgs/unified_analytics/lib/src/event.dart | 13 ++++++ pkgs/unified_analytics/pubspec.yaml | 2 +- pkgs/unified_analytics/test/event_test.dart | 44 +++++++++++++++++++++ 5 files changed, 66 insertions(+), 1 deletion(-) diff --git a/pkgs/unified_analytics/CHANGELOG.md b/pkgs/unified_analytics/CHANGELOG.md index bb1ba6ad4..33d92d7e9 100644 --- a/pkgs/unified_analytics/CHANGELOG.md +++ b/pkgs/unified_analytics/CHANGELOG.md @@ -1,3 +1,6 @@ +## 8.0.4 +- Changed `Event.flutterWasmDryRun` to track dart2wasm dry run metrics from Flutter. + ## 8.0.3 - Changed `Event.contextStructure` to make optional data that's no longer being collected and to add data about the size of library cycles. diff --git a/pkgs/unified_analytics/lib/src/enums.dart b/pkgs/unified_analytics/lib/src/enums.dart index b051e8487..84eca3ec4 100644 --- a/pkgs/unified_analytics/lib/src/enums.dart +++ b/pkgs/unified_analytics/lib/src/enums.dart @@ -103,6 +103,11 @@ enum DashEvent { description: 'Provides information about flutter commands that ran', toolOwner: DashTool.flutterTool, ), + flutterWasmDryRun( + label: 'wasm_dry_run', + description: 'Information for a dart2wasm dry run invoked from Flutter', + toolOwner: DashTool.flutterTool, + ), flutterInjectDarwinPlugins( label: 'flutter_inject_darwin_plugins', description: 'Information on plugins injected into an iOS/macOS project', diff --git a/pkgs/unified_analytics/lib/src/event.dart b/pkgs/unified_analytics/lib/src/event.dart index 45b0ac5ff..64d789dce 100644 --- a/pkgs/unified_analytics/lib/src/event.dart +++ b/pkgs/unified_analytics/lib/src/event.dart @@ -567,6 +567,19 @@ final class Event { }, ); + Event.flutterWasmDryRun({ + required String result, + required int exitCode, + String? findingsSummary, + }) : this._( + eventName: DashEvent.flutterWasmDryRun, + eventData: { + 'result': result, + 'exitCode': result, + if (findingsSummary != null) 'findings': findingsSummary, + }, + ); + /// Provides information about the plugins injected into an iOS or macOS /// project. /// diff --git a/pkgs/unified_analytics/pubspec.yaml b/pkgs/unified_analytics/pubspec.yaml index b3517d69a..4bf2d041e 100644 --- a/pkgs/unified_analytics/pubspec.yaml +++ b/pkgs/unified_analytics/pubspec.yaml @@ -5,7 +5,7 @@ description: >- # LINT.IfChange # When updating this, keep the version consistent with the changelog and the # value in lib/src/constants.dart. -version: 8.0.3 +version: 8.0.4 # LINT.ThenChange(lib/src/constants.dart) repository: https://github.com/dart-lang/tools/tree/main/pkgs/unified_analytics issue_tracker: https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Aunified_analytics diff --git a/pkgs/unified_analytics/test/event_test.dart b/pkgs/unified_analytics/test/event_test.dart index 98924fbb9..a8e5ba856 100644 --- a/pkgs/unified_analytics/test/event_test.dart +++ b/pkgs/unified_analytics/test/event_test.dart @@ -400,6 +400,50 @@ void main() { expect(constructedEvent.eventData.length, 4); }); + test('Event.flutterWasmDryRun constructed', () { + Event generateEventNoFindings() => Event.flutterWasmDryRun( + result: 'success', + exitCode: 123, + ); + + final constructedEvent1 = generateEventNoFindings(); + + expect(generateEventNoFindings, returnsNormally); + expect(constructedEvent1.eventName, DashEvent.flutterWasmDryRun); + expect(constructedEvent1.eventData['result'], 'success'); + expect(constructedEvent1.eventData['exitCode'], 123); + expect(constructedEvent1.eventData.length, 2); + }); + test('Event.flutterWasmDryRun constructed', () { + Event generateEventNoFindings() => Event.flutterWasmDryRun( + result: 'success', + exitCode: 123, + ); + + final constructedEvent1 = generateEventNoFindings(); + + expect(generateEventNoFindings, returnsNormally); + expect(constructedEvent1.eventName, DashEvent.flutterWasmDryRun); + expect(constructedEvent1.eventData['result'], 'success'); + expect(constructedEvent1.eventData['exitCode'], 123); + expect(constructedEvent1.eventData.length, 2); + + Event generateEventFindings() => Event.flutterWasmDryRun( + result: 'success', + exitCode: 123, + findingsSummary: '1,2,3' + ); + + final constructedEvent2 = generateEventFindings(); + + expect(generateEventFindings, returnsNormally); + expect(constructedEvent2.eventName, DashEvent.flutterWasmDryRun); + expect(constructedEvent2.eventData['result'], 'success'); + expect(constructedEvent2.eventData['exitCode'], 123); + expect(constructedEvent2.eventData['findingsSummary'], '1,2,3'); + expect(constructedEvent2.eventData.length, 3); + }); + test('Event.flutterInjectDarwinPlugins constructed', () { Event generateEvent() => Event.flutterInjectDarwinPlugins( platform: 'ios', From d48736adeb92a3098eee71fe2f72762256b68928 Mon Sep 17 00:00:00 2001 From: Liam Appelbe Date: Wed, 9 Jul 2025 09:53:37 +1000 Subject: [PATCH 32/67] [coverage] Expose `filterIgnored` function (#2123) --- pkgs/coverage/CHANGELOG.md | 6 ++ pkgs/coverage/lib/src/hitmap.dart | 106 +++++++------------- pkgs/coverage/lib/src/util.dart | 47 ++++++--- pkgs/coverage/pubspec.yaml | 2 +- pkgs/coverage/test/filter_ignored_test.dart | 103 +++++++++++++++++++ pkgs/coverage/test/util_test.dart | 38 +++++-- 6 files changed, 211 insertions(+), 91 deletions(-) create mode 100644 pkgs/coverage/test/filter_ignored_test.dart diff --git a/pkgs/coverage/CHANGELOG.md b/pkgs/coverage/CHANGELOG.md index a9825bca9..eb1c7c3e0 100644 --- a/pkgs/coverage/CHANGELOG.md +++ b/pkgs/coverage/CHANGELOG.md @@ -1,3 +1,9 @@ +## 1.15.0 + +- Expose `filterIgnored` function, which filters the coverage data according to + `// coverage:ignore-...` comments. Previously this filtering functionality + was only available when loading coverage data from json using `parseJson` etc. + ## 1.14.1 - Remove dependency on `package:pubspec_parse`. diff --git a/pkgs/coverage/lib/src/hitmap.dart b/pkgs/coverage/lib/src/hitmap.dart index 133c24be8..3aca584df 100644 --- a/pkgs/coverage/lib/src/hitmap.dart +++ b/pkgs/coverage/lib/src/hitmap.dart @@ -59,8 +59,6 @@ class HitMap { required Map>?> ignoredLinesInFilesCache, required Resolver resolver, }) { - final loader = Loader(); - // Map of source file to map of line to hit count for that line. final globalHitMap = {}; @@ -71,66 +69,6 @@ class HitMap { continue; } - var ignoredLinesList = >[]; - - if (checkIgnoredLines) { - if (ignoredLinesInFilesCache.containsKey(source)) { - final cacheHit = ignoredLinesInFilesCache[source]; - if (cacheHit == null) { - // Null-entry indicates that the whole file was ignored. - continue; - } - ignoredLinesList = cacheHit; - } else { - final path = resolver.resolve(source); - if (path != null) { - final lines = loader.loadSync(path) ?? []; - ignoredLinesList = getIgnoredLines(path, lines); - - // Ignore the whole file. - if (ignoredLinesList.length == 1 && - ignoredLinesList[0][0] == 0 && - ignoredLinesList[0][1] == lines.length) { - // Null-entry indicates that the whole file was ignored. - ignoredLinesInFilesCache[source] = null; - continue; - } - ignoredLinesInFilesCache[source] = ignoredLinesList; - } else { - // Couldn't resolve source. Allow cache to answer next time - // anyway. - ignoredLinesInFilesCache[source] = ignoredLinesList; - } - } - } - - // Move to the first ignore range. - final ignoredLines = ignoredLinesList.iterator; - var hasCurrent = ignoredLines.moveNext(); - - bool shouldIgnoreLine(Iterator> ignoredRanges, int line) { - if (!hasCurrent || ignoredRanges.current.isEmpty) { - return false; - } - - if (line < ignoredRanges.current[0]) return false; - - while (hasCurrent && - ignoredRanges.current.isNotEmpty && - ignoredRanges.current[1] < line) { - hasCurrent = ignoredRanges.moveNext(); - } - - if (hasCurrent && - ignoredRanges.current.isNotEmpty && - ignoredRanges.current[0] <= line && - line <= ignoredRanges.current[1]) { - return true; - } - - return false; - } - void addToMap(Map map, int line, int count) { final oldCount = map.putIfAbsent(line, () => 0); map[line] = count + oldCount; @@ -147,8 +85,6 @@ class HitMap { final k = hits[i]; if (k is int) { // Single line. - if (shouldIgnoreLine(ignoredLines, k)) continue; - addToMap(hitMap, k, hits[i + 1] as int); } else if (k is String) { // Linerange. We expand line ranges to actual lines at this point. @@ -156,8 +92,6 @@ class HitMap { final start = int.parse(k.substring(0, splitPos)); final end = int.parse(k.substring(splitPos + 1)); for (var j = start; j <= end; j++) { - if (shouldIgnoreLine(ignoredLines, j)) continue; - addToMap(hitMap, j, hits[i + 1] as int); } } else { @@ -185,7 +119,12 @@ class HitMap { fillHitMap(e['branchHits'] as List, sourceHitMap.branchHits!); } } - return globalHitMap; + return checkIgnoredLines + ? globalHitMap.filterIgnored( + ignoredLinesInFilesCache: ignoredLinesInFilesCache, + resolver: resolver, + ) + : globalHitMap; } /// Creates a single hitmap from a raw json object. @@ -268,6 +207,39 @@ extension FileHitMaps on Map { } }); } + + /// Filters out lines that are ignored by ignore comments. + Map filterIgnored({ + required Map>?> ignoredLinesInFilesCache, + required Resolver resolver, + }) { + final loader = Loader(); + final newHitMaps = {}; + for (final MapEntry(key: source, value: hitMap) in entries) { + final ignoredLinesList = ignoredLinesInFilesCache.putIfAbsent(source, () { + final path = resolver.resolve(source); + if (path == null) return >[]; + return getIgnoredLines(path, loader.loadSync(path)); + }); + // Null here means that the whole file is ignored. + if (ignoredLinesList == null) continue; + + Map? filterHits(Map? hits) => hits == null + ? null + : { + for (final MapEntry(key: line, value: count) in hits.entries) + if (!ignoredLinesList.ignoredContains(line)) line: count, + }; + + newHitMaps[source] = HitMap( + filterHits(hitMap.lineHits), + filterHits(hitMap.funcHits), + hitMap.funcNames, + filterHits(hitMap.branchHits), + ); + } + return newHitMaps; + } } /// Class containing information about a coverage hit. diff --git a/pkgs/coverage/lib/src/util.dart b/pkgs/coverage/lib/src/util.dart index 8ae1a4b79..06812cb3c 100644 --- a/pkgs/coverage/lib/src/util.dart +++ b/pkgs/coverage/lib/src/util.dart @@ -59,16 +59,16 @@ Uri? extractVMServiceUri(String str) { return null; } -final muliLineIgnoreStart = RegExp(r'//\s*coverage:ignore-start[\w\d\s]*$'); -final muliLineIgnoreEnd = RegExp(r'//\s*coverage:ignore-end[\w\d\s]*$'); +final multiLineIgnoreStart = RegExp(r'//\s*coverage:ignore-start[\w\d\s]*$'); +final multiLineIgnoreEnd = RegExp(r'//\s*coverage:ignore-end[\w\d\s]*$'); final singleLineIgnore = RegExp(r'//\s*coverage:ignore-line[\w\d\s]*$'); final ignoreFile = RegExp(r'//\s*coverage:ignore-file[\w\d\s]*$'); -/// Return list containing inclusive range of lines to be ignored by coverage. +/// Return list containing inclusive ranges of lines to be ignored by coverage. /// If there is a error in balancing the statements it will throw a /// [FormatException], /// unless `coverage:ignore-file` is found. -/// Return [0, lines.length] if the whole file is ignored. +/// Return null if the whole file is ignored. /// /// ``` /// 1. final str = ''; // coverage:ignore-line @@ -86,20 +86,17 @@ final ignoreFile = RegExp(r'//\s*coverage:ignore-file[\w\d\s]*$'); /// ] /// ``` /// -List> getIgnoredLines(String filePath, List? lines) { +/// The returned ranges are in sorted order, and never overlap. +List>? getIgnoredLines(String filePath, List? lines) { final ignoredLines = >[]; if (lines == null) return ignoredLines; - final allLines = [ - [0, lines.length] - ]; - FormatException? err; var i = 0; while (i < lines.length) { - if (lines[i].contains(ignoreFile)) return allLines; + if (lines[i].contains(ignoreFile)) return null; - if (lines[i].contains(muliLineIgnoreEnd)) { + if (lines[i].contains(multiLineIgnoreEnd)) { err ??= FormatException( 'unmatched coverage:ignore-end found at $filePath:${i + 1}', ); @@ -107,13 +104,13 @@ List> getIgnoredLines(String filePath, List? lines) { if (lines[i].contains(singleLineIgnore)) ignoredLines.add([i + 1, i + 1]); - if (lines[i].contains(muliLineIgnoreStart)) { + if (lines[i].contains(multiLineIgnoreStart)) { final start = i; var isUnmatched = true; ++i; while (i < lines.length) { - if (lines[i].contains(ignoreFile)) return allLines; - if (lines[i].contains(muliLineIgnoreStart)) { + if (lines[i].contains(ignoreFile)) return null; + if (lines[i].contains(multiLineIgnoreStart)) { err ??= FormatException( 'coverage:ignore-start found at $filePath:${i + 1}' ' before previous coverage:ignore-start ended', @@ -121,7 +118,7 @@ List> getIgnoredLines(String filePath, List? lines) { break; } - if (lines[i].contains(muliLineIgnoreEnd)) { + if (lines[i].contains(multiLineIgnoreEnd)) { ignoredLines.add([start + 1, i + 1]); isUnmatched = false; break; @@ -146,6 +143,26 @@ List> getIgnoredLines(String filePath, List? lines) { throw err; } +extension IgnoredLinesContains on List> { + /// Returns whether this list of line ranges contains the given line. + bool ignoredContains(int line) { + if (length == 0 || this[0][0] > line) return false; + + // Binary search for the range with the largest start value that is <= line. + var lo = 0; + var hi = length; + while (lo < hi - 1) { + final mid = lo + (hi - lo) ~/ 2; + if (this[mid][0] <= line) { + lo = mid; + } else { + hi = mid; + } + } + return this[lo][1] >= line; + } +} + extension StandardOutExtension on Stream> { Stream lines() => transform(const SystemEncoding().decoder).transform(const LineSplitter()); diff --git a/pkgs/coverage/pubspec.yaml b/pkgs/coverage/pubspec.yaml index 844914f72..7700486aa 100644 --- a/pkgs/coverage/pubspec.yaml +++ b/pkgs/coverage/pubspec.yaml @@ -1,5 +1,5 @@ name: coverage -version: 1.14.1 +version: 1.15.0 description: Coverage data manipulation and formatting repository: https://github.com/dart-lang/tools/tree/main/pkgs/coverage issue_tracker: https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Acoverage diff --git a/pkgs/coverage/test/filter_ignored_test.dart b/pkgs/coverage/test/filter_ignored_test.dart new file mode 100644 index 000000000..ac5300489 --- /dev/null +++ b/pkgs/coverage/test/filter_ignored_test.dart @@ -0,0 +1,103 @@ +// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:io'; + +import 'package:coverage/coverage.dart' show Resolver; +import 'package:coverage/src/hitmap.dart'; +import 'package:test/test.dart'; + +String fileUri(String relativePath) => + Uri.file(File(relativePath).absolute.path).toString(); + +void main() { + test('filter ignored', () async { + // The ignored lines come from the comments in the test dart files. But the + // hitmaps are fake, and don't have to correspond to real coverage data. + final hitmaps = { + fileUri('nonexistent_file.dart'): HitMap( + {1: 1, 2: 2, 3: 3}, + {1: 1, 2: 2, 3: 3}, + {1: 'abc', 2: 'def'}, + {1: 1, 2: 2, 3: 3}, + ), + fileUri('another_nonexistent_file.dart'): HitMap( + {1: 1, 2: 2, 3: 3}, + ), + fileUri('test/test_files/test_app.dart'): HitMap( + {1: 1, 2: 2, 3: 3}, + {1: 1, 2: 2, 3: 3}, + {1: 'abc', 2: 'def'}, + {1: 1, 2: 2, 3: 3}, + ), + fileUri('test/test_files/test_app_isolate.dart'): HitMap( + {for (var i = 50; i < 100; ++i) i: i}, + {for (var i = 50; i < 100; ++i) i: i}, + {for (var i = 50; i < 100; ++i) i: '$i'}, + {for (var i = 50; i < 100; ++i) i: i}, + ), + }; + + // Lines ignored in test/test_files/test_app_isolate.dart. + const ignores = [ + 52, + 54, + 55, + 56, + 57, + 58, + 63, + 64, + 65, + 66, + 67, + 68, + 69, + 70, + 71, + 72, + 73, + ]; + + final expected = { + fileUri('nonexistent_file.dart'): HitMap( + {1: 1, 2: 2, 3: 3}, + {1: 1, 2: 2, 3: 3}, + {1: 'abc', 2: 'def'}, + {1: 1, 2: 2, 3: 3}, + ), + fileUri('another_nonexistent_file.dart'): HitMap( + {1: 1, 2: 2, 3: 3}, + ), + fileUri('test/test_files/test_app_isolate.dart'): HitMap( + { + for (var i = 50; i < 100; ++i) + if (!ignores.contains(i)) i: i + }, + { + for (var i = 50; i < 100; ++i) + if (!ignores.contains(i)) i: i + }, + {for (var i = 50; i < 100; ++i) i: '$i'}, + { + for (var i = 50; i < 100; ++i) + if (!ignores.contains(i)) i: i + }, + ), + }; + + final resolver = await Resolver.create(packagePath: '.'); + + final actual = + hitmaps.filterIgnored(ignoredLinesInFilesCache: {}, resolver: resolver); + + expect(actual.keys.toList(), expected.keys.toList()); + for (final source in expected.keys) { + expect(actual[source]!.lineHits, expected[source]!.lineHits); + expect(actual[source]!.funcHits, expected[source]!.funcHits); + expect(actual[source]!.funcNames, expected[source]!.funcNames); + expect(actual[source]!.branchHits, expected[source]!.branchHits); + } + }); +} diff --git a/pkgs/coverage/test/util_test.dart b/pkgs/coverage/test/util_test.dart index 75405bae7..0c9b527ea 100644 --- a/pkgs/coverage/test/util_test.dart +++ b/pkgs/coverage/test/util_test.dart @@ -5,6 +5,7 @@ // ignore_for_file: only_throw_errors import 'dart:async'; +import 'dart:math'; import 'package:coverage/src/util.dart'; import 'package:test/test.dart'; @@ -232,27 +233,23 @@ void main() { }); test( - 'returns [[0,lines.length]] when the annotations are not ' + 'returns null when the annotations are not ' 'balanced but the whole file is ignored', () { for (final content in invalidSources) { final lines = content.split('\n'); lines.add(' // coverage:ignore-file'); - expect(getIgnoredLines('', lines), [ - [0, lines.length] - ]); + expect(getIgnoredLines('', lines), null); } }); - test('returns [[0,lines.length]] when the whole file is ignored', () { + test('returns null when the whole file is ignored', () { final lines = '''final str = ''; // coverage:ignore-start final str = ''; // coverage:ignore-end final str = ''; // coverage:ignore-file ''' .split('\n'); - expect(getIgnoredLines('', lines), [ - [0, lines.length] - ]); + expect(getIgnoredLines('', lines), null); }); test('return the correct range of lines ignored', () { @@ -373,4 +370,29 @@ void main() { 'bar_example', ])); }); + + test('IgnoredLinesContains', () { + (List>, int) createRandomRanges(int len) { + final ranges = >[]; + var line = 0; + final rand = Random(); + while (ranges.length < len) { + final start = line += 1 + rand.nextInt(5); + final end = line += rand.nextInt(5); + ranges.add([start, end]); + } + return (ranges, line); + } + + bool naiveIgnoredContains(List> ranges, int line) => + ranges.any((range) => range[0] <= line && range[1] >= line); + + for (final len in [0, 1, 2, 3, 10, 100, 1000]) { + final (ranges, end) = createRandomRanges(len); + for (var line = 0; line < end + 3; ++line) { + expect( + ranges.ignoredContains(line), naiveIgnoredContains(ranges, line)); + } + } + }); } From cedd940718721c52e9f38ae7f4c9afe6db90df9e Mon Sep 17 00:00:00 2001 From: Nate Biggs Date: Wed, 9 Jul 2025 13:49:54 -0400 Subject: [PATCH 33/67] Fix wasm dry run event and tests (#2128) --- pkgs/unified_analytics/CHANGELOG.md | 3 +++ pkgs/unified_analytics/lib/src/constants.dart | 2 +- pkgs/unified_analytics/lib/src/event.dart | 14 +++++++------- pkgs/unified_analytics/pubspec.yaml | 2 +- pkgs/unified_analytics/test/event_test.dart | 9 +++------ 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/pkgs/unified_analytics/CHANGELOG.md b/pkgs/unified_analytics/CHANGELOG.md index 33d92d7e9..a3a04a9dc 100644 --- a/pkgs/unified_analytics/CHANGELOG.md +++ b/pkgs/unified_analytics/CHANGELOG.md @@ -1,3 +1,6 @@ +## 8.0.5 +- Fix `Event.flutterWasmDryRun` fields. + ## 8.0.4 - Changed `Event.flutterWasmDryRun` to track dart2wasm dry run metrics from Flutter. diff --git a/pkgs/unified_analytics/lib/src/constants.dart b/pkgs/unified_analytics/lib/src/constants.dart index 449c591a8..f546bd200 100644 --- a/pkgs/unified_analytics/lib/src/constants.dart +++ b/pkgs/unified_analytics/lib/src/constants.dart @@ -87,7 +87,7 @@ const int kMaxLogFileSize = 25 * (1 << 20); const String kLogFileName = 'dart-flutter-telemetry.log'; /// The current version of the package, should be in line with pubspec version. -const String kPackageVersion = '8.0.3'; +const String kPackageVersion = '8.0.5'; /// The minimum length for a session. const int kSessionDurationMinutes = 30; diff --git a/pkgs/unified_analytics/lib/src/event.dart b/pkgs/unified_analytics/lib/src/event.dart index 64d789dce..e8b84f36f 100644 --- a/pkgs/unified_analytics/lib/src/event.dart +++ b/pkgs/unified_analytics/lib/src/event.dart @@ -572,13 +572,13 @@ final class Event { required int exitCode, String? findingsSummary, }) : this._( - eventName: DashEvent.flutterWasmDryRun, - eventData: { - 'result': result, - 'exitCode': result, - if (findingsSummary != null) 'findings': findingsSummary, - }, - ); + eventName: DashEvent.flutterWasmDryRun, + eventData: { + 'result': result, + 'exitCode': exitCode, + if (findingsSummary != null) 'findings': findingsSummary, + }, + ); /// Provides information about the plugins injected into an iOS or macOS /// project. diff --git a/pkgs/unified_analytics/pubspec.yaml b/pkgs/unified_analytics/pubspec.yaml index 4bf2d041e..1840adac3 100644 --- a/pkgs/unified_analytics/pubspec.yaml +++ b/pkgs/unified_analytics/pubspec.yaml @@ -5,7 +5,7 @@ description: >- # LINT.IfChange # When updating this, keep the version consistent with the changelog and the # value in lib/src/constants.dart. -version: 8.0.4 +version: 8.0.5 # LINT.ThenChange(lib/src/constants.dart) repository: https://github.com/dart-lang/tools/tree/main/pkgs/unified_analytics issue_tracker: https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Aunified_analytics diff --git a/pkgs/unified_analytics/test/event_test.dart b/pkgs/unified_analytics/test/event_test.dart index a8e5ba856..301f4c7f8 100644 --- a/pkgs/unified_analytics/test/event_test.dart +++ b/pkgs/unified_analytics/test/event_test.dart @@ -429,10 +429,7 @@ void main() { expect(constructedEvent1.eventData.length, 2); Event generateEventFindings() => Event.flutterWasmDryRun( - result: 'success', - exitCode: 123, - findingsSummary: '1,2,3' - ); + result: 'success', exitCode: 123, findingsSummary: '1,2,3'); final constructedEvent2 = generateEventFindings(); @@ -440,7 +437,7 @@ void main() { expect(constructedEvent2.eventName, DashEvent.flutterWasmDryRun); expect(constructedEvent2.eventData['result'], 'success'); expect(constructedEvent2.eventData['exitCode'], 123); - expect(constructedEvent2.eventData['findingsSummary'], '1,2,3'); + expect(constructedEvent2.eventData['findings'], '1,2,3'); expect(constructedEvent2.eventData.length, 3); }); @@ -732,7 +729,7 @@ void main() { // Change this integer below if your PR either adds or removes // an Event constructor - final eventsAccountedForInTests = 29; + final eventsAccountedForInTests = 30; expect(eventsAccountedForInTests, constructorCount, reason: 'If you added or removed an event constructor, ' 'ensure you have updated ' From f10484b29922afecb47c7431aff63e3c6befcea5 Mon Sep 17 00:00:00 2001 From: Konstantin Scheglov Date: Thu, 10 Jul 2025 13:23:28 -0700 Subject: [PATCH 34/67] Improve test_reflective_loader/README.md (#2129) --- pkgs/test_reflective_loader/README.md | 100 ++++++++++++++++++++++---- 1 file changed, 88 insertions(+), 12 deletions(-) diff --git a/pkgs/test_reflective_loader/README.md b/pkgs/test_reflective_loader/README.md index 9b5a83d4e..15a9c14fe 100644 --- a/pkgs/test_reflective_loader/README.md +++ b/pkgs/test_reflective_loader/README.md @@ -2,24 +2,100 @@ [![pub package](https://img.shields.io/pub/v/test_reflective_loader.svg)](https://pub.dev/packages/test_reflective_loader) [![package publisher](https://img.shields.io/pub/publisher/test_reflective_loader.svg)](https://pub.dev/packages/test_reflective_loader/publisher) -Support for discovering tests and test suites using reflection. +Support for discovering and running tests and test suites using reflection. -This package follows the xUnit style where each class is a test suite, and each -method with the name prefix `test_` is a single test. +This package follows an xUnit style where each class can be a test suite. +Test methods within the class are discovered reflectively based on their names or annotations. -Methods with names starting with `test_` are run using the `test()` function with -the corresponding name. If the class defines methods `setUp()` or `tearDown()`, -they are executed before / after each test correspondingly, even if the test fails. +## Usage -Methods with names starting with `solo_test_` are run using the `solo_test()` function. +A test file will typically have a `main` function that kicks off the test loader, and one or more classes annotated with +`@reflectiveTest`. -Methods with names starting with `fail_` are expected to fail. +Here is a simple example: -Methods with names starting with `solo_fail_` are run using the `solo_test()` function -and expected to fail. +```dart +import 'package:test/test.dart'; +import 'package:test_reflective_loader/test_reflective_loader.dart'; -Method returning `Future` class instances are asynchronous, so `tearDown()` is -executed after the returned `Future` completes. +void main() { + defineReflectiveSuite(() { + defineReflectiveTests(MyTestClass); + }); +} + +@reflectiveTest +class MyTestClass { + void test_simpleSuccess() { + expect(true, isTrue); + } + + @failingTest + void test_expectedToFail() { + expect(false, isTrue); + } + + @skippedTest + void test_skipped() { + // This test won't be run. + } + + void setUp() { + // setUp is called before each test. + print('setting up'); + } + + void tearDown() { + // tearDown is called after each test. + print('tearing down'); + } +} +``` + +To run the tests, you would execute your test file with `dart test`. + +### Test Discovery + +The `defineReflectiveSuite` and `defineReflectiveTests` functions are used to discover and define tests. + +* `defineReflectiveSuite`: This function creates a test suite. It takes a closure as an argument, and within that + closure, you can define tests and other nested suites. This allows you to group related tests together. + +* `defineReflectiveTests`: This function tells the test runner to look for tests in a given class. The class must be + annotated with `@reflectiveTest`. The test runner will then reflectively look for methods in that class that are + considered tests (see below). + +### Test Suites + +Test suites are classes annotated with `@reflectiveTest`. The loader will instantiate this class for each test method, +so tests are isolated from each other. + +### Test Methods + +The loader discovers tests in a few ways: + +* Methods whose names start with `test_` are treated as tests. +* Methods whose names start with `fail_` are treated as tests that are expected to fail. +* Methods whose names start with `skip_` are skipped. + +### Annotations + +* `@failingTest`: Marks a test that is expected to fail. This is useful for tests that demonstrate a bug that has not + yet been fixed. +* `@skippedTest`: Marks a test that should be skipped and not run. +* `@soloTest`: Can be applied to a test class or a test method to indicate that only this test (or the tests in this + class) should be run. If multiple tests/classes have this annotation, they will all run. + +### `setUp` and `tearDown` + +If a test class defines a `setUp()` method, it will be run before each test method in that class. If it defines a +`tearDown()` method, it will be run after each test method, even if the test fails. These are useful for setting up and +cleaning up test fixtures. Both `setUp()` and `tearDown()` can be `async` and return a `Future`. + +### Asynchronous Tests + +If a test method, `setUp()`, or `tearDown()` returns a `Future`, the test runner will wait for the future to complete. +The `tearDown` method (if any) will be executed after the test method and its `Future` completes. ## Features and bugs From 2dd2f9e7117c2eb3b065c369151c1d09eb36abc4 Mon Sep 17 00:00:00 2001 From: Daco Harkes Date: Tue, 15 Jul 2025 13:13:27 +0200 Subject: [PATCH 35/67] [cli_util] Add base directories (#2130) --- pkgs/cli_util/CHANGELOG.md | 4 + pkgs/cli_util/lib/cli_util.dart | 7 +- pkgs/cli_util/lib/src/base_directories.dart | 287 ++++++++++++++++++ pkgs/cli_util/pubspec.yaml | 2 +- pkgs/cli_util/test/base_directories_test.dart | 50 +++ pkgs/cli_util/test/cli_util_test.dart | 2 + 6 files changed, 349 insertions(+), 3 deletions(-) create mode 100644 pkgs/cli_util/lib/src/base_directories.dart create mode 100644 pkgs/cli_util/test/base_directories_test.dart diff --git a/pkgs/cli_util/CHANGELOG.md b/pkgs/cli_util/CHANGELOG.md index 9e77b8fe2..dc376fb07 100644 --- a/pkgs/cli_util/CHANGELOG.md +++ b/pkgs/cli_util/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.5.0-wip + +- Add `BaseDirectories` class and deprecate `applicationConfigHome`. + ## 0.4.2 - Add `sdkPath` getter, deprecate `getSdkPath` function. diff --git a/pkgs/cli_util/lib/cli_util.dart b/pkgs/cli_util/lib/cli_util.dart index e497d68b5..39de8d4d6 100644 --- a/pkgs/cli_util/lib/cli_util.dart +++ b/pkgs/cli_util/lib/cli_util.dart @@ -10,6 +10,8 @@ import 'dart:io'; import 'package:path/path.dart' as path; +export 'src/base_directories.dart'; + /// The path to the current Dart SDK. String get sdkPath => path.dirname(path.dirname(Platform.resolvedExecutable)); @@ -41,6 +43,7 @@ String getSdkPath() => sdkPath; /// /// [1]: https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html /// [2]: https://developer.apple.com/library/archive/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/FileSystemOverview/FileSystemOverview.html#//apple_ref/doc/uid/TP40010672-CH2-SW1 +@Deprecated('Use BaseDirectories(tool: productName).configHome() instead.') String applicationConfigHome(String productName) => path.join(_configHome, productName); @@ -73,8 +76,8 @@ String _requireEnv(String name) => /// Exception thrown if a required environment entry does not exist. /// -/// Thrown by [applicationConfigHome] if an expected and required -/// platform specific environment entry is not available. +/// Thrown if an expected and required platform specific environment entry is +/// not available. class EnvironmentNotFoundException implements Exception { /// Name of environment entry which was needed, but not found. final String entryName; diff --git a/pkgs/cli_util/lib/src/base_directories.dart b/pkgs/cli_util/lib/src/base_directories.dart new file mode 100644 index 000000000..a8f7304d9 --- /dev/null +++ b/pkgs/cli_util/lib/src/base_directories.dart @@ -0,0 +1,287 @@ +// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:io' show Platform; + +import 'package:path/path.dart' as path; + +import '../cli_util.dart'; + +/// The standard system paths for a Dart tool. +/// +/// These paths respects the following directory standards: +/// +/// - On Linux, the [XDG Base Directory +/// Specification](https://specifications.freedesktop.org/basedir-spec/latest/). +/// - On MacOS, the +/// [Library](https://developer.apple.com/library/archive/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/FileSystemOverview/FileSystemOverview.html) +/// directory. +/// - On Windows, `%APPDATA%` and `%LOCALAPPDATA%`. +/// +/// Note that [cacheHome], [configHome], [dataHome], [runtimeHome], and +/// [stateHome] may be overlapping or nested. +/// +/// Note that the directories won't be created, the methods merely return the +/// recommended locations. +final class BaseDirectories { + /// The name of the Dart tool. + /// + /// The name is used to provide a subdirectory inside the base directories. + /// + /// This should be a valid directory name on every operating system. The name + /// is typically camel-cased. For example: `"MyApp"`. + final String tool; + + /// The environment variables to use to determine the base directories. + /// + /// Defaults to [Platform.environment]. + final Map _environment; + + /// Constructs a [BaseDirectories] instance for the given [tool] name. + /// + /// The [environment] map, if provided, is used to determine the base + /// directories. If omitted, it defaults to using [Platform.environment]. + BaseDirectories( + this.tool, { + Map? environment, + }) : _environment = environment ?? Platform.environment; + + /// Path of the directory where the tool will place its caches. + /// + /// The cache may be purged by the operating system or user at any time. + /// Applications should be able to reconstruct any data stored here. If [tool] + /// cannot handle data being purged, use [runtimeHome] or [dataHome] instead. + /// + /// This is a location appropriate for storing non-essential files that may be + /// removed at any point. For example: intermediate compilation artifacts. + /// + /// The directory location depends on the current [Platform.operatingSystem]: + /// - on **Windows**: + /// - `%LOCALAPPDATA%\` + /// - on **Mac OS**: + /// - `$HOME/Library/Caches/` + /// - on **Linux**: + /// - `$XDG_CACHE_HOME/` if `$XDG_CACHE_HOME` is defined, and + /// - `$HOME/.cache/` otherwise. + /// + /// The directory won't be created, the method merely returns the recommended + /// location. + /// + /// On some platforms, this path may overlap with [runtimeHome] and + /// [stateHome]. + /// + /// Throws an [EnvironmentNotFoundException] if a necessary environment + /// variable is undefined. + late final String cacheHome = + path.join(_baseDirectory(_XdgBaseDirectoryKind.cache)!, tool); + + /// Path of the directory where the tool will place its configuration. + /// + /// The configuration may be synchronized across devices by the OS and may + /// survive application removal. + /// + /// This is a location appropriate for storing application specific + /// configuration for the current user. + /// + /// The directory location depends on the current [Platform.operatingSystem] + /// and what file types are stored: + /// - on **Windows**: + /// - `%APPDATA%\` + /// - on **Mac OS**: + /// - `$HOME/Library/Application Support/` + /// - on **Linux**: + /// - `$XDG_CONFIG_HOME/` if `$XDG_CONFIG_HOME` is defined, and + /// - `$HOME/.config/` otherwise. + /// + /// The directory won't be created, the method merely returns the recommended + /// location. + /// + /// On some platforms, this path may overlap with [dataHome]. + /// + /// Throws an [EnvironmentNotFoundException] if a necessary environment + /// variable is undefined. + late final String configHome = + path.join(_baseDirectory(_XdgBaseDirectoryKind.config)!, tool); + + /// Path of the directory where the tool will place its user data. + /// + /// The data may be backed up and synchronized to other devices by the + /// operating system. For large data use [stateHome] instead. + /// + /// This is a location appropriate for storing application specific + /// data for the current user. For example: documents created by the user. + /// + /// The directory location depends on the current [Platform.operatingSystem]: + /// - on **Windows**: + /// - `%APPDATA%\` + /// - on **Mac OS**: + /// - `$HOME/Library/Application Support/` + /// - on **Linux**: + /// - `$XDG_DATA_HOME/` if `$XDG_DATA_HOME` is defined, and + /// - `$HOME/.local/share/` otherwise. + /// + /// The directory won't be created, the method merely returns the recommended + /// location. + /// + /// On some platforms, this path may overlap with [configHome] and + /// [stateHome]. + /// + /// Throws an [EnvironmentNotFoundException] if a necessary environment + /// variable is undefined. + late final String dataHome = + path.join(_baseDirectory(_XdgBaseDirectoryKind.data)!, tool); + + /// Path of the directory where the tool will place its runtime data. + /// + /// The runtime data may be deleted in between user logins by the OS. For data + /// that needs to persist between sessions, use [stateHome] instead. + /// + /// This is a location appropriate for storing runtime data for the current + /// session. For example: undo history. + /// + /// This directory might be undefined on Linux, in such case a warning should + /// be printed and a suitable fallback (such as a temporary directory) should + /// be used. + /// + /// The directory location depends on the current [Platform.operatingSystem]: + /// - on **Windows**: + /// - `%LOCALAPPDATA%\` + /// - on **Mac OS**: + /// - `$HOME/Library/Caches/TemporaryItems/` + /// - on **Linux**: + /// - `$XDG_RUNTIME_HOME/` if `$XDG_RUNTIME_HOME` is defined, and + /// - `null` otherwise. + /// + /// The directory won't be created, the method merely returns the recommended + /// location. + /// + /// On some platforms, this path may overlap [cacheHome] and [stateHome] or be + /// nested in [cacheHome]. + /// + /// Throws an [EnvironmentNotFoundException] if a necessary environment + /// variable is undefined. + late final String? runtimeHome = + _join(_baseDirectory(_XdgBaseDirectoryKind.runtime), tool); + + /// Path of the directory where the tool will place its state. + /// + /// The state directory is likely not backed up or synchronized accross + /// devices by the OS. For data that may be backed up and synchronized, use + /// [dataHome] instead. + /// + /// This is a location appropriate for storing data which is either not + /// important enougn, not small enough, or not portable enough to store in + /// [dataHome]. For example: logs and indices. + /// + /// The directory location depends on the current [Platform.operatingSystem]: + /// - on **Windows**: + /// - `%LOCALAPPDATA%\` + /// - on **Mac OS**: + /// - `$HOME/Library/Application Support/` + /// - on **Linux**: + /// - `$XDG_STATE_HOME/` if `$XDG_STATE_HOME` is defined, and + /// - `$HOME/.local/state/` otherwise. + /// + /// The directory won't be created, the method merely returns the recommended + /// location. + /// + /// On some platforms, this path may overlap with [cacheHome], and + /// [runtimeHome]. + /// + /// Throws an [EnvironmentNotFoundException] if a necessary environment + /// variable is undefined. + late final String stateHome = + path.join(_baseDirectory(_XdgBaseDirectoryKind.state)!, tool); + + String? _baseDirectory(_XdgBaseDirectoryKind directoryKind) { + if (Platform.isWindows) { + return _baseDirectoryWindows(directoryKind); + } + if (Platform.isMacOS) { + return _baseDirectoryMacOs(directoryKind); + } + if (Platform.isLinux) { + return _baseDirectoryLinux(directoryKind); + } + throw UnsupportedError('Unsupported platform: ${Platform.operatingSystem}'); + } + + String _baseDirectoryWindows(_XdgBaseDirectoryKind dir) => switch (dir) { + _XdgBaseDirectoryKind.config || + _XdgBaseDirectoryKind.data => + _requireEnv('APPDATA'), + _XdgBaseDirectoryKind.cache || + _XdgBaseDirectoryKind.runtime || + _XdgBaseDirectoryKind.state => + _requireEnv('LOCALAPPDATA'), + }; + + String _baseDirectoryMacOs(_XdgBaseDirectoryKind dir) => switch (dir) { + _XdgBaseDirectoryKind.config || + // `$HOME/Library/Preferences/` may only contain `.plist` files, so use + // `Application Support` instead. + _XdgBaseDirectoryKind.data || + _XdgBaseDirectoryKind.state => + path.join(_home, 'Library', 'Application Support'), + _XdgBaseDirectoryKind.cache => path.join(_home, 'Library', 'Caches'), + _XdgBaseDirectoryKind.runtime => + // https://stackoverflow.com/a/76799489 + path.join(_home, 'Library', 'Caches', 'TemporaryItems'), + }; + + String? _baseDirectoryLinux(_XdgBaseDirectoryKind dir) { + if (Platform.isLinux) { + final xdgEnv = switch (dir) { + _XdgBaseDirectoryKind.config => 'XDG_CONFIG_HOME', + _XdgBaseDirectoryKind.data => 'XDG_DATA_HOME', + _XdgBaseDirectoryKind.state => 'XDG_STATE_HOME', + _XdgBaseDirectoryKind.cache => 'XDG_CACHE_HOME', + _XdgBaseDirectoryKind.runtime => 'XDG_RUNTIME_DIR', + }; + final envVar = _environment[xdgEnv]; + if (envVar != null) { + return envVar; + } + } + + switch (dir) { + case _XdgBaseDirectoryKind.runtime: + // Applications should chose a different directory and print a warning. + return null; + case _XdgBaseDirectoryKind.cache: + return path.join(_home, '.cache'); + case _XdgBaseDirectoryKind.config: + return path.join(_home, '.config'); + case _XdgBaseDirectoryKind.data: + return path.join(_home, '.local', 'share'); + case _XdgBaseDirectoryKind.state: + return path.join(_home, '.local', 'state'); + } + } + + String get _home => _requireEnv('HOME'); + + String _requireEnv(String name) => + _environment[name] ?? (throw EnvironmentNotFoundException(name)); +} + +/// A kind from the XDG base directory specification for Linux. +/// +/// MacOS and Windows have less kinds. +enum _XdgBaseDirectoryKind { + cache, + config, + data, + // Executables are also mentioned in the XDG spec, but these do not have as + // well defined of locations on Windows and MacOS. + runtime, + state, +} + +String? _join(String? part1, String? part2) { + if (part1 == null || part2 == null) { + return null; + } + return path.join(part1, part2); +} diff --git a/pkgs/cli_util/pubspec.yaml b/pkgs/cli_util/pubspec.yaml index ab879dbd0..e885bff9b 100644 --- a/pkgs/cli_util/pubspec.yaml +++ b/pkgs/cli_util/pubspec.yaml @@ -1,5 +1,5 @@ name: cli_util -version: 0.4.2 +version: 0.5.0-wip description: A library to help in building Dart command-line apps. repository: https://github.com/dart-lang/tools/tree/main/pkgs/cli_util issue_tracker: https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Acli_util diff --git a/pkgs/cli_util/test/base_directories_test.dart b/pkgs/cli_util/test/base_directories_test.dart new file mode 100644 index 000000000..4acda8afb --- /dev/null +++ b/pkgs/cli_util/test/base_directories_test.dart @@ -0,0 +1,50 @@ +// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:io'; + +import 'package:cli_util/cli_util.dart'; +import 'package:path/path.dart' as p; +import 'package:test/test.dart'; + +void main() { + final baseDirectories = BaseDirectories('my_app'); + + test('returns a non-empty string', () { + expect(baseDirectories.cacheHome, isNotEmpty); + expect(baseDirectories.configHome, isNotEmpty); + expect(baseDirectories.dataHome, isNotEmpty); + expect(baseDirectories.runtimeHome, isNotEmpty); + expect(baseDirectories.stateHome, isNotEmpty); + }); + + test('has an ancestor folder that exists', () { + void expectAncestorExists(String? path) { + if (path == null) { + // runtimeHome may be undefined on Linux. + return; + } + // We expect that first two segments of the path exist. This is really + // just a dummy check that some part of the path exists. + final ancestorPath = p.joinAll(p.split(path).take(2)); + expect( + Directory(ancestorPath).existsSync(), + isTrue, + ); + } + + expectAncestorExists(baseDirectories.cacheHome); + expectAncestorExists(baseDirectories.configHome); + expectAncestorExists(baseDirectories.dataHome); + expectAncestorExists(baseDirectories.runtimeHome); + expectAncestorExists(baseDirectories.stateHome); + }); + + test('empty environment throws exception', () async { + expect( + () => BaseDirectories('Dart', environment: {}).configHome, + throwsA(isA()), + ); + }); +} diff --git a/pkgs/cli_util/test/cli_util_test.dart b/pkgs/cli_util/test/cli_util_test.dart index e16bc593f..5a2e4b2e1 100644 --- a/pkgs/cli_util/test/cli_util_test.dart +++ b/pkgs/cli_util/test/cli_util_test.dart @@ -2,6 +2,8 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. +// ignore_for_file: deprecated_member_use_from_same_package + import 'dart:async'; import 'dart:io'; From eee7e0d8c12974d5cb6aa29ce71841ab3342e709 Mon Sep 17 00:00:00 2001 From: Nikechukwu <150845642+nikeokoronkwo@users.noreply.github.com> Date: Thu, 17 Jul 2025 00:33:29 -0700 Subject: [PATCH 36/67] [code_builder] Set `external` and `static` in correct order (#2120) --- pkgs/code_builder/CHANGELOG.md | 1 + pkgs/code_builder/lib/src/emitter.dart | 6 ++-- pkgs/code_builder/test/specs/field_test.dart | 30 ++++++++++++++++++++ 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/pkgs/code_builder/CHANGELOG.md b/pkgs/code_builder/CHANGELOG.md index 1d86e5cbb..f8459a322 100644 --- a/pkgs/code_builder/CHANGELOG.md +++ b/pkgs/code_builder/CHANGELOG.md @@ -3,6 +3,7 @@ * Upgrade `dart_style` and `source_gen` to remove `package:macros` dependency. * Require Dart `^3.6.0` due to the upgrades. * Support `Expression.newInstanceNamed` with empty name +* Fixed bug: Fields declared with `static` and `external` now produce code with correct order ## 4.10.1 diff --git a/pkgs/code_builder/lib/src/emitter.dart b/pkgs/code_builder/lib/src/emitter.dart index 12109cc38..64e2aa46c 100644 --- a/pkgs/code_builder/lib/src/emitter.dart +++ b/pkgs/code_builder/lib/src/emitter.dart @@ -437,15 +437,15 @@ class DartEmitter extends Object for (var a in spec.annotations) { visitAnnotation(a, output); } + if (spec.external) { + output.write('external '); + } if (spec.static) { output.write('static '); } if (spec.late && _useNullSafetySyntax) { output.write('late '); } - if (spec.external) { - output.write('external '); - } switch (spec.modifier) { case FieldModifier.var$: if (spec.type == null) { diff --git a/pkgs/code_builder/test/specs/field_test.dart b/pkgs/code_builder/test/specs/field_test.dart index 3d53687bc..0b8d6d905 100644 --- a/pkgs/code_builder/test/specs/field_test.dart +++ b/pkgs/code_builder/test/specs/field_test.dart @@ -110,4 +110,34 @@ void main() { '''), ); }); + + test('should create a late static field', () { + expect( + Field((b) => b + ..name = 'value' + ..static = true + ..late = true + ..type = refer('String') + ..annotations.addAll([refer('JS').call([])])), + equalsDart(r''' + @JS() + static late String value; + ''', DartEmitter(useNullSafetySyntax: true)), + ); + }); + + test('should create an external static field', () { + expect( + Field((b) => b + ..name = 'value' + ..external = true + ..static = true + ..type = refer('double') + ..annotations.addAll([refer('JS').call([])])), + equalsDart(r''' + @JS() + external static double value; + '''), + ); + }); } From 342285d813c9e84c230e9b755ea486f6b975775d Mon Sep 17 00:00:00 2001 From: Devon Carew Date: Tue, 29 Jul 2025 09:25:38 -0700 Subject: [PATCH 37/67] disable failing test (#2136) --- pkgs/extension_discovery/test/find_extensions_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/extension_discovery/test/find_extensions_test.dart b/pkgs/extension_discovery/test/find_extensions_test.dart index 33e8bf7d6..c5415da2d 100644 --- a/pkgs/extension_discovery/test/find_extensions_test.dart +++ b/pkgs/extension_discovery/test/find_extensions_test.dart @@ -346,5 +346,5 @@ writtenAsYaml: true isTrue, ); } - }); + }, skip: 'https://github.com/dart-lang/tools/issues/2134'); } From 94c2bdc3afe937e51a87e3e17b271d16712d6dbc Mon Sep 17 00:00:00 2001 From: Jacob MacDonald Date: Wed, 30 Jul 2025 07:40:26 -0700 Subject: [PATCH 38/67] Fix mixtures of parentheses and spaces in windows command paths (#2138) --- .github/workflows/process.yaml | 4 +- pkgs/process/CHANGELOG.md | 4 + pkgs/process/lib/src/interface/common.dart | 18 --- .../src/interface/local_process_manager.dart | 12 +- pkgs/process/pubspec.yaml | 2 +- .../test/src/interface/common_test.dart | 103 +++++++++++++----- 6 files changed, 87 insertions(+), 56 deletions(-) diff --git a/.github/workflows/process.yaml b/.github/workflows/process.yaml index 98044335b..4d05828b4 100644 --- a/.github/workflows/process.yaml +++ b/.github/workflows/process.yaml @@ -22,10 +22,10 @@ defaults: jobs: build: - runs-on: ubuntu-latest + runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-latest] + os: [ubuntu-latest, windows-latest] sdk: [stable, dev] steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 diff --git a/pkgs/process/CHANGELOG.md b/pkgs/process/CHANGELOG.md index bfa1e9664..311d021de 100644 --- a/pkgs/process/CHANGELOG.md +++ b/pkgs/process/CHANGELOG.md @@ -1,3 +1,7 @@ +## 5.0.5 + +* Fix mixtures of parentheses and spaces in windows command paths. + ## 5.0.4 * Updates minimum supported SDK version to Flutter 3.22/Dart 3.4. diff --git a/pkgs/process/lib/src/interface/common.dart b/pkgs/process/lib/src/interface/common.dart index d374631cb..65fbd1777 100644 --- a/pkgs/process/lib/src/interface/common.dart +++ b/pkgs/process/lib/src/interface/common.dart @@ -18,24 +18,6 @@ const Map _osToPathStyle = { 'windows': 'windows', }; -/// Sanitizes the executable path on Windows. -/// https://github.com/dart-lang/sdk/issues/37751 -String sanitizeExecutablePath(String executable, - {Platform platform = const LocalPlatform()}) { - if (executable.isEmpty) { - return executable; - } - if (!platform.isWindows) { - return executable; - } - if (executable.contains(' ') && !executable.contains('"')) { - // Use quoted strings to indicate where the file name ends and the arguments begin; - // otherwise, the file name is ambiguous. - return '"$executable"'; - } - return executable; -} - /// Searches the `PATH` for the executable that [executable] is supposed to launch. /// /// This first builds a list of candidate paths where the executable may reside. diff --git a/pkgs/process/lib/src/interface/local_process_manager.dart b/pkgs/process/lib/src/interface/local_process_manager.dart index ab7e96a31..81e3eca98 100644 --- a/pkgs/process/lib/src/interface/local_process_manager.dart +++ b/pkgs/process/lib/src/interface/local_process_manager.dart @@ -40,11 +40,11 @@ class LocalProcessManager implements ProcessManager { }) { try { return Process.start( - sanitizeExecutablePath(_getExecutable( + _getExecutable( command, workingDirectory, runInShell, - )), + ), _getArguments(command), workingDirectory: workingDirectory, environment: environment, @@ -70,11 +70,11 @@ class LocalProcessManager implements ProcessManager { }) { try { return Process.run( - sanitizeExecutablePath(_getExecutable( + _getExecutable( command, workingDirectory, runInShell, - )), + ), _getArguments(command), workingDirectory: workingDirectory, environment: environment, @@ -101,11 +101,11 @@ class LocalProcessManager implements ProcessManager { }) { try { return Process.runSync( - sanitizeExecutablePath(_getExecutable( + _getExecutable( command, workingDirectory, runInShell, - )), + ), _getArguments(command), workingDirectory: workingDirectory, environment: environment, diff --git a/pkgs/process/pubspec.yaml b/pkgs/process/pubspec.yaml index 2a4194cfc..32a977708 100644 --- a/pkgs/process/pubspec.yaml +++ b/pkgs/process/pubspec.yaml @@ -1,6 +1,6 @@ name: process description: A pluggable, mockable process invocation abstraction for Dart. -version: 5.0.4 +version: 5.0.5 repository: https://github.com/dart-lang/tools/tree/main/pkgs/process issue_tracker: https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Aprocess diff --git a/pkgs/process/test/src/interface/common_test.dart b/pkgs/process/test/src/interface/common_test.dart index 8290b5ef4..58265895f 100644 --- a/pkgs/process/test/src/interface/common_test.dart +++ b/pkgs/process/test/src/interface/common_test.dart @@ -238,28 +238,6 @@ void main() { ' C:\\.tmp_rand0\\dir2_rand0\n')))); }); - test('when path has spaces', () { - expect( - sanitizeExecutablePath(r'Program Files\bla.exe', - platform: platform), - r'"Program Files\bla.exe"'); - expect( - sanitizeExecutablePath(r'ProgramFiles\bla.exe', platform: platform), - r'ProgramFiles\bla.exe'); - expect( - sanitizeExecutablePath(r'"Program Files\bla.exe"', - platform: platform), - r'"Program Files\bla.exe"'); - expect( - sanitizeExecutablePath(r'"Program Files\bla.exe"', - platform: platform), - r'"Program Files\bla.exe"'); - expect( - sanitizeExecutablePath(r'C:\"Program Files"\bla.exe', - platform: platform), - r'C:\"Program Files"\bla.exe'); - }); - test('with absolute path when currentDirectory getter throws', () { final FileSystem fsNoCwd = MemoryFileSystemNoCwd(fs); final String command = fs.path.join(dir3.path, 'bla.exe'); @@ -378,13 +356,6 @@ void main() { ' /.tmp_rand0/dir1_rand0\n' ' /.tmp_rand0/dir2_rand0\n')))); }); - - test('when path has spaces', () { - expect( - sanitizeExecutablePath('/usr/local/bin/foo bar', - platform: platform), - '/usr/local/bin/foo bar'); - }); }); }); group('Real Filesystem', () { @@ -571,6 +542,80 @@ void main() { ' ${tmpDir.path}/path4\n' ' ${tmpDir.path}/path5\n')))); }); + + group('can actually execute files', () { + void testCompileAndExecute(File mainFile) { + final localProcessManager = LocalProcessManager(); + final exePath = '${mainFile.path}.exe'; + // Create an executable we can actually run. + expect( + localProcessManager.runSync([ + io.Platform.resolvedExecutable, + 'compile', + 'exe', + mainFile.path, + '-o', + exePath + ]).exitCode, + 0); + + for (final runInShell in const [true, false]) { + final result = + localProcessManager.runSync([exePath], runInShell: runInShell); + expect(result.exitCode, 0, + reason: 'runInShell: $runInShell\nstdout: ${result.stdout}\n' + 'stderr: ${result.stderr}'); + expect(result.stdout, contains('hello')); + } + } + + test('with spaces in the command name', () { + final dir = tmpDir.childDirectory('the path'); + final main = dir.childFile('main.dart') + ..createSync(recursive: true) + ..writeAsStringSync(''' +void main() { + print('hello'); +}'''); + testCompileAndExecute(main); + }); + + test('with parenthesis in the command name', () async { + final dir = tmpDir.childDirectory('theP()ath'); + final main = dir.childFile('main.dart') + ..createSync(recursive: true) + ..writeAsStringSync(''' +void main() { + print('hello'); +}'''); + testCompileAndExecute(main); + }, + skip: io.Platform.isWindows + ? 'https://github.com/dart-lang/tools/issues/2139' + : null); + + test('with spaces and parenthesis in the command name', () async { + final dir = tmpDir.childDirectory('the P()ath'); + final main = dir.childFile('main.dart') + ..createSync(recursive: true) + ..writeAsStringSync(''' +void main() { + print('hello'); +}'''); + testCompileAndExecute(main); + }); + + test('with spaces inside parenthesis in the command name', () async { + final dir = tmpDir.childDirectory('the P( )ath'); + final main = dir.childFile('main.dart') + ..createSync(recursive: true) + ..writeAsStringSync(''' +void main() { + print('hello'); +}'''); + testCompileAndExecute(main); + }); + }); }); } From 700195ad104d988ba0cb58a9fb7e8fff93e502a0 Mon Sep 17 00:00:00 2001 From: Moritz Date: Wed, 6 Aug 2025 21:18:52 +0200 Subject: [PATCH 39/67] Configure Gemini code review (#2141) --- .gemini/config.yaml | 12 ++++++++++++ .gemini/styleguide.md | 20 ++++++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 .gemini/config.yaml create mode 100644 .gemini/styleguide.md diff --git a/.gemini/config.yaml b/.gemini/config.yaml new file mode 100644 index 000000000..91abdc920 --- /dev/null +++ b/.gemini/config.yaml @@ -0,0 +1,12 @@ +# See https://developers.google.com/gemini-code-assist/docs/customize-gemini-behavior-github#custom-configurations + +have_fun: false # We are serious people in the Dart team! +code_review: + disable: false + comment_severity_threshold: MEDIUM + max_review_comments: 100 + pull_request_opened: + help: true + summary: true + code_review: true +ignore_patterns: [] diff --git a/.gemini/styleguide.md b/.gemini/styleguide.md new file mode 100644 index 000000000..28793841f --- /dev/null +++ b/.gemini/styleguide.md @@ -0,0 +1,20 @@ +# Dart Style Guide + +This style guide outlines the coding conventions for contributions to the +`dart-lang/**` repositories. + +## Best Practices + +- Follow [the contribution guide](CONTRIBUTING.md). +- Code should be tested. +- Write code follow the guidance described in [effective Dart](https://dart.dev/effective-dart). + +## General Philosophy + +- Optimize for readability - code is read more often than it is written. + +## Documentation + +- At least all public members should have documentation, answering the *why*. +- Assume the reader does not know everything. Link to definitions. +- Provide sample code. From 48ddaa4ec2d00ca4a2a686342b162a8616ae38a8 Mon Sep 17 00:00:00 2001 From: Nate Bosch Date: Fri, 15 Aug 2025 17:33:14 -0700 Subject: [PATCH 40/67] Consistent blank after => members in class-likes (#2146) Closes #1143 The `class` emitter already used `writeln` following a `=>` method to include blank lines between members. Make this consistent for enums, extensions, and mixins. Extract a method and use it for each type. Sort changelog with user-facing changes at the top. --- pkgs/code_builder/CHANGELOG.md | 6 ++- pkgs/code_builder/lib/src/emitter.dart | 50 +++++++-------------- pkgs/code_builder/test/specs/enum_test.dart | 1 + 3 files changed, 20 insertions(+), 37 deletions(-) diff --git a/pkgs/code_builder/CHANGELOG.md b/pkgs/code_builder/CHANGELOG.md index f8459a322..893d23f50 100644 --- a/pkgs/code_builder/CHANGELOG.md +++ b/pkgs/code_builder/CHANGELOG.md @@ -1,9 +1,11 @@ ## 4.10.2-wip +* Support `Expression.newInstanceNamed` with empty name +* Consistently add blank lines between `=>` in class-like definitions. +* Fixed bug: Fields declared with `static` and `external` now produce code with + correct order * Upgrade `dart_style` and `source_gen` to remove `package:macros` dependency. * Require Dart `^3.6.0` due to the upgrades. -* Support `Expression.newInstanceNamed` with empty name -* Fixed bug: Fields declared with `static` and `external` now produce code with correct order ## 4.10.1 diff --git a/pkgs/code_builder/lib/src/emitter.dart b/pkgs/code_builder/lib/src/emitter.dart index 64e2aa46c..5d94eb929 100644 --- a/pkgs/code_builder/lib/src/emitter.dart +++ b/pkgs/code_builder/lib/src/emitter.dart @@ -162,13 +162,7 @@ class DartEmitter extends Object visitField(f, out); out.writeln(); } - for (var m in spec.methods) { - visitMethod(m, out); - if (_isLambdaMethod(m)) { - out.writeln(';'); - } - out.writeln(); - } + _visitMethods(spec.methods, out); out.writeln(' }'); return out; } @@ -201,13 +195,7 @@ class DartEmitter extends Object visitField(f, out); out.writeln(); } - for (var m in spec.methods) { - visitMethod(m, out); - if (_isLambdaMethod(m)) { - out.write(';'); - } - out.writeln(); - } + _visitMethods(spec.methods, out); out.write(' }'); return out; } @@ -326,13 +314,7 @@ class DartEmitter extends Object visitField(f, out); out.writeln(); } - for (var m in spec.methods) { - visitMethod(m, out); - if (_isLambdaMethod(m)) { - out.write(';'); - } - out.writeln(); - } + _visitMethods(spec.methods, out); out.writeln(' }'); return out; } @@ -372,13 +354,7 @@ class DartEmitter extends Object visitField(f, out); out.writeln(); } - for (var m in spec.methods) { - visitMethod(m, out); - if (_isLambdaMethod(m)) { - out.writeln(';'); - } - out.writeln(); - } + _visitMethods(spec.methods, out); out.writeln('}'); return out; } @@ -800,6 +776,16 @@ class DartEmitter extends Object } } + void _visitMethods(Iterable methods, StringSink out) { + for (final m in methods) { + visitMethod(m, out); + if (_isLambdaMethod(m)) { + out.writeln(';'); + } + out.writeln(); + } + } + @override StringSink visitReference(Reference spec, [StringSink? output]) => (output ??= StringBuffer())..write(allocator.allocate(spec)); @@ -907,13 +893,7 @@ class DartEmitter extends Object visitField(f, out); out.writeln(); } - for (var m in spec.methods) { - visitMethod(m, out); - if (_isLambdaMethod(m)) { - out.write(';'); - } - out.writeln(); - } + _visitMethods(spec.methods, out); out.writeln(' }'); return out; } diff --git a/pkgs/code_builder/test/specs/enum_test.dart b/pkgs/code_builder/test/specs/enum_test.dart index 5a0b19a27..3a8238a52 100644 --- a/pkgs/code_builder/test/specs/enum_test.dart +++ b/pkgs/code_builder/test/specs/enum_test.dart @@ -373,6 +373,7 @@ void main() { c; int get myInt => 123; + Iterable myStrings() sync* { yield 'a'; yield 'b'; From ee355b9175c3bd13085950fcfe2335a44e823367 Mon Sep 17 00:00:00 2001 From: "Morgan :)" Date: Mon, 25 Aug 2025 03:54:25 -0700 Subject: [PATCH 41/67] Windows DirectoryWatcher buffer exhaustion recovery workaround. (#2149) --- pkgs/watcher/CHANGELOG.md | 8 + pkgs/watcher/lib/src/directory_watcher.dart | 14 +- .../lib/src/directory_watcher/windows.dart | 134 +++++++++------- pkgs/watcher/pubspec.yaml | 2 +- .../test/directory_watcher/windows_test.dart | 147 ++++++++++++++---- 5 files changed, 215 insertions(+), 90 deletions(-) diff --git a/pkgs/watcher/CHANGELOG.md b/pkgs/watcher/CHANGELOG.md index 6d22c5ea6..b4727e5cb 100644 --- a/pkgs/watcher/CHANGELOG.md +++ b/pkgs/watcher/CHANGELOG.md @@ -1,3 +1,11 @@ +## 1.1.3-wip + +- Improve handling of + `FileSystemException: Directory watcher closed unexpectedly` on Windows. The + watcher was already attempting to restart after this error and resume sending + events. But, the restart would sometimes silently fail. Now, it is more + reliable. + ## 1.1.2 - Fix a bug on Windows where a file creation event could be reported twice when creating diff --git a/pkgs/watcher/lib/src/directory_watcher.dart b/pkgs/watcher/lib/src/directory_watcher.dart index 158b86b05..8caf09f8a 100644 --- a/pkgs/watcher/lib/src/directory_watcher.dart +++ b/pkgs/watcher/lib/src/directory_watcher.dart @@ -12,6 +12,14 @@ import 'directory_watcher/windows.dart'; /// Watches the contents of a directory and emits [WatchEvent]s when something /// in the directory has changed. +/// +/// On Windows, the underlying SDK `Directory.watch` fails if too many events +/// are received while Dart is busy, for example during a long-running +/// synchronous operation. When this happens, some events are dropped. +/// `DirectoryWatcher` restarts the watch and sends a `FileSystemException` with +/// the message "Directory watcher closed unexpectedly" on the event stream. The +/// code using the watcher needs to do additional work to account for the +/// dropped events, for example by recomputing interesting files from scratch. abstract class DirectoryWatcher implements Watcher { /// The directory whose contents are being monitored. @Deprecated('Expires in 1.0.0. Use DirectoryWatcher.path instead.') @@ -29,8 +37,10 @@ abstract class DirectoryWatcher implements Watcher { /// watchers. factory DirectoryWatcher(String directory, {Duration? pollingDelay}) { if (FileSystemEntity.isWatchSupported) { - var customWatcher = - createCustomDirectoryWatcher(directory, pollingDelay: pollingDelay); + var customWatcher = createCustomDirectoryWatcher( + directory, + pollingDelay: pollingDelay, + ); if (customWatcher != null) return customWatcher; if (Platform.isLinux) return LinuxDirectoryWatcher(directory); if (Platform.isMacOS) return MacOSDirectoryWatcher(directory); diff --git a/pkgs/watcher/lib/src/directory_watcher/windows.dart b/pkgs/watcher/lib/src/directory_watcher/windows.dart index 5607948dd..6d88e65a9 100644 --- a/pkgs/watcher/lib/src/directory_watcher/windows.dart +++ b/pkgs/watcher/lib/src/directory_watcher/windows.dart @@ -126,30 +126,34 @@ class _WindowsDirectoryWatcher // Check if [path] is already the root directory. if (FileSystemEntity.identicalSync(parent, path)) return; var parentStream = Directory(parent).watch(recursive: false); - _parentWatchSubscription = parentStream.listen((event) { - // Only look at events for 'directory'. - if (p.basename(event.path) != p.basename(absoluteDir)) return; - // Test if the directory is removed. FileSystemEntity.typeSync will - // return NOT_FOUND if it's unable to decide upon the type, including - // access denied issues, which may happen when the directory is deleted. - // FileSystemMoveEvent and FileSystemDeleteEvent events will always mean - // the directory is now gone. - if (event is FileSystemMoveEvent || - event is FileSystemDeleteEvent || - (FileSystemEntity.typeSync(path) == FileSystemEntityType.notFound)) { - for (var path in _files.paths) { - _emitEvent(ChangeType.REMOVE, path); + _parentWatchSubscription = parentStream.listen( + (event) { + // Only look at events for 'directory'. + if (p.basename(event.path) != p.basename(absoluteDir)) return; + // Test if the directory is removed. FileSystemEntity.typeSync will + // return NOT_FOUND if it's unable to decide upon the type, including + // access denied issues, which may happen when the directory is deleted. + // FileSystemMoveEvent and FileSystemDeleteEvent events will always mean + // the directory is now gone. + if (event is FileSystemMoveEvent || + event is FileSystemDeleteEvent || + (FileSystemEntity.typeSync(path) == + FileSystemEntityType.notFound)) { + for (var path in _files.paths) { + _emitEvent(ChangeType.REMOVE, path); + } + _files.clear(); + close(); } - _files.clear(); - close(); - } - }, onError: (error) { - // Ignore errors, simply close the stream. The user listens on - // [directory], and while it can fail to listen on the parent, we may - // still be able to listen on the path requested. - _parentWatchSubscription?.cancel(); - _parentWatchSubscription = null; - }); + }, + onError: (error) { + // Ignore errors, simply close the stream. The user listens on + // [directory], and while it can fail to listen on the parent, we may + // still be able to listen on the path requested. + _parentWatchSubscription?.cancel(); + _parentWatchSubscription = null; + }, + ); } void _onEvent(FileSystemEvent event) { @@ -225,16 +229,18 @@ class _WindowsDirectoryWatcher // Events within directories that already have events are superfluous; the // directory's full contents will be examined anyway, so we ignore such // events. Emitting them could cause useless or out-of-order events. - var directories = unionAll(batch.map((event) { - if (!event.isDirectory) return {}; - if (event is FileSystemMoveEvent) { - var destination = event.destination; - if (destination != null) { - return {event.path, destination}; + var directories = unionAll( + batch.map((event) { + if (!event.isDirectory) return {}; + if (event is FileSystemMoveEvent) { + var destination = event.destination; + if (destination != null) { + return {event.path, destination}; + } } - } - return {event.path}; - })); + return {event.path}; + }), + ); bool isInModifiedDirectory(String path) => directories.any((dir) => path != dir && p.isWithin(dir, path)); @@ -285,9 +291,11 @@ class _WindowsDirectoryWatcher // REMOVE; otherwise there will also be a REMOVE or CREATE event // (respectively) that will be contradictory. if (event is FileSystemModifyEvent) continue; - assert(event is FileSystemCreateEvent || - event is FileSystemDeleteEvent || - event is FileSystemMoveEvent); + assert( + event is FileSystemCreateEvent || + event is FileSystemDeleteEvent || + event is FileSystemMoveEvent, + ); // If we previously thought this was a MODIFY, we now consider it to be a // CREATE or REMOVE event. This is safe for the same reason as above. @@ -297,9 +305,11 @@ class _WindowsDirectoryWatcher } // A CREATE event contradicts a REMOVE event and vice versa. - assert(type == FileSystemEvent.create || - type == FileSystemEvent.delete || - type == FileSystemEvent.move); + assert( + type == FileSystemEvent.create || + type == FileSystemEvent.delete || + type == FileSystemEvent.move, + ); if (type != event.type) return null; } @@ -383,21 +393,31 @@ class _WindowsDirectoryWatcher void _startWatch() { // Note: "watcher closed" exceptions do not get sent over the stream // returned by watch, and must be caught via a zone handler. - runZonedGuarded(() { - var innerStream = Directory(path).watch(recursive: true); - _watchSubscription = innerStream.listen(_onEvent, - onError: _eventsController.addError, onDone: _onDone); - }, (error, stackTrace) { - if (error is FileSystemException && - error.message.startsWith('Directory watcher closed unexpectedly')) { - _watchSubscription?.cancel(); - _eventsController.addError(error, stackTrace); - _startWatch(); - } else { - // ignore: only_throw_errors - throw error; - } - }); + runZonedGuarded( + () { + var innerStream = Directory(path).watch(recursive: true); + _watchSubscription = innerStream.listen( + _onEvent, + onError: _eventsController.addError, + onDone: _onDone, + ); + }, + (error, stackTrace) async { + if (error is FileSystemException && + error.message.startsWith('Directory watcher closed unexpectedly')) { + // Wait to work around https://github.com/dart-lang/sdk/issues/61378. + // Give the VM time to reset state after the error. See the issue for + // more discussion of the workaround. + await _watchSubscription?.cancel(); + await Future.delayed(const Duration(milliseconds: 1)); + _eventsController.addError(error, stackTrace); + _startWatch(); + } else { + // ignore: only_throw_errors + throw error; + } + }, + ); } /// Starts or restarts listing the watched directory to get an initial picture @@ -413,8 +433,12 @@ class _WindowsDirectoryWatcher if (entity is! Directory) _files.add(entity.path); } - _initialListSubscription = stream.listen(handleEntity, - onError: _emitError, onDone: completer.complete, cancelOnError: true); + _initialListSubscription = stream.listen( + handleEntity, + onError: _emitError, + onDone: completer.complete, + cancelOnError: true, + ); return completer.future; } diff --git a/pkgs/watcher/pubspec.yaml b/pkgs/watcher/pubspec.yaml index b86dbf11b..bccdfacf9 100644 --- a/pkgs/watcher/pubspec.yaml +++ b/pkgs/watcher/pubspec.yaml @@ -1,5 +1,5 @@ name: watcher -version: 1.1.2 +version: 1.1.3-wip description: >- A file system watcher. It monitors changes to contents of directories and sends notifications when files have been added, removed, or modified. diff --git a/pkgs/watcher/test/directory_watcher/windows_test.dart b/pkgs/watcher/test/directory_watcher/windows_test.dart index 5be87ec32..a6bc14dec 100644 --- a/pkgs/watcher/test/directory_watcher/windows_test.dart +++ b/pkgs/watcher/test/directory_watcher/windows_test.dart @@ -25,40 +25,123 @@ void main() { expect(DirectoryWatcher('.'), const TypeMatcher()); }); - test('Regression test for https://github.com/dart-lang/tools/issues/2110', - () async { - late StreamSubscription sub; - try { - final temp = Directory.systemTemp.createTempSync(); + test( + 'Regression test for https://github.com/dart-lang/tools/issues/2110', + () async { + late StreamSubscription sub; + try { + final temp = Directory.systemTemp.createTempSync(); + final watcher = DirectoryWatcher(temp.path); + final events = []; + sub = watcher.events.listen(events.add); + await watcher.ready; + + // Create a file in a directory that doesn't exist. This forces the + // directory to be created first before the child file. + // + // When directory creation is detected by the watcher, it calls + // `Directory.list` on the directory to determine if there's files that + // have been created or modified. It's possible that the watcher will + // have already detected the file creation event before `Directory.list` + // returns. Before https://github.com/dart-lang/tools/issues/2110 was + // resolved, the check to ensure an event hadn't already been emitted + // for the file creation was incorrect, leading to the event being + // emitted again in some circumstances. + final file = File(p.join(temp.path, 'foo', 'file.txt')) + ..createSync(recursive: true); + + // Introduce a short delay to allow for the directory watcher to detect + // the creation of foo/ and foo/file.txt. + await Future.delayed(const Duration(seconds: 1)); + + // There should only be a single file added event. + expect(events, hasLength(1)); + expect( + events.first.toString(), + WatchEvent(ChangeType.ADD, file.path).toString(), + ); + } finally { + await sub.cancel(); + } + }, + ); + + // The Windows native watcher has a buffer that gets exhausted if events are + // not handled quickly enough. Then, it throws an error and stops watching. + // The exhaustion is reliably triggered if enough events arrive during a sync + // block. The `package:watcher` implementation tries to catch this and recover + // by starting a new watcher. + group('Buffer exhaustion', () { + late StreamSubscription subscription; + late Directory temp; + late int eventsSeen; + late int errorsSeen; + + setUp(() async { + temp = Directory.systemTemp.createTempSync(); final watcher = DirectoryWatcher(temp.path); - final events = []; - sub = watcher.events.listen(events.add); + + eventsSeen = 0; + errorsSeen = 0; + subscription = watcher.events.listen( + (e) { + ++eventsSeen; + }, + onError: (_, __) { + ++errorsSeen; + }, + ); await watcher.ready; + }); + + tearDown(() { + subscription.cancel(); + }); + + test('recovery', () async { + // Use a long filename to fill the buffer. + final file = File('${temp.path}\\file'.padRight(255, 'a')); + + // Repeatedly trigger buffer exhaustion, to check that recovery is + // reliable. + for (var times = 0; times != 200; ++times) { + errorsSeen = 0; + eventsSeen = 0; + + // Syncronously trigger 200 events. Because this is a sync block, the VM + // won't handle the events, so this has a very high chance of triggering + // a buffer exhaustion. + // + // If a buffer exhaustion happens, `package:watcher` turns this into an + // error on the event stream, so `errorsSeen` will get incremented once. + // The number of changes 200 is chosen so this is very likely to happen. + // If there is _not_ an exhaustion, the 200 events will show on the + // stream as a single event because they are changes of the same file. + // So, `eventsSeen` will instead be incremented once. + for (var i = 0; i != 200; ++i) { + file.writeAsStringSync(''); + } + + // Events only happen when there is an async gap, wait for such a gap. + await Future.delayed(const Duration(milliseconds: 10)); - // Create a file in a directory that doesn't exist. This forces the - // directory to be created first before the child file. - // - // When directory creation is detected by the watcher, it calls - // `Directory.list` on the directory to determine if there's files that - // have been created or modified. It's possible that the watcher will have - // already detected the file creation event before `Directory.list` - // returns. Before https://github.com/dart-lang/tools/issues/2110 was - // resolved, the check to ensure an event hadn't already been emitted for - // the file creation was incorrect, leading to the event being emitted - // again in some circumstances. - final file = File(p.join(temp.path, 'foo', 'file.txt')) - ..createSync(recursive: true); - - // Introduce a short delay to allow for the directory watcher to detect - // the creation of foo/ and foo/file.txt. - await Future.delayed(const Duration(seconds: 1)); - - // There should only be a single file added event. - expect(events, hasLength(1)); - expect(events.first.toString(), - WatchEvent(ChangeType.ADD, file.path).toString()); - } finally { - await sub.cancel(); - } + // If everything is going well, there should have been either one event + // seen or one error seen. + if (errorsSeen == 0 && eventsSeen == 0) { + // It looks like the watcher is now broken: there were file changes + // but no event and no error. Do some non-sync writes to confirm + // whether the watcher really is now broken. + for (var i = 0; i != 5; ++i) { + await file.writeAsString(''); + } + await Future.delayed(const Duration(milliseconds: 10)); + fail( + 'On attempt ${times + 1}, watcher registered nothing. ' + 'On retry, it registered: $errorsSeen error(s), $eventsSeen ' + 'event(s).', + ); + } + } + }); }); } From bde668bb3a2122f838f9532fb45666f2e4a3dc43 Mon Sep 17 00:00:00 2001 From: "Morgan :)" Date: Mon, 25 Aug 2025 06:59:31 -0700 Subject: [PATCH 42/67] List directory failure (#2151) --- pkgs/watcher/CHANGELOG.md | 4 +- .../lib/src/directory_watcher/windows.dart | 9 +++- pkgs/watcher/pubspec.yaml | 2 +- .../test/directory_watcher/windows_test.dart | 46 +++++++++++++++++++ 4 files changed, 58 insertions(+), 3 deletions(-) diff --git a/pkgs/watcher/CHANGELOG.md b/pkgs/watcher/CHANGELOG.md index b4727e5cb..30a19756e 100644 --- a/pkgs/watcher/CHANGELOG.md +++ b/pkgs/watcher/CHANGELOG.md @@ -1,10 +1,12 @@ -## 1.1.3-wip +## 1.1.3 - Improve handling of `FileSystemException: Directory watcher closed unexpectedly` on Windows. The watcher was already attempting to restart after this error and resume sending events. But, the restart would sometimes silently fail. Now, it is more reliable. +- Improving handling of directories that are created then immediately deleted on + Windows. Previously, that could cause a `PathNotfoundException` to be thrown. ## 1.1.2 diff --git a/pkgs/watcher/lib/src/directory_watcher/windows.dart b/pkgs/watcher/lib/src/directory_watcher/windows.dart index 6d88e65a9..8f212684c 100644 --- a/pkgs/watcher/lib/src/directory_watcher/windows.dart +++ b/pkgs/watcher/lib/src/directory_watcher/windows.dart @@ -198,7 +198,14 @@ class _WindowsDirectoryWatcher }); subscription.onError((Object e, StackTrace stackTrace) { _listSubscriptions.remove(subscription); - _emitError(e, stackTrace); + // "Path not found" can be caused by creating then quickly removing + // a directory: continue without reporting an error. Nested files + // that get removed during the `list` are already ignored by `list` + // itself, so there are no other types of "path not found" that + // might need different handling here. + if (e is! PathNotFoundException) { + _emitError(e, stackTrace); + } }); _listSubscriptions.add(subscription); } else if (event is FileSystemModifyEvent) { diff --git a/pkgs/watcher/pubspec.yaml b/pkgs/watcher/pubspec.yaml index bccdfacf9..16af27bc2 100644 --- a/pkgs/watcher/pubspec.yaml +++ b/pkgs/watcher/pubspec.yaml @@ -1,5 +1,5 @@ name: watcher -version: 1.1.3-wip +version: 1.1.3 description: >- A file system watcher. It monitors changes to contents of directories and sends notifications when files have been added, removed, or modified. diff --git a/pkgs/watcher/test/directory_watcher/windows_test.dart b/pkgs/watcher/test/directory_watcher/windows_test.dart index a6bc14dec..6489771c6 100644 --- a/pkgs/watcher/test/directory_watcher/windows_test.dart +++ b/pkgs/watcher/test/directory_watcher/windows_test.dart @@ -66,6 +66,52 @@ void main() { }, ); + // Regression test for https://github.com/dart-lang/tools/issues/2152: + // watcher can throws if a directory is created then quickly deleted. + group('Transient directory', () { + late StreamSubscription subscription; + late Directory temp; + late Watcher watcher; + late int errorsSeen; + + setUp(() async { + temp = Directory.systemTemp.createTempSync(); + watcher = DirectoryWatcher(temp.path); + errorsSeen = 0; + subscription = watcher.events.listen( + (e) {}, + onError: (Object e, _) { + print('Event stream error: $e'); + ++errorsSeen; + }, + ); + await watcher.ready; + }); + + tearDown(() { + subscription.cancel(); + }); + + test('does not break watching', () async { + // Iterate creating 10 directories and deleting 1-10 of them. This means + // the directories will exist for different lengths of times, exploring + // possible race conditions in directory handling. + for (var i = 0; i != 50; ++i) { + for (var j = 0; j != 10; ++j) { + File('${temp.path}\\$j\\file').createSync(recursive: true); + } + await Future.delayed(const Duration(milliseconds: 1)); + for (var j = 0; j != i % 10 + 1; ++j) { + final d = Directory('${temp.path}\\$j'); + d.deleteSync(recursive: true); + } + await Future.delayed(const Duration(milliseconds: 1)); + } + + expect(errorsSeen, 0); + }); + }); + // The Windows native watcher has a buffer that gets exhausted if events are // not handled quickly enough. Then, it throws an error and stops watching. // The exhaustion is reliably triggered if enough events arrive during a sync From 4f805304d1568b45d3a46bdc9a464f997821f7b9 Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Wed, 10 Sep 2025 10:39:47 -0700 Subject: [PATCH 43/67] code_builder: update deps, prepare release (#2160) - reformat code for latest min SDK - fix leaked types - add and fix a lint --- .github/workflows/code_builder.yaml | 2 +- pkgs/code_builder/CHANGELOG.md | 12 +- pkgs/code_builder/analysis_options.yaml | 1 + pkgs/code_builder/example/example.dart | 77 +- pkgs/code_builder/lib/code_builder.dart | 5 + pkgs/code_builder/lib/src/allocator.dart | 5 +- pkgs/code_builder/lib/src/emitter.dart | 86 +- pkgs/code_builder/lib/src/matchers.dart | 14 +- pkgs/code_builder/lib/src/specs/class.dart | 13 +- pkgs/code_builder/lib/src/specs/class.g.dart | 90 ++- pkgs/code_builder/lib/src/specs/code.dart | 4 +- pkgs/code_builder/lib/src/specs/code.g.dart | 8 +- .../lib/src/specs/constructor.g.dart | 96 ++- .../code_builder/lib/src/specs/directive.dart | 82 +- .../lib/src/specs/directive.g.dart | 53 +- pkgs/code_builder/lib/src/specs/enum.dart | 12 +- pkgs/code_builder/lib/src/specs/enum.g.dart | 115 +-- .../lib/src/specs/expression.dart | 407 +++++----- .../lib/src/specs/expression/invoke.dart | 19 +- .../lib/src/specs/expression/literal.dart | 48 +- .../code_builder/lib/src/specs/extension.dart | 5 +- .../lib/src/specs/extension.g.dart | 46 +- .../lib/src/specs/extension_type.dart | 11 +- .../lib/src/specs/extension_type.g.dart | 188 +++-- pkgs/code_builder/lib/src/specs/field.dart | 11 +- pkgs/code_builder/lib/src/specs/field.g.dart | 66 +- pkgs/code_builder/lib/src/specs/library.dart | 5 +- .../code_builder/lib/src/specs/library.g.dart | 55 +- pkgs/code_builder/lib/src/specs/method.dart | 16 +- pkgs/code_builder/lib/src/specs/method.g.dart | 182 +++-- pkgs/code_builder/lib/src/specs/mixin.dart | 5 +- pkgs/code_builder/lib/src/specs/mixin.g.dart | 51 +- .../code_builder/lib/src/specs/reference.dart | 79 +- .../lib/src/specs/type_function.dart | 22 +- .../lib/src/specs/type_function.g.dart | 64 +- .../lib/src/specs/type_record.dart | 21 +- .../lib/src/specs/type_record.g.dart | 35 +- .../lib/src/specs/type_reference.dart | 66 +- .../lib/src/specs/type_reference.g.dart | 38 +- pkgs/code_builder/lib/src/specs/typedef.dart | 5 +- .../code_builder/lib/src/specs/typedef.g.dart | 48 +- pkgs/code_builder/pubspec.yaml | 26 +- pkgs/code_builder/test/allocator_test.dart | 14 +- pkgs/code_builder/test/const_test.dart | 63 +- pkgs/code_builder/test/directive_test.dart | 82 +- .../code_builder/test/e2e/injection_test.dart | 60 +- pkgs/code_builder/test/matcher_test.dart | 12 +- pkgs/code_builder/test/specs/class_test.dart | 497 ++++++++---- .../test/specs/code/expression_test.dart | 433 +++++----- .../test/specs/code/statement_test.dart | 21 +- pkgs/code_builder/test/specs/enum_test.dart | 747 +++++++++++------- .../test/specs/extension_test.dart | 114 +-- .../test/specs/extension_type_test.dart | 367 ++++++--- pkgs/code_builder/test/specs/field_test.dart | 108 ++- .../code_builder/test/specs/library_test.dart | 233 +++--- pkgs/code_builder/test/specs/method_test.dart | 641 +++++++++------ pkgs/code_builder/test/specs/mixin_test.dart | 117 +-- .../test/specs/record_type_test.dart | 80 +- .../test/specs/type_reference_test.dart | 36 +- 59 files changed, 3424 insertions(+), 2395 deletions(-) diff --git a/.github/workflows/code_builder.yaml b/.github/workflows/code_builder.yaml index 04addcdae..ad3280108 100644 --- a/.github/workflows/code_builder.yaml +++ b/.github/workflows/code_builder.yaml @@ -51,7 +51,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest] - sdk: [3.6.0, dev] + sdk: [3.7.0, dev] steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c diff --git a/pkgs/code_builder/CHANGELOG.md b/pkgs/code_builder/CHANGELOG.md index 893d23f50..ddd8505be 100644 --- a/pkgs/code_builder/CHANGELOG.md +++ b/pkgs/code_builder/CHANGELOG.md @@ -1,11 +1,17 @@ -## 4.10.2-wip +## 4.11.0 +* Export `SpecVisitor`, `ClosureExpression`, `LiteralMapExpression` + `LiteralRecordExpression`, `LiteralSetExpression` types. * Support `Expression.newInstanceNamed` with empty name * Consistently add blank lines between `=>` in class-like definitions. * Fixed bug: Fields declared with `static` and `external` now produce code with correct order -* Upgrade `dart_style` and `source_gen` to remove `package:macros` dependency. -* Require Dart `^3.6.0` due to the upgrades. +* Require `built_collection: ^5.1.1` +* Require `built_value: ^8.10.1` +* Require `collection: ^1.19.0` +* Require `matcher: ^0.12.16+1` +* Require `meta: ^1.16.0` +* Require `sdk: ^3.7.0` ## 4.10.1 diff --git a/pkgs/code_builder/analysis_options.yaml b/pkgs/code_builder/analysis_options.yaml index d052c68a8..8bbd1b716 100644 --- a/pkgs/code_builder/analysis_options.yaml +++ b/pkgs/code_builder/analysis_options.yaml @@ -22,4 +22,5 @@ linter: - prefer_expression_function_bodies - prefer_final_locals - unnecessary_await_in_return + - unnecessary_ignore - use_string_buffers diff --git a/pkgs/code_builder/example/example.dart b/pkgs/code_builder/example/example.dart index 6f73598e9..ac54beff5 100644 --- a/pkgs/code_builder/example/example.dart +++ b/pkgs/code_builder/example/example.dart @@ -23,12 +23,20 @@ void main() { /// } /// ``` String animalClass() { - final animal = Class((b) => b - ..name = 'Animal' - ..extend = refer('Organism') - ..methods.add(Method.returnsVoid((b) => b - ..name = 'eat' - ..body = refer('print').call([literalString('Yum!')]).code))); + final animal = Class( + (b) => + b + ..name = 'Animal' + ..extend = refer('Organism') + ..methods.add( + Method.returnsVoid( + (b) => + b + ..name = 'eat' + ..body = refer('print').call([literalString('Yum!')]).code, + ), + ), + ); return _dartfmt.format('${animal.accept(DartEmitter())}'); } @@ -43,14 +51,20 @@ String animalClass() { /// ``` String scopedLibrary() { final methods = [ - Method((b) => b - ..body = const Code('') - ..name = 'doThing' - ..returns = refer('Thing', 'package:a/a.dart')), - Method((b) => b - ..body = const Code('') - ..name = 'doOther' - ..returns = refer('Other', 'package:b/b.dart')), + Method( + (b) => + b + ..body = const Code('') + ..name = 'doThing' + ..returns = refer('Thing', 'package:a/a.dart'), + ), + Method( + (b) => + b + ..body = const Code('') + ..name = 'doOther' + ..returns = refer('Other', 'package:b/b.dart'), + ), ]; final library = Library((b) => b.body.addAll(methods)); return _dartfmt.format('${library.accept(DartEmitter.scoped())}'); @@ -68,19 +82,28 @@ String scopedLibrary() { /// ``` String jsonEnum() { final values = [ - EnumValue((b) => b - ..name = 'metric' - ..annotations.addAll([ - refer('JsonKey').call([literalString('m')]) - ])), - EnumValue((b) => b - ..name = 'imperial' - ..annotations.addAll([ - refer('JsonKey').call([literalString('i')]) - ])), + EnumValue( + (b) => + b + ..name = 'metric' + ..annotations.addAll([ + refer('JsonKey').call([literalString('m')]), + ]), + ), + EnumValue( + (b) => + b + ..name = 'imperial' + ..annotations.addAll([ + refer('JsonKey').call([literalString('i')]), + ]), + ), ]; - final e = Enum((b) => b - ..name = 'Unit' - ..values.addAll(values)); + final e = Enum( + (b) => + b + ..name = 'Unit' + ..values.addAll(values), + ); return _dartfmt.format('${e.accept(DartEmitter())}'); } diff --git a/pkgs/code_builder/lib/code_builder.dart b/pkgs/code_builder/lib/code_builder.dart index 9cd15b958..47d416c69 100644 --- a/pkgs/code_builder/lib/code_builder.dart +++ b/pkgs/code_builder/lib/code_builder.dart @@ -17,6 +17,7 @@ export 'src/specs/enum.dart' export 'src/specs/expression.dart' show BinaryExpression, + ClosureExpression, CodeExpression, Expression, ExpressionEmitter, @@ -25,6 +26,9 @@ export 'src/specs/expression.dart' InvokeExpressionType, LiteralExpression, LiteralListExpression, + LiteralMapExpression, + LiteralRecordExpression, + LiteralSetExpression, ParenthesizedExpression, ToCodeExpression, declareConst, @@ -70,3 +74,4 @@ export 'src/specs/type_function.dart' show FunctionType, FunctionTypeBuilder; export 'src/specs/type_record.dart' show RecordType, RecordTypeBuilder; export 'src/specs/type_reference.dart' show TypeReference, TypeReferenceBuilder; export 'src/specs/typedef.dart' show TypeDef, TypeDefBuilder; +export 'src/visitors.dart' show SpecVisitor; diff --git a/pkgs/code_builder/lib/src/allocator.dart b/pkgs/code_builder/lib/src/allocator.dart index 18d2d8c0d..2e732b361 100644 --- a/pkgs/code_builder/lib/src/allocator.dart +++ b/pkgs/code_builder/lib/src/allocator.dart @@ -90,7 +90,6 @@ class _PrefixedAllocator implements Allocator { int _nextKey() => _keys++; @override - Iterable get imports => _imports.keys.map( - (u) => Directive.import(u, as: '_i${_imports[u]}'), - ); + Iterable get imports => + _imports.keys.map((u) => Directive.import(u, as: '_i${_imports[u]}')); } diff --git a/pkgs/code_builder/lib/src/emitter.dart b/pkgs/code_builder/lib/src/emitter.dart index 5d94eb929..26fb2577b 100644 --- a/pkgs/code_builder/lib/src/emitter.dart +++ b/pkgs/code_builder/lib/src/emitter.dart @@ -76,19 +76,21 @@ class DartEmitter extends Object /// May specify an [Allocator] to use for references and imports, /// otherwise uses [Allocator.none] which never prefixes references and will /// not automatically emit import directives. - DartEmitter( - {this.allocator = Allocator.none, - this.orderDirectives = false, - bool useNullSafetySyntax = false}) - : _useNullSafetySyntax = useNullSafetySyntax; + DartEmitter({ + this.allocator = Allocator.none, + this.orderDirectives = false, + bool useNullSafetySyntax = false, + }) : _useNullSafetySyntax = useNullSafetySyntax; /// Creates a new instance of [DartEmitter] with simple automatic imports. - factory DartEmitter.scoped( - {bool orderDirectives = false, bool useNullSafetySyntax = false}) => - DartEmitter( - allocator: Allocator.simplePrefixing(), - orderDirectives: orderDirectives, - useNullSafetySyntax: useNullSafetySyntax); + factory DartEmitter.scoped({ + bool orderDirectives = false, + bool useNullSafetySyntax = false, + }) => DartEmitter( + allocator: Allocator.simplePrefixing(), + orderDirectives: orderDirectives, + useNullSafetySyntax: useNullSafetySyntax, + ); static bool _isLambdaBody(Code? code) => code is ToCodeExpression && !code.isStatement; @@ -145,13 +147,17 @@ class DartEmitter extends Object out ..write(' with ') ..writeAll( - spec.mixins.map((m) => m.type.accept(this)), ','); + spec.mixins.map((m) => m.type.accept(this)), + ',', + ); } if (spec.implements.isNotEmpty) { out ..write(' implements ') ..writeAll( - spec.implements.map((m) => m.type.accept(this)), ','); + spec.implements.map((m) => m.type.accept(this)), + ',', + ); } out.write(' {'); for (var c in spec.constructors) { @@ -188,7 +194,9 @@ class DartEmitter extends Object out ..write(' implements ') ..writeAll( - spec.implements.map((m) => m.type.accept(this)), ','); + spec.implements.map((m) => m.type.accept(this)), + ',', + ); } out.write(' {'); for (var f in spec.fields) { @@ -201,8 +209,11 @@ class DartEmitter extends Object } @override - StringSink visitConstructor(Constructor spec, String clazz, - [StringSink? output]) { + StringSink visitConstructor( + Constructor spec, + String clazz, [ + StringSink? output, + ]) { output ??= StringBuffer(); spec.docs.forEach(output.writeln); for (var a in spec.annotations) { @@ -342,7 +353,9 @@ class DartEmitter extends Object out ..write(' implements ') ..writeAll( - spec.implements.map((m) => m.type.accept(this)), ','); + spec.implements.map((m) => m.type.accept(this)), + ',', + ); } out.writeln(' {'); @@ -360,7 +373,9 @@ class DartEmitter extends Object } void _visitRepresentationDeclaration( - RepresentationDeclaration spec, StringSink out) { + RepresentationDeclaration spec, + StringSink out, + ) { spec.docs.forEach(out.writeln); for (var a in spec.annotations) { visitAnnotation(a, out); @@ -509,7 +524,8 @@ class DartEmitter extends Object Directive? previous; if (directives.any((d) => d.as?.startsWith('_') ?? false)) { output.writeln( - '// ignore_for_file: no_leading_underscores_for_library_prefixes'); + '// ignore_for_file: no_leading_underscores_for_library_prefixes', + ); } for (final directive in directives) { if (_newLineBetween(orderDirectives, previous, directive)) { @@ -543,7 +559,8 @@ class DartEmitter extends Object out.write('>'); } out.write('('); - final needsTrailingComma = spec.requiredParameters.length + + final needsTrailingComma = + spec.requiredParameters.length + spec.optionalParameters.length + spec.namedRequiredParameters.length + spec.namedParameters.length > @@ -551,7 +568,8 @@ class DartEmitter extends Object visitAll(spec.requiredParameters, out, (spec) { spec.accept(this, out); }); - final hasNamedParameters = spec.namedRequiredParameters.isNotEmpty || + final hasNamedParameters = + spec.namedRequiredParameters.isNotEmpty || spec.namedParameters.isNotEmpty; if (spec.requiredParameters.isNotEmpty && (needsTrailingComma || @@ -610,8 +628,9 @@ class DartEmitter extends Object out.write(', '); } out.write('{'); - visitAll>(spec.namedFieldTypes.entries, out, - (entry) { + visitAll>(spec.namedFieldTypes.entries, out, ( + entry, + ) { entry.value.accept(this, out); out.write(' ${entry.key}'); }); @@ -811,8 +830,10 @@ class DartEmitter extends Object } @override - StringSink visitTypeParameters(Iterable specs, - [StringSink? output]) { + StringSink visitTypeParameters( + Iterable specs, [ + StringSink? output, + ]) { output ??= StringBuffer(); if (specs.isNotEmpty) { output @@ -836,13 +857,17 @@ class DartEmitter extends Object out ..write(' with ') ..writeAll( - spec.mixins.map((m) => m.type.accept(this)), ', '); + spec.mixins.map((m) => m.type.accept(this)), + ', ', + ); } if (spec.implements.isNotEmpty) { out ..write(' implements ') ..writeAll( - spec.implements.map((m) => m.type.accept(this)), ', '); + spec.implements.map((m) => m.type.accept(this)), + ', ', + ); } out.write(' { '); for (var v in spec.values) { @@ -855,7 +880,8 @@ class DartEmitter extends Object out.write('.${v.constructorName}'); } visitTypeParameters(v.types.map((r) => r.type), out); - final takesArguments = v.constructorName != null || + final takesArguments = + v.constructorName != null || v.arguments.isNotEmpty || v.namedArguments.isNotEmpty; if (takesArguments) { @@ -863,7 +889,9 @@ class DartEmitter extends Object } if (v.arguments.isNotEmpty) { out.writeAll( - v.arguments.map((arg) => arg.accept(this)), ', '); + v.arguments.map((arg) => arg.accept(this)), + ', ', + ); } if (v.arguments.isNotEmpty && v.namedArguments.isNotEmpty) { out.write(', '); diff --git a/pkgs/code_builder/lib/src/matchers.dart b/pkgs/code_builder/lib/src/matchers.dart index 0cdf73997..fe48e2832 100644 --- a/pkgs/code_builder/lib/src/matchers.dart +++ b/pkgs/code_builder/lib/src/matchers.dart @@ -16,10 +16,7 @@ String _dart(Spec spec, DartEmitter emitter) => /// Both [source] and the result emitted from the compared [Spec] are formatted /// with [EqualsDart.format]. A plain [DartEmitter] is used by default and may /// be overridden with [emitter]. -Matcher equalsDart( - String source, [ - DartEmitter? emitter, -]) => +Matcher equalsDart(String source, [DartEmitter? emitter]) => EqualsDart._(EqualsDart._format(source), emitter ?? DartEmitter()); /// Implementation detail of using the [equalsDart] matcher. @@ -59,12 +56,9 @@ class EqualsDart extends Matcher { bool verbose, ) { final actualSource = _dart(item, _emitter); - return equals(_expectedSource).describeMismatch( - actualSource, - mismatchDescription, - matchState, - verbose, - ); + return equals( + _expectedSource, + ).describeMismatch(actualSource, mismatchDescription, matchState, verbose); } @override diff --git a/pkgs/code_builder/lib/src/specs/class.dart b/pkgs/code_builder/lib/src/specs/class.dart index 4c1915eaf..459496f0f 100644 --- a/pkgs/code_builder/lib/src/specs/class.dart +++ b/pkgs/code_builder/lib/src/specs/class.dart @@ -62,10 +62,7 @@ abstract class Class extends Object String get name; @override - R accept( - SpecVisitor visitor, [ - R? context, - ]) => + R accept(SpecVisitor visitor, [R? context]) => visitor.visitClass(this, context); } @@ -75,10 +72,10 @@ enum ClassModifier { interface; String get name => switch (this) { - ClassModifier.base => 'base', - ClassModifier.final$ => 'final', - ClassModifier.interface => 'interface' - }; + ClassModifier.base => 'base', + ClassModifier.final$ => 'final', + ClassModifier.interface => 'interface', + }; } abstract class ClassBuilder extends Object diff --git a/pkgs/code_builder/lib/src/specs/class.g.dart b/pkgs/code_builder/lib/src/specs/class.g.dart index 423f576ce..3a558a54f 100644 --- a/pkgs/code_builder/lib/src/specs/class.g.dart +++ b/pkgs/code_builder/lib/src/specs/class.g.dart @@ -39,22 +39,22 @@ class _$Class extends Class { factory _$Class([void Function(ClassBuilder)? updates]) => (new ClassBuilder()..update(updates)).build() as _$Class; - _$Class._( - {required this.abstract, - required this.sealed, - required this.mixin, - this.modifier, - required this.annotations, - required this.docs, - this.extend, - required this.implements, - required this.mixins, - required this.types, - required this.constructors, - required this.methods, - required this.fields, - required this.name}) - : super._() { + _$Class._({ + required this.abstract, + required this.sealed, + required this.mixin, + this.modifier, + required this.annotations, + required this.docs, + this.extend, + required this.implements, + required this.mixins, + required this.types, + required this.constructors, + required this.methods, + required this.fields, + required this.name, + }) : super._() { BuiltValueNullFieldError.checkNotNull(abstract, r'Class', 'abstract'); BuiltValueNullFieldError.checkNotNull(sealed, r'Class', 'sealed'); BuiltValueNullFieldError.checkNotNull(mixin, r'Class', 'mixin'); @@ -64,7 +64,10 @@ class _$Class extends Class { BuiltValueNullFieldError.checkNotNull(mixins, r'Class', 'mixins'); BuiltValueNullFieldError.checkNotNull(types, r'Class', 'types'); BuiltValueNullFieldError.checkNotNull( - constructors, r'Class', 'constructors'); + constructors, + r'Class', + 'constructors', + ); BuiltValueNullFieldError.checkNotNull(methods, r'Class', 'methods'); BuiltValueNullFieldError.checkNotNull(fields, r'Class', 'fields'); BuiltValueNullFieldError.checkNotNull(name, r'Class', 'name'); @@ -351,26 +354,36 @@ class _$ClassBuilder extends ClassBuilder { _$Class _build() { _$Class _$result; try { - _$result = _$v ?? + _$result = + _$v ?? new _$Class._( - abstract: BuiltValueNullFieldError.checkNotNull( - abstract, r'Class', 'abstract'), - sealed: BuiltValueNullFieldError.checkNotNull( - sealed, r'Class', 'sealed'), - mixin: BuiltValueNullFieldError.checkNotNull( - mixin, r'Class', 'mixin'), - modifier: modifier, - annotations: annotations.build(), - docs: docs.build(), - extend: extend, - implements: implements.build(), - mixins: mixins.build(), - types: types.build(), - constructors: constructors.build(), - methods: methods.build(), - fields: fields.build(), - name: BuiltValueNullFieldError.checkNotNull( - name, r'Class', 'name')); + abstract: BuiltValueNullFieldError.checkNotNull( + abstract, + r'Class', + 'abstract', + ), + sealed: BuiltValueNullFieldError.checkNotNull( + sealed, + r'Class', + 'sealed', + ), + mixin: BuiltValueNullFieldError.checkNotNull( + mixin, + r'Class', + 'mixin', + ), + modifier: modifier, + annotations: annotations.build(), + docs: docs.build(), + extend: extend, + implements: implements.build(), + mixins: mixins.build(), + types: types.build(), + constructors: constructors.build(), + methods: methods.build(), + fields: fields.build(), + name: BuiltValueNullFieldError.checkNotNull(name, r'Class', 'name'), + ); } catch (_) { late String _$failedField; try { @@ -393,7 +406,10 @@ class _$ClassBuilder extends ClassBuilder { fields.build(); } catch (e) { throw new BuiltValueNestedFieldError( - r'Class', _$failedField, e.toString()); + r'Class', + _$failedField, + e.toString(), + ); } rethrow; } diff --git a/pkgs/code_builder/lib/src/specs/code.dart b/pkgs/code_builder/lib/src/specs/code.dart index 521eb1fe0..070c5700f 100644 --- a/pkgs/code_builder/lib/src/specs/code.dart +++ b/pkgs/code_builder/lib/src/specs/code.dart @@ -36,9 +36,7 @@ abstract class Code implements Spec { /// return '${a.allocate(fooType)}()' /// }); /// ``` - const factory Code.scope( - String Function(Allocate) scope, - ) = ScopedCode._; + const factory Code.scope(String Function(Allocate) scope) = ScopedCode._; @override R accept(covariant CodeVisitor visitor, [R? context]); diff --git a/pkgs/code_builder/lib/src/specs/code.g.dart b/pkgs/code_builder/lib/src/specs/code.g.dart index 7b5ba7825..27d3f4567 100644 --- a/pkgs/code_builder/lib/src/specs/code.g.dart +++ b/pkgs/code_builder/lib/src/specs/code.g.dart @@ -41,8 +41,7 @@ class _$Block extends Block { @override String toString() { return (newBuiltValueToStringHelper(r'Block') - ..add('statements', statements)) - .toString(); + ..add('statements', statements)).toString(); } } @@ -97,7 +96,10 @@ class _$BlockBuilder extends BlockBuilder { statements.build(); } catch (e) { throw new BuiltValueNestedFieldError( - r'Block', _$failedField, e.toString()); + r'Block', + _$failedField, + e.toString(), + ); } rethrow; } diff --git a/pkgs/code_builder/lib/src/specs/constructor.g.dart b/pkgs/code_builder/lib/src/specs/constructor.g.dart index 3f0693272..4a72a0207 100644 --- a/pkgs/code_builder/lib/src/specs/constructor.g.dart +++ b/pkgs/code_builder/lib/src/specs/constructor.g.dart @@ -35,29 +35,41 @@ class _$Constructor extends Constructor { factory _$Constructor([void Function(ConstructorBuilder)? updates]) => (new ConstructorBuilder()..update(updates)).build() as _$Constructor; - _$Constructor._( - {required this.annotations, - required this.docs, - required this.optionalParameters, - required this.requiredParameters, - required this.initializers, - this.body, - required this.external, - required this.constant, - required this.factory, - this.lambda, - this.name, - this.redirect}) - : super._() { + _$Constructor._({ + required this.annotations, + required this.docs, + required this.optionalParameters, + required this.requiredParameters, + required this.initializers, + this.body, + required this.external, + required this.constant, + required this.factory, + this.lambda, + this.name, + this.redirect, + }) : super._() { BuiltValueNullFieldError.checkNotNull( - annotations, r'Constructor', 'annotations'); + annotations, + r'Constructor', + 'annotations', + ); BuiltValueNullFieldError.checkNotNull(docs, r'Constructor', 'docs'); BuiltValueNullFieldError.checkNotNull( - optionalParameters, r'Constructor', 'optionalParameters'); + optionalParameters, + r'Constructor', + 'optionalParameters', + ); BuiltValueNullFieldError.checkNotNull( - requiredParameters, r'Constructor', 'requiredParameters'); + requiredParameters, + r'Constructor', + 'requiredParameters', + ); BuiltValueNullFieldError.checkNotNull( - initializers, r'Constructor', 'initializers'); + initializers, + r'Constructor', + 'initializers', + ); BuiltValueNullFieldError.checkNotNull(external, r'Constructor', 'external'); BuiltValueNullFieldError.checkNotNull(constant, r'Constructor', 'constant'); BuiltValueNullFieldError.checkNotNull(factory, r'Constructor', 'factory'); @@ -312,23 +324,34 @@ class _$ConstructorBuilder extends ConstructorBuilder { _$Constructor _build() { _$Constructor _$result; try { - _$result = _$v ?? + _$result = + _$v ?? new _$Constructor._( - annotations: annotations.build(), - docs: docs.build(), - optionalParameters: optionalParameters.build(), - requiredParameters: requiredParameters.build(), - initializers: initializers.build(), - body: body, - external: BuiltValueNullFieldError.checkNotNull( - external, r'Constructor', 'external'), - constant: BuiltValueNullFieldError.checkNotNull( - constant, r'Constructor', 'constant'), - factory: BuiltValueNullFieldError.checkNotNull( - factory, r'Constructor', 'factory'), - lambda: lambda, - name: name, - redirect: redirect); + annotations: annotations.build(), + docs: docs.build(), + optionalParameters: optionalParameters.build(), + requiredParameters: requiredParameters.build(), + initializers: initializers.build(), + body: body, + external: BuiltValueNullFieldError.checkNotNull( + external, + r'Constructor', + 'external', + ), + constant: BuiltValueNullFieldError.checkNotNull( + constant, + r'Constructor', + 'constant', + ), + factory: BuiltValueNullFieldError.checkNotNull( + factory, + r'Constructor', + 'factory', + ), + lambda: lambda, + name: name, + redirect: redirect, + ); } catch (_) { late String _$failedField; try { @@ -344,7 +367,10 @@ class _$ConstructorBuilder extends ConstructorBuilder { initializers.build(); } catch (e) { throw new BuiltValueNestedFieldError( - r'Constructor', _$failedField, e.toString()); + r'Constructor', + _$failedField, + e.toString(), + ); } rethrow; } diff --git a/pkgs/code_builder/lib/src/specs/directive.dart b/pkgs/code_builder/lib/src/specs/directive.dart index e10c0ea2a..fb1d07742 100644 --- a/pkgs/code_builder/lib/src/specs/directive.dart +++ b/pkgs/code_builder/lib/src/specs/directive.dart @@ -21,46 +21,58 @@ abstract class Directive String? as, List show = const [], List hide = const [], - }) => - Directive((builder) => builder - ..as = as - ..type = DirectiveType.import - ..url = url - ..show.addAll(show) - ..hide.addAll(hide)); + }) => Directive( + (builder) => + builder + ..as = as + ..type = DirectiveType.import + ..url = url + ..show.addAll(show) + ..hide.addAll(hide), + ); factory Directive.importDeferredAs( String url, String as, { List show = const [], List hide = const [], - }) => - Directive((builder) => builder - ..as = as - ..type = DirectiveType.import - ..url = url - ..deferred = true - ..show.addAll(show) - ..hide.addAll(hide)); + }) => Directive( + (builder) => + builder + ..as = as + ..type = DirectiveType.import + ..url = url + ..deferred = true + ..show.addAll(show) + ..hide.addAll(hide), + ); factory Directive.export( String url, { List show = const [], List hide = const [], - }) => - Directive((builder) => builder - ..type = DirectiveType.export - ..url = url - ..show.addAll(show) - ..hide.addAll(hide)); - - factory Directive.part(String url) => Directive((builder) => builder - ..type = DirectiveType.part - ..url = url); - - factory Directive.partOf(String url) => Directive((builder) => builder - ..type = DirectiveType.partOf - ..url = url); + }) => Directive( + (builder) => + builder + ..type = DirectiveType.export + ..url = url + ..show.addAll(show) + ..hide.addAll(hide), + ); + + factory Directive.part(String url) => Directive( + (builder) => + builder + ..type = DirectiveType.part + ..url = url, + ); + + factory Directive.partOf(String url) => Directive( + (builder) => + builder + ..type = DirectiveType.partOf + ..url = url, + ); Directive._(); @@ -77,10 +89,7 @@ abstract class Directive bool get deferred; @override - R accept( - SpecVisitor visitor, [ - R? context, - ]) => + R accept(SpecVisitor visitor, [R? context]) => visitor.visitDirective(this, context); @override @@ -106,12 +115,7 @@ abstract class DirectiveBuilder DirectiveType? type; } -enum DirectiveType { - import, - export, - part, - partOf, -} +enum DirectiveType { import, export, part, partOf } /// Sort import URIs represented by [a] and [b] to honor the /// "Effective Dart" ordering rules which are enforced by the diff --git a/pkgs/code_builder/lib/src/specs/directive.g.dart b/pkgs/code_builder/lib/src/specs/directive.g.dart index b28158e07..0cc014a0c 100644 --- a/pkgs/code_builder/lib/src/specs/directive.g.dart +++ b/pkgs/code_builder/lib/src/specs/directive.g.dart @@ -23,14 +23,14 @@ class _$Directive extends Directive { factory _$Directive([void Function(DirectiveBuilder)? updates]) => (new DirectiveBuilder()..update(updates)).build() as _$Directive; - _$Directive._( - {this.as, - required this.url, - required this.type, - required this.show, - required this.hide, - required this.deferred}) - : super._() { + _$Directive._({ + this.as, + required this.url, + required this.type, + required this.show, + required this.hide, + required this.deferred, + }) : super._() { BuiltValueNullFieldError.checkNotNull(url, r'Directive', 'url'); BuiltValueNullFieldError.checkNotNull(type, r'Directive', 'type'); BuiltValueNullFieldError.checkNotNull(show, r'Directive', 'show'); @@ -189,19 +189,32 @@ class _$DirectiveBuilder extends DirectiveBuilder { Directive build() => _build(); _$Directive _build() { - final _$result = _$v ?? + final _$result = + _$v ?? new _$Directive._( - as: as, - url: - BuiltValueNullFieldError.checkNotNull(url, r'Directive', 'url'), - type: BuiltValueNullFieldError.checkNotNull( - type, r'Directive', 'type'), - show: BuiltValueNullFieldError.checkNotNull( - show, r'Directive', 'show'), - hide: BuiltValueNullFieldError.checkNotNull( - hide, r'Directive', 'hide'), - deferred: BuiltValueNullFieldError.checkNotNull( - deferred, r'Directive', 'deferred')); + as: as, + url: BuiltValueNullFieldError.checkNotNull(url, r'Directive', 'url'), + type: BuiltValueNullFieldError.checkNotNull( + type, + r'Directive', + 'type', + ), + show: BuiltValueNullFieldError.checkNotNull( + show, + r'Directive', + 'show', + ), + hide: BuiltValueNullFieldError.checkNotNull( + hide, + r'Directive', + 'hide', + ), + deferred: BuiltValueNullFieldError.checkNotNull( + deferred, + r'Directive', + 'deferred', + ), + ); replace(_$result); return _$result; } diff --git a/pkgs/code_builder/lib/src/specs/enum.dart b/pkgs/code_builder/lib/src/specs/enum.dart index c58f92cb0..82562ee79 100644 --- a/pkgs/code_builder/lib/src/specs/enum.dart +++ b/pkgs/code_builder/lib/src/specs/enum.dart @@ -6,11 +6,16 @@ import 'package:built_collection/built_collection.dart'; import 'package:built_value/built_value.dart'; import 'package:meta/meta.dart'; -import '../../code_builder.dart'; +import '../base.dart' show Spec; import '../mixins/annotations.dart'; import '../mixins/dartdoc.dart'; import '../mixins/generics.dart'; import '../visitors.dart'; +import 'constructor.dart' show Constructor; +import 'expression.dart' show Expression; +import 'field.dart' show Field; +import 'method.dart' show Method; +import 'reference.dart' show Reference; part 'enum.g.dart'; @@ -44,10 +49,7 @@ abstract class Enum extends Object BuiltList get fields; @override - R accept( - SpecVisitor visitor, [ - R? context, - ]) => + R accept(SpecVisitor visitor, [R? context]) => visitor.visitEnum(this, context); } diff --git a/pkgs/code_builder/lib/src/specs/enum.g.dart b/pkgs/code_builder/lib/src/specs/enum.g.dart index 651cc610a..8c2e35830 100644 --- a/pkgs/code_builder/lib/src/specs/enum.g.dart +++ b/pkgs/code_builder/lib/src/specs/enum.g.dart @@ -31,18 +31,18 @@ class _$Enum extends Enum { factory _$Enum([void Function(EnumBuilder)? updates]) => (new EnumBuilder()..update(updates)).build() as _$Enum; - _$Enum._( - {required this.name, - required this.values, - required this.annotations, - required this.docs, - required this.implements, - required this.mixins, - required this.types, - required this.constructors, - required this.methods, - required this.fields}) - : super._() { + _$Enum._({ + required this.name, + required this.values, + required this.annotations, + required this.docs, + required this.implements, + required this.mixins, + required this.types, + required this.constructors, + required this.methods, + required this.fields, + }) : super._() { BuiltValueNullFieldError.checkNotNull(name, r'Enum', 'name'); BuiltValueNullFieldError.checkNotNull(values, r'Enum', 'values'); BuiltValueNullFieldError.checkNotNull(annotations, r'Enum', 'annotations'); @@ -51,7 +51,10 @@ class _$Enum extends Enum { BuiltValueNullFieldError.checkNotNull(mixins, r'Enum', 'mixins'); BuiltValueNullFieldError.checkNotNull(types, r'Enum', 'types'); BuiltValueNullFieldError.checkNotNull( - constructors, r'Enum', 'constructors'); + constructors, + r'Enum', + 'constructors', + ); BuiltValueNullFieldError.checkNotNull(methods, r'Enum', 'methods'); BuiltValueNullFieldError.checkNotNull(fields, r'Enum', 'fields'); } @@ -273,19 +276,20 @@ class _$EnumBuilder extends EnumBuilder { _$Enum _build() { _$Enum _$result; try { - _$result = _$v ?? + _$result = + _$v ?? new _$Enum._( - name: - BuiltValueNullFieldError.checkNotNull(name, r'Enum', 'name'), - values: values.build(), - annotations: annotations.build(), - docs: docs.build(), - implements: implements.build(), - mixins: mixins.build(), - types: types.build(), - constructors: constructors.build(), - methods: methods.build(), - fields: fields.build()); + name: BuiltValueNullFieldError.checkNotNull(name, r'Enum', 'name'), + values: values.build(), + annotations: annotations.build(), + docs: docs.build(), + implements: implements.build(), + mixins: mixins.build(), + types: types.build(), + constructors: constructors.build(), + methods: methods.build(), + fields: fields.build(), + ); } catch (_) { late String _$failedField; try { @@ -309,7 +313,10 @@ class _$EnumBuilder extends EnumBuilder { fields.build(); } catch (e) { throw new BuiltValueNestedFieldError( - r'Enum', _$failedField, e.toString()); + r'Enum', + _$failedField, + e.toString(), + ); } rethrow; } @@ -337,23 +344,29 @@ class _$EnumValue extends EnumValue { factory _$EnumValue([void Function(EnumValueBuilder)? updates]) => (new EnumValueBuilder()..update(updates)).build() as _$EnumValue; - _$EnumValue._( - {required this.name, - required this.annotations, - required this.docs, - this.constructorName, - required this.types, - required this.arguments, - required this.namedArguments}) - : super._() { + _$EnumValue._({ + required this.name, + required this.annotations, + required this.docs, + this.constructorName, + required this.types, + required this.arguments, + required this.namedArguments, + }) : super._() { BuiltValueNullFieldError.checkNotNull(name, r'EnumValue', 'name'); BuiltValueNullFieldError.checkNotNull( - annotations, r'EnumValue', 'annotations'); + annotations, + r'EnumValue', + 'annotations', + ); BuiltValueNullFieldError.checkNotNull(docs, r'EnumValue', 'docs'); BuiltValueNullFieldError.checkNotNull(types, r'EnumValue', 'types'); BuiltValueNullFieldError.checkNotNull(arguments, r'EnumValue', 'arguments'); BuiltValueNullFieldError.checkNotNull( - namedArguments, r'EnumValue', 'namedArguments'); + namedArguments, + r'EnumValue', + 'namedArguments', + ); } @override @@ -525,16 +538,21 @@ class _$EnumValueBuilder extends EnumValueBuilder { _$EnumValue _build() { _$EnumValue _$result; try { - _$result = _$v ?? + _$result = + _$v ?? new _$EnumValue._( - name: BuiltValueNullFieldError.checkNotNull( - name, r'EnumValue', 'name'), - annotations: annotations.build(), - docs: docs.build(), - constructorName: constructorName, - types: types.build(), - arguments: arguments.build(), - namedArguments: namedArguments.build()); + name: BuiltValueNullFieldError.checkNotNull( + name, + r'EnumValue', + 'name', + ), + annotations: annotations.build(), + docs: docs.build(), + constructorName: constructorName, + types: types.build(), + arguments: arguments.build(), + namedArguments: namedArguments.build(), + ); } catch (_) { late String _$failedField; try { @@ -551,7 +569,10 @@ class _$EnumValueBuilder extends EnumValueBuilder { namedArguments.build(); } catch (e) { throw new BuiltValueNestedFieldError( - r'EnumValue', _$failedField, e.toString()); + r'EnumValue', + _$failedField, + e.toString(), + ); } rethrow; } diff --git a/pkgs/code_builder/lib/src/specs/expression.dart b/pkgs/code_builder/lib/src/specs/expression.dart index aa06de283..133609205 100644 --- a/pkgs/code_builder/lib/src/specs/expression.dart +++ b/pkgs/code_builder/lib/src/specs/expression.dart @@ -66,116 +66,69 @@ abstract class Expression implements Spec { /// Returns the result of `this` `as` [other]. Expression asA(Expression other) => - ParenthesizedExpression._(BinaryExpression._( - expression, - other, - 'as', - )); + ParenthesizedExpression._(BinaryExpression._(expression, other, 'as')); /// Returns accessing the index operator (`[]`) on `this`. Expression index(Expression index) => BinaryExpression._( - expression, - CodeExpression(Block.of([ - const Code('['), - index.code, - const Code(']'), - ])), - '', - ); + expression, + CodeExpression(Block.of([const Code('['), index.code, const Code(']')])), + '', + ); /// Returns the result of `this` `is` [other]. - Expression isA(Expression other) => BinaryExpression._( - expression, - other, - 'is', - ); + Expression isA(Expression other) => + BinaryExpression._(expression, other, 'is'); /// Returns the result of `this` `is!` [other]. - Expression isNotA(Expression other) => BinaryExpression._( - expression, - other, - 'is!', - ); + Expression isNotA(Expression other) => + BinaryExpression._(expression, other, 'is!'); /// Returns the result of `this` `==` [other]. - Expression equalTo(Expression other) => BinaryExpression._( - expression, - other, - '==', - ); + Expression equalTo(Expression other) => + BinaryExpression._(expression, other, '=='); /// Returns the result of `this` `!=` [other]. - Expression notEqualTo(Expression other) => BinaryExpression._( - expression, - other, - '!=', - ); + Expression notEqualTo(Expression other) => + BinaryExpression._(expression, other, '!='); /// Returns the result of `this` `>` [other]. - Expression greaterThan(Expression other) => BinaryExpression._( - expression, - other, - '>', - ); + Expression greaterThan(Expression other) => + BinaryExpression._(expression, other, '>'); /// Returns the result of `this` `<` [other]. - Expression lessThan(Expression other) => BinaryExpression._( - expression, - other, - '<', - ); + Expression lessThan(Expression other) => + BinaryExpression._(expression, other, '<'); /// Returns the result of `this` `>=` [other]. - Expression greaterOrEqualTo(Expression other) => BinaryExpression._( - expression, - other, - '>=', - ); + Expression greaterOrEqualTo(Expression other) => + BinaryExpression._(expression, other, '>='); /// Returns the result of `this` `<=` [other]. - Expression lessOrEqualTo(Expression other) => BinaryExpression._( - expression, - other, - '<=', - ); + Expression lessOrEqualTo(Expression other) => + BinaryExpression._(expression, other, '<='); /// Returns the result of `this` `+` [other]. - Expression operatorAdd(Expression other) => BinaryExpression._( - expression, - other, - '+', - ); + Expression operatorAdd(Expression other) => + BinaryExpression._(expression, other, '+'); /// Returns the result of `this` `-` [other]. - Expression operatorSubtract(Expression other) => BinaryExpression._( - expression, - other, - '-', - ); + Expression operatorSubtract(Expression other) => + BinaryExpression._(expression, other, '-'); @Deprecated('Use `operatorSubtract` instead') Expression operatorSubstract(Expression other) => operatorSubtract(other); /// Returns the result of `this` `/` [other]. - Expression operatorDivide(Expression other) => BinaryExpression._( - expression, - other, - '/', - ); + Expression operatorDivide(Expression other) => + BinaryExpression._(expression, other, '/'); /// Returns the result of `this` `*` [other]. - Expression operatorMultiply(Expression other) => BinaryExpression._( - expression, - other, - '*', - ); + Expression operatorMultiply(Expression other) => + BinaryExpression._(expression, other, '*'); /// Returns the result of `this` `%` [other]. - Expression operatorEuclideanModulo(Expression other) => BinaryExpression._( - expression, - other, - '%', - ); + Expression operatorEuclideanModulo(Expression other) => + BinaryExpression._(expression, other, '%'); /// Returns the result of `this` `~/` [other]. Expression operatorIntDivide(Expression other) => @@ -189,11 +142,7 @@ abstract class Expression implements Spec { ); /// This expression preceded by `await`. - Expression get awaited => BinaryExpression._( - _empty, - this, - 'await', - ); + Expression get awaited => BinaryExpression._(_empty, this, 'await'); /// Returns the result of `++this`. Expression operatorUnaryPrefixIncrement() => @@ -296,139 +245,118 @@ abstract class Expression implements Spec { BinaryExpression._(this, other, '|='); /// Return `{this} ?? {other}`. - Expression ifNullThen(Expression other) => BinaryExpression._( - this, - other, - '??', - ); + Expression ifNullThen(Expression other) => + BinaryExpression._(this, other, '??'); /// Return `{this} ??= {other}`. - Expression assignNullAware(Expression other) => BinaryExpression._( - this, - other, - '??=', - ); + Expression assignNullAware(Expression other) => + BinaryExpression._(this, other, '??='); /// Return `var {name} = {this}`. @Deprecated('Use `declareVar(name).assign(expression)`') Expression assignVar(String name, [Reference? type]) => BinaryExpression._( - type == null - ? LiteralExpression._('var $name') - : BinaryExpression._( - type.expression, - LiteralExpression._(name), - '', - ), - this, - '=', - ); + type == null + ? LiteralExpression._('var $name') + : BinaryExpression._(type.expression, LiteralExpression._(name), ''), + this, + '=', + ); /// Return `final {name} = {this}`. @Deprecated('Use `declareFinal(name).assign(expression)`') Expression assignFinal(String name, [Reference? type]) => BinaryExpression._( - type == null - ? const LiteralExpression._('final') - : BinaryExpression._( - const LiteralExpression._('final'), - type.expression, - '', - ), - this, - '$name =', - ); + type == null + ? const LiteralExpression._('final') + : BinaryExpression._( + const LiteralExpression._('final'), + type.expression, + '', + ), + this, + '$name =', + ); /// Return `const {name} = {this}`. @Deprecated('Use `declareConst(name).assign(expression)`') Expression assignConst(String name, [Reference? type]) => BinaryExpression._( - type == null - ? const LiteralExpression._('const') - : BinaryExpression._( - const LiteralExpression._('const'), - type.expression, - '', - ), - this, - '$name =', - isConst: true, - ); + type == null + ? const LiteralExpression._('const') + : BinaryExpression._( + const LiteralExpression._('const'), + type.expression, + '', + ), + this, + '$name =', + isConst: true, + ); /// Call this expression as a method. Expression call( Iterable positionalArguments, [ Map namedArguments = const {}, List typeArguments = const [], - ]) => - InvokeExpression._( - this, - positionalArguments.toList(), - namedArguments, - typeArguments, - ); + ]) => InvokeExpression._( + this, + positionalArguments.toList(), + namedArguments, + typeArguments, + ); /// Returns an expression accessing `.` on this expression. - Expression property(String name) => BinaryExpression._( - this, - LiteralExpression._(name), - '.', - addSpace: false, - ); + Expression property(String name) => + BinaryExpression._(this, LiteralExpression._(name), '.', addSpace: false); /// Returns an expression accessing `..` on this expression. Expression cascade(String name) => BinaryExpression._( - this, - LiteralExpression._(name), - '..', - addSpace: false, - ); + this, + LiteralExpression._(name), + '..', + addSpace: false, + ); /// Returns an expression accessing `?.` on this expression. Expression nullSafeProperty(String name) => BinaryExpression._( - this, - LiteralExpression._(name), - '?.', - addSpace: false, - ); + this, + LiteralExpression._(name), + '?.', + addSpace: false, + ); /// Applies the null check operator on this expression, returning `this` `!`. /// /// Please note that this is only valid when emitting code with the null /// safety syntax enabled. Expression get nullChecked => BinaryExpression._( - this, - const LiteralExpression._('!'), - '', - addSpace: false, - ); + this, + const LiteralExpression._('!'), + '', + addSpace: false, + ); /// This expression preceded by `return`. - Expression get returned => BinaryExpression._( - const LiteralExpression._('return'), - this, - '', - ); + Expression get returned => + BinaryExpression._(const LiteralExpression._('return'), this, ''); /// This expression preceded by the spread operator `...`. Expression get spread => BinaryExpression._( - const LiteralExpression._('...'), - this, - '', - addSpace: false, - ); + const LiteralExpression._('...'), + this, + '', + addSpace: false, + ); /// This expression preceded by the null safe spread operator `?...`. Expression get nullSafeSpread => BinaryExpression._( - const LiteralExpression._('...?'), - this, - '', - addSpace: false, - ); + const LiteralExpression._('...?'), + this, + '', + addSpace: false, + ); /// This expression preceded by `throw`. - Expression get thrown => BinaryExpression._( - const LiteralExpression._('throw'), - this, - '', - ); + Expression get thrown => + BinaryExpression._(const LiteralExpression._('throw'), this, ''); /// May be overridden to support other types implementing [Expression]. @visibleForOverriding @@ -443,49 +371,63 @@ abstract class Expression implements Spec { /// Returns `const {variableName}`, or `const {type} {variableName}`. Expression declareConst(String variableName, {Reference? type}) => BinaryExpression._( - const LiteralExpression._('const'), - type == null - ? LiteralExpression._(variableName) - : _typedVar(variableName, type), - '', - isConst: true); + const LiteralExpression._('const'), + type == null + ? LiteralExpression._(variableName) + : _typedVar(variableName, type), + '', + isConst: true, + ); /// Declare a final variable named [variableName]. /// /// Returns `final {variableName}`, or `final {type} {variableName}`. /// If [late] is true the declaration is prefixed with `late`. -Expression declareFinal(String variableName, - {Reference? type, bool late = false}) => - _late( - late, - type == null - ? LiteralExpression._('final $variableName') - : BinaryExpression._(const LiteralExpression._('final'), - _typedVar(variableName, type), '')); +Expression declareFinal( + String variableName, { + Reference? type, + bool late = false, +}) => _late( + late, + type == null + ? LiteralExpression._('final $variableName') + : BinaryExpression._( + const LiteralExpression._('final'), + _typedVar(variableName, type), + '', + ), +); /// Declare a variable named [variableName]. /// /// Returns `var {variableName}`, or `{type} {variableName}`. /// If [late] is true the declaration is prefixed with `late`. -Expression declareVar(String variableName, - {Reference? type, bool late = false}) => - _late( - late, - type == null - ? LiteralExpression._('var $variableName') - : _typedVar(variableName, type)); +Expression declareVar( + String variableName, { + Reference? type, + bool late = false, +}) => _late( + late, + type == null + ? LiteralExpression._('var $variableName') + : _typedVar(variableName, type), +); Expression _typedVar(String variableName, Reference type) => BinaryExpression._(type.expression, LiteralExpression._(variableName), ''); -Expression _late(bool late, Expression expression) => late - ? BinaryExpression._(const LiteralExpression._('late'), expression, '') - : expression; +Expression _late(bool late, Expression expression) => + late + ? BinaryExpression._(const LiteralExpression._('late'), expression, '') + : expression; /// Creates `typedef {name} =`. -Code createTypeDef(String name, FunctionType type) => BinaryExpression._( - LiteralExpression._('typedef $name'), type.expression, '=') - .statement; +Code createTypeDef(String name, FunctionType type) => + BinaryExpression._( + LiteralExpression._('typedef $name'), + type.expression, + '=', + ).statement; class ToCodeExpression implements Code { final Expression code; @@ -517,10 +459,14 @@ abstract class ExpressionVisitor implements SpecVisitor { T visitLiteralListExpression(LiteralListExpression expression, [T? context]); T visitLiteralSetExpression(LiteralSetExpression expression, [T? context]); T visitLiteralMapExpression(LiteralMapExpression expression, [T? context]); - T visitLiteralRecordExpression(LiteralRecordExpression expression, - [T? context]); - T visitParenthesizedExpression(ParenthesizedExpression expression, - [T? context]); + T visitLiteralRecordExpression( + LiteralRecordExpression expression, [ + T? context, + ]); + T visitParenthesizedExpression( + ParenthesizedExpression expression, [ + T? context, + ]); } /// Knowledge of how to write valid Dart code from [ExpressionVisitor]. @@ -529,8 +475,10 @@ abstract class ExpressionVisitor implements SpecVisitor { abstract mixin class ExpressionEmitter implements ExpressionVisitor { @override - StringSink visitToCodeExpression(ToCodeExpression expression, - [StringSink? output]) { + StringSink visitToCodeExpression( + ToCodeExpression expression, [ + StringSink? output, + ]) { output ??= StringBuffer(); expression.code.accept(this, output); if (expression.isStatement) { @@ -540,8 +488,10 @@ abstract mixin class ExpressionEmitter } @override - StringSink visitBinaryExpression(BinaryExpression expression, - [StringSink? output]) { + StringSink visitBinaryExpression( + BinaryExpression expression, [ + StringSink? output, + ]) { output ??= StringBuffer(); expression.left.accept(this, output); if (expression.addSpace) { @@ -558,23 +508,29 @@ abstract mixin class ExpressionEmitter } @override - StringSink visitClosureExpression(ClosureExpression expression, - [StringSink? output]) { + StringSink visitClosureExpression( + ClosureExpression expression, [ + StringSink? output, + ]) { output ??= StringBuffer(); return expression.method.accept(this, output); } @override - StringSink visitCodeExpression(CodeExpression expression, - [StringSink? output]) { + StringSink visitCodeExpression( + CodeExpression expression, [ + StringSink? output, + ]) { output ??= StringBuffer(); final visitor = this as CodeVisitor; return expression.code.accept(visitor, output); } @override - StringSink visitInvokeExpression(InvokeExpression expression, - [StringSink? output]) { + StringSink visitInvokeExpression( + InvokeExpression expression, [ + StringSink? output, + ]) { final out = output ??= StringBuffer(); return _writeConstExpression(out, expression.isConst, () { expression.target.accept(this, out); @@ -604,7 +560,8 @@ abstract mixin class ExpressionEmitter ..write(': '); expression.namedArguments[name]!.accept(this, out); }); - final argumentCount = expression.positionalArguments.length + + final argumentCount = + expression.positionalArguments.length + expression.namedArguments.length; if (argumentCount > 1) { out.write(', '); @@ -614,8 +571,10 @@ abstract mixin class ExpressionEmitter } @override - StringSink visitLiteralExpression(LiteralExpression expression, - [StringSink? output]) { + StringSink visitLiteralExpression( + LiteralExpression expression, [ + StringSink? output, + ]) { output ??= StringBuffer(); return output..write(expression.literal); } @@ -731,10 +690,13 @@ abstract mixin class ExpressionEmitter out.write(','); } visitAll>( - expression.namedFieldValues.entries, out, (entry) { - out.write('${entry.key}: '); - _acceptLiteral(entry.value, out); - }); + expression.namedFieldValues.entries, + out, + (entry) { + out.write('${entry.key}: '); + _acceptLiteral(entry.value, out); + }, + ); return out..write(')'); }); } @@ -756,10 +718,7 @@ abstract mixin class ExpressionEmitter /// /// This allows constant expressions to omit the `const` keyword if they /// are already within a constant expression. - void startConstCode( - bool isConst, - Null Function() visit, - ) { + void startConstCode(bool isConst, Null Function() visit) { final previousConstContext = _withInConstExpression; if (isConst) { _withInConstExpression = true; diff --git a/pkgs/code_builder/lib/src/specs/expression/invoke.dart b/pkgs/code_builder/lib/src/specs/expression/invoke.dart index f54dc0ecd..ff1f151e9 100644 --- a/pkgs/code_builder/lib/src/specs/expression/invoke.dart +++ b/pkgs/code_builder/lib/src/specs/expression/invoke.dart @@ -28,9 +28,9 @@ class InvokeExpression extends Expression { this.positionalArguments, this.namedArguments, this.typeArguments, - ) : name = null, - type = null, - isConst = false; + ) : name = null, + type = null, + isConst = false; const InvokeExpression.newOf( this.target, @@ -38,8 +38,8 @@ class InvokeExpression extends Expression { this.namedArguments = const {}, this.typeArguments = const [], this.name, - ]) : type = InvokeExpressionType.newInstance, - isConst = false; + ]) : type = InvokeExpressionType.newInstance, + isConst = false; const InvokeExpression.constOf( this.target, @@ -47,8 +47,8 @@ class InvokeExpression extends Expression { this.namedArguments = const {}, this.typeArguments = const [], this.name, - ]) : type = InvokeExpressionType.constInstance, - isConst = true; + ]) : type = InvokeExpressionType.constInstance, + isConst = true; @override R accept(ExpressionVisitor visitor, [R? context]) => @@ -59,7 +59,4 @@ class InvokeExpression extends Expression { '${type ?? ''} $target($positionalArguments, $namedArguments)'; } -enum InvokeExpressionType { - newInstance, - constInstance, -} +enum InvokeExpressionType { newInstance, constInstance } diff --git a/pkgs/code_builder/lib/src/specs/expression/literal.dart b/pkgs/code_builder/lib/src/specs/expression/literal.dart index ced6cf90f..d4b687a6d 100644 --- a/pkgs/code_builder/lib/src/specs/expression/literal.dart +++ b/pkgs/code_builder/lib/src/specs/expression/literal.dart @@ -69,14 +69,16 @@ Expression literalSpread() => LiteralSpreadExpression._(false); Expression literalNullSafeSpread() => LiteralSpreadExpression._(true); /// Creates a literal list expression from [values]. -LiteralListExpression literalList(Iterable values, - [Reference? type]) => - LiteralListExpression._(false, values.toList(), type); +LiteralListExpression literalList( + Iterable values, [ + Reference? type, +]) => LiteralListExpression._(false, values.toList(), type); /// Creates a literal `const` list expression from [values]. -LiteralListExpression literalConstList(List values, - [Reference? type]) => - LiteralListExpression._(true, values, type); +LiteralListExpression literalConstList( + List values, [ + Reference? type, +]) => LiteralListExpression._(true, values, type); /// Creates a literal set expression from [values]. LiteralSetExpression literalSet(Iterable values, [Reference? type]) => @@ -91,28 +93,28 @@ LiteralMapExpression literalMap( Map values, [ Reference? keyType, Reference? valueType, -]) => - LiteralMapExpression._(false, values, keyType, valueType); +]) => LiteralMapExpression._(false, values, keyType, valueType); /// Create a literal `const` map expression from [values]. LiteralMapExpression literalConstMap( Map values, [ Reference? keyType, Reference? valueType, -]) => - LiteralMapExpression._(true, values, keyType, valueType); +]) => LiteralMapExpression._(true, values, keyType, valueType); /// Create a literal record expression from [positionalFieldValues] and /// [namedFieldValues]. -LiteralRecordExpression literalRecord(List positionalFieldValues, - Map namedFieldValues) => - LiteralRecordExpression._(false, positionalFieldValues, namedFieldValues); +LiteralRecordExpression literalRecord( + List positionalFieldValues, + Map namedFieldValues, +) => LiteralRecordExpression._(false, positionalFieldValues, namedFieldValues); /// Create a literal `const` record expression from [positionalFieldValues] and /// [namedFieldValues]. -LiteralRecordExpression literalConstRecord(List positionalFieldValues, - Map namedFieldValues) => - LiteralRecordExpression._(true, positionalFieldValues, namedFieldValues); +LiteralRecordExpression literalConstRecord( + List positionalFieldValues, + Map namedFieldValues, +) => LiteralRecordExpression._(true, positionalFieldValues, namedFieldValues); /// Represents a literal value in Dart source code. /// @@ -139,7 +141,7 @@ class LiteralExpression extends Expression { class LiteralSpreadExpression extends LiteralExpression { LiteralSpreadExpression._(bool nullAware) - : super._('...${nullAware ? '?' : ''}'); + : super._('...${nullAware ? '?' : ''}'); } class LiteralListExpression extends Expression { @@ -203,7 +205,10 @@ class LiteralRecordExpression extends Expression { final Map namedFieldValues; const LiteralRecordExpression._( - this.isConst, this.positionalFieldValues, this.namedFieldValues); + this.isConst, + this.positionalFieldValues, + this.namedFieldValues, + ); @override R accept(ExpressionVisitor visitor, [R? context]) => @@ -211,8 +216,11 @@ class LiteralRecordExpression extends Expression { @override String toString() { - final allFields = positionalFieldValues.map((v) => v.toString()).followedBy( - namedFieldValues.entries.map((e) => '${e.key}: ${e.value}')); + final allFields = positionalFieldValues + .map((v) => v.toString()) + .followedBy( + namedFieldValues.entries.map((e) => '${e.key}: ${e.value}'), + ); return '(${allFields.join(', ')})'; } } diff --git a/pkgs/code_builder/lib/src/specs/extension.dart b/pkgs/code_builder/lib/src/specs/extension.dart index 395994fc7..ba7ee00a5 100644 --- a/pkgs/code_builder/lib/src/specs/extension.dart +++ b/pkgs/code_builder/lib/src/specs/extension.dart @@ -45,10 +45,7 @@ abstract class Extension extends Object String? get name; @override - R accept( - SpecVisitor visitor, [ - R? context, - ]) => + R accept(SpecVisitor visitor, [R? context]) => visitor.visitExtension(this, context); } diff --git a/pkgs/code_builder/lib/src/specs/extension.g.dart b/pkgs/code_builder/lib/src/specs/extension.g.dart index 83de17614..5782c3da7 100644 --- a/pkgs/code_builder/lib/src/specs/extension.g.dart +++ b/pkgs/code_builder/lib/src/specs/extension.g.dart @@ -25,17 +25,20 @@ class _$Extension extends Extension { factory _$Extension([void Function(ExtensionBuilder)? updates]) => (new ExtensionBuilder()..update(updates)).build() as _$Extension; - _$Extension._( - {required this.annotations, - required this.docs, - this.on, - required this.types, - required this.methods, - required this.fields, - this.name}) - : super._() { + _$Extension._({ + required this.annotations, + required this.docs, + this.on, + required this.types, + required this.methods, + required this.fields, + this.name, + }) : super._() { BuiltValueNullFieldError.checkNotNull( - annotations, r'Extension', 'annotations'); + annotations, + r'Extension', + 'annotations', + ); BuiltValueNullFieldError.checkNotNull(docs, r'Extension', 'docs'); BuiltValueNullFieldError.checkNotNull(types, r'Extension', 'types'); BuiltValueNullFieldError.checkNotNull(methods, r'Extension', 'methods'); @@ -211,15 +214,17 @@ class _$ExtensionBuilder extends ExtensionBuilder { _$Extension _build() { _$Extension _$result; try { - _$result = _$v ?? + _$result = + _$v ?? new _$Extension._( - annotations: annotations.build(), - docs: docs.build(), - on: on, - types: types.build(), - methods: methods.build(), - fields: fields.build(), - name: name); + annotations: annotations.build(), + docs: docs.build(), + on: on, + types: types.build(), + methods: methods.build(), + fields: fields.build(), + name: name, + ); } catch (_) { late String _$failedField; try { @@ -236,7 +241,10 @@ class _$ExtensionBuilder extends ExtensionBuilder { fields.build(); } catch (e) { throw new BuiltValueNestedFieldError( - r'Extension', _$failedField, e.toString()); + r'Extension', + _$failedField, + e.toString(), + ); } rethrow; } diff --git a/pkgs/code_builder/lib/src/specs/extension_type.dart b/pkgs/code_builder/lib/src/specs/extension_type.dart index ec3bd3355..2d5196a87 100644 --- a/pkgs/code_builder/lib/src/specs/extension_type.dart +++ b/pkgs/code_builder/lib/src/specs/extension_type.dart @@ -57,10 +57,7 @@ abstract class ExtensionType extends Object BuiltList get methods; @override - R accept( - SpecVisitor visitor, [ - R? context, - ]) => + R accept(SpecVisitor visitor, [R? context]) => visitor.visitExtensionType(this, context); } @@ -109,9 +106,9 @@ abstract class RepresentationDeclaration extends Object with HasAnnotations, HasDartDocs implements Built { - factory RepresentationDeclaration( - [void Function(RepresentationDeclarationBuilder)? updates]) = - _$RepresentationDeclaration; + factory RepresentationDeclaration([ + void Function(RepresentationDeclarationBuilder)? updates, + ]) = _$RepresentationDeclaration; RepresentationDeclaration._(); diff --git a/pkgs/code_builder/lib/src/specs/extension_type.g.dart b/pkgs/code_builder/lib/src/specs/extension_type.g.dart index 576984032..17443ce54 100644 --- a/pkgs/code_builder/lib/src/specs/extension_type.g.dart +++ b/pkgs/code_builder/lib/src/specs/extension_type.g.dart @@ -33,34 +33,52 @@ class _$ExtensionType extends ExtensionType { factory _$ExtensionType([void Function(ExtensionTypeBuilder)? updates]) => (new ExtensionTypeBuilder()..update(updates)).build() as _$ExtensionType; - _$ExtensionType._( - {required this.annotations, - required this.docs, - required this.constant, - required this.name, - required this.types, - required this.primaryConstructorName, - required this.representationDeclaration, - required this.implements, - required this.constructors, - required this.fields, - required this.methods}) - : super._() { + _$ExtensionType._({ + required this.annotations, + required this.docs, + required this.constant, + required this.name, + required this.types, + required this.primaryConstructorName, + required this.representationDeclaration, + required this.implements, + required this.constructors, + required this.fields, + required this.methods, + }) : super._() { BuiltValueNullFieldError.checkNotNull( - annotations, r'ExtensionType', 'annotations'); + annotations, + r'ExtensionType', + 'annotations', + ); BuiltValueNullFieldError.checkNotNull(docs, r'ExtensionType', 'docs'); BuiltValueNullFieldError.checkNotNull( - constant, r'ExtensionType', 'constant'); + constant, + r'ExtensionType', + 'constant', + ); BuiltValueNullFieldError.checkNotNull(name, r'ExtensionType', 'name'); BuiltValueNullFieldError.checkNotNull(types, r'ExtensionType', 'types'); BuiltValueNullFieldError.checkNotNull( - primaryConstructorName, r'ExtensionType', 'primaryConstructorName'); - BuiltValueNullFieldError.checkNotNull(representationDeclaration, - r'ExtensionType', 'representationDeclaration'); + primaryConstructorName, + r'ExtensionType', + 'primaryConstructorName', + ); BuiltValueNullFieldError.checkNotNull( - implements, r'ExtensionType', 'implements'); + representationDeclaration, + r'ExtensionType', + 'representationDeclaration', + ); BuiltValueNullFieldError.checkNotNull( - constructors, r'ExtensionType', 'constructors'); + implements, + r'ExtensionType', + 'implements', + ); + BuiltValueNullFieldError.checkNotNull( + constructors, + r'ExtensionType', + 'constructors', + ); BuiltValueNullFieldError.checkNotNull(fields, r'ExtensionType', 'fields'); BuiltValueNullFieldError.checkNotNull(methods, r'ExtensionType', 'methods'); } @@ -209,7 +227,8 @@ class _$ExtensionTypeBuilder extends ExtensionTypeBuilder { @override set representationDeclaration( - RepresentationDeclaration? representationDeclaration) { + RepresentationDeclaration? representationDeclaration, + ) { _$this; super.representationDeclaration = representationDeclaration; } @@ -300,27 +319,37 @@ class _$ExtensionTypeBuilder extends ExtensionTypeBuilder { _$ExtensionType _build() { _$ExtensionType _$result; try { - _$result = _$v ?? + _$result = + _$v ?? new _$ExtensionType._( - annotations: annotations.build(), - docs: docs.build(), - constant: BuiltValueNullFieldError.checkNotNull( - constant, r'ExtensionType', 'constant'), - name: BuiltValueNullFieldError.checkNotNull( - name, r'ExtensionType', 'name'), - types: types.build(), - primaryConstructorName: BuiltValueNullFieldError.checkNotNull( - primaryConstructorName, - r'ExtensionType', - 'primaryConstructorName'), - representationDeclaration: BuiltValueNullFieldError.checkNotNull( - representationDeclaration, - r'ExtensionType', - 'representationDeclaration'), - implements: implements.build(), - constructors: constructors.build(), - fields: fields.build(), - methods: methods.build()); + annotations: annotations.build(), + docs: docs.build(), + constant: BuiltValueNullFieldError.checkNotNull( + constant, + r'ExtensionType', + 'constant', + ), + name: BuiltValueNullFieldError.checkNotNull( + name, + r'ExtensionType', + 'name', + ), + types: types.build(), + primaryConstructorName: BuiltValueNullFieldError.checkNotNull( + primaryConstructorName, + r'ExtensionType', + 'primaryConstructorName', + ), + representationDeclaration: BuiltValueNullFieldError.checkNotNull( + representationDeclaration, + r'ExtensionType', + 'representationDeclaration', + ), + implements: implements.build(), + constructors: constructors.build(), + fields: fields.build(), + methods: methods.build(), + ); } catch (_) { late String _$failedField; try { @@ -342,7 +371,10 @@ class _$ExtensionTypeBuilder extends ExtensionTypeBuilder { methods.build(); } catch (e) { throw new BuiltValueNestedFieldError( - r'ExtensionType', _$failedField, e.toString()); + r'ExtensionType', + _$failedField, + e.toString(), + ); } rethrow; } @@ -361,31 +393,44 @@ class _$RepresentationDeclaration extends RepresentationDeclaration { @override final String name; - factory _$RepresentationDeclaration( - [void Function(RepresentationDeclarationBuilder)? updates]) => + factory _$RepresentationDeclaration([ + void Function(RepresentationDeclarationBuilder)? updates, + ]) => (new RepresentationDeclarationBuilder()..update(updates)).build() as _$RepresentationDeclaration; - _$RepresentationDeclaration._( - {required this.annotations, - required this.docs, - required this.declaredRepresentationType, - required this.name}) - : super._() { + _$RepresentationDeclaration._({ + required this.annotations, + required this.docs, + required this.declaredRepresentationType, + required this.name, + }) : super._() { + BuiltValueNullFieldError.checkNotNull( + annotations, + r'RepresentationDeclaration', + 'annotations', + ); BuiltValueNullFieldError.checkNotNull( - annotations, r'RepresentationDeclaration', 'annotations'); + docs, + r'RepresentationDeclaration', + 'docs', + ); BuiltValueNullFieldError.checkNotNull( - docs, r'RepresentationDeclaration', 'docs'); - BuiltValueNullFieldError.checkNotNull(declaredRepresentationType, - r'RepresentationDeclaration', 'declaredRepresentationType'); + declaredRepresentationType, + r'RepresentationDeclaration', + 'declaredRepresentationType', + ); BuiltValueNullFieldError.checkNotNull( - name, r'RepresentationDeclaration', 'name'); + name, + r'RepresentationDeclaration', + 'name', + ); } @override RepresentationDeclaration rebuild( - void Function(RepresentationDeclarationBuilder) updates) => - (toBuilder()..update(updates)).build(); + void Function(RepresentationDeclarationBuilder) updates, + ) => (toBuilder()..update(updates)).build(); @override _$RepresentationDeclarationBuilder toBuilder() => @@ -506,16 +551,22 @@ class _$RepresentationDeclarationBuilder _$RepresentationDeclaration _build() { _$RepresentationDeclaration _$result; try { - _$result = _$v ?? + _$result = + _$v ?? new _$RepresentationDeclaration._( - annotations: annotations.build(), - docs: docs.build(), - declaredRepresentationType: BuiltValueNullFieldError.checkNotNull( - declaredRepresentationType, - r'RepresentationDeclaration', - 'declaredRepresentationType'), - name: BuiltValueNullFieldError.checkNotNull( - name, r'RepresentationDeclaration', 'name')); + annotations: annotations.build(), + docs: docs.build(), + declaredRepresentationType: BuiltValueNullFieldError.checkNotNull( + declaredRepresentationType, + r'RepresentationDeclaration', + 'declaredRepresentationType', + ), + name: BuiltValueNullFieldError.checkNotNull( + name, + r'RepresentationDeclaration', + 'name', + ), + ); } catch (_) { late String _$failedField; try { @@ -525,7 +576,10 @@ class _$RepresentationDeclarationBuilder docs.build(); } catch (e) { throw new BuiltValueNestedFieldError( - r'RepresentationDeclaration', _$failedField, e.toString()); + r'RepresentationDeclaration', + _$failedField, + e.toString(), + ); } rethrow; } diff --git a/pkgs/code_builder/lib/src/specs/field.dart b/pkgs/code_builder/lib/src/specs/field.dart index 7930bf6fa..bb77dbd15 100644 --- a/pkgs/code_builder/lib/src/specs/field.dart +++ b/pkgs/code_builder/lib/src/specs/field.dart @@ -52,18 +52,11 @@ abstract class Field extends Object FieldModifier get modifier; @override - R accept( - SpecVisitor visitor, [ - R? context, - ]) => + R accept(SpecVisitor visitor, [R? context]) => visitor.visitField(this, context); } -enum FieldModifier { - var$, - final$, - constant, -} +enum FieldModifier { var$, final$, constant } abstract class FieldBuilder extends Object with HasAnnotationsBuilder, HasDartDocsBuilder diff --git a/pkgs/code_builder/lib/src/specs/field.g.dart b/pkgs/code_builder/lib/src/specs/field.g.dart index d15f1c7dc..15913b891 100644 --- a/pkgs/code_builder/lib/src/specs/field.g.dart +++ b/pkgs/code_builder/lib/src/specs/field.g.dart @@ -29,17 +29,17 @@ class _$Field extends Field { factory _$Field([void Function(FieldBuilder)? updates]) => (new FieldBuilder()..update(updates)).build() as _$Field; - _$Field._( - {required this.annotations, - required this.docs, - this.assignment, - required this.static, - required this.late, - required this.external, - required this.name, - this.type, - required this.modifier}) - : super._() { + _$Field._({ + required this.annotations, + required this.docs, + this.assignment, + required this.static, + required this.late, + required this.external, + required this.name, + this.type, + required this.modifier, + }) : super._() { BuiltValueNullFieldError.checkNotNull(annotations, r'Field', 'annotations'); BuiltValueNullFieldError.checkNotNull(docs, r'Field', 'docs'); BuiltValueNullFieldError.checkNotNull(static, r'Field', 'static'); @@ -250,22 +250,31 @@ class _$FieldBuilder extends FieldBuilder { _$Field _build() { _$Field _$result; try { - _$result = _$v ?? + _$result = + _$v ?? new _$Field._( - annotations: annotations.build(), - docs: docs.build(), - assignment: assignment, - static: BuiltValueNullFieldError.checkNotNull( - static, r'Field', 'static'), - late: - BuiltValueNullFieldError.checkNotNull(late, r'Field', 'late'), - external: BuiltValueNullFieldError.checkNotNull( - external, r'Field', 'external'), - name: - BuiltValueNullFieldError.checkNotNull(name, r'Field', 'name'), - type: type, - modifier: BuiltValueNullFieldError.checkNotNull( - modifier, r'Field', 'modifier')); + annotations: annotations.build(), + docs: docs.build(), + assignment: assignment, + static: BuiltValueNullFieldError.checkNotNull( + static, + r'Field', + 'static', + ), + late: BuiltValueNullFieldError.checkNotNull(late, r'Field', 'late'), + external: BuiltValueNullFieldError.checkNotNull( + external, + r'Field', + 'external', + ), + name: BuiltValueNullFieldError.checkNotNull(name, r'Field', 'name'), + type: type, + modifier: BuiltValueNullFieldError.checkNotNull( + modifier, + r'Field', + 'modifier', + ), + ); } catch (_) { late String _$failedField; try { @@ -275,7 +284,10 @@ class _$FieldBuilder extends FieldBuilder { docs.build(); } catch (e) { throw new BuiltValueNestedFieldError( - r'Field', _$failedField, e.toString()); + r'Field', + _$failedField, + e.toString(), + ); } rethrow; } diff --git a/pkgs/code_builder/lib/src/specs/library.dart b/pkgs/code_builder/lib/src/specs/library.dart index bbfbf3f86..619c4a819 100644 --- a/pkgs/code_builder/lib/src/specs/library.dart +++ b/pkgs/code_builder/lib/src/specs/library.dart @@ -49,10 +49,7 @@ abstract class Library String? get name; @override - R accept( - SpecVisitor visitor, [ - R? context, - ]) => + R accept(SpecVisitor visitor, [R? context]) => visitor.visitLibrary(this, context); } diff --git a/pkgs/code_builder/lib/src/specs/library.g.dart b/pkgs/code_builder/lib/src/specs/library.g.dart index 63cfc200b..36e946302 100644 --- a/pkgs/code_builder/lib/src/specs/library.g.dart +++ b/pkgs/code_builder/lib/src/specs/library.g.dart @@ -27,24 +27,30 @@ class _$Library extends Library { factory _$Library([void Function(LibraryBuilder)? updates]) => (new LibraryBuilder()..update(updates)).build() as _$Library; - _$Library._( - {required this.annotations, - required this.docs, - required this.directives, - required this.body, - required this.comments, - this.generatedByComment, - required this.ignoreForFile, - this.name}) - : super._() { + _$Library._({ + required this.annotations, + required this.docs, + required this.directives, + required this.body, + required this.comments, + this.generatedByComment, + required this.ignoreForFile, + this.name, + }) : super._() { BuiltValueNullFieldError.checkNotNull( - annotations, r'Library', 'annotations'); + annotations, + r'Library', + 'annotations', + ); BuiltValueNullFieldError.checkNotNull(docs, r'Library', 'docs'); BuiltValueNullFieldError.checkNotNull(directives, r'Library', 'directives'); BuiltValueNullFieldError.checkNotNull(body, r'Library', 'body'); BuiltValueNullFieldError.checkNotNull(comments, r'Library', 'comments'); BuiltValueNullFieldError.checkNotNull( - ignoreForFile, r'Library', 'ignoreForFile'); + ignoreForFile, + r'Library', + 'ignoreForFile', + ); } @override @@ -232,16 +238,18 @@ class _$LibraryBuilder extends LibraryBuilder { _$Library _build() { _$Library _$result; try { - _$result = _$v ?? + _$result = + _$v ?? new _$Library._( - annotations: annotations.build(), - docs: docs.build(), - directives: directives.build(), - body: body.build(), - comments: comments.build(), - generatedByComment: generatedByComment, - ignoreForFile: ignoreForFile.build(), - name: name); + annotations: annotations.build(), + docs: docs.build(), + directives: directives.build(), + body: body.build(), + comments: comments.build(), + generatedByComment: generatedByComment, + ignoreForFile: ignoreForFile.build(), + name: name, + ); } catch (_) { late String _$failedField; try { @@ -260,7 +268,10 @@ class _$LibraryBuilder extends LibraryBuilder { ignoreForFile.build(); } catch (e) { throw new BuiltValueNestedFieldError( - r'Library', _$failedField, e.toString()); + r'Library', + _$failedField, + e.toString(), + ); } rethrow; } diff --git a/pkgs/code_builder/lib/src/specs/method.dart b/pkgs/code_builder/lib/src/specs/method.dart index 2c0a645dc..08d6331d6 100644 --- a/pkgs/code_builder/lib/src/specs/method.dart +++ b/pkgs/code_builder/lib/src/specs/method.dart @@ -80,10 +80,7 @@ abstract class Method extends Object Reference? get returns; @override - R accept( - SpecVisitor visitor, [ - R? context, - ]) => + R accept(SpecVisitor visitor, [R? context]) => visitor.visitMethod(this, context); /// This method as a closure. @@ -148,16 +145,9 @@ abstract class MethodBuilder extends Object Reference? returns; } -enum MethodType { - getter, - setter, -} +enum MethodType { getter, setter } -enum MethodModifier { - async, - asyncStar, - syncStar, -} +enum MethodModifier { async, asyncStar, syncStar } abstract class Parameter extends Object with HasAnnotations, HasGenerics, HasDartDocs diff --git a/pkgs/code_builder/lib/src/specs/method.g.dart b/pkgs/code_builder/lib/src/specs/method.g.dart index 214f6b214..ecdf9023a 100644 --- a/pkgs/code_builder/lib/src/specs/method.g.dart +++ b/pkgs/code_builder/lib/src/specs/method.g.dart @@ -37,29 +37,38 @@ class _$Method extends Method { factory _$Method([void Function(MethodBuilder)? updates]) => (new MethodBuilder()..update(updates)).build() as _$Method; - _$Method._( - {required this.annotations, - required this.docs, - required this.types, - required this.optionalParameters, - required this.requiredParameters, - this.body, - required this.external, - this.lambda, - required this.static, - this.name, - this.type, - this.modifier, - this.returns}) - : super._() { + _$Method._({ + required this.annotations, + required this.docs, + required this.types, + required this.optionalParameters, + required this.requiredParameters, + this.body, + required this.external, + this.lambda, + required this.static, + this.name, + this.type, + this.modifier, + this.returns, + }) : super._() { BuiltValueNullFieldError.checkNotNull( - annotations, r'Method', 'annotations'); + annotations, + r'Method', + 'annotations', + ); BuiltValueNullFieldError.checkNotNull(docs, r'Method', 'docs'); BuiltValueNullFieldError.checkNotNull(types, r'Method', 'types'); BuiltValueNullFieldError.checkNotNull( - optionalParameters, r'Method', 'optionalParameters'); + optionalParameters, + r'Method', + 'optionalParameters', + ); BuiltValueNullFieldError.checkNotNull( - requiredParameters, r'Method', 'requiredParameters'); + requiredParameters, + r'Method', + 'requiredParameters', + ); BuiltValueNullFieldError.checkNotNull(external, r'Method', 'external'); BuiltValueNullFieldError.checkNotNull(static, r'Method', 'static'); } @@ -329,23 +338,31 @@ class _$MethodBuilder extends MethodBuilder { _$Method _build() { _$Method _$result; try { - _$result = _$v ?? + _$result = + _$v ?? new _$Method._( - annotations: annotations.build(), - docs: docs.build(), - types: types.build(), - optionalParameters: optionalParameters.build(), - requiredParameters: requiredParameters.build(), - body: body, - external: BuiltValueNullFieldError.checkNotNull( - external, r'Method', 'external'), - lambda: lambda, - static: BuiltValueNullFieldError.checkNotNull( - static, r'Method', 'static'), - name: name, - type: type, - modifier: modifier, - returns: returns); + annotations: annotations.build(), + docs: docs.build(), + types: types.build(), + optionalParameters: optionalParameters.build(), + requiredParameters: requiredParameters.build(), + body: body, + external: BuiltValueNullFieldError.checkNotNull( + external, + r'Method', + 'external', + ), + lambda: lambda, + static: BuiltValueNullFieldError.checkNotNull( + static, + r'Method', + 'static', + ), + name: name, + type: type, + modifier: modifier, + returns: returns, + ); } catch (_) { late String _$failedField; try { @@ -361,7 +378,10 @@ class _$MethodBuilder extends MethodBuilder { requiredParameters.build(); } catch (e) { throw new BuiltValueNestedFieldError( - r'Method', _$failedField, e.toString()); + r'Method', + _$failedField, + e.toString(), + ); } rethrow; } @@ -397,25 +417,28 @@ class _$Parameter extends Parameter { factory _$Parameter([void Function(ParameterBuilder)? updates]) => (new ParameterBuilder()..update(updates)).build() as _$Parameter; - _$Parameter._( - {this.defaultTo, - required this.name, - required this.named, - required this.toThis, - required this.toSuper, - required this.annotations, - required this.docs, - required this.types, - this.type, - required this.required, - required this.covariant}) - : super._() { + _$Parameter._({ + this.defaultTo, + required this.name, + required this.named, + required this.toThis, + required this.toSuper, + required this.annotations, + required this.docs, + required this.types, + this.type, + required this.required, + required this.covariant, + }) : super._() { BuiltValueNullFieldError.checkNotNull(name, r'Parameter', 'name'); BuiltValueNullFieldError.checkNotNull(named, r'Parameter', 'named'); BuiltValueNullFieldError.checkNotNull(toThis, r'Parameter', 'toThis'); BuiltValueNullFieldError.checkNotNull(toSuper, r'Parameter', 'toSuper'); BuiltValueNullFieldError.checkNotNull( - annotations, r'Parameter', 'annotations'); + annotations, + r'Parameter', + 'annotations', + ); BuiltValueNullFieldError.checkNotNull(docs, r'Parameter', 'docs'); BuiltValueNullFieldError.checkNotNull(types, r'Parameter', 'types'); BuiltValueNullFieldError.checkNotNull(required, r'Parameter', 'required'); @@ -655,25 +678,45 @@ class _$ParameterBuilder extends ParameterBuilder { _$Parameter _build() { _$Parameter _$result; try { - _$result = _$v ?? + _$result = + _$v ?? new _$Parameter._( - defaultTo: defaultTo, - name: BuiltValueNullFieldError.checkNotNull( - name, r'Parameter', 'name'), - named: BuiltValueNullFieldError.checkNotNull( - named, r'Parameter', 'named'), - toThis: BuiltValueNullFieldError.checkNotNull( - toThis, r'Parameter', 'toThis'), - toSuper: BuiltValueNullFieldError.checkNotNull( - toSuper, r'Parameter', 'toSuper'), - annotations: annotations.build(), - docs: docs.build(), - types: types.build(), - type: type, - required: BuiltValueNullFieldError.checkNotNull( - required, r'Parameter', 'required'), - covariant: BuiltValueNullFieldError.checkNotNull( - covariant, r'Parameter', 'covariant')); + defaultTo: defaultTo, + name: BuiltValueNullFieldError.checkNotNull( + name, + r'Parameter', + 'name', + ), + named: BuiltValueNullFieldError.checkNotNull( + named, + r'Parameter', + 'named', + ), + toThis: BuiltValueNullFieldError.checkNotNull( + toThis, + r'Parameter', + 'toThis', + ), + toSuper: BuiltValueNullFieldError.checkNotNull( + toSuper, + r'Parameter', + 'toSuper', + ), + annotations: annotations.build(), + docs: docs.build(), + types: types.build(), + type: type, + required: BuiltValueNullFieldError.checkNotNull( + required, + r'Parameter', + 'required', + ), + covariant: BuiltValueNullFieldError.checkNotNull( + covariant, + r'Parameter', + 'covariant', + ), + ); } catch (_) { late String _$failedField; try { @@ -685,7 +728,10 @@ class _$ParameterBuilder extends ParameterBuilder { types.build(); } catch (e) { throw new BuiltValueNestedFieldError( - r'Parameter', _$failedField, e.toString()); + r'Parameter', + _$failedField, + e.toString(), + ); } rethrow; } diff --git a/pkgs/code_builder/lib/src/specs/mixin.dart b/pkgs/code_builder/lib/src/specs/mixin.dart index e666bddaa..e60735788 100644 --- a/pkgs/code_builder/lib/src/specs/mixin.dart +++ b/pkgs/code_builder/lib/src/specs/mixin.dart @@ -49,10 +49,7 @@ abstract class Mixin extends Object String get name; @override - R accept( - SpecVisitor visitor, [ - R? context, - ]) => + R accept(SpecVisitor visitor, [R? context]) => visitor.visitMixin(this, context); } diff --git a/pkgs/code_builder/lib/src/specs/mixin.g.dart b/pkgs/code_builder/lib/src/specs/mixin.g.dart index 28c7356c4..e84a379fc 100644 --- a/pkgs/code_builder/lib/src/specs/mixin.g.dart +++ b/pkgs/code_builder/lib/src/specs/mixin.g.dart @@ -29,17 +29,17 @@ class _$Mixin extends Mixin { factory _$Mixin([void Function(MixinBuilder)? updates]) => (new MixinBuilder()..update(updates)).build() as _$Mixin; - _$Mixin._( - {required this.base, - required this.annotations, - required this.docs, - this.on, - required this.implements, - required this.types, - required this.methods, - required this.fields, - required this.name}) - : super._() { + _$Mixin._({ + required this.base, + required this.annotations, + required this.docs, + this.on, + required this.implements, + required this.types, + required this.methods, + required this.fields, + required this.name, + }) : super._() { BuiltValueNullFieldError.checkNotNull(base, r'Mixin', 'base'); BuiltValueNullFieldError.checkNotNull(annotations, r'Mixin', 'annotations'); BuiltValueNullFieldError.checkNotNull(docs, r'Mixin', 'docs'); @@ -251,19 +251,19 @@ class _$MixinBuilder extends MixinBuilder { _$Mixin _build() { _$Mixin _$result; try { - _$result = _$v ?? + _$result = + _$v ?? new _$Mixin._( - base: - BuiltValueNullFieldError.checkNotNull(base, r'Mixin', 'base'), - annotations: annotations.build(), - docs: docs.build(), - on: on, - implements: implements.build(), - types: types.build(), - methods: methods.build(), - fields: fields.build(), - name: BuiltValueNullFieldError.checkNotNull( - name, r'Mixin', 'name')); + base: BuiltValueNullFieldError.checkNotNull(base, r'Mixin', 'base'), + annotations: annotations.build(), + docs: docs.build(), + on: on, + implements: implements.build(), + types: types.build(), + methods: methods.build(), + fields: fields.build(), + name: BuiltValueNullFieldError.checkNotNull(name, r'Mixin', 'name'), + ); } catch (_) { late String _$failedField; try { @@ -282,7 +282,10 @@ class _$MixinBuilder extends MixinBuilder { fields.build(); } catch (e) { throw new BuiltValueNestedFieldError( - r'Mixin', _$failedField, e.toString()); + r'Mixin', + _$failedField, + e.toString(), + ); } rethrow; } diff --git a/pkgs/code_builder/lib/src/specs/reference.dart b/pkgs/code_builder/lib/src/specs/reference.dart index 06032ad3c..40a3db711 100644 --- a/pkgs/code_builder/lib/src/specs/reference.dart +++ b/pkgs/code_builder/lib/src/specs/reference.dart @@ -35,10 +35,7 @@ class Reference extends Expression implements Spec { const Reference(this.symbol, [this.url]); @override - R accept( - SpecVisitor visitor, [ - R? context, - ]) => + R accept(SpecVisitor visitor, [R? context]) => visitor.visitReference(this, context); @override @@ -53,13 +50,12 @@ class Reference extends Expression implements Spec { Iterable positionalArguments, [ Map namedArguments = const {}, List typeArguments = const [], - ]) => - InvokeExpression.newOf( - this, - positionalArguments.toList(), - namedArguments, - typeArguments, - ); + ]) => InvokeExpression.newOf( + this, + positionalArguments.toList(), + namedArguments, + typeArguments, + ); /// Returns a new instance of this expression with a named constructor. Expression newInstanceNamed( @@ -67,27 +63,25 @@ class Reference extends Expression implements Spec { Iterable positionalArguments, [ Map namedArguments = const {}, List typeArguments = const [], - ]) => - InvokeExpression.newOf( - this, - positionalArguments.toList(), - namedArguments, - typeArguments, - name, - ); + ]) => InvokeExpression.newOf( + this, + positionalArguments.toList(), + namedArguments, + typeArguments, + name, + ); /// Returns a const instance of this expression. Expression constInstance( Iterable positionalArguments, [ Map namedArguments = const {}, List typeArguments = const [], - ]) => - InvokeExpression.constOf( - this, - positionalArguments.toList(), - namedArguments, - typeArguments, - ); + ]) => InvokeExpression.constOf( + this, + positionalArguments.toList(), + namedArguments, + typeArguments, + ); /// Returns a const instance of this expression with a named constructor. Expression constInstanceNamed( @@ -95,26 +89,29 @@ class Reference extends Expression implements Spec { Iterable positionalArguments, [ Map namedArguments = const {}, List typeArguments = const [], - ]) => - InvokeExpression.constOf( - this, - positionalArguments.toList(), - namedArguments, - typeArguments, - name, - ); + ]) => InvokeExpression.constOf( + this, + positionalArguments.toList(), + namedArguments, + typeArguments, + name, + ); @override Expression get expression => CodeExpression(Code.scope((a) => a(this))); @override - String toString() => (newBuiltValueToStringHelper('Reference') - ..add('url', url) - ..add('symbol', symbol)) - .toString(); + String toString() => + (newBuiltValueToStringHelper('Reference') + ..add('url', url) + ..add('symbol', symbol)) + .toString(); /// Returns as a [TypeReference], which allows adding generic type parameters. - Reference get type => TypeReference((b) => b - ..url = url - ..symbol = symbol); + Reference get type => TypeReference( + (b) => + b + ..url = url + ..symbol = symbol, + ); } diff --git a/pkgs/code_builder/lib/src/specs/type_function.dart b/pkgs/code_builder/lib/src/specs/type_function.dart index be7c3eacc..57287f4a1 100644 --- a/pkgs/code_builder/lib/src/specs/type_function.dart +++ b/pkgs/code_builder/lib/src/specs/type_function.dart @@ -19,17 +19,13 @@ part 'type_function.g.dart'; abstract class FunctionType extends Expression with HasGenerics implements Built, Reference, Spec { - factory FunctionType([ - void Function(FunctionTypeBuilder) updates, - ]) = _$FunctionType; + factory FunctionType([void Function(FunctionTypeBuilder) updates]) = + _$FunctionType; FunctionType._(); @override - R accept( - SpecVisitor visitor, [ - R? context, - ]) => + R accept(SpecVisitor visitor, [R? context]) => visitor.visitFunctionType(this, context); /// Return type. @@ -70,8 +66,7 @@ abstract class FunctionType extends Expression Iterable positionalArguments, [ Map namedArguments = const {}, List typeArguments = const [], - ]) => - throw UnsupportedError('Cannot instantiate a function type.'); + ]) => throw UnsupportedError('Cannot instantiate a function type.'); @override Expression newInstanceNamed( @@ -79,16 +74,14 @@ abstract class FunctionType extends Expression Iterable positionalArguments, [ Map namedArguments = const {}, List typeArguments = const [], - ]) => - throw UnsupportedError('Cannot instantiate a function type.'); + ]) => throw UnsupportedError('Cannot instantiate a function type.'); @override Expression constInstance( Iterable positionalArguments, [ Map namedArguments = const {}, List typeArguments = const [], - ]) => - throw UnsupportedError('Cannot "const" a function type.'); + ]) => throw UnsupportedError('Cannot "const" a function type.'); @override Expression constInstanceNamed( @@ -96,8 +89,7 @@ abstract class FunctionType extends Expression Iterable positionalArguments, [ Map namedArguments = const {}, List typeArguments = const [], - ]) => - throw UnsupportedError('Cannot "const" a function type.'); + ]) => throw UnsupportedError('Cannot "const" a function type.'); /// A typedef assignment to this type. Code toTypeDef(String name) => createTypeDef(name, this); diff --git a/pkgs/code_builder/lib/src/specs/type_function.g.dart b/pkgs/code_builder/lib/src/specs/type_function.g.dart index d09f59b03..d817c407f 100644 --- a/pkgs/code_builder/lib/src/specs/type_function.g.dart +++ b/pkgs/code_builder/lib/src/specs/type_function.g.dart @@ -25,24 +25,36 @@ class _$FunctionType extends FunctionType { factory _$FunctionType([void Function(FunctionTypeBuilder)? updates]) => (new FunctionTypeBuilder()..update(updates)).build() as _$FunctionType; - _$FunctionType._( - {this.returnType, - required this.types, - required this.requiredParameters, - required this.optionalParameters, - required this.namedParameters, - required this.namedRequiredParameters, - this.isNullable}) - : super._() { + _$FunctionType._({ + this.returnType, + required this.types, + required this.requiredParameters, + required this.optionalParameters, + required this.namedParameters, + required this.namedRequiredParameters, + this.isNullable, + }) : super._() { BuiltValueNullFieldError.checkNotNull(types, r'FunctionType', 'types'); BuiltValueNullFieldError.checkNotNull( - requiredParameters, r'FunctionType', 'requiredParameters'); + requiredParameters, + r'FunctionType', + 'requiredParameters', + ); BuiltValueNullFieldError.checkNotNull( - optionalParameters, r'FunctionType', 'optionalParameters'); + optionalParameters, + r'FunctionType', + 'optionalParameters', + ); BuiltValueNullFieldError.checkNotNull( - namedParameters, r'FunctionType', 'namedParameters'); + namedParameters, + r'FunctionType', + 'namedParameters', + ); BuiltValueNullFieldError.checkNotNull( - namedRequiredParameters, r'FunctionType', 'namedRequiredParameters'); + namedRequiredParameters, + r'FunctionType', + 'namedRequiredParameters', + ); } @override @@ -165,7 +177,8 @@ class _$FunctionTypeBuilder extends FunctionTypeBuilder { @override set namedRequiredParameters( - MapBuilder namedRequiredParameters) { + MapBuilder namedRequiredParameters, + ) { _$this; super.namedRequiredParameters = namedRequiredParameters; } @@ -216,15 +229,17 @@ class _$FunctionTypeBuilder extends FunctionTypeBuilder { _$FunctionType _build() { _$FunctionType _$result; try { - _$result = _$v ?? + _$result = + _$v ?? new _$FunctionType._( - returnType: returnType, - types: types.build(), - requiredParameters: requiredParameters.build(), - optionalParameters: optionalParameters.build(), - namedParameters: namedParameters.build(), - namedRequiredParameters: namedRequiredParameters.build(), - isNullable: isNullable); + returnType: returnType, + types: types.build(), + requiredParameters: requiredParameters.build(), + optionalParameters: optionalParameters.build(), + namedParameters: namedParameters.build(), + namedRequiredParameters: namedRequiredParameters.build(), + isNullable: isNullable, + ); } catch (_) { late String _$failedField; try { @@ -240,7 +255,10 @@ class _$FunctionTypeBuilder extends FunctionTypeBuilder { namedRequiredParameters.build(); } catch (e) { throw new BuiltValueNestedFieldError( - r'FunctionType', _$failedField, e.toString()); + r'FunctionType', + _$failedField, + e.toString(), + ); } rethrow; } diff --git a/pkgs/code_builder/lib/src/specs/type_record.dart b/pkgs/code_builder/lib/src/specs/type_record.dart index 2f0594c77..939f30e17 100644 --- a/pkgs/code_builder/lib/src/specs/type_record.dart +++ b/pkgs/code_builder/lib/src/specs/type_record.dart @@ -16,17 +16,12 @@ part 'type_record.g.dart'; @immutable abstract class RecordType extends Expression implements Built, Reference, Spec { - factory RecordType([ - void Function(RecordTypeBuilder) updates, - ]) = _$RecordType; + factory RecordType([void Function(RecordTypeBuilder) updates]) = _$RecordType; RecordType._(); @override - R accept( - SpecVisitor visitor, [ - R? context, - ]) => + R accept(SpecVisitor visitor, [R? context]) => visitor.visitRecordType(this, context); BuiltList get positionalFieldTypes; @@ -50,8 +45,7 @@ abstract class RecordType extends Expression Iterable positionalArguments, [ Map namedArguments = const {}, List typeArguments = const [], - ]) => - throw UnsupportedError('Cannot instantiate a record type.'); + ]) => throw UnsupportedError('Cannot instantiate a record type.'); @override Expression newInstanceNamed( @@ -59,16 +53,14 @@ abstract class RecordType extends Expression Iterable positionalArguments, [ Map namedArguments = const {}, List typeArguments = const [], - ]) => - throw UnsupportedError('Cannot instantiate a record type.'); + ]) => throw UnsupportedError('Cannot instantiate a record type.'); @override Expression constInstance( Iterable positionalArguments, [ Map namedArguments = const {}, List typeArguments = const [], - ]) => - throw UnsupportedError('Cannot "const" a record type.'); + ]) => throw UnsupportedError('Cannot "const" a record type.'); @override Expression constInstanceNamed( @@ -76,8 +68,7 @@ abstract class RecordType extends Expression Iterable positionalArguments, [ Map namedArguments = const {}, List typeArguments = const [], - ]) => - throw UnsupportedError('Cannot "const" a record type.'); + ]) => throw UnsupportedError('Cannot "const" a record type.'); } abstract class RecordTypeBuilder extends Object diff --git a/pkgs/code_builder/lib/src/specs/type_record.g.dart b/pkgs/code_builder/lib/src/specs/type_record.g.dart index b1d47dfbd..b36108691 100644 --- a/pkgs/code_builder/lib/src/specs/type_record.g.dart +++ b/pkgs/code_builder/lib/src/specs/type_record.g.dart @@ -17,15 +17,21 @@ class _$RecordType extends RecordType { factory _$RecordType([void Function(RecordTypeBuilder)? updates]) => (new RecordTypeBuilder()..update(updates)).build() as _$RecordType; - _$RecordType._( - {required this.positionalFieldTypes, - required this.namedFieldTypes, - this.isNullable}) - : super._() { + _$RecordType._({ + required this.positionalFieldTypes, + required this.namedFieldTypes, + this.isNullable, + }) : super._() { BuiltValueNullFieldError.checkNotNull( - positionalFieldTypes, r'RecordType', 'positionalFieldTypes'); + positionalFieldTypes, + r'RecordType', + 'positionalFieldTypes', + ); BuiltValueNullFieldError.checkNotNull( - namedFieldTypes, r'RecordType', 'namedFieldTypes'); + namedFieldTypes, + r'RecordType', + 'namedFieldTypes', + ); } @override @@ -133,11 +139,13 @@ class _$RecordTypeBuilder extends RecordTypeBuilder { _$RecordType _build() { _$RecordType _$result; try { - _$result = _$v ?? + _$result = + _$v ?? new _$RecordType._( - positionalFieldTypes: positionalFieldTypes.build(), - namedFieldTypes: namedFieldTypes.build(), - isNullable: isNullable); + positionalFieldTypes: positionalFieldTypes.build(), + namedFieldTypes: namedFieldTypes.build(), + isNullable: isNullable, + ); } catch (_) { late String _$failedField; try { @@ -147,7 +155,10 @@ class _$RecordTypeBuilder extends RecordTypeBuilder { namedFieldTypes.build(); } catch (e) { throw new BuiltValueNestedFieldError( - r'RecordType', _$failedField, e.toString()); + r'RecordType', + _$failedField, + e.toString(), + ); } rethrow; } diff --git a/pkgs/code_builder/lib/src/specs/type_reference.dart b/pkgs/code_builder/lib/src/specs/type_reference.dart index 74c7840a3..d192de588 100644 --- a/pkgs/code_builder/lib/src/specs/type_reference.dart +++ b/pkgs/code_builder/lib/src/specs/type_reference.dart @@ -19,9 +19,8 @@ part 'type_reference.g.dart'; abstract class TypeReference extends Expression with HasGenerics implements Built, Reference, Spec { - factory TypeReference([ - void Function(TypeReferenceBuilder) updates, - ]) = _$TypeReference; + factory TypeReference([void Function(TypeReferenceBuilder) updates]) = + _$TypeReference; TypeReference._(); @@ -44,10 +43,7 @@ abstract class TypeReference extends Expression bool? get isNullable; @override - R accept( - SpecVisitor visitor, [ - R? context, - ]) => + R accept(SpecVisitor visitor, [R? context]) => visitor.visitType(this, context); @override @@ -61,13 +57,12 @@ abstract class TypeReference extends Expression Iterable positionalArguments, [ Map namedArguments = const {}, List typeArguments = const [], - ]) => - InvokeExpression.newOf( - this, - positionalArguments.toList(), - namedArguments, - typeArguments, - ); + ]) => InvokeExpression.newOf( + this, + positionalArguments.toList(), + namedArguments, + typeArguments, + ); @override Expression newInstanceNamed( @@ -75,27 +70,25 @@ abstract class TypeReference extends Expression Iterable positionalArguments, [ Map namedArguments = const {}, List typeArguments = const [], - ]) => - InvokeExpression.newOf( - this, - positionalArguments.toList(), - namedArguments, - typeArguments, - name, - ); + ]) => InvokeExpression.newOf( + this, + positionalArguments.toList(), + namedArguments, + typeArguments, + name, + ); @override Expression constInstance( Iterable positionalArguments, [ Map namedArguments = const {}, List typeArguments = const [], - ]) => - InvokeExpression.constOf( - this, - positionalArguments.toList(), - namedArguments, - typeArguments, - ); + ]) => InvokeExpression.constOf( + this, + positionalArguments.toList(), + namedArguments, + typeArguments, + ); @override Expression constInstanceNamed( @@ -103,14 +96,13 @@ abstract class TypeReference extends Expression Iterable positionalArguments, [ Map namedArguments = const {}, List typeArguments = const [], - ]) => - InvokeExpression.constOf( - this, - positionalArguments.toList(), - namedArguments, - typeArguments, - name, - ); + ]) => InvokeExpression.constOf( + this, + positionalArguments.toList(), + namedArguments, + typeArguments, + name, + ); } abstract class TypeReferenceBuilder extends Object diff --git a/pkgs/code_builder/lib/src/specs/type_reference.g.dart b/pkgs/code_builder/lib/src/specs/type_reference.g.dart index 124e8b4f0..d6b1677c0 100644 --- a/pkgs/code_builder/lib/src/specs/type_reference.g.dart +++ b/pkgs/code_builder/lib/src/specs/type_reference.g.dart @@ -21,13 +21,13 @@ class _$TypeReference extends TypeReference { factory _$TypeReference([void Function(TypeReferenceBuilder)? updates]) => (new TypeReferenceBuilder()..update(updates)).build() as _$TypeReference; - _$TypeReference._( - {required this.symbol, - this.url, - this.bound, - required this.types, - this.isNullable}) - : super._() { + _$TypeReference._({ + required this.symbol, + this.url, + this.bound, + required this.types, + this.isNullable, + }) : super._() { BuiltValueNullFieldError.checkNotNull(symbol, r'TypeReference', 'symbol'); BuiltValueNullFieldError.checkNotNull(types, r'TypeReference', 'types'); } @@ -170,14 +170,19 @@ class _$TypeReferenceBuilder extends TypeReferenceBuilder { _$TypeReference _build() { _$TypeReference _$result; try { - _$result = _$v ?? + _$result = + _$v ?? new _$TypeReference._( - symbol: BuiltValueNullFieldError.checkNotNull( - symbol, r'TypeReference', 'symbol'), - url: url, - bound: bound, - types: types.build(), - isNullable: isNullable); + symbol: BuiltValueNullFieldError.checkNotNull( + symbol, + r'TypeReference', + 'symbol', + ), + url: url, + bound: bound, + types: types.build(), + isNullable: isNullable, + ); } catch (_) { late String _$failedField; try { @@ -185,7 +190,10 @@ class _$TypeReferenceBuilder extends TypeReferenceBuilder { types.build(); } catch (e) { throw new BuiltValueNestedFieldError( - r'TypeReference', _$failedField, e.toString()); + r'TypeReference', + _$failedField, + e.toString(), + ); } rethrow; } diff --git a/pkgs/code_builder/lib/src/specs/typedef.dart b/pkgs/code_builder/lib/src/specs/typedef.dart index fe5d8f12c..c31afa56c 100644 --- a/pkgs/code_builder/lib/src/specs/typedef.dart +++ b/pkgs/code_builder/lib/src/specs/typedef.dart @@ -33,10 +33,7 @@ abstract class TypeDef extends Object Expression get definition; @override - R accept( - SpecVisitor visitor, [ - R? context, - ]) => + R accept(SpecVisitor visitor, [R? context]) => visitor.visitTypeDef(this, context); } diff --git a/pkgs/code_builder/lib/src/specs/typedef.g.dart b/pkgs/code_builder/lib/src/specs/typedef.g.dart index 8c2a16c2d..e1d1f556e 100644 --- a/pkgs/code_builder/lib/src/specs/typedef.g.dart +++ b/pkgs/code_builder/lib/src/specs/typedef.g.dart @@ -21,17 +21,20 @@ class _$TypeDef extends TypeDef { factory _$TypeDef([void Function(TypeDefBuilder)? updates]) => (new TypeDefBuilder()..update(updates)).build() as _$TypeDef; - _$TypeDef._( - {required this.name, - required this.definition, - required this.annotations, - required this.docs, - required this.types}) - : super._() { + _$TypeDef._({ + required this.name, + required this.definition, + required this.annotations, + required this.docs, + required this.types, + }) : super._() { BuiltValueNullFieldError.checkNotNull(name, r'TypeDef', 'name'); BuiltValueNullFieldError.checkNotNull(definition, r'TypeDef', 'definition'); BuiltValueNullFieldError.checkNotNull( - annotations, r'TypeDef', 'annotations'); + annotations, + r'TypeDef', + 'annotations', + ); BuiltValueNullFieldError.checkNotNull(docs, r'TypeDef', 'docs'); BuiltValueNullFieldError.checkNotNull(types, r'TypeDef', 'types'); } @@ -173,15 +176,23 @@ class _$TypeDefBuilder extends TypeDefBuilder { _$TypeDef _build() { _$TypeDef _$result; try { - _$result = _$v ?? + _$result = + _$v ?? new _$TypeDef._( - name: BuiltValueNullFieldError.checkNotNull( - name, r'TypeDef', 'name'), - definition: BuiltValueNullFieldError.checkNotNull( - definition, r'TypeDef', 'definition'), - annotations: annotations.build(), - docs: docs.build(), - types: types.build()); + name: BuiltValueNullFieldError.checkNotNull( + name, + r'TypeDef', + 'name', + ), + definition: BuiltValueNullFieldError.checkNotNull( + definition, + r'TypeDef', + 'definition', + ), + annotations: annotations.build(), + docs: docs.build(), + types: types.build(), + ); } catch (_) { late String _$failedField; try { @@ -193,7 +204,10 @@ class _$TypeDefBuilder extends TypeDefBuilder { types.build(); } catch (e) { throw new BuiltValueNestedFieldError( - r'TypeDef', _$failedField, e.toString()); + r'TypeDef', + _$failedField, + e.toString(), + ); } rethrow; } diff --git a/pkgs/code_builder/pubspec.yaml b/pkgs/code_builder/pubspec.yaml index 6bf096579..ebf286e98 100644 --- a/pkgs/code_builder/pubspec.yaml +++ b/pkgs/code_builder/pubspec.yaml @@ -1,24 +1,24 @@ name: code_builder -version: 4.10.2-wip +version: 4.11.0 description: A fluent, builder-based library for generating valid Dart code. repository: https://github.com/dart-lang/tools/tree/main/pkgs/code_builder issue_tracker: https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Acode_builder environment: - sdk: ^3.6.0 + sdk: ^3.7.0 dependencies: - built_collection: ^5.0.0 - built_value: ^8.0.0 - collection: ^1.15.0 - matcher: ^0.12.10 - meta: ^1.3.0 + built_collection: ^5.1.1 + built_value: ^8.10.1 + collection: ^1.19.0 + matcher: ^0.12.16+1 + meta: ^1.16.0 dev_dependencies: - build: ^2.0.0 - build_runner: ^2.0.3 - built_value_generator: ^8.0.0 + build: ^4.0.0 + build_runner: ^2.7.2 + built_value_generator: ^8.11.2 dart_flutter_team_lints: ^3.0.0 - dart_style: ^3.0.1 - source_gen: ^2.0.0 - test: ^1.16.0 + dart_style: ^3.1.2 + source_gen: ^4.0.1 + test: ^1.26.3 diff --git a/pkgs/code_builder/test/allocator_test.dart b/pkgs/code_builder/test/allocator_test.dart index 09f135c56..71c93cbf2 100644 --- a/pkgs/code_builder/test/allocator_test.dart +++ b/pkgs/code_builder/test/allocator_test.dart @@ -19,10 +19,11 @@ void main() { }); test('should collect import URLs', () { - allocator = Allocator() - ..allocate(refer('List', 'dart:core')) - ..allocate(refer('LinkedHashMap', 'dart:collection')) - ..allocate(refer('someSymbol')); + allocator = + Allocator() + ..allocate(refer('List', 'dart:core')) + ..allocate(refer('LinkedHashMap', 'dart:collection')) + ..allocate(refer('someSymbol')); expect(allocator.imports.map((d) => d.url), [ 'dart:core', 'dart:collection', @@ -37,10 +38,7 @@ void main() { test('.simplePrefixing should add import prefixes', () { allocator = Allocator.simplePrefixing(); - expect( - allocator.allocate(refer('List', 'dart:core')), - 'List', - ); + expect(allocator.allocate(refer('List', 'dart:core')), 'List'); expect( allocator.allocate(refer('LinkedHashMap', 'dart:collection')), '_i1.LinkedHashMap', diff --git a/pkgs/code_builder/test/const_test.dart b/pkgs/code_builder/test/const_test.dart index c4d69c61b..e8d1bc3ac 100644 --- a/pkgs/code_builder/test/const_test.dart +++ b/pkgs/code_builder/test/const_test.dart @@ -16,47 +16,72 @@ void main() { }); test('expression', () { - expect(constMap, equalsDart(r''' - const {'list': [], 'duration': Duration(), }''')); + expect( + constMap, + equalsDart(r''' + const {'list': [], 'duration': Duration(), }'''), + ); }); test('assignConst', () { expect( // ignore: deprecated_member_use_from_same_package constMap.assignConst('constField'), - equalsDart(r''' + equalsDart( + r''' const constField = {'list': [], 'duration': Duration(), }''', - DartEmitter.scoped()), + DartEmitter.scoped(), + ), ); }); test('assign to declared constant', () { expect( declareConst('constField').assign(constMap), - equalsDart(r''' + equalsDart( + r''' const constField = {'list': [], 'duration': Duration(), }''', - DartEmitter.scoped()), + DartEmitter.scoped(), + ), ); }); test('assign to declared non-constant', () { expect( - declareVar('varField').assign(constMap), - equalsDart(r''' + declareVar('varField').assign(constMap), + equalsDart( + r''' var varField = const {'list': [], 'duration': Duration(), }''', - DartEmitter.scoped())); + DartEmitter.scoped(), + ), + ); }); - final library = Library((b) => b - ..body.add(Field((b) => b - ..name = 'val1' - ..modifier = FieldModifier.constant - ..assignment = refer('ConstClass').constInstance([]).code)) - ..body.add(Field((b) => b - ..name = 'val2' - ..modifier = FieldModifier.constant - ..assignment = - refer('ConstClass').constInstanceNamed('other', []).code))); + final library = Library( + (b) => + b + ..body.add( + Field( + (b) => + b + ..name = 'val1' + ..modifier = FieldModifier.constant + ..assignment = refer('ConstClass').constInstance([]).code, + ), + ) + ..body.add( + Field( + (b) => + b + ..name = 'val2' + ..modifier = FieldModifier.constant + ..assignment = + refer( + 'ConstClass', + ).constInstanceNamed('other', []).code, + ), + ), + ); test('should emit a source file with imports in defined order', () { expect( diff --git a/pkgs/code_builder/test/directive_test.dart b/pkgs/code_builder/test/directive_test.dart index 0aee6ae57..31bd2a494 100644 --- a/pkgs/code_builder/test/directive_test.dart +++ b/pkgs/code_builder/test/directive_test.dart @@ -12,27 +12,61 @@ void main() { final $LinkedHashMap = refer('LinkedHashMap', 'dart:collection'); - final library = Library((b) => b - ..directives.add(Directive.export('../relative.dart')) - ..directives.add(Directive.export('package:foo/foo.dart')) - ..directives.add(Directive.part('lib.g.dart')) - ..body.add(Field((b) => b - ..name = 'relativeRef' - ..modifier = FieldModifier.final$ - ..assignment = - refer('Relative', '../relative.dart').newInstance([]).code)) - ..body.add(Field((b) => b - ..name = 'pkgRefFoo' - ..modifier = FieldModifier.final$ - ..assignment = refer('Foo', 'package:foo/foo.dart').newInstance([]).code)) - ..body.add(Field((b) => b - ..name = 'pkgRefBar' - ..modifier = FieldModifier.final$ - ..assignment = refer('Bar', 'package:foo/bar.dart').newInstance([]).code)) - ..body.add(Field((b) => b - ..name = 'collectionRef' - ..modifier = FieldModifier.final$ - ..assignment = $LinkedHashMap.newInstance([]).code))); + final library = Library( + (b) => + b + ..directives.add(Directive.export('../relative.dart')) + ..directives.add(Directive.export('package:foo/foo.dart')) + ..directives.add(Directive.part('lib.g.dart')) + ..body.add( + Field( + (b) => + b + ..name = 'relativeRef' + ..modifier = FieldModifier.final$ + ..assignment = + refer( + 'Relative', + '../relative.dart', + ).newInstance([]).code, + ), + ) + ..body.add( + Field( + (b) => + b + ..name = 'pkgRefFoo' + ..modifier = FieldModifier.final$ + ..assignment = + refer( + 'Foo', + 'package:foo/foo.dart', + ).newInstance([]).code, + ), + ) + ..body.add( + Field( + (b) => + b + ..name = 'pkgRefBar' + ..modifier = FieldModifier.final$ + ..assignment = + refer( + 'Bar', + 'package:foo/bar.dart', + ).newInstance([]).code, + ), + ) + ..body.add( + Field( + (b) => + b + ..name = 'collectionRef' + ..modifier = FieldModifier.final$ + ..assignment = $LinkedHashMap.newInstance([]).code, + ), + ), + ); test('should emit a source file with imports in defined order', () { expect( @@ -57,7 +91,8 @@ void main() { test('should emit a source file with ordered', () { expect( library, - equalsDart(r''' + equalsDart( + r''' // ignore_for_file: no_leading_underscores_for_library_prefixes import 'dart:collection' as _i4; @@ -75,7 +110,8 @@ void main() { final pkgRefFoo = _i2.Foo(); final pkgRefBar = _i3.Bar(); final collectionRef = _i4.LinkedHashMap();''', - DartEmitter.scoped(orderDirectives: true)), + DartEmitter.scoped(orderDirectives: true), + ), ); }); } diff --git a/pkgs/code_builder/test/e2e/injection_test.dart b/pkgs/code_builder/test/e2e/injection_test.dart index 129f02cd1..fac43da72 100644 --- a/pkgs/code_builder/test/e2e/injection_test.dart +++ b/pkgs/code_builder/test/e2e/injection_test.dart @@ -16,25 +16,47 @@ void main() { final $Module = refer('Module', 'package:app/module.dart'); final $Thing = refer('Thing', 'package:app/thing.dart'); - final clazz = ClassBuilder() - ..name = 'Injector' - ..implements.add($App) - ..fields.add(Field((b) => b - ..modifier = FieldModifier.final$ - ..name = '_module' - ..type = $Module.type)) - ..constructors.add(Constructor((b) => b - ..requiredParameters.add(Parameter((b) => b - ..name = '_module' - ..toThis = true)))) - ..methods.add(Method((b) => b - ..name = 'getThing' - ..body = $Thing.newInstance([ - refer('_module').property('get1').call([]), - refer('_module').property('get2').call([]), - ]).code - ..returns = $Thing - ..annotations.add(refer('override')))); + final clazz = + ClassBuilder() + ..name = 'Injector' + ..implements.add($App) + ..fields.add( + Field( + (b) => + b + ..modifier = FieldModifier.final$ + ..name = '_module' + ..type = $Module.type, + ), + ) + ..constructors.add( + Constructor( + (b) => + b + ..requiredParameters.add( + Parameter( + (b) => + b + ..name = '_module' + ..toThis = true, + ), + ), + ), + ) + ..methods.add( + Method( + (b) => + b + ..name = 'getThing' + ..body = + $Thing.newInstance([ + refer('_module').property('get1').call([]), + refer('_module').property('get2').call([]), + ]).code + ..returns = $Thing + ..annotations.add(refer('override')), + ), + ); expect( clazz.build(), diff --git a/pkgs/code_builder/test/matcher_test.dart b/pkgs/code_builder/test/matcher_test.dart index c9475a847..bb4c9cf16 100644 --- a/pkgs/code_builder/test/matcher_test.dart +++ b/pkgs/code_builder/test/matcher_test.dart @@ -23,8 +23,14 @@ void main() { extension on Matcher { void expectMismatch(dynamic actual, String mismatch) { expect( - () => expect(actual, this), - throwsA(isA().having( - (e) => e.message, 'message', equalsIgnoringWhitespace(mismatch)))); + () => expect(actual, this), + throwsA( + isA().having( + (e) => e.message, + 'message', + equalsIgnoringWhitespace(mismatch), + ), + ), + ); } } diff --git a/pkgs/code_builder/test/specs/class_test.dart b/pkgs/code_builder/test/specs/class_test.dart index 562bca4c6..3bd990233 100644 --- a/pkgs/code_builder/test/specs/class_test.dart +++ b/pkgs/code_builder/test/specs/class_test.dart @@ -21,9 +21,12 @@ void main() { test('should create an abstract class', () { expect( - Class((b) => b - ..name = 'Foo' - ..abstract = true), + Class( + (b) => + b + ..name = 'Foo' + ..abstract = true, + ), equalsDart(r''' abstract class Foo {} '''), @@ -32,10 +35,13 @@ void main() { test('should create an abstract base class', () { expect( - Class((b) => b - ..name = 'Foo' - ..abstract = true - ..modifier = ClassModifier.base), + Class( + (b) => + b + ..name = 'Foo' + ..abstract = true + ..modifier = ClassModifier.base, + ), equalsDart(r''' abstract base class Foo {} '''), @@ -44,9 +50,12 @@ void main() { test('should create a final class', () { expect( - Class((b) => b - ..name = 'Foo' - ..modifier = ClassModifier.final$), + Class( + (b) => + b + ..name = 'Foo' + ..modifier = ClassModifier.final$, + ), equalsDart(r''' final class Foo {} '''), @@ -55,9 +64,12 @@ void main() { test('should create a sealed class', () { expect( - Class((b) => b - ..name = 'Foo' - ..sealed = true), + Class( + (b) => + b + ..name = 'Foo' + ..sealed = true, + ), equalsDart(r''' sealed class Foo {} '''), @@ -66,10 +78,13 @@ void main() { test('should create an abstract interface class', () { expect( - Class((b) => b - ..name = 'Foo' - ..abstract = true - ..modifier = ClassModifier.interface), + Class( + (b) => + b + ..name = 'Foo' + ..abstract = true + ..modifier = ClassModifier.interface, + ), equalsDart(r''' abstract interface class Foo {} '''), @@ -78,9 +93,12 @@ void main() { test('should create a mixin class', () { expect( - Class((b) => b - ..name = 'Foo' - ..mixin = true), + Class( + (b) => + b + ..name = 'Foo' + ..mixin = true, + ), equalsDart(r''' mixin class Foo {} '''), @@ -89,10 +107,13 @@ void main() { test('should create an abstract mixin class', () { expect( - Class((b) => b - ..name = 'Foo' - ..abstract = true - ..mixin = true), + Class( + (b) => + b + ..name = 'Foo' + ..abstract = true + ..mixin = true, + ), equalsDart(r''' abstract mixin class Foo {} '''), @@ -101,10 +122,13 @@ void main() { test('should create a base mixin class', () { expect( - Class((b) => b - ..name = 'Foo' - ..mixin = true - ..modifier = ClassModifier.base), + Class( + (b) => + b + ..name = 'Foo' + ..mixin = true + ..modifier = ClassModifier.base, + ), equalsDart(r''' base mixin class Foo {} '''), @@ -113,11 +137,14 @@ void main() { test('should create an abstract base mixin class', () { expect( - Class((b) => b - ..name = 'Foo' - ..abstract = true - ..mixin = true - ..modifier = ClassModifier.base), + Class( + (b) => + b + ..name = 'Foo' + ..abstract = true + ..mixin = true + ..modifier = ClassModifier.base, + ), equalsDart(r''' abstract base mixin class Foo {} '''), @@ -127,13 +154,10 @@ void main() { test('should create a class with documentations', () { expect( Class( - (b) => b - ..name = 'Foo' - ..docs.addAll( - const [ - '/// My favorite class.', - ], - ), + (b) => + b + ..name = 'Foo' + ..docs.addAll(const ['/// My favorite class.']), ), equalsDart(r''' /// My favorite class. @@ -145,12 +169,15 @@ void main() { test('should create a class with annotations', () { expect( Class( - (b) => b - ..name = 'Foo' - ..annotations.addAll([ - refer('deprecated'), - refer('Deprecated').call([literalString('This is an old class')]) - ]), + (b) => + b + ..name = 'Foo' + ..annotations.addAll([ + refer('deprecated'), + refer( + 'Deprecated', + ).call([literalString('This is an old class')]), + ]), ), equalsDart(r''' @deprecated @@ -162,9 +189,12 @@ void main() { test('should create a class with a generic type', () { expect( - Class((b) => b - ..name = 'List' - ..types.add(refer('T'))), + Class( + (b) => + b + ..name = 'List' + ..types.add(refer('T')), + ), equalsDart(r''' class List {} '''), @@ -174,12 +204,10 @@ void main() { test('should create a class with multiple generic types', () { expect( Class( - (b) => b - ..name = 'Map' - ..types.addAll([ - refer('K'), - refer('V'), - ]), + (b) => + b + ..name = 'Map' + ..types.addAll([refer('K'), refer('V')]), ), equalsDart(r''' class Map {} @@ -189,13 +217,24 @@ void main() { test('should create a class with a bound generic type', () { expect( - Class((b) => b - ..name = 'Comparable' - ..types.add(TypeReference((b) => b - ..symbol = 'T' - ..bound = TypeReference((b) => b - ..symbol = 'Comparable' - ..types.add(refer('T').type))))), + Class( + (b) => + b + ..name = 'Comparable' + ..types.add( + TypeReference( + (b) => + b + ..symbol = 'T' + ..bound = TypeReference( + (b) => + b + ..symbol = 'Comparable' + ..types.add(refer('T').type), + ), + ), + ), + ), equalsDart(r''' class Comparable> {} '''), @@ -204,9 +243,12 @@ void main() { test('should create a class extending another class', () { expect( - Class((b) => b - ..name = 'Foo' - ..extend = TypeReference((b) => b.symbol = 'Bar')), + Class( + (b) => + b + ..name = 'Foo' + ..extend = TypeReference((b) => b.symbol = 'Bar'), + ), equalsDart(r''' class Foo extends Bar {} '''), @@ -215,10 +257,13 @@ void main() { test('should create a class mixing in another class', () { expect( - Class((b) => b - ..name = 'Foo' - ..extend = TypeReference((b) => b.symbol = 'Bar') - ..mixins.add(TypeReference((b) => b.symbol = 'Foo'))), + Class( + (b) => + b + ..name = 'Foo' + ..extend = TypeReference((b) => b.symbol = 'Bar') + ..mixins.add(TypeReference((b) => b.symbol = 'Foo')), + ), equalsDart(r''' class Foo extends Bar with Foo {} '''), @@ -227,10 +272,13 @@ void main() { test('should create a class implementing another class', () { expect( - Class((b) => b - ..name = 'Foo' - ..extend = TypeReference((b) => b.symbol = 'Bar') - ..implements.add(TypeReference((b) => b.symbol = 'Foo'))), + Class( + (b) => + b + ..name = 'Foo' + ..extend = TypeReference((b) => b.symbol = 'Bar') + ..implements.add(TypeReference((b) => b.symbol = 'Foo')), + ), equalsDart(r''' class Foo extends Bar implements Foo {} '''), @@ -239,9 +287,12 @@ void main() { test('should create a class with a constructor', () { expect( - Class((b) => b - ..name = 'Foo' - ..constructors.add(Constructor())), + Class( + (b) => + b + ..name = 'Foo' + ..constructors.add(Constructor()), + ), equalsDart(r''' class Foo { Foo(); @@ -253,17 +304,19 @@ void main() { test('should create a class with a constructor with initializers', () { expect( Class( - (b) => b - ..name = 'Foo' - ..constructors.add( - Constructor( - (b) => b - ..initializers.addAll([ - const Code('a = 5'), - const Code('super()'), - ]), - ), - ), + (b) => + b + ..name = 'Foo' + ..constructors.add( + Constructor( + (b) => + b + ..initializers.addAll([ + const Code('a = 5'), + const Code('super()'), + ]), + ), + ), ), equalsDart(r''' class Foo { @@ -275,10 +328,14 @@ void main() { test('should create a class with a annotated constructor', () { expect( - Class((b) => b - ..name = 'Foo' - ..constructors - .add(Constructor((b) => b..annotations.add(refer('deprecated'))))), + Class( + (b) => + b + ..name = 'Foo' + ..constructors.add( + Constructor((b) => b..annotations.add(refer('deprecated'))), + ), + ), equalsDart(r''' class Foo { @deprecated @@ -290,9 +347,12 @@ void main() { test('should create a class with a named constructor', () { expect( - Class((b) => b - ..name = 'Foo' - ..constructors.add(Constructor((b) => b..name = 'named'))), + Class( + (b) => + b + ..name = 'Foo' + ..constructors.add(Constructor((b) => b..name = 'named')), + ), equalsDart(r''' class Foo { Foo.named(); @@ -303,9 +363,12 @@ void main() { test('should create a class with a const constructor', () { expect( - Class((b) => b - ..name = 'Foo' - ..constructors.add(Constructor((b) => b..constant = true))), + Class( + (b) => + b + ..name = 'Foo' + ..constructors.add(Constructor((b) => b..constant = true)), + ), equalsDart(r''' class Foo { const Foo(); @@ -316,9 +379,12 @@ void main() { test('should create a class with an external constructor', () { expect( - Class((b) => b - ..name = 'Foo' - ..constructors.add(Constructor((b) => b..external = true))), + Class( + (b) => + b + ..name = 'Foo' + ..constructors.add(Constructor((b) => b..external = true)), + ), equalsDart(r''' class Foo { external Foo(); @@ -329,11 +395,19 @@ void main() { test('should create a class with a factory constructor', () { expect( - Class((b) => b - ..name = 'Foo' - ..constructors.add(Constructor((b) => b - ..factory = true - ..redirect = refer('_Foo')))), + Class( + (b) => + b + ..name = 'Foo' + ..constructors.add( + Constructor( + (b) => + b + ..factory = true + ..redirect = refer('_Foo'), + ), + ), + ), equalsDart(r''' class Foo { factory Foo() = _Foo; @@ -344,12 +418,20 @@ void main() { test('should create a class with a const factory constructor', () { expect( - Class((b) => b - ..name = 'Foo' - ..constructors.add(Constructor((b) => b - ..factory = true - ..constant = true - ..redirect = refer('_Foo')))), + Class( + (b) => + b + ..name = 'Foo' + ..constructors.add( + Constructor( + (b) => + b + ..factory = true + ..constant = true + ..redirect = refer('_Foo'), + ), + ), + ), equalsDart(r''' class Foo { const factory Foo() = _Foo; @@ -360,12 +442,20 @@ void main() { test('should create a class with a factory lambda constructor', () { expect( - Class((b) => b - ..name = 'Foo' - ..constructors.add(Constructor((b) => b - ..factory = true - ..lambda = true - ..body = const Code('_Foo()')))), + Class( + (b) => + b + ..name = 'Foo' + ..constructors.add( + Constructor( + (b) => + b + ..factory = true + ..lambda = true + ..body = const Code('_Foo()'), + ), + ), + ), equalsDart(r''' class Foo { factory Foo() => _Foo(); @@ -376,11 +466,19 @@ void main() { test('should create a class with an implicit factory lambda constructor', () { expect( - Class((b) => b - ..name = 'Foo' - ..constructors.add(Constructor((b) => b - ..factory = true - ..body = refer('_Foo').newInstance([]).code))), + Class( + (b) => + b + ..name = 'Foo' + ..constructors.add( + Constructor( + (b) => + b + ..factory = true + ..body = refer('_Foo').newInstance([]).code, + ), + ), + ), equalsDart(r''' class Foo { factory Foo() => _Foo(); @@ -391,11 +489,19 @@ void main() { test('should create a class with a constructor with a body', () { expect( - Class((b) => b - ..name = 'Foo' - ..constructors.add(Constructor((b) => b - ..factory = true - ..body = const Code('return _Foo();')))), + Class( + (b) => + b + ..name = 'Foo' + ..constructors.add( + Constructor( + (b) => + b + ..factory = true + ..body = const Code('return _Foo();'), + ), + ), + ), equalsDart(r''' class Foo { factory Foo() { @@ -408,18 +514,29 @@ void main() { test('should create a class with a constructor with parameters', () { expect( - Class((b) => b - ..name = 'Foo' - ..constructors.add(Constructor((b) => b - ..requiredParameters.addAll([ - Parameter((b) => b..name = 'a'), - Parameter((b) => b..name = 'b'), - ]) - ..optionalParameters.addAll([ - Parameter((b) => b - ..name = 'c' - ..named = true), - ])))), + Class( + (b) => + b + ..name = 'Foo' + ..constructors.add( + Constructor( + (b) => + b + ..requiredParameters.addAll([ + Parameter((b) => b..name = 'a'), + Parameter((b) => b..name = 'b'), + ]) + ..optionalParameters.addAll([ + Parameter( + (b) => + b + ..name = 'c' + ..named = true, + ), + ]), + ), + ), + ), equalsDart(r''' class Foo { Foo(a, b, {c, }); @@ -430,23 +547,40 @@ void main() { test('should create a class with a constructor+field-formal parameters', () { expect( - Class((b) => b - ..name = 'Foo' - ..constructors.add(Constructor((b) => b - ..requiredParameters.addAll([ - Parameter((b) => b - ..name = 'a' - ..toThis = true), - Parameter((b) => b - ..name = 'b' - ..toThis = true), - ]) - ..optionalParameters.addAll([ - Parameter((b) => b - ..name = 'c' - ..named = true - ..toThis = true), - ])))), + Class( + (b) => + b + ..name = 'Foo' + ..constructors.add( + Constructor( + (b) => + b + ..requiredParameters.addAll([ + Parameter( + (b) => + b + ..name = 'a' + ..toThis = true, + ), + Parameter( + (b) => + b + ..name = 'b' + ..toThis = true, + ), + ]) + ..optionalParameters.addAll([ + Parameter( + (b) => + b + ..name = 'c' + ..named = true + ..toThis = true, + ), + ]), + ), + ), + ), equalsDart(r''' class Foo { Foo(this.a, this.b, {this.c, }); @@ -457,23 +591,40 @@ void main() { test('should create a class with a constructor+super-formal parameters', () { expect( - Class((b) => b - ..name = 'Foo' - ..constructors.add(Constructor((b) => b - ..requiredParameters.addAll([ - Parameter((b) => b - ..name = 'a' - ..toSuper = true), - Parameter((b) => b - ..name = 'b' - ..toSuper = true), - ]) - ..optionalParameters.addAll([ - Parameter((b) => b - ..name = 'c' - ..named = true - ..toSuper = true), - ])))), + Class( + (b) => + b + ..name = 'Foo' + ..constructors.add( + Constructor( + (b) => + b + ..requiredParameters.addAll([ + Parameter( + (b) => + b + ..name = 'a' + ..toSuper = true, + ), + Parameter( + (b) => + b + ..name = 'b' + ..toSuper = true, + ), + ]) + ..optionalParameters.addAll([ + Parameter( + (b) => + b + ..name = 'c' + ..named = true + ..toSuper = true, + ), + ]), + ), + ), + ), equalsDart(r''' class Foo { Foo(super.a, super.b, {super.c, }); diff --git a/pkgs/code_builder/test/specs/code/expression_test.dart b/pkgs/code_builder/test/specs/code/expression_test.dart index 7a424fd8e..bdf78328e 100644 --- a/pkgs/code_builder/test/specs/code/expression_test.dart +++ b/pkgs/code_builder/test/specs/code/expression_test.dart @@ -45,14 +45,19 @@ void main() { }); test('uses `onError` for unhandled types', () { expect( - literal(Uri.https('google.com'), onError: (value) { + literal( + Uri.https('google.com'), + onError: (value) { if (value is Uri) { - return refer('Uri') - .newInstanceNamed('parse', [literalString(value.toString())]); + return refer( + 'Uri', + ).newInstanceNamed('parse', [literalString(value.toString())]); } throw UnsupportedError('Not supported: $value'); - }), - equalsDart("Uri.parse('https://google.com')")); + }, + ), + equalsDart("Uri.parse('https://google.com')"), + ); }); }); @@ -162,7 +167,7 @@ void main() { Set(), true, null, - refer('Map').newInstance([]) + refer('Map').newInstance([]), ]), equalsDart('[[], {}, true, null, Map(), ]'), ); @@ -174,14 +179,13 @@ void main() { test('should emit a set of other literals and expressions', () { expect( - // ignore: prefer_collection_literals literalSet([ [], // ignore: prefer_collection_literals Set(), true, null, - refer('Map').newInstance([]) + refer('Map').newInstance([]), ]), equalsDart('{[], {}, true, null, Map(), }'), ); @@ -204,25 +208,29 @@ void main() { }); test('should emit a record with only named fields', () { - expect(literalRecord([], {'named': 1, 'other': []}), - equalsDart('(named: 1, other: [])')); + expect( + literalRecord([], {'named': 1, 'other': []}), + equalsDart('(named: 1, other: [])'), + ); }); test('should emit a record with both positional and named fields', () { - expect(literalRecord([0], {'x': true, 'y': 0}), - equalsDart('(0, x: true, y: 0)')); + expect( + literalRecord([0], {'x': true, 'y': 0}), + equalsDart('(0, x: true, y: 0)'), + ); }); test('should emit a record of other literals and expressions', () { expect( - literalRecord([ - 1, - refer('one'), - 'one' - ], { - 'named': refer('Foo').newInstance([literalNum(1)]) - }), - equalsDart("(1, one, 'one', named: Foo(1))")); + literalRecord( + [1, refer('one'), 'one'], + { + 'named': refer('Foo').newInstance([literalNum(1)]), + }, + ), + equalsDart("(1, one, 'one', named: Foo(1))"), + ); }); test('should emit a type as an expression', () { @@ -233,36 +241,26 @@ void main() { expect( refer('Foo', 'package:foo/foo.dart'), equalsDart( - '_i1.Foo', DartEmitter(allocator: Allocator.simplePrefixing())), + '_i1.Foo', + DartEmitter(allocator: Allocator.simplePrefixing()), + ), ); }); test('should emit invoking Type()', () { - expect( - refer('Map').newInstance([]), - equalsDart('Map()'), - ); + expect(refer('Map').newInstance([]), equalsDart('Map()')); }); test('should emit invoking named constructor', () { - expect( - refer('Foo').newInstanceNamed('bar', []), - equalsDart('Foo.bar()'), - ); + expect(refer('Foo').newInstanceNamed('bar', []), equalsDart('Foo.bar()')); }); test('should emit invoking unnamed constructor when name is empty', () { - expect( - refer('Foo').newInstanceNamed('', []), - equalsDart('Foo()'), - ); + expect(refer('Foo').newInstanceNamed('', []), equalsDart('Foo()')); }); test('should emit invoking const Type()', () { - expect( - refer('Object').constInstance([]), - equalsDart('const Object()'), - ); + expect(refer('Object').constInstance([]), equalsDart('const Object()')); }); test('should emit invoking a property accessor', () { @@ -278,79 +276,47 @@ void main() { }); test('should emit invoking a method with a single positional argument', () { - expect( - refer('foo').call([ - literal(1), - ]), - equalsDart('foo(1)'), - ); + expect(refer('foo').call([literal(1)]), equalsDart('foo(1)')); }); test('should emit invoking a method with positional arguments', () { expect( - refer('foo').call([ - literal(1), - literal(2), - literal(3), - ]), + refer('foo').call([literal(1), literal(2), literal(3)]), equalsDart('foo(1, 2, 3, )'), ); }); test('should emit invoking a method with a single named argument', () { expect( - refer('foo').call([], { - 'bar': literal(1), - }), + refer('foo').call([], {'bar': literal(1)}), equalsDart('foo(bar: 1)'), ); }); test('should emit invoking a method with named arguments', () { expect( - refer('foo').call([], { - 'bar': literal(1), - 'baz': literal(2), - }), + refer('foo').call([], {'bar': literal(1), 'baz': literal(2)}), equalsDart('foo(bar: 1, baz: 2, )'), ); }); test('should emit invoking a method with positional and named arguments', () { expect( - refer('foo').call([ - literal(1) - ], { - 'bar': literal(2), - 'baz': literal(3), - }), + refer('foo').call([literal(1)], {'bar': literal(2), 'baz': literal(3)}), equalsDart('foo(1, bar: 2, baz: 3, )'), ); }); test('should emit invoking a method with a single type argument', () { expect( - refer('foo').call( - [], - {}, - [ - refer('String'), - ], - ), + refer('foo').call([], {}, [refer('String')]), equalsDart('foo()'), ); }); test('should emit invoking a method with type arguments', () { expect( - refer('foo').call( - [], - {}, - [ - refer('String'), - refer('int'), - ], - ), + refer('foo').call([], {}, [refer('String'), refer('int')]), equalsDart('foo()'), ); }); @@ -371,9 +337,12 @@ void main() { test('should emit a function type with type parameters', () { expect( - FunctionType((b) => b - ..returnType = refer('T') - ..types.add(refer('T'))), + FunctionType( + (b) => + b + ..returnType = refer('T') + ..types.add(refer('T')), + ), equalsDart('T Function()'), ); }); @@ -387,46 +356,51 @@ void main() { test('should emit a function type with parameters', () { expect( - FunctionType((b) => b - ..requiredParameters.add(refer('String')) - ..optionalParameters.add(refer('int'))), + FunctionType( + (b) => + b + ..requiredParameters.add(refer('String')) + ..optionalParameters.add(refer('int')), + ), equalsDart('Function(String, [int, ])'), ); }); test('should emit a function type with named parameters', () { expect( - FunctionType((b) => b - ..namedParameters.addAll({ - 'x': refer('int'), - 'y': refer('int'), - })), + FunctionType( + (b) => + b..namedParameters.addAll({'x': refer('int'), 'y': refer('int')}), + ), equalsDart('Function({int x, int y, })'), ); }); test( - 'should emit a function type with named required and optional parameters', - () { - expect( - FunctionType((b) => b - ..namedRequiredParameters.addAll({ - 'x': refer('int'), - }) - ..namedParameters.addAll({ - 'y': refer('int'), - })), - equalsDart('Function({required int x, int y, })'), - ); - }); + 'should emit a function type with named required and optional parameters', + () { + expect( + FunctionType( + (b) => + b + ..namedRequiredParameters.addAll({'x': refer('int')}) + ..namedParameters.addAll({'y': refer('int')}), + ), + equalsDart('Function({required int x, int y, })'), + ); + }, + ); test('should emit a function type with named required parameters', () { expect( - FunctionType((b) => b - ..namedRequiredParameters.addAll({ - 'x': refer('int'), - 'y': refer('int'), - })), + FunctionType( + (b) => + b + ..namedRequiredParameters.addAll({ + 'x': refer('int'), + 'y': refer('int'), + }), + ), equalsDart('Function({required int x, required int y, })'), ); }); @@ -434,18 +408,24 @@ void main() { test('should emit a nullable function type in a Null Safety library', () { final emitter = DartEmitter.scoped(useNullSafetySyntax: true); expect( - FunctionType((b) => b - ..requiredParameters.add(refer('String')) - ..isNullable = true), + FunctionType( + (b) => + b + ..requiredParameters.add(refer('String')) + ..isNullable = true, + ), equalsDart('Function(String)?', emitter), ); }); test('should emit a nullable function type in pre-Null Safety library', () { expect( - FunctionType((b) => b - ..requiredParameters.add(refer('String')) - ..isNullable = true), + FunctionType( + (b) => + b + ..requiredParameters.add(refer('String')) + ..isNullable = true, + ), equalsDart('Function(String)'), ); }); @@ -453,22 +433,30 @@ void main() { test('should emit a non-nullable function type in a Null Safety library', () { final emitter = DartEmitter.scoped(useNullSafetySyntax: true); expect( - FunctionType((b) => b - ..requiredParameters.add(refer('String')) - ..isNullable = false), + FunctionType( + (b) => + b + ..requiredParameters.add(refer('String')) + ..isNullable = false, + ), equalsDart('Function(String)', emitter), ); }); - test('should emit a non-nullable function type in pre-Null Safety library', - () { - expect( - FunctionType((b) => b - ..requiredParameters.add(refer('String')) - ..isNullable = false), - equalsDart('Function(String)'), - ); - }); + test( + 'should emit a non-nullable function type in pre-Null Safety library', + () { + expect( + FunctionType( + (b) => + b + ..requiredParameters.add(refer('String')) + ..isNullable = false, + ), + equalsDart('Function(String)'), + ); + }, + ); test('should emit a closure', () { expect( @@ -484,26 +472,23 @@ void main() { expect( refer('map').property('putIfAbsent').call([ literalString('foo'), - Method((b) => b - ..types.add(refer('T')) - ..body = literalTrue.code).genericClosure, + Method( + (b) => + b + ..types.add(refer('T')) + ..body = literalTrue.code, + ).genericClosure, ]), equalsDart("map.putIfAbsent('foo', () => true, )"), ); }); test('should emit an assignment', () { - expect( - refer('foo').assign(literalTrue), - equalsDart('foo = true'), - ); + expect(refer('foo').assign(literalTrue), equalsDart('foo = true')); }); test('should emit an if null assignment', () { - expect( - refer('foo').ifNullThen(literalTrue), - equalsDart('foo ?? true'), - ); + expect(refer('foo').ifNullThen(literalTrue), equalsDart('foo ?? true')); }); test('should emit a null check', () { @@ -594,38 +579,23 @@ void main() { }); test('should emit await', () { - expect( - refer('future').awaited, - equalsDart('await future'), - ); + expect(refer('future').awaited, equalsDart('await future')); }); test('should emit return', () { - expect( - literalNull.returned, - equalsDart('return null'), - ); + expect(literalNull.returned, equalsDart('return null')); }); test('should emit spread', () { - expect( - refer('foo').spread, - equalsDart('...foo'), - ); + expect(refer('foo').spread, equalsDart('...foo')); }); test('should emit null safe spread', () { - expect( - refer('foo').nullSafeSpread, - equalsDart('...?foo'), - ); + expect(refer('foo').nullSafeSpread, equalsDart('...?foo')); }); test('should emit throw', () { - expect( - literalNull.thrown, - equalsDart('throw null'), - ); + expect(literalNull.thrown, equalsDart('throw null')); }); test('should emit an explicit cast', () { @@ -636,17 +606,11 @@ void main() { }); test('should emit an is check', () { - expect( - refer('foo').isA(refer('String')), - equalsDart('foo is String'), - ); + expect(refer('foo').isA(refer('String')), equalsDart('foo is String')); }); test('should emit an is! check', () { - expect( - refer('foo').isNotA(refer('String')), - equalsDart('foo is! String'), - ); + expect(refer('foo').isNotA(refer('String')), equalsDart('foo is! String')); }); test('should emit an equality check', () { @@ -703,9 +667,11 @@ void main() { }); test('should emit an operator subtract call', () { - // ignore: deprecated_member_use_from_same_package - expect(refer('foo').operatorSubstract(refer('foo2')), - equalsDart('foo - foo2')); + expect( + // ignore: deprecated_member_use_from_same_package + refer('foo').operatorSubstract(refer('foo2')), + equalsDart('foo - foo2'), + ); expect( refer('foo').operatorSubtract(refer('foo2')), @@ -715,17 +681,23 @@ void main() { test('should emit an operator divide call', () { expect( - refer('foo').operatorDivide(refer('foo2')), equalsDart('foo / foo2')); + refer('foo').operatorDivide(refer('foo2')), + equalsDart('foo / foo2'), + ); }); test('should emit an operator multiply call', () { expect( - refer('foo').operatorMultiply(refer('foo2')), equalsDart('foo * foo2')); + refer('foo').operatorMultiply(refer('foo2')), + equalsDart('foo * foo2'), + ); }); test('should emit an euclidean modulo operator call', () { - expect(refer('foo').operatorEuclideanModulo(refer('foo2')), - equalsDart('foo % foo2')); + expect( + refer('foo').operatorEuclideanModulo(refer('foo2')), + equalsDart('foo % foo2'), + ); }); test('should emit an operator int divide call', () { @@ -777,10 +749,7 @@ void main() { }); test('should emit a unary bitwise complement operator call', () { - expect( - refer('foo').operatorUnaryBitwiseComplement(), - equalsDart('~foo'), - ); + expect(refer('foo').operatorUnaryBitwiseComplement(), equalsDart('~foo')); }); test('should emit a shift left operator call', () { @@ -805,45 +774,69 @@ void main() { }); test('should emit a const variable declaration', () { - expect(declareConst('foo').assign(refer('bar')), - equalsDart('const foo = bar')); + expect( + declareConst('foo').assign(refer('bar')), + equalsDart('const foo = bar'), + ); }); test('should emit a typed const variable declaration', () { - expect(declareConst('foo', type: refer('String')).assign(refer('bar')), - equalsDart('const String foo = bar')); + expect( + declareConst('foo', type: refer('String')).assign(refer('bar')), + equalsDart('const String foo = bar'), + ); }); test('should emit a final variable declaration', () { - expect(declareFinal('foo').assign(refer('bar')), - equalsDart('final foo = bar')); + expect( + declareFinal('foo').assign(refer('bar')), + equalsDart('final foo = bar'), + ); }); test('should emit a typed final variable declaration', () { - expect(declareFinal('foo', type: refer('String')).assign(refer('bar')), - equalsDart('final String foo = bar')); + expect( + declareFinal('foo', type: refer('String')).assign(refer('bar')), + equalsDart('final String foo = bar'), + ); }); - test('should emit a nullable typed final variable declaration', () { - final emitter = DartEmitter.scoped(useNullSafetySyntax: true); - expect( - declareFinal('foo', - type: TypeReference((b) => b - ..symbol = 'String' - ..isNullable = true)).assign(refer('bar')), - equalsDart('final String? foo = bar', emitter)); - }, skip: 'https://github.com/dart-lang/code_builder/issues/315'); + test( + 'should emit a nullable typed final variable declaration', + () { + final emitter = DartEmitter.scoped(useNullSafetySyntax: true); + expect( + declareFinal( + 'foo', + type: TypeReference( + (b) => + b + ..symbol = 'String' + ..isNullable = true, + ), + ).assign(refer('bar')), + equalsDart('final String? foo = bar', emitter), + ); + }, + skip: 'https://github.com/dart-lang/code_builder/issues/315', + ); test('should emit a late final variable declaration', () { - expect(declareFinal('foo', late: true).assign(refer('bar')), - equalsDart('late final foo = bar')); + expect( + declareFinal('foo', late: true).assign(refer('bar')), + equalsDart('late final foo = bar'), + ); }); test('should emit a late typed final variable declaration', () { expect( - declareFinal('foo', type: refer('String'), late: true) - .assign(refer('bar')), - equalsDart('late final String foo = bar')); + declareFinal( + 'foo', + type: refer('String'), + late: true, + ).assign(refer('bar')), + equalsDart('late final String foo = bar'), + ); }); test('should emit a variable declaration', () { @@ -851,57 +844,51 @@ void main() { }); test('should emit a typed variable declaration', () { - expect(declareVar('foo', type: refer('String')).assign(refer('bar')), - equalsDart('String foo = bar')); + expect( + declareVar('foo', type: refer('String')).assign(refer('bar')), + equalsDart('String foo = bar'), + ); }); test('should emit a late variable declaration', () { - expect(declareVar('foo', late: true).assign(refer('bar')), - equalsDart('late var foo = bar')); + expect( + declareVar('foo', late: true).assign(refer('bar')), + equalsDart('late var foo = bar'), + ); }); test('should emit a late typed variable declaration', () { expect( - declareVar('foo', type: refer('String'), late: true) - .assign(refer('bar')), - equalsDart('late String foo = bar')); + declareVar('foo', type: refer('String'), late: true).assign(refer('bar')), + equalsDart('late String foo = bar'), + ); }); test('should emit a perenthesized epression', () { expect( - refer('foo').ifNullThen(refer('FormatException') - .newInstance([literalString('missing foo')]) - .thrown - .parenthesized), - equalsDart('foo ?? (throw FormatException(\'missing foo\'))')); + refer('foo').ifNullThen( + refer( + 'FormatException', + ).newInstance([literalString('missing foo')]).thrown.parenthesized, + ), + equalsDart('foo ?? (throw FormatException(\'missing foo\'))'), + ); }); test('should emit an addition assigment expression', () { - expect( - refer('foo').addAssign(refer('bar')), - equalsDart('foo += bar'), - ); + expect(refer('foo').addAssign(refer('bar')), equalsDart('foo += bar')); }); test('should emit a subtraction assigment expression', () { - expect( - refer('foo').subtractAssign(refer('bar')), - equalsDart('foo -= bar'), - ); + expect(refer('foo').subtractAssign(refer('bar')), equalsDart('foo -= bar')); }); test('should emit a multiplication assigment expression', () { - expect( - refer('foo').multiplyAssign(refer('bar')), - equalsDart('foo *= bar'), - ); + expect(refer('foo').multiplyAssign(refer('bar')), equalsDart('foo *= bar')); }); test('should emit a division assigment expression', () { - expect( - refer('foo').divideAssign(refer('bar')), - equalsDart('foo /= bar'), - ); + expect(refer('foo').divideAssign(refer('bar')), equalsDart('foo /= bar')); }); test('should emit an int division assigment expression', () { diff --git a/pkgs/code_builder/test/specs/code/statement_test.dart b/pkgs/code_builder/test/specs/code/statement_test.dart index cd3d53afc..0b16ac6c1 100644 --- a/pkgs/code_builder/test/specs/code/statement_test.dart +++ b/pkgs/code_builder/test/specs/code/statement_test.dart @@ -42,15 +42,18 @@ void main() { test('should emit a block of code with lazily invoked generators', () { expect( - Method((b) => b - ..name = 'main' - ..body = Block.of([ - const Code('if ('), - lazyCode(() => refer('foo').code), - const Code(') {'), - refer('print')([literalTrue]).statement, - const Code('}'), - ])), + Method( + (b) => + b + ..name = 'main' + ..body = Block.of([ + const Code('if ('), + lazyCode(() => refer('foo').code), + const Code(') {'), + refer('print')([literalTrue]).statement, + const Code('}'), + ]), + ), equalsDart(r''' main() { if (foo) { diff --git a/pkgs/code_builder/test/specs/enum_test.dart b/pkgs/code_builder/test/specs/enum_test.dart index 3a8238a52..61a2cf4e6 100644 --- a/pkgs/code_builder/test/specs/enum_test.dart +++ b/pkgs/code_builder/test/specs/enum_test.dart @@ -12,58 +12,77 @@ void main() { test('should create an enum', () { expect( - Enum((b) => b - ..name = 'E' - ..values.addAll([ - EnumValue((b) => b..name = 'a'), - EnumValue((b) => b..name = 'b'), - ])), - equalsDart(r''' + Enum( + (b) => + b + ..name = 'E' + ..values.addAll([ + EnumValue((b) => b..name = 'a'), + EnumValue((b) => b..name = 'b'), + ]), + ), + equalsDart(r''' enum E { a, b } - ''')); + '''), + ); }); test('should create an enum with annotations', () { expect( - Enum((b) => b - ..annotations.addAll([ - refer('deprecated'), - refer('Deprecated').call([literalString('This is an old enum')]) - ]) - ..name = 'V' - ..values.addAll([ - EnumValue((b) => b..name = 'x'), - ])), - equalsDart(r''' + Enum( + (b) => + b + ..annotations.addAll([ + refer('deprecated'), + refer( + 'Deprecated', + ).call([literalString('This is an old enum')]), + ]) + ..name = 'V' + ..values.addAll([EnumValue((b) => b..name = 'x')]), + ), + equalsDart(r''' @deprecated @Deprecated('This is an old enum') enum V { x } - ''')); + '''), + ); }); test('should create an enum with annotated values', () { expect( - Enum((b) => b - ..name = 'Status' - ..values.addAll([ - EnumValue((b) => b - ..name = 'okay' - ..annotations.addAll([ - refer('deprecated'), - refer('Deprecated').call([literalString('use Good instead')]), - ])), - EnumValue((b) => b - ..name = 'good' - ..annotations.addAll([ - refer('JsonKey').call([literalString('good')]) - ])), - ])), - equalsDart(r''' + Enum( + (b) => + b + ..name = 'Status' + ..values.addAll([ + EnumValue( + (b) => + b + ..name = 'okay' + ..annotations.addAll([ + refer('deprecated'), + refer( + 'Deprecated', + ).call([literalString('use Good instead')]), + ]), + ), + EnumValue( + (b) => + b + ..name = 'good' + ..annotations.addAll([ + refer('JsonKey').call([literalString('good')]), + ]), + ), + ]), + ), + equalsDart(r''' enum Status { @deprecated @Deprecated('use Good instead') @@ -71,51 +90,66 @@ void main() { @JsonKey('good') good } - ''')); + '''), + ); }); test('should create an enum which mixes in and implements specs', () { - final myEnum = Enum((b) => b - ..name = 'MyEnum' - ..implements.addAll(const [ - Reference('InterfaceA'), - Reference('InterfaceB'), - ]) - ..mixins.addAll(const [ - Reference('Mixin1'), - Reference('Mixin2'), - ]) - ..values.addAll([ - EnumValue((v) => v..name = 'a'), - EnumValue((v) => v..name = 'b'), - EnumValue((v) => v..name = 'c'), - ])); - expect(myEnum, equalsDart(''' + final myEnum = Enum( + (b) => + b + ..name = 'MyEnum' + ..implements.addAll(const [ + Reference('InterfaceA'), + Reference('InterfaceB'), + ]) + ..mixins.addAll(const [Reference('Mixin1'), Reference('Mixin2')]) + ..values.addAll([ + EnumValue((v) => v..name = 'a'), + EnumValue((v) => v..name = 'b'), + EnumValue((v) => v..name = 'c'), + ]), + ); + expect( + myEnum, + equalsDart(''' enum MyEnum with Mixin1, Mixin2 implements InterfaceA, InterfaceB { a, b, c } - ''')); + '''), + ); }); test('should create an enum which targets a named constructor', () { - final myEnum = Enum((b) => b - ..name = 'MyEnum' - ..constructors.addAll([ - Constructor((c) => c..constant = true), - Constructor((c) => c - ..constant = true - ..name = 'named'), - ]) - ..values.addAll([ - EnumValue((v) => v..name = 'a'), - EnumValue((v) => v - ..name = 'b' - ..constructorName = 'named'), - EnumValue((v) => v..name = 'c'), - ])); - expect(myEnum, equalsDart(''' + final myEnum = Enum( + (b) => + b + ..name = 'MyEnum' + ..constructors.addAll([ + Constructor((c) => c..constant = true), + Constructor( + (c) => + c + ..constant = true + ..name = 'named', + ), + ]) + ..values.addAll([ + EnumValue((v) => v..name = 'a'), + EnumValue( + (v) => + v + ..name = 'b' + ..constructorName = 'named', + ), + EnumValue((v) => v..name = 'c'), + ]), + ); + expect( + myEnum, + equalsDart(''' enum MyEnum { a, b.named(), @@ -125,29 +159,39 @@ void main() { const MyEnum.named(); } - ''')); + '''), + ); }); test('should create an enum which targets a redirecting constructor', () { - final myEnum = Enum((b) => b - ..name = 'MyEnum' - ..constructors.addAll([ - Constructor((c) => c..constant = true), - Constructor((c) => c - ..constant = true - ..name = 'redirect' - ..initializers.add( - refer('this').call([]).code, - )), - ]) - ..values.addAll([ - EnumValue((v) => v..name = 'a'), - EnumValue((v) => v - ..name = 'b' - ..constructorName = 'redirect'), - EnumValue((v) => v..name = 'c'), - ])); - expect(myEnum, equalsDart(''' + final myEnum = Enum( + (b) => + b + ..name = 'MyEnum' + ..constructors.addAll([ + Constructor((c) => c..constant = true), + Constructor( + (c) => + c + ..constant = true + ..name = 'redirect' + ..initializers.add(refer('this').call([]).code), + ), + ]) + ..values.addAll([ + EnumValue((v) => v..name = 'a'), + EnumValue( + (v) => + v + ..name = 'b' + ..constructorName = 'redirect', + ), + EnumValue((v) => v..name = 'c'), + ]), + ); + expect( + myEnum, + equalsDart(''' enum MyEnum { a, b.redirect(), @@ -157,43 +201,65 @@ void main() { const MyEnum.redirect() : this(); } - ''')); + '''), + ); }); - test('should create an enum which targets a redirecting factory constructor', - () { - final myEnum = Enum((b) => b - ..name = 'MyEnum' - ..constructors.addAll([ - Constructor((c) => c..constant = true), - Constructor((c) => c - ..constant = true - ..factory = true - ..name = 'redirect' - ..redirect = refer('MyOtherEnum.named') - ..optionalParameters.addAll([ - Parameter((p) => p - ..type = refer('int?') - ..name = 'myInt'), - Parameter((p) => p - ..type = refer('String?') - ..name = 'myString') - ])) - ]) - ..values.addAll([ - EnumValue((v) => v..name = 'a'), - EnumValue((v) => v - ..name = 'b' - ..constructorName = 'redirect' - ..arguments.addAll([ - literalNum(1), - literalString('abc'), - ])), - EnumValue((v) => v - ..name = 'c' - ..constructorName = 'redirect'), - ])); - expect(myEnum, equalsDart(''' + test( + 'should create an enum which targets a redirecting factory constructor', + () { + final myEnum = Enum( + (b) => + b + ..name = 'MyEnum' + ..constructors.addAll([ + Constructor((c) => c..constant = true), + Constructor( + (c) => + c + ..constant = true + ..factory = true + ..name = 'redirect' + ..redirect = refer('MyOtherEnum.named') + ..optionalParameters.addAll([ + Parameter( + (p) => + p + ..type = refer('int?') + ..name = 'myInt', + ), + Parameter( + (p) => + p + ..type = refer('String?') + ..name = 'myString', + ), + ]), + ), + ]) + ..values.addAll([ + EnumValue((v) => v..name = 'a'), + EnumValue( + (v) => + v + ..name = 'b' + ..constructorName = 'redirect' + ..arguments.addAll([ + literalNum(1), + literalString('abc'), + ]), + ), + EnumValue( + (v) => + v + ..name = 'c' + ..constructorName = 'redirect', + ), + ]), + ); + expect( + myEnum, + equalsDart(''' enum MyEnum { a, b.redirect(1, 'abc'), @@ -206,43 +272,67 @@ void main() { String? myString, ]) = MyOtherEnum.named; } - ''')); - }); + '''), + ); + }, + ); test('should create an enum which targets an unnamed constructor', () { - final myEnum = Enum((b) => b - ..name = 'MyEnum' - ..constructors.add(Constructor((c) => c - ..constant = true - ..optionalParameters.addAll([ - Parameter((p) => p - ..toThis = true - ..name = 'myInt'), - Parameter((p) => p - ..toThis = true - ..name = 'myString') - ]))) - ..fields.addAll([ - Field((f) => f - ..modifier = FieldModifier.final$ - ..type = refer('int?') - ..name = 'myInt'), - Field((f) => f - ..modifier = FieldModifier.final$ - ..type = refer('String?') - ..name = 'myString') - ]) - ..values.addAll([ - EnumValue((v) => v..name = 'a'), - EnumValue((v) => v - ..name = 'b' - ..arguments.addAll([ - literalNum(1), - literalString('abc'), - ])), - EnumValue((v) => v..name = 'c'), - ])); - expect(myEnum, equalsDart(''' + final myEnum = Enum( + (b) => + b + ..name = 'MyEnum' + ..constructors.add( + Constructor( + (c) => + c + ..constant = true + ..optionalParameters.addAll([ + Parameter( + (p) => + p + ..toThis = true + ..name = 'myInt', + ), + Parameter( + (p) => + p + ..toThis = true + ..name = 'myString', + ), + ]), + ), + ) + ..fields.addAll([ + Field( + (f) => + f + ..modifier = FieldModifier.final$ + ..type = refer('int?') + ..name = 'myInt', + ), + Field( + (f) => + f + ..modifier = FieldModifier.final$ + ..type = refer('String?') + ..name = 'myString', + ), + ]) + ..values.addAll([ + EnumValue((v) => v..name = 'a'), + EnumValue( + (v) => + v + ..name = 'b' + ..arguments.addAll([literalNum(1), literalString('abc')]), + ), + EnumValue((v) => v..name = 'c'), + ]), + ); + expect( + myEnum, + equalsDart(''' enum MyEnum { a, b(1, 'abc'), @@ -257,39 +347,67 @@ void main() { final String? myString; } - ''')); + '''), + ); }); test('should create an enum with generics', () { - final myEnum = Enum((b) => b - ..name = 'MyEnum' - ..types.add(const Reference('T')) - ..constructors.add(Constructor((c) => c - ..constant = true - ..requiredParameters.add(Parameter((p) => p - ..toThis = true - ..name = 'value')))) - ..fields.add( - Field((f) => f - ..modifier = FieldModifier.final$ - ..type = refer('T') - ..name = 'value'), - ) - ..values.addAll([ - EnumValue((v) => v - ..name = 'a' - ..types.add(const Reference('int')) - ..arguments.add(literalNum(123))), - EnumValue((v) => v - ..name = 'b' - ..types.add(const Reference('String')) - ..arguments.add(literalString('abc'))), - EnumValue((v) => v - ..name = 'c' - ..types.add(const Reference('MyEnum')) - ..arguments.add(refer('MyEnum').property('a'))), - ])); - expect(myEnum, equalsDart(''' + final myEnum = Enum( + (b) => + b + ..name = 'MyEnum' + ..types.add(const Reference('T')) + ..constructors.add( + Constructor( + (c) => + c + ..constant = true + ..requiredParameters.add( + Parameter( + (p) => + p + ..toThis = true + ..name = 'value', + ), + ), + ), + ) + ..fields.add( + Field( + (f) => + f + ..modifier = FieldModifier.final$ + ..type = refer('T') + ..name = 'value', + ), + ) + ..values.addAll([ + EnumValue( + (v) => + v + ..name = 'a' + ..types.add(const Reference('int')) + ..arguments.add(literalNum(123)), + ), + EnumValue( + (v) => + v + ..name = 'b' + ..types.add(const Reference('String')) + ..arguments.add(literalString('abc')), + ), + EnumValue( + (v) => + v + ..name = 'c' + ..types.add(const Reference('MyEnum')) + ..arguments.add(refer('MyEnum').property('a')), + ), + ]), + ); + expect( + myEnum, + equalsDart(''' enum MyEnum { a(123), b('abc'), @@ -299,35 +417,57 @@ void main() { final T value; } - ''')); + '''), + ); }); test('should create an enum with fields', () { - final myEnum = Enum((b) => b - ..name = 'MyEnum' - ..constructors.add(Constructor((c) => c - ..constant = true - ..optionalParameters.add(Parameter((p) => p - ..toThis = true - ..name = 'myInt')))) - ..fields.addAll([ - Field((f) => f - ..modifier = FieldModifier.final$ - ..type = refer('int?') - ..name = 'myInt'), - Field((f) => f - ..static = true - ..modifier = FieldModifier.constant - ..type = refer('String') - ..name = 'myString' - ..assignment = literalString('abc').code), - ]) - ..values.addAll([ - EnumValue((v) => v..name = 'a'), - EnumValue((v) => v..name = 'b'), - EnumValue((v) => v..name = 'c'), - ])); - expect(myEnum, equalsDart(''' + final myEnum = Enum( + (b) => + b + ..name = 'MyEnum' + ..constructors.add( + Constructor( + (c) => + c + ..constant = true + ..optionalParameters.add( + Parameter( + (p) => + p + ..toThis = true + ..name = 'myInt', + ), + ), + ), + ) + ..fields.addAll([ + Field( + (f) => + f + ..modifier = FieldModifier.final$ + ..type = refer('int?') + ..name = 'myInt', + ), + Field( + (f) => + f + ..static = true + ..modifier = FieldModifier.constant + ..type = refer('String') + ..name = 'myString' + ..assignment = literalString('abc').code, + ), + ]) + ..values.addAll([ + EnumValue((v) => v..name = 'a'), + EnumValue((v) => v..name = 'b'), + EnumValue((v) => v..name = 'c'), + ]), + ); + expect( + myEnum, + equalsDart(''' enum MyEnum { a, b, @@ -339,34 +479,46 @@ void main() { static const String myString = 'abc'; } - ''')); + '''), + ); }); test('should create an enum with methods', () { - final myEnum = Enum((b) => b - ..name = 'MyEnum' - ..methods.addAll([ - Method((m) => m - ..returns = refer('int') - ..type = MethodType.getter - ..name = 'myInt' - ..body = literalNum(123).code), - Method((m) => m - ..returns = refer('Iterable') - ..name = 'myStrings' - ..modifier = MethodModifier.syncStar - ..body = Block.of(const [ - Code("yield 'a';"), - Code("yield 'b';"), - Code("yield 'c';"), - ])) - ]) - ..values.addAll([ - EnumValue((v) => v..name = 'a'), - EnumValue((v) => v..name = 'b'), - EnumValue((v) => v..name = 'c'), - ])); - expect(myEnum, equalsDart(''' + final myEnum = Enum( + (b) => + b + ..name = 'MyEnum' + ..methods.addAll([ + Method( + (m) => + m + ..returns = refer('int') + ..type = MethodType.getter + ..name = 'myInt' + ..body = literalNum(123).code, + ), + Method( + (m) => + m + ..returns = refer('Iterable') + ..name = 'myStrings' + ..modifier = MethodModifier.syncStar + ..body = Block.of(const [ + Code("yield 'a';"), + Code("yield 'b';"), + Code("yield 'c';"), + ]), + ), + ]) + ..values.addAll([ + EnumValue((v) => v..name = 'a'), + EnumValue((v) => v..name = 'b'), + EnumValue((v) => v..name = 'c'), + ]), + ); + expect( + myEnum, + equalsDart(''' enum MyEnum { a, b, @@ -380,50 +532,75 @@ void main() { yield 'c'; } } - ''')); + '''), + ); }); - test('should create an enum which named and unnamed constructor parameters', - () { - final myEnum = Enum((b) => b - ..name = 'MyEnum' - ..constructors.add(Constructor((c) => c - ..constant = true - ..requiredParameters.addAll([ - Parameter((p) => p - ..toThis = true - ..name = 'myInt') - ]) - ..optionalParameters.addAll([ - Parameter((p) => p - ..toThis = true - ..named = true - ..required = true - ..name = 'myString') - ]))) - ..fields.addAll([ - Field((f) => f - ..modifier = FieldModifier.final$ - ..type = refer('int?') - ..name = 'myInt'), - Field((f) => f - ..modifier = FieldModifier.final$ - ..type = refer('String?') - ..name = 'myString') - ]) - ..values.addAll([ - EnumValue((v) => v..name = 'a'), - EnumValue((v) => v - ..name = 'b' - ..arguments.addAll([ - literalNum(1), - ]) - ..namedArguments.addAll({ - 'myString': literalString('abc'), - })), - EnumValue((v) => v..name = 'c'), - ])); - expect(myEnum, equalsDart(''' + test( + 'should create an enum which named and unnamed constructor parameters', + () { + final myEnum = Enum( + (b) => + b + ..name = 'MyEnum' + ..constructors.add( + Constructor( + (c) => + c + ..constant = true + ..requiredParameters.addAll([ + Parameter( + (p) => + p + ..toThis = true + ..name = 'myInt', + ), + ]) + ..optionalParameters.addAll([ + Parameter( + (p) => + p + ..toThis = true + ..named = true + ..required = true + ..name = 'myString', + ), + ]), + ), + ) + ..fields.addAll([ + Field( + (f) => + f + ..modifier = FieldModifier.final$ + ..type = refer('int?') + ..name = 'myInt', + ), + Field( + (f) => + f + ..modifier = FieldModifier.final$ + ..type = refer('String?') + ..name = 'myString', + ), + ]) + ..values.addAll([ + EnumValue((v) => v..name = 'a'), + EnumValue( + (v) => + v + ..name = 'b' + ..arguments.addAll([literalNum(1)]) + ..namedArguments.addAll({ + 'myString': literalString('abc'), + }), + ), + EnumValue((v) => v..name = 'c'), + ]), + ); + expect( + myEnum, + equalsDart(''' enum MyEnum { a, b(1, myString: 'abc'), @@ -438,6 +615,8 @@ void main() { final String? myString; } - ''')); - }); + '''), + ); + }, + ); } diff --git a/pkgs/code_builder/test/specs/extension_test.dart b/pkgs/code_builder/test/specs/extension_test.dart index b45fa6580..6a0f307bb 100644 --- a/pkgs/code_builder/test/specs/extension_test.dart +++ b/pkgs/code_builder/test/specs/extension_test.dart @@ -12,9 +12,12 @@ void main() { test('should create an extension', () { expect( - Extension((b) => b - ..name = 'Foo' - ..on = TypeReference((b) => b.symbol = 'Bar')), + Extension( + (b) => + b + ..name = 'Foo' + ..on = TypeReference((b) => b.symbol = 'Bar'), + ), equalsDart(r''' extension Foo on Bar {} '''), @@ -33,14 +36,11 @@ void main() { test('should create an extension with documentation', () { expect( Extension( - (b) => b - ..name = 'Foo' - ..on = TypeReference((b) => b.symbol = 'Bar') - ..docs.addAll( - const [ - '/// My favorite extension.', - ], - ), + (b) => + b + ..name = 'Foo' + ..on = TypeReference((b) => b.symbol = 'Bar') + ..docs.addAll(const ['/// My favorite extension.']), ), equalsDart(r''' /// My favorite extension. @@ -52,14 +52,16 @@ void main() { test('should create an extension with annotations', () { expect( Extension( - (b) => b - ..name = 'Foo' - ..on = TypeReference((b) => b.symbol = 'Bar') - ..annotations.addAll([ - refer('deprecated'), - refer('Deprecated') - .call([literalString('This is an old extension')]) - ]), + (b) => + b + ..name = 'Foo' + ..on = TypeReference((b) => b.symbol = 'Bar') + ..annotations.addAll([ + refer('deprecated'), + refer( + 'Deprecated', + ).call([literalString('This is an old extension')]), + ]), ), equalsDart(r''' @deprecated @@ -71,10 +73,13 @@ void main() { test('should create an extension with a generic type', () { expect( - Extension((b) => b - ..name = 'Foo' - ..on = TypeReference((b) => b.symbol = 'Bar') - ..types.add(refer('T'))), + Extension( + (b) => + b + ..name = 'Foo' + ..on = TypeReference((b) => b.symbol = 'Bar') + ..types.add(refer('T')), + ), equalsDart(r''' extension Foo on Bar {} '''), @@ -84,13 +89,11 @@ void main() { test('should create an extension with multiple generic types', () { expect( Extension( - (b) => b - ..name = 'Map' - ..on = TypeReference((b) => b.symbol = 'Bar') - ..types.addAll([ - refer('K'), - refer('V'), - ]), + (b) => + b + ..name = 'Map' + ..on = TypeReference((b) => b.symbol = 'Bar') + ..types.addAll([refer('K'), refer('V')]), ), equalsDart(r''' extension Map on Bar {} @@ -100,14 +103,25 @@ void main() { test('should create an extension with a bound generic type', () { expect( - Extension((b) => b - ..name = 'Foo' - ..on = TypeReference((b) => b.symbol = 'Bar') - ..types.add(TypeReference((b) => b - ..symbol = 'T' - ..bound = TypeReference((b) => b - ..symbol = 'Comparable' - ..types.add(refer('T').type))))), + Extension( + (b) => + b + ..name = 'Foo' + ..on = TypeReference((b) => b.symbol = 'Bar') + ..types.add( + TypeReference( + (b) => + b + ..symbol = 'T' + ..bound = TypeReference( + (b) => + b + ..symbol = 'Comparable' + ..types.add(refer('T').type), + ), + ), + ), + ), equalsDart(r''' extension Foo> on Bar {} '''), @@ -116,15 +130,21 @@ void main() { test('should create an extension with a method', () { expect( - Extension((b) => b - ..name = 'Foo' - ..on = TypeReference((b) => b.symbol = 'Bar') - ..methods.add(Method((b) => b - ..name = 'parseInt' - ..returns = refer('int') - ..body = Code.scope( - (a) => 'return int.parse(this);', - )))), + Extension( + (b) => + b + ..name = 'Foo' + ..on = TypeReference((b) => b.symbol = 'Bar') + ..methods.add( + Method( + (b) => + b + ..name = 'parseInt' + ..returns = refer('int') + ..body = Code.scope((a) => 'return int.parse(this);'), + ), + ), + ), equalsDart(r''' extension Foo on Bar { int parseInt() { diff --git a/pkgs/code_builder/test/specs/extension_type_test.dart b/pkgs/code_builder/test/specs/extension_type_test.dart index cc510468f..b3b942adb 100644 --- a/pkgs/code_builder/test/specs/extension_type_test.dart +++ b/pkgs/code_builder/test/specs/extension_type_test.dart @@ -12,11 +12,19 @@ void main() { test('minimum extension type', () { expect( - ExtensionType((b) => b - ..name = 'Foo' - ..representationDeclaration = RepresentationDeclaration((b) => b - ..declaredRepresentationType = TypeReference((b) => b.symbol = 'int') - ..name = 'bar')), + ExtensionType( + (b) => + b + ..name = 'Foo' + ..representationDeclaration = RepresentationDeclaration( + (b) => + b + ..declaredRepresentationType = TypeReference( + (b) => b.symbol = 'int', + ) + ..name = 'bar', + ), + ), equalsDart(r''' extension type Foo(int bar) { } '''), @@ -25,12 +33,20 @@ void main() { test('const extension type', () { expect( - ExtensionType((b) => b - ..name = 'Foo' - ..constant = true - ..representationDeclaration = RepresentationDeclaration((b) => b - ..declaredRepresentationType = TypeReference((b) => b.symbol = 'int') - ..name = 'bar')), + ExtensionType( + (b) => + b + ..name = 'Foo' + ..constant = true + ..representationDeclaration = RepresentationDeclaration( + (b) => + b + ..declaredRepresentationType = TypeReference( + (b) => b.symbol = 'int', + ) + ..name = 'bar', + ), + ), equalsDart(r''' extension type const Foo(int bar) { } '''), @@ -39,19 +55,26 @@ void main() { test('extension type with metadata', () { expect( - ExtensionType((b) => b - ..name = 'Foo' - ..representationDeclaration = RepresentationDeclaration((b) => b - ..declaredRepresentationType = TypeReference((b) => b.symbol = 'int') - ..name = 'bar') - ..docs.add( - '/// My favorite extension type.', - ) - ..annotations.addAll([ - refer('deprecated'), - refer('Deprecated') - .call([literalString('This is an old extension type')]) - ])), + ExtensionType( + (b) => + b + ..name = 'Foo' + ..representationDeclaration = RepresentationDeclaration( + (b) => + b + ..declaredRepresentationType = TypeReference( + (b) => b.symbol = 'int', + ) + ..name = 'bar', + ) + ..docs.add('/// My favorite extension type.') + ..annotations.addAll([ + refer('deprecated'), + refer( + 'Deprecated', + ).call([literalString('This is an old extension type')]), + ]), + ), equalsDart(r''' /// My favorite extension type. @deprecated @@ -63,15 +86,23 @@ void main() { test('extension type with generics', () { expect( - ExtensionType((b) => b - ..name = 'Foo' - ..types.addAll([ - TypeReference((b) => b..symbol = 'T'), - TypeReference((b) => b..symbol = 'U') - ]) - ..representationDeclaration = RepresentationDeclaration((b) => b - ..declaredRepresentationType = TypeReference((b) => b.symbol = 'T') - ..name = 'bar')), + ExtensionType( + (b) => + b + ..name = 'Foo' + ..types.addAll([ + TypeReference((b) => b..symbol = 'T'), + TypeReference((b) => b..symbol = 'U'), + ]) + ..representationDeclaration = RepresentationDeclaration( + (b) => + b + ..declaredRepresentationType = TypeReference( + (b) => b.symbol = 'T', + ) + ..name = 'bar', + ), + ), equalsDart(r''' extension type Foo(T bar) { } '''), @@ -80,14 +111,27 @@ void main() { test('extension type with generics bound', () { expect( - ExtensionType((b) => b - ..name = 'Foo' - ..types.add(TypeReference((b) => b - ..symbol = 'T' - ..bound = TypeReference((b) => b..symbol = 'num'))) - ..representationDeclaration = RepresentationDeclaration((b) => b - ..declaredRepresentationType = TypeReference((b) => b.symbol = 'T') - ..name = 'bar')), + ExtensionType( + (b) => + b + ..name = 'Foo' + ..types.add( + TypeReference( + (b) => + b + ..symbol = 'T' + ..bound = TypeReference((b) => b..symbol = 'num'), + ), + ) + ..representationDeclaration = RepresentationDeclaration( + (b) => + b + ..declaredRepresentationType = TypeReference( + (b) => b.symbol = 'T', + ) + ..name = 'bar', + ), + ), equalsDart(r''' extension type Foo(T bar) { } '''), @@ -96,12 +140,20 @@ void main() { test('extension type with named primary constructor', () { expect( - ExtensionType((b) => b - ..name = 'Foo' - ..primaryConstructorName = 'named' - ..representationDeclaration = RepresentationDeclaration((b) => b - ..declaredRepresentationType = TypeReference((b) => b.symbol = 'int') - ..name = 'bar')), + ExtensionType( + (b) => + b + ..name = 'Foo' + ..primaryConstructorName = 'named' + ..representationDeclaration = RepresentationDeclaration( + (b) => + b + ..declaredRepresentationType = TypeReference( + (b) => b.symbol = 'int', + ) + ..name = 'bar', + ), + ), equalsDart(r''' extension type Foo.named(int bar) { } '''), @@ -110,19 +162,28 @@ void main() { test('extension type with metadata on field', () { expect( - ExtensionType((b) => b - ..name = 'Foo' - ..representationDeclaration = RepresentationDeclaration((b) => b - ..declaredRepresentationType = TypeReference((b) => b.symbol = 'int') - ..name = 'bar' - ..docs.add( - '/// My favorite representation declaration.', - ) - ..annotations.addAll([ - refer('deprecated'), - refer('Deprecated').call( - [literalString('This is an old representation declaration')]) - ]))), + ExtensionType( + (b) => + b + ..name = 'Foo' + ..representationDeclaration = RepresentationDeclaration( + (b) => + b + ..declaredRepresentationType = TypeReference( + (b) => b.symbol = 'int', + ) + ..name = 'bar' + ..docs.add('/// My favorite representation declaration.') + ..annotations.addAll([ + refer('deprecated'), + refer('Deprecated').call([ + literalString( + 'This is an old representation declaration', + ), + ]), + ]), + ), + ), equalsDart(r''' extension type Foo(/// My favorite representation declaration. @deprecated @@ -134,12 +195,20 @@ void main() { test('extension type with implements', () { expect( - ExtensionType((b) => b - ..name = 'Foo' - ..implements.add(TypeReference((b) => b.symbol = 'num')) - ..representationDeclaration = RepresentationDeclaration((b) => b - ..declaredRepresentationType = TypeReference((b) => b.symbol = 'int') - ..name = 'bar')), + ExtensionType( + (b) => + b + ..name = 'Foo' + ..implements.add(TypeReference((b) => b.symbol = 'num')) + ..representationDeclaration = RepresentationDeclaration( + (b) => + b + ..declaredRepresentationType = TypeReference( + (b) => b.symbol = 'int', + ) + ..name = 'bar', + ), + ), equalsDart(r''' extension type Foo(int bar) implements num { } '''), @@ -148,15 +217,23 @@ void main() { test('extension type with multiple implements', () { expect( - ExtensionType((b) => b - ..name = 'Foo' - ..implements.addAll([ - TypeReference((b) => b.symbol = 'num'), - TypeReference((b) => b.symbol = 'Object') - ]) - ..representationDeclaration = RepresentationDeclaration((b) => b - ..declaredRepresentationType = TypeReference((b) => b.symbol = 'int') - ..name = 'bar')), + ExtensionType( + (b) => + b + ..name = 'Foo' + ..implements.addAll([ + TypeReference((b) => b.symbol = 'num'), + TypeReference((b) => b.symbol = 'Object'), + ]) + ..representationDeclaration = RepresentationDeclaration( + (b) => + b + ..declaredRepresentationType = TypeReference( + (b) => b.symbol = 'int', + ) + ..name = 'bar', + ), + ), equalsDart(r''' extension type Foo(int bar) implements num,Object { } '''), @@ -165,24 +242,49 @@ void main() { test('extension type with constructors', () { expect( - ExtensionType((b) => b - ..name = 'Foo' - ..primaryConstructorName = '_' - ..representationDeclaration = RepresentationDeclaration((b) => b - ..declaredRepresentationType = TypeReference((b) => b.symbol = 'int') - ..name = 'bar') - ..constructors.addAll([ - Constructor((b) => b.requiredParameters.add(Parameter((b) => b - ..toThis = true - ..name = 'bar'))), - Constructor((b) => b - ..name = 'named' - ..factory = true - ..requiredParameters.add(Parameter((b) => b - ..type = TypeReference((b) => b.symbol = 'int') - ..name = 'baz')) - ..body = const Code('return Foo(baz);')) - ])), + ExtensionType( + (b) => + b + ..name = 'Foo' + ..primaryConstructorName = '_' + ..representationDeclaration = RepresentationDeclaration( + (b) => + b + ..declaredRepresentationType = TypeReference( + (b) => b.symbol = 'int', + ) + ..name = 'bar', + ) + ..constructors.addAll([ + Constructor( + (b) => b.requiredParameters.add( + Parameter( + (b) => + b + ..toThis = true + ..name = 'bar', + ), + ), + ), + Constructor( + (b) => + b + ..name = 'named' + ..factory = true + ..requiredParameters.add( + Parameter( + (b) => + b + ..type = TypeReference( + (b) => b.symbol = 'int', + ) + ..name = 'baz', + ), + ) + ..body = const Code('return Foo(baz);'), + ), + ]), + ), equalsDart(r''' extension type Foo._(int bar) { Foo(this.bar); @@ -197,15 +299,28 @@ void main() { test('extension type with external field', () { expect( - ExtensionType((b) => b - ..name = 'Foo' - ..representationDeclaration = RepresentationDeclaration((b) => b - ..declaredRepresentationType = TypeReference((b) => b.symbol = 'int') - ..name = 'bar') - ..fields.add(Field((b) => b - ..external = true - ..type = TypeReference((b) => b.symbol = 'int') - ..name = 'property'))), + ExtensionType( + (b) => + b + ..name = 'Foo' + ..representationDeclaration = RepresentationDeclaration( + (b) => + b + ..declaredRepresentationType = TypeReference( + (b) => b.symbol = 'int', + ) + ..name = 'bar', + ) + ..fields.add( + Field( + (b) => + b + ..external = true + ..type = TypeReference((b) => b.symbol = 'int') + ..name = 'property', + ), + ), + ), equalsDart(r''' extension type Foo(int bar) { external int property; @@ -216,23 +331,37 @@ void main() { test('extension type with methods', () { expect( - ExtensionType((b) => b - ..name = 'Foo' - ..representationDeclaration = RepresentationDeclaration((b) => b - ..declaredRepresentationType = TypeReference((b) => b.symbol = 'int') - ..name = 'bar') - ..methods.addAll([ - Method((b) => b - ..type = MethodType.getter - ..returns = TypeReference((b) => b.symbol = 'int') - ..name = 'value' - ..body = const Code('return this.bar;')), - Method((b) => b - ..returns = TypeReference((b) => b.symbol = 'int') - ..name = 'getValue' - ..lambda = true - ..body = const Code('this.bar')) - ])), + ExtensionType( + (b) => + b + ..name = 'Foo' + ..representationDeclaration = RepresentationDeclaration( + (b) => + b + ..declaredRepresentationType = TypeReference( + (b) => b.symbol = 'int', + ) + ..name = 'bar', + ) + ..methods.addAll([ + Method( + (b) => + b + ..type = MethodType.getter + ..returns = TypeReference((b) => b.symbol = 'int') + ..name = 'value' + ..body = const Code('return this.bar;'), + ), + Method( + (b) => + b + ..returns = TypeReference((b) => b.symbol = 'int') + ..name = 'getValue' + ..lambda = true + ..body = const Code('this.bar'), + ), + ]), + ), equalsDart(r''' extension type Foo(int bar) { int get value { return this.bar; } diff --git a/pkgs/code_builder/test/specs/field_test.dart b/pkgs/code_builder/test/specs/field_test.dart index 0b8d6d905..a887fe77e 100644 --- a/pkgs/code_builder/test/specs/field_test.dart +++ b/pkgs/code_builder/test/specs/field_test.dart @@ -21,9 +21,12 @@ void main() { test('should create a typed field', () { expect( - Field((b) => b - ..name = 'foo' - ..type = refer('String')), + Field( + (b) => + b + ..name = 'foo' + ..type = refer('String'), + ), equalsDart(r''' String foo; '''), @@ -32,9 +35,12 @@ void main() { test('should create a final field', () { expect( - Field((b) => b - ..name = 'foo' - ..modifier = FieldModifier.final$), + Field( + (b) => + b + ..name = 'foo' + ..modifier = FieldModifier.final$, + ), equalsDart(r''' final foo; '''), @@ -43,9 +49,12 @@ void main() { test('should create a constant field', () { expect( - Field((b) => b - ..name = 'foo' - ..modifier = FieldModifier.constant), + Field( + (b) => + b + ..name = 'foo' + ..modifier = FieldModifier.constant, + ), equalsDart(r''' const foo; '''), @@ -54,9 +63,12 @@ void main() { test('should create a late field if using null-safety', () { expect( - Field((b) => b - ..late = true - ..name = 'foo'), + Field( + (b) => + b + ..late = true + ..name = 'foo', + ), equalsDart(r''' late var foo; ''', DartEmitter(useNullSafetySyntax: true)), @@ -65,9 +77,12 @@ void main() { test('should not create a late field if not using null-safety', () { expect( - Field((b) => b - ..late = true - ..name = 'foo'), + Field( + (b) => + b + ..late = true + ..name = 'foo', + ), equalsDart(r''' var foo; '''), @@ -76,10 +91,13 @@ void main() { test('should create a static late field', () { expect( - Field((b) => b - ..static = true - ..late = true - ..name = 'foo'), + Field( + (b) => + b + ..static = true + ..late = true + ..name = 'foo', + ), equalsDart(r''' static late var foo; ''', DartEmitter(useNullSafetySyntax: true)), @@ -88,9 +106,12 @@ void main() { test('should create a field with an assignment', () { expect( - Field((b) => b - ..name = 'foo' - ..assignment = const Code('1')), + Field( + (b) => + b + ..name = 'foo' + ..assignment = const Code('1'), + ), equalsDart(r''' var foo = 1; '''), @@ -99,11 +120,14 @@ void main() { test('should create a external field', () { expect( - Field((b) => b - ..name = 'value' - ..external = true - ..type = refer('double') - ..annotations.addAll([refer('Float').call([])])), + Field( + (b) => + b + ..name = 'value' + ..external = true + ..type = refer('double') + ..annotations.addAll([refer('Float').call([])]), + ), equalsDart(r''' @Float() external double value; @@ -113,12 +137,15 @@ void main() { test('should create a late static field', () { expect( - Field((b) => b - ..name = 'value' - ..static = true - ..late = true - ..type = refer('String') - ..annotations.addAll([refer('JS').call([])])), + Field( + (b) => + b + ..name = 'value' + ..static = true + ..late = true + ..type = refer('String') + ..annotations.addAll([refer('JS').call([])]), + ), equalsDart(r''' @JS() static late String value; @@ -128,12 +155,15 @@ void main() { test('should create an external static field', () { expect( - Field((b) => b - ..name = 'value' - ..external = true - ..static = true - ..type = refer('double') - ..annotations.addAll([refer('JS').call([])])), + Field( + (b) => + b + ..name = 'value' + ..external = true + ..static = true + ..type = refer('double') + ..annotations.addAll([refer('JS').call([])]), + ), equalsDart(r''' @JS() external static double value; diff --git a/pkgs/code_builder/test/specs/library_test.dart b/pkgs/code_builder/test/specs/library_test.dart index 8ea4c5811..426f44227 100644 --- a/pkgs/code_builder/test/specs/library_test.dart +++ b/pkgs/code_builder/test/specs/library_test.dart @@ -16,11 +16,10 @@ void main() { test('should emit a source file with leading line comments', () { expect( Library( - (b) => b - ..comments.add('Generated by foo.') - ..body.add( - Class((b) => b..name = 'Foo'), - ), + (b) => + b + ..comments.add('Generated by foo.') + ..body.add(Class((b) => b..name = 'Foo')), ), equalsDart(r''' // Generated by foo. @@ -33,15 +32,14 @@ void main() { test('should emit a source file with multiple leading comments', () { expect( Library( - (b) => b - ..comments.addAll([ - 'Generated by foo!', - '', - 'Avoid editing by hand.', - ]) - ..body.add( - Class((b) => b..name = 'Foo'), - ), + (b) => + b + ..comments.addAll([ + 'Generated by foo!', + '', + 'Avoid editing by hand.', + ]) + ..body.add(Class((b) => b..name = 'Foo')), ), equalsDart(r''' // Generated by foo! @@ -56,11 +54,10 @@ void main() { test('should emit a source file with a generated-by comment', () { expect( Library( - (b) => b - ..generatedByComment = 'Generated by fooBar.' - ..body.add( - Class((b) => b..name = 'Foo'), - ), + (b) => + b + ..generatedByComment = 'Generated by fooBar.' + ..body.add(Class((b) => b..name = 'Foo')), ), equalsDart(r''' // Generated by fooBar. @@ -73,11 +70,10 @@ void main() { test('should emit a source file with ignore comments', () { expect( Library( - (b) => b - ..ignoreForFile.add('sort_constructors_first') - ..body.add( - Class((b) => b..name = 'Foo'), - ), + (b) => + b + ..ignoreForFile.add('sort_constructors_first') + ..body.add(Class((b) => b..name = 'Foo')), ), equalsDart(r''' // ignore_for_file: sort_constructors_first @@ -90,16 +86,15 @@ void main() { test('should emit a source file with multiple, sorted ignore comments', () { expect( Library( - (b) => b - ..ignoreForFile.addAll([ - 'type=lint', - 'sort_constructors_first', - 'implementation_imports', - 'file_names', - ]) - ..body.add( - Class((b) => b..name = 'Foo'), - ), + (b) => + b + ..ignoreForFile.addAll([ + 'type=lint', + 'sort_constructors_first', + 'implementation_imports', + 'file_names', + ]) + ..body.add(Class((b) => b..name = 'Foo')), ), equalsDart(r''' // ignore_for_file: file_names, implementation_imports, sort_constructors_first @@ -110,19 +105,19 @@ void main() { ); }); - test('should emit with line comments, generated-by, and ignore-for-file', - () { - expect( - Library( - (b) => b - ..comments.add('Generic copyright statement.') - ..generatedByComment = 'Generated by fooBar.' - ..ignoreForFile.add('sort_constructors_first') - ..body.add( - Class((b) => b..name = 'Foo'), - ), - ), - equalsDart(r''' + test( + 'should emit with line comments, generated-by, and ignore-for-file', + () { + expect( + Library( + (b) => + b + ..comments.add('Generic copyright statement.') + ..generatedByComment = 'Generated by fooBar.' + ..ignoreForFile.add('sort_constructors_first') + ..body.add(Class((b) => b..name = 'Foo')), + ), + equalsDart(r''' // Generic copyright statement. // Generated by fooBar. @@ -131,17 +126,26 @@ void main() { class Foo { } ''', DartEmitter(allocator: Allocator())), - ); - }); + ); + }, + ); test('should emit a source file with manual imports', () { expect( - Library((b) => b - ..directives.add(Directive.import('dart:collection')) - ..body.add(Field((b) => b - ..name = 'test' - ..modifier = FieldModifier.final$ - ..assignment = $LinkedHashMap.newInstance([]).code))), + Library( + (b) => + b + ..directives.add(Directive.import('dart:collection')) + ..body.add( + Field( + (b) => + b + ..name = 'test' + ..modifier = FieldModifier.final$ + ..assignment = $LinkedHashMap.newInstance([]).code, + ), + ), + ), equalsDart(r''' import 'dart:collection'; @@ -153,13 +157,11 @@ void main() { test('should emit a source file with a deferred import', () { expect( Library( - (b) => b - ..directives.add( - Directive.importDeferredAs( - 'package:foo/foo.dart', - 'foo', - ), - ), + (b) => + b + ..directives.add( + Directive.importDeferredAs('package:foo/foo.dart', 'foo'), + ), ), equalsDart(r''' import 'package:foo/foo.dart' deferred as foo; @@ -170,13 +172,14 @@ void main() { test('should emit a source file with a "show" combinator', () { expect( Library( - (b) => b - ..directives.add( - Directive.import( - 'package:foo/foo.dart', - show: ['Foo', 'Bar'], - ), - ), + (b) => + b + ..directives.add( + Directive.import( + 'package:foo/foo.dart', + show: ['Foo', 'Bar'], + ), + ), ), equalsDart(r''' import 'package:foo/foo.dart' show Foo, Bar; @@ -187,13 +190,14 @@ void main() { test('should emit a source file with a "hide" combinator', () { expect( Library( - (b) => b - ..directives.add( - Directive.import( - 'package:foo/foo.dart', - hide: ['Foo', 'Bar'], - ), - ), + (b) => + b + ..directives.add( + Directive.import( + 'package:foo/foo.dart', + hide: ['Foo', 'Bar'], + ), + ), ), equalsDart(r''' import 'package:foo/foo.dart' hide Foo, Bar; @@ -203,11 +207,21 @@ void main() { test('should emit a source file with allocation', () { expect( - Library((b) => b - ..body.add(Field((b) => b - ..name = 'test' - ..modifier = FieldModifier.final$ - ..assignment = Code.scope((a) => '${a($LinkedHashMap)}()')))), + Library( + (b) => + b + ..body.add( + Field( + (b) => + b + ..name = 'test' + ..modifier = FieldModifier.final$ + ..assignment = Code.scope( + (a) => '${a($LinkedHashMap)}()', + ), + ), + ), + ), equalsDart(r''' import 'dart:collection'; @@ -218,11 +232,21 @@ void main() { test('should emit a source file with allocation + prefixing', () { expect( - Library((b) => b - ..body.add(Field((b) => b - ..name = 'test' - ..modifier = FieldModifier.final$ - ..assignment = Code.scope((a) => '${a($LinkedHashMap)}()')))), + Library( + (b) => + b + ..body.add( + Field( + (b) => + b + ..name = 'test' + ..modifier = FieldModifier.final$ + ..assignment = Code.scope( + (a) => '${a($LinkedHashMap)}()', + ), + ), + ), + ), equalsDart(r''' // ignore_for_file: no_leading_underscores_for_library_prefixes import 'dart:collection' as _i1; @@ -234,12 +258,7 @@ void main() { test('should emit a source file with part directives', () { expect( - Library( - (b) => b - ..directives.add( - Directive.part('test.g.dart'), - ), - ), + Library((b) => b..directives.add(Directive.part('test.g.dart'))), equalsDart(r''' part 'test.g.dart'; ''', DartEmitter()), @@ -248,12 +267,7 @@ void main() { test('should emit a source file with part of directives', () { expect( - Library( - (b) => b - ..directives.add( - Directive.partOf('test.dart'), - ), - ), + Library((b) => b..directives.add(Directive.partOf('test.dart'))), equalsDart(r''' part of 'test.dart'; ''', DartEmitter()), @@ -263,11 +277,10 @@ void main() { test('should emit a source file with annotations', () { expect( Library( - (b) => b - ..name = 'js_interop' - ..annotations.add( - refer('JS', 'package:js/js.dart').call([]), - ), + (b) => + b + ..name = 'js_interop' + ..annotations.add(refer('JS', 'package:js/js.dart').call([])), ), equalsDart(r''' @JS() @@ -280,10 +293,7 @@ void main() { test('should emit an unnamed library source file with annotations', () { expect( Library( - (b) => b - ..annotations.add( - refer('JS', 'package:js/js.dart').call([]), - ), + (b) => b..annotations.add(refer('JS', 'package:js/js.dart').call([])), ), equalsDart(r''' @JS() @@ -295,14 +305,7 @@ void main() { test('should emit an unnamed library source file with documentation', () { expect( - Library( - (b) => b - ..docs.addAll( - const [ - '/// My favorite library.', - ], - ), - ), + Library((b) => b..docs.addAll(const ['/// My favorite library.'])), equalsDart(r''' /// My favorite library. library; diff --git a/pkgs/code_builder/test/specs/method_test.dart b/pkgs/code_builder/test/specs/method_test.dart index 5621bc52c..0a3362110 100644 --- a/pkgs/code_builder/test/specs/method_test.dart +++ b/pkgs/code_builder/test/specs/method_test.dart @@ -21,10 +21,13 @@ void main() { test('should create an async method', () { expect( - Method((b) => b - ..name = 'foo' - ..modifier = MethodModifier.async - ..body = literalNull.code), + Method( + (b) => + b + ..name = 'foo' + ..modifier = MethodModifier.async + ..body = literalNull.code, + ), equalsDart(r''' foo() async => null '''), @@ -33,10 +36,13 @@ void main() { test('should create an async* method', () { expect( - Method((b) => b - ..name = 'foo' - ..modifier = MethodModifier.asyncStar - ..body = literalNull.code), + Method( + (b) => + b + ..name = 'foo' + ..modifier = MethodModifier.asyncStar + ..body = literalNull.code, + ), equalsDart(r''' foo() async* => null '''), @@ -45,10 +51,13 @@ void main() { test('should create an sync* method', () { expect( - Method((b) => b - ..name = 'foo' - ..modifier = MethodModifier.syncStar - ..body = literalNull.code), + Method( + (b) => + b + ..name = 'foo' + ..modifier = MethodModifier.syncStar + ..body = literalNull.code, + ), equalsDart(r''' foo() sync* => null '''), @@ -57,10 +66,13 @@ void main() { test('should create a lambda method implicitly', () { expect( - Method((b) => b - ..name = 'returnsTrue' - ..returns = refer('bool') - ..body = literalTrue.code), + Method( + (b) => + b + ..name = 'returnsTrue' + ..returns = refer('bool') + ..body = literalTrue.code, + ), equalsDart(r''' bool returnsTrue() => true '''), @@ -69,10 +81,13 @@ void main() { test('should create a lambda method if the value is cast', () { expect( - Method((b) => b - ..name = 'returnsCastedValue' - ..returns = refer('Foo') - ..body = refer('bar').asA(refer('Foo')).code), + Method( + (b) => + b + ..name = 'returnsCastedValue' + ..returns = refer('Foo') + ..body = refer('bar').asA(refer('Foo')).code, + ), equalsDart(r''' Foo returnsCastedValue() => (bar as Foo) '''), @@ -81,9 +96,12 @@ void main() { test('should create a normal method implicitly', () { expect( - Method.returnsVoid((b) => b - ..name = 'assignTrue' - ..body = refer('topLevelFoo').assign(literalTrue).statement), + Method.returnsVoid( + (b) => + b + ..name = 'assignTrue' + ..body = refer('topLevelFoo').assign(literalTrue).statement, + ), equalsDart(r''' void assignTrue() { topLevelFoo = true; @@ -94,10 +112,13 @@ void main() { test('should create a getter', () { expect( - Method((b) => b - ..name = 'foo' - ..external = true - ..type = MethodType.getter), + Method( + (b) => + b + ..name = 'foo' + ..external = true + ..type = MethodType.getter, + ), equalsDart(r''' external get foo; '''), @@ -106,11 +127,14 @@ void main() { test('should create a setter', () { expect( - Method((b) => b - ..name = 'foo' - ..external = true - ..requiredParameters.add(Parameter((b) => b..name = 'foo')) - ..type = MethodType.setter), + Method( + (b) => + b + ..name = 'foo' + ..external = true + ..requiredParameters.add(Parameter((b) => b..name = 'foo')) + ..type = MethodType.setter, + ), equalsDart(r''' external set foo(foo); '''), @@ -119,9 +143,12 @@ void main() { test('should create a method with a return type', () { expect( - Method((b) => b - ..name = 'foo' - ..returns = refer('String')), + Method( + (b) => + b + ..name = 'foo' + ..returns = refer('String'), + ), equalsDart(r''' String foo(); '''), @@ -139,39 +166,50 @@ void main() { test('should create a method with a function type return type', () { expect( - Method((b) => b - ..name = 'foo' - ..returns = FunctionType((b) => b - ..returnType = refer('String') - ..requiredParameters.addAll([ - refer('int'), - ]))), + Method( + (b) => + b + ..name = 'foo' + ..returns = FunctionType( + (b) => + b + ..returnType = refer('String') + ..requiredParameters.addAll([refer('int')]), + ), + ), equalsDart(r''' String Function(int) foo(); '''), ); }); - test('should create a function type with an optional positional parameter', - () { - expect( - FunctionType((b) => b - ..returnType = refer('String') - ..optionalParameters.add(refer('int'))), - equalsDart(r''' + test( + 'should create a function type with an optional positional parameter', + () { + expect( + FunctionType( + (b) => + b + ..returnType = refer('String') + ..optionalParameters.add(refer('int')), + ), + equalsDart(r''' String Function([int]) '''), - ); - }); + ); + }, + ); - test( - 'should create a function type with a required ' + test('should create a function type with a required ' 'and an optional positional parameter', () { expect( - FunctionType((b) => b - ..returnType = refer('String') - ..requiredParameters.add(refer('int')) - ..optionalParameters.add(refer('int'))), + FunctionType( + (b) => + b + ..returnType = refer('String') + ..requiredParameters.add(refer('int')) + ..optionalParameters.add(refer('int')), + ), equalsDart(r''' String Function(int, [int, ]) '''), @@ -189,23 +227,28 @@ void main() { test('should create a function type with an optional named parameter', () { expect( - FunctionType((b) => b - ..returnType = refer('String') - ..namedParameters['named'] = refer('int')), + FunctionType( + (b) => + b + ..returnType = refer('String') + ..namedParameters['named'] = refer('int'), + ), equalsDart(r''' String Function({int named}) '''), ); }); - test( - 'should create a function type with a required ' + test('should create a function type with a required ' 'and an optional named parameter', () { expect( - FunctionType((b) => b - ..returnType = refer('String') - ..requiredParameters.add(refer('int')) - ..namedParameters['named'] = refer('int')), + FunctionType( + (b) => + b + ..returnType = refer('String') + ..requiredParameters.add(refer('int')) + ..namedParameters['named'] = refer('int'), + ), equalsDart(r''' String Function(int, {int named, }) '''), @@ -214,23 +257,28 @@ void main() { test('should create a function type with a required named parameter', () { expect( - FunctionType((b) => b - ..returnType = refer('String') - ..namedRequiredParameters['named'] = refer('int')), + FunctionType( + (b) => + b + ..returnType = refer('String') + ..namedRequiredParameters['named'] = refer('int'), + ), equalsDart(r''' String Function({required int named}) '''), ); }); - test( - 'should create a function type with a required named and an optional ' + test('should create a function type with a required named and an optional ' 'named parameter', () { expect( - FunctionType((b) => b - ..returnType = refer('String') - ..namedRequiredParameters['named'] = refer('int') - ..namedParameters['optional'] = refer('int')), + FunctionType( + (b) => + b + ..returnType = refer('String') + ..namedRequiredParameters['named'] = refer('int') + ..namedParameters['optional'] = refer('int'), + ), equalsDart(r''' String Function({required int named, int optional, }) '''), @@ -239,9 +287,12 @@ void main() { test('should create a typedef to a reference', () { expect( - TypeDef((b) => b - ..name = 'i32' - ..definition = const Reference('int')), + TypeDef( + (b) => + b + ..name = 'i32' + ..definition = const Reference('int'), + ), equalsDart(r''' typedef i32 = int; '''), @@ -250,11 +301,17 @@ void main() { test('should create a typedef to a function type', () { expect( - TypeDef((b) => b - ..name = 'MyMapper' - ..definition = FunctionType((b) => b - ..returnType = refer('String') - ..optionalParameters.add(refer('int')))), + TypeDef( + (b) => + b + ..name = 'MyMapper' + ..definition = FunctionType( + (b) => + b + ..returnType = refer('String') + ..optionalParameters.add(refer('int')), + ), + ), equalsDart(r''' typedef MyMapper = String Function([int]); '''), @@ -263,15 +320,22 @@ void main() { test('should create a method with a nested function type return type', () { expect( - Method((b) => b - ..name = 'foo' - ..returns = FunctionType((b) => b - ..returnType = FunctionType((b) => b - ..returnType = refer('String') - ..requiredParameters.add(refer('String'))) - ..requiredParameters.addAll([ - refer('int'), - ]))), + Method( + (b) => + b + ..name = 'foo' + ..returns = FunctionType( + (b) => + b + ..returnType = FunctionType( + (b) => + b + ..returnType = refer('String') + ..requiredParameters.add(refer('String')), + ) + ..requiredParameters.addAll([refer('int')]), + ), + ), equalsDart(r''' String Function(String) Function(int) foo(); '''), @@ -280,39 +344,71 @@ void main() { test('should create a method with a function type argument', () { expect( - Method((b) => b - ..name = 'foo' - ..requiredParameters.add(Parameter((b) => b - ..type = FunctionType((b) => b - ..returnType = refer('String') - ..requiredParameters.add(refer('int'))) - ..name = 'argument'))), - equalsDart(r''' + Method( + (b) => + b + ..name = 'foo' + ..requiredParameters.add( + Parameter( + (b) => + b + ..type = FunctionType( + (b) => + b + ..returnType = refer('String') + ..requiredParameters.add(refer('int')), + ) + ..name = 'argument', + ), + ), + ), + equalsDart(r''' foo(String Function(int) argument); - ''')); + '''), + ); }); test('should create a method with a nested function type argument', () { expect( - Method((b) => b - ..name = 'foo' - ..requiredParameters.add(Parameter((b) => b - ..type = FunctionType((b) => b - ..returnType = FunctionType((b) => b - ..returnType = refer('String') - ..requiredParameters.add(refer('String'))) - ..requiredParameters.add(refer('int'))) - ..name = 'argument'))), - equalsDart(r''' + Method( + (b) => + b + ..name = 'foo' + ..requiredParameters.add( + Parameter( + (b) => + b + ..type = FunctionType( + (b) => + b + ..returnType = FunctionType( + (b) => + b + ..returnType = refer('String') + ..requiredParameters.add( + refer('String'), + ), + ) + ..requiredParameters.add(refer('int')), + ) + ..name = 'argument', + ), + ), + ), + equalsDart(r''' foo(String Function(String) Function(int) argument); - ''')); + '''), + ); }); test('should create a method with generic types', () { expect( - Method((b) => b - ..name = 'foo' - ..types.add(refer('T'))), + Method( + (b) => + b + ..name = 'foo' + ..types.add(refer('T')), + ), equalsDart(r''' foo(); '''), @@ -321,9 +417,12 @@ void main() { test('should create an external method', () { expect( - Method((b) => b - ..name = 'foo' - ..external = true), + Method( + (b) => + b + ..name = 'foo' + ..external = true, + ), equalsDart(r''' external foo(); '''), @@ -332,9 +431,12 @@ void main() { test('should create a method with a body', () { expect( - Method((b) => b - ..name = 'foo' - ..body = const Code('return 1+ 2;')), + Method( + (b) => + b + ..name = 'foo' + ..body = const Code('return 1+ 2;'), + ), equalsDart(r''' foo() { return 1 + 2; @@ -345,10 +447,13 @@ void main() { test('should create a lambda method (explicitly)', () { expect( - Method((b) => b - ..name = 'foo' - ..lambda = true - ..body = const Code('1 + 2')), + Method( + (b) => + b + ..name = 'foo' + ..lambda = true + ..body = const Code('1 + 2'), + ), equalsDart(r''' foo() => 1 + 2 '''), @@ -358,11 +463,12 @@ void main() { test('should create a method with a body with references', () { final $LinkedHashMap = refer('LinkedHashMap', 'dart:collection'); expect( - Method((b) => b - ..name = 'foo' - ..body = Code.scope( - (a) => 'return ${a($LinkedHashMap)}();', - )), + Method( + (b) => + b + ..name = 'foo' + ..body = Code.scope((a) => 'return ${a($LinkedHashMap)}();'), + ), equalsDart(r''' foo() { return LinkedHashMap(); @@ -374,11 +480,10 @@ void main() { test('should create a method with a parameter', () { expect( Method( - (b) => b - ..name = 'fib' - ..requiredParameters.add( - Parameter((b) => b.name = 'i'), - ), + (b) => + b + ..name = 'fib' + ..requiredParameters.add(Parameter((b) => b.name = 'i')), ), equalsDart(r''' fib(i); @@ -389,13 +494,17 @@ void main() { test('should create a method with an annotated parameter', () { expect( Method( - (b) => b - ..name = 'fib' - ..requiredParameters.add( - Parameter((b) => b - ..name = 'i' - ..annotations.add(refer('deprecated'))), - ), + (b) => + b + ..name = 'fib' + ..requiredParameters.add( + Parameter( + (b) => + b + ..name = 'i' + ..annotations.add(refer('deprecated')), + ), + ), ), equalsDart(r''' fib(@deprecated i); @@ -406,15 +515,17 @@ void main() { test('should create a method with a parameter with a type', () { expect( Method( - (b) => b - ..name = 'fib' - ..requiredParameters.add( - Parameter( - (b) => b - ..name = 'i' - ..type = refer('int').type, - ), - ), + (b) => + b + ..name = 'fib' + ..requiredParameters.add( + Parameter( + (b) => + b + ..name = 'i' + ..type = refer('int').type, + ), + ), ), equalsDart(r''' fib(int i); @@ -425,16 +536,18 @@ void main() { test('should create a method with a covariant parameter with a type', () { expect( Method( - (b) => b - ..name = 'fib' - ..requiredParameters.add( - Parameter( - (b) => b - ..name = 'i' - ..covariant = true - ..type = refer('int').type, - ), - ), + (b) => + b + ..name = 'fib' + ..requiredParameters.add( + Parameter( + (b) => + b + ..name = 'i' + ..covariant = true + ..type = refer('int').type, + ), + ), ), equalsDart(r''' fib(covariant int i); @@ -445,23 +558,36 @@ void main() { test('should create a method with a parameter with a generic type', () { expect( Method( - (b) => b - ..name = 'foo' - ..types.add(TypeReference((b) => b - ..symbol = 'T' - ..bound = refer('Iterable'))) - ..requiredParameters.addAll([ - Parameter( - (b) => b - ..name = 't' - ..type = refer('T'), - ), - Parameter((b) => b - ..name = 'x' - ..type = TypeReference((b) => b - ..symbol = 'X' - ..types.add(refer('T')))), - ]), + (b) => + b + ..name = 'foo' + ..types.add( + TypeReference( + (b) => + b + ..symbol = 'T' + ..bound = refer('Iterable'), + ), + ) + ..requiredParameters.addAll([ + Parameter( + (b) => + b + ..name = 't' + ..type = refer('T'), + ), + Parameter( + (b) => + b + ..name = 'x' + ..type = TypeReference( + (b) => + b + ..symbol = 'X' + ..types.add(refer('T')), + ), + ), + ]), ), equalsDart(r''' foo(T t, X x, ); @@ -472,11 +598,10 @@ void main() { test('should create a method with an optional parameter', () { expect( Method( - (b) => b - ..name = 'fib' - ..optionalParameters.add( - Parameter((b) => b.name = 'i'), - ), + (b) => + b + ..name = 'fib' + ..optionalParameters.add(Parameter((b) => b.name = 'i')), ), equalsDart(r''' fib([i]); @@ -487,12 +612,13 @@ void main() { test('should create a method with multiple optional parameters', () { expect( Method( - (b) => b - ..name = 'foo' - ..optionalParameters.addAll([ - Parameter((b) => b.name = 'a'), - Parameter((b) => b.name = 'b'), - ]), + (b) => + b + ..name = 'foo' + ..optionalParameters.addAll([ + Parameter((b) => b.name = 'a'), + Parameter((b) => b.name = 'b'), + ]), ), equalsDart(r''' foo([a, b, ]); @@ -503,13 +629,17 @@ void main() { test('should create a method with an optional parameter with a value', () { expect( Method( - (b) => b - ..name = 'fib' - ..optionalParameters.add( - Parameter((b) => b - ..name = 'i' - ..defaultTo = const Code('0')), - ), + (b) => + b + ..name = 'fib' + ..optionalParameters.add( + Parameter( + (b) => + b + ..name = 'i' + ..defaultTo = const Code('0'), + ), + ), ), equalsDart(r''' fib([i = 0]); @@ -520,17 +650,19 @@ void main() { test('should create a method with a named required parameter', () { expect( Method( - (b) => b - ..name = 'fib' - ..optionalParameters.add( - Parameter( - (b) => b - ..name = 'i' - ..named = true - ..required = true - ..type = refer('int').type, - ), - ), + (b) => + b + ..name = 'fib' + ..optionalParameters.add( + Parameter( + (b) => + b + ..name = 'i' + ..named = true + ..required = true + ..type = refer('int').type, + ), + ), ), equalsDart(r''' fib({required int i}); @@ -541,18 +673,20 @@ void main() { test('should create a method with a named required covariant parameter', () { expect( Method( - (b) => b - ..name = 'fib' - ..optionalParameters.add( - Parameter( - (b) => b - ..name = 'i' - ..named = true - ..required = true - ..covariant = true - ..type = refer('int').type, - ), - ), + (b) => + b + ..name = 'fib' + ..optionalParameters.add( + Parameter( + (b) => + b + ..name = 'i' + ..named = true + ..required = true + ..covariant = true + ..type = refer('int').type, + ), + ), ), equalsDart(r''' fib({required covariant int i}); @@ -563,13 +697,17 @@ void main() { test('should create a method with a named optional parameter', () { expect( Method( - (b) => b - ..name = 'fib' - ..optionalParameters.add( - Parameter((b) => b - ..named = true - ..name = 'i'), - ), + (b) => + b + ..name = 'fib' + ..optionalParameters.add( + Parameter( + (b) => + b + ..named = true + ..name = 'i', + ), + ), ), equalsDart(r''' fib({i}); @@ -580,14 +718,18 @@ void main() { test('should create a method with a named optional parameter with value', () { expect( Method( - (b) => b - ..name = 'fib' - ..optionalParameters.add( - Parameter((b) => b - ..named = true - ..name = 'i' - ..defaultTo = const Code('0')), - ), + (b) => + b + ..name = 'fib' + ..optionalParameters.add( + Parameter( + (b) => + b + ..named = true + ..name = 'i' + ..defaultTo = const Code('0'), + ), + ), ), equalsDart(r''' fib({i = 0}); @@ -598,16 +740,18 @@ void main() { test('should create a method with a mix of parameters', () { expect( Method( - (b) => b - ..name = 'foo' - ..requiredParameters.add( - Parameter((b) => b..name = 'a'), - ) - ..optionalParameters.add( - Parameter((b) => b - ..named = true - ..name = 'b'), - ), + (b) => + b + ..name = 'foo' + ..requiredParameters.add(Parameter((b) => b..name = 'a')) + ..optionalParameters.add( + Parameter( + (b) => + b + ..named = true + ..name = 'b', + ), + ), ), equalsDart(r''' foo(a, {b, }); @@ -618,11 +762,10 @@ void main() { test('should create a method as a closure', () { expect( Method( - (b) => b - ..requiredParameters.add( - Parameter((b) => b..name = 'a'), - ) - ..body = const Code(''), + (b) => + b + ..requiredParameters.add(Parameter((b) => b..name = 'a')) + ..body = const Code(''), ).closure, equalsDart(r''' (a) { } diff --git a/pkgs/code_builder/test/specs/mixin_test.dart b/pkgs/code_builder/test/specs/mixin_test.dart index e167d02a5..97e9529d1 100644 --- a/pkgs/code_builder/test/specs/mixin_test.dart +++ b/pkgs/code_builder/test/specs/mixin_test.dart @@ -21,9 +21,12 @@ void main() { test('should create a base mixin', () { expect( - Mixin((b) => b - ..name = 'Foo' - ..base = true), + Mixin( + (b) => + b + ..name = 'Foo' + ..base = true, + ), equalsDart(r''' base mixin Foo {} '''), @@ -33,13 +36,10 @@ void main() { test('should create a mixin with documentations', () { expect( Mixin( - (b) => b - ..name = 'Foo' - ..docs.addAll( - const [ - '/// My favorite mixin.', - ], - ), + (b) => + b + ..name = 'Foo' + ..docs.addAll(const ['/// My favorite mixin.']), ), equalsDart(r''' /// My favorite mixin. @@ -51,12 +51,15 @@ void main() { test('should create a mixin with annotations', () { expect( Mixin( - (b) => b - ..name = 'Foo' - ..annotations.addAll([ - refer('deprecated'), - refer('Deprecated').call([literalString('This is an old mixin')]) - ]), + (b) => + b + ..name = 'Foo' + ..annotations.addAll([ + refer('deprecated'), + refer( + 'Deprecated', + ).call([literalString('This is an old mixin')]), + ]), ), equalsDart(r''' @deprecated @@ -68,9 +71,12 @@ void main() { test('should create a mixin with a generic type', () { expect( - Mixin((b) => b - ..name = 'List' - ..types.add(refer('T'))), + Mixin( + (b) => + b + ..name = 'List' + ..types.add(refer('T')), + ), equalsDart(r''' mixin List {} '''), @@ -80,12 +86,10 @@ void main() { test('should create a mixin with multiple generic types', () { expect( Mixin( - (b) => b - ..name = 'Map' - ..types.addAll([ - refer('K'), - refer('V'), - ]), + (b) => + b + ..name = 'Map' + ..types.addAll([refer('K'), refer('V')]), ), equalsDart(r''' mixin Map {} @@ -95,13 +99,24 @@ void main() { test('should create a mixin with a bound generic type', () { expect( - Mixin((b) => b - ..name = 'Comparable' - ..types.add(TypeReference((b) => b - ..symbol = 'T' - ..bound = TypeReference((b) => b - ..symbol = 'Comparable' - ..types.add(refer('T').type))))), + Mixin( + (b) => + b + ..name = 'Comparable' + ..types.add( + TypeReference( + (b) => + b + ..symbol = 'T' + ..bound = TypeReference( + (b) => + b + ..symbol = 'Comparable' + ..types.add(refer('T').type), + ), + ), + ), + ), equalsDart(r''' mixin Comparable> {} '''), @@ -110,9 +125,12 @@ void main() { test('should create a mixin on another mixin', () { expect( - Mixin((b) => b - ..name = 'Foo' - ..on = TypeReference((b) => b.symbol = 'Bar')), + Mixin( + (b) => + b + ..name = 'Foo' + ..on = TypeReference((b) => b.symbol = 'Bar'), + ), equalsDart(r''' mixin Foo on Bar {} '''), @@ -121,10 +139,13 @@ void main() { test('should create a mixin implementing another mixin', () { expect( - Mixin((b) => b - ..name = 'Foo' - ..on = TypeReference((b) => b.symbol = 'Bar') - ..implements.add(TypeReference((b) => b.symbol = 'Foo'))), + Mixin( + (b) => + b + ..name = 'Foo' + ..on = TypeReference((b) => b.symbol = 'Bar') + ..implements.add(TypeReference((b) => b.symbol = 'Foo')), + ), equalsDart(r''' mixin Foo on Bar implements Foo {} '''), @@ -133,11 +154,19 @@ void main() { test('should create a mixin with a method', () { expect( - Mixin((b) => b - ..name = 'Foo' - ..methods.add(Method((b) => b - ..name = 'foo' - ..body = const Code('return 1+ 2;')))), + Mixin( + (b) => + b + ..name = 'Foo' + ..methods.add( + Method( + (b) => + b + ..name = 'foo' + ..body = const Code('return 1+ 2;'), + ), + ), + ), equalsDart(r''' mixin Foo { foo() { diff --git a/pkgs/code_builder/test/specs/record_type_test.dart b/pkgs/code_builder/test/specs/record_type_test.dart index 0084c507d..55f5318ed 100644 --- a/pkgs/code_builder/test/specs/record_type_test.dart +++ b/pkgs/code_builder/test/specs/record_type_test.dart @@ -15,9 +15,12 @@ void main() { setUp(() => emitter = DartEmitter.scoped(useNullSafetySyntax: true)); final intRef = TypeReference((b) => b.symbol = 'int'); - TypeReference listRef(TypeReference argType) => TypeReference((b) => b - ..symbol = 'List' - ..types.add(argType)); + TypeReference listRef(TypeReference argType) => TypeReference( + (b) => + b + ..symbol = 'List' + ..types.add(argType), + ); test('should create an empty record type', () { expect(RecordType(), equalsDart('()', emitter)); @@ -25,44 +28,71 @@ void main() { test('should create a record type with positional fields', () { expect( - RecordType((b) => b - ..positionalFieldTypes.addAll( - [intRef, listRef(intRef).rebuild((b) => b..isNullable = true)]) - ..isNullable = true), + RecordType( + (b) => + b + ..positionalFieldTypes.addAll([ + intRef, + listRef(intRef).rebuild((b) => b..isNullable = true), + ]) + ..isNullable = true, + ), equalsDart('(int, List?)?', emitter), ); }); test('should create a record type with one positional field', () { - expect(RecordType((b) => b..positionalFieldTypes.add(intRef)), - equalsDart('(int,)', emitter)); + expect( + RecordType((b) => b..positionalFieldTypes.add(intRef)), + equalsDart('(int,)', emitter), + ); }); test('should create a record type with named fields', () { expect( - RecordType((b) => b - ..namedFieldTypes.addAll({ - 'named': intRef, - 'other': listRef(intRef), - })), - equalsDart('({int named, List other})', emitter)); + RecordType( + (b) => + b + ..namedFieldTypes.addAll({ + 'named': intRef, + 'other': listRef(intRef), + }), + ), + equalsDart('({int named, List other})', emitter), + ); }); test('should create a record type with both positional and named fields', () { expect( - RecordType((b) => b - ..positionalFieldTypes.add(listRef(intRef)) - ..namedFieldTypes.addAll({'named': intRef}) - ..isNullable = true), - equalsDart('(List, {int named})?', emitter)); + RecordType( + (b) => + b + ..positionalFieldTypes.add(listRef(intRef)) + ..namedFieldTypes.addAll({'named': intRef}) + ..isNullable = true, + ), + equalsDart('(List, {int named})?', emitter), + ); }); test('should create a nested record type', () { expect( - RecordType((b) => b - ..positionalFieldTypes.add(RecordType((b) => b - ..namedFieldTypes.addAll({'named': intRef, 'other': intRef}) - ..isNullable = true))), - equalsDart('(({int named, int other})?,)', emitter)); + RecordType( + (b) => + b + ..positionalFieldTypes.add( + RecordType( + (b) => + b + ..namedFieldTypes.addAll({ + 'named': intRef, + 'other': intRef, + }) + ..isNullable = true, + ), + ), + ), + equalsDart('(({int named, int other})?,)', emitter), + ); }); } diff --git a/pkgs/code_builder/test/specs/type_reference_test.dart b/pkgs/code_builder/test/specs/type_reference_test.dart index 35365cbf9..9291cad85 100644 --- a/pkgs/code_builder/test/specs/type_reference_test.dart +++ b/pkgs/code_builder/test/specs/type_reference_test.dart @@ -12,9 +12,12 @@ void main() { test('should create a nullable type in a pre-Null Safety library', () { expect( - TypeReference((b) => b - ..symbol = 'Foo' - ..isNullable = true), + TypeReference( + (b) => + b + ..symbol = 'Foo' + ..isNullable = true, + ), equalsDart(r''' Foo '''), @@ -28,9 +31,12 @@ void main() { test('should create a nullable type', () { expect( - TypeReference((b) => b - ..symbol = 'Foo' - ..isNullable = true), + TypeReference( + (b) => + b + ..symbol = 'Foo' + ..isNullable = true, + ), equalsDart(r'Foo?', emitter), ); }); @@ -44,11 +50,19 @@ void main() { test('should create a type with nullable type arguments', () { expect( - TypeReference((b) => b - ..symbol = 'List' - ..types.add(TypeReference((b) => b - ..symbol = 'int' - ..isNullable = true))), + TypeReference( + (b) => + b + ..symbol = 'List' + ..types.add( + TypeReference( + (b) => + b + ..symbol = 'int' + ..isNullable = true, + ), + ), + ), equalsDart(r'List', emitter), ); }); From 58b51b325d5622e52b0a6a1788c9728142599260 Mon Sep 17 00:00:00 2001 From: Devon Carew Date: Thu, 11 Sep 2025 13:50:35 -0700 Subject: [PATCH 44/67] Update README.md (#2161) --- pkgs/mime/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkgs/mime/README.md b/pkgs/mime/README.md index da06de62b..6ba58de02 100644 --- a/pkgs/mime/README.md +++ b/pkgs/mime/README.md @@ -1,9 +1,9 @@ -[![Build Status](https://github.com/dart-lang/tools/actions/workflows/mime.yml/badge.svg)](https://github.com/dart-lang/tools/actions/workflows/mime.yml) +[![package:mime](https://github.com/dart-lang/tools/actions/workflows/mime.yaml/badge.svg)](https://github.com/dart-lang/tools/actions/workflows/mime.yaml) [![Pub Package](https://img.shields.io/pub/v/mime.svg)](https://pub.dev/packages/mime) [![package publisher](https://img.shields.io/pub/publisher/mime.svg)](https://pub.dev/packages/mime/publisher) -Package for working with MIME type definitions and for processing -streams of MIME multipart media types. +Package for working with MIME type definitions and for processing streams of +MIME multipart media types. ## Determining the MIME type for a file From 4b9ab594721bb5d5b5e68ce7352bb0fd8c113717 Mon Sep 17 00:00:00 2001 From: Devon Carew Date: Thu, 11 Sep 2025 13:51:22 -0700 Subject: [PATCH 45/67] Update gemini config.yaml (#2162) --- .gemini/config.yaml | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/.gemini/config.yaml b/.gemini/config.yaml index 91abdc920..b827b98b9 100644 --- a/.gemini/config.yaml +++ b/.gemini/config.yaml @@ -1,12 +1,18 @@ # See https://developers.google.com/gemini-code-assist/docs/customize-gemini-behavior-github#custom-configurations -have_fun: false # We are serious people in the Dart team! +# We are serious people in the Dart team! +have_fun: false + code_review: disable: false + # Valid levels are LOW, MEDIUM, or HIGH. comment_severity_threshold: MEDIUM max_review_comments: 100 pull_request_opened: - help: true - summary: true code_review: true + # Make the gemini PR comment more terse. + help: false + # Make the gemini PR comment more terse. + summary: false + ignore_patterns: [] From c345401a3d9b4c1ee19446b16504bdf55d2e1e39 Mon Sep 17 00:00:00 2001 From: Devon Carew Date: Wed, 17 Sep 2025 07:58:08 -0700 Subject: [PATCH 46/67] rev package:pool in prep for publishing (#2166) --- pkgs/pool/CHANGELOG.md | 2 +- pkgs/pool/pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/pool/CHANGELOG.md b/pkgs/pool/CHANGELOG.md index 56424fc6f..d7ab9abed 100644 --- a/pkgs/pool/CHANGELOG.md +++ b/pkgs/pool/CHANGELOG.md @@ -1,4 +1,4 @@ -## 1.5.2-wip +## 1.5.2 * Require Dart 3.4. * Move to `dart-lang/tools` monorepo. diff --git a/pkgs/pool/pubspec.yaml b/pkgs/pool/pubspec.yaml index 3324028b8..71b94cc43 100644 --- a/pkgs/pool/pubspec.yaml +++ b/pkgs/pool/pubspec.yaml @@ -1,5 +1,5 @@ name: pool -version: 1.5.2-wip +version: 1.5.2 description: >- Manage a finite pool of resources. Useful for controlling concurrent file system or network requests. From 0dc77ef9d53b8de4d862b8d543c1abc206e21712 Mon Sep 17 00:00:00 2001 From: RohitSaily Date: Thu, 18 Sep 2025 15:15:45 -0400 Subject: [PATCH 47/67] add support for `setUpClass` and `tearDownClass` (#2164) --- pkgs/test_reflective_loader/CHANGELOG.md | 5 ++ .../lib/test_reflective_loader.dart | 46 ++++++++-- pkgs/test_reflective_loader/pubspec.yaml | 2 +- .../test/location_test.dart | 5 ++ .../test/test_reflective_loader_test.dart | 84 +++++++++++++++++-- 5 files changed, 127 insertions(+), 15 deletions(-) diff --git a/pkgs/test_reflective_loader/CHANGELOG.md b/pkgs/test_reflective_loader/CHANGELOG.md index 61ba81bde..27a14c6f8 100644 --- a/pkgs/test_reflective_loader/CHANGELOG.md +++ b/pkgs/test_reflective_loader/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.4.0 + +- Add support for one-time set up and teardown in test classes via static + `setUpClass` and `tearDownClass` methods + ## 0.3.0 - Require Dart `^3.5.0`. diff --git a/pkgs/test_reflective_loader/lib/test_reflective_loader.dart b/pkgs/test_reflective_loader/lib/test_reflective_loader.dart index 9c3a103ce..8ea837dee 100644 --- a/pkgs/test_reflective_loader/lib/test_reflective_loader.dart +++ b/pkgs/test_reflective_loader/lib/test_reflective_loader.dart @@ -87,8 +87,8 @@ void defineReflectiveTests(Type type) { { var isSolo = _hasAnnotationInstance(classMirror, soloTest); var className = MirrorSystem.getName(classMirror.simpleName); - group = _Group(isSolo, _combineNames(_currentSuiteName, className), - classMirror.testLocation); + group = _Group( + isSolo, _combineNames(_currentSuiteName, className), classMirror); _currentGroups.add(group); } @@ -151,10 +151,24 @@ void _addTestsIfTopLevelSuite() { if (_currentSuiteLevel == 0) { void runTests({required bool allGroups, required bool allTests}) { for (var group in _currentGroups) { + var runTestCount = 0; if (allGroups || group.isSolo) { for (var test in group.tests) { if (allTests || test.isSolo) { - test_package.test(test.name, test.function, + if (!test.isSkipped) { + runTestCount += 1; + } + test_package.test(test.name, () async { + await group.ensureSetUpClass(); + try { + await test.function(); + } finally { + runTestCount -= 1; + if (runTestCount == 0) { + group.tearDownClass(); + } + } + }, timeout: test.timeout, skip: test.isSkipped, location: test.location); @@ -210,14 +224,14 @@ bool _hasSkippedTestAnnotation(MethodMirror method) => _hasAnnotationInstance(method, skippedTest); Future _invokeSymbolIfExists( - InstanceMirror instanceMirror, Symbol symbol) { + ObjectMirror objectMirror, Symbol symbol) { Object? invocationResult; InstanceMirror? closure; try { - closure = instanceMirror.getField(symbol); + closure = objectMirror.getField(symbol); // ignore: avoid_catching_errors } on NoSuchMethodError { - // ignore + // ignore: empty_catches } if (closure is ClosureMirror) { @@ -307,10 +321,13 @@ class _AssertFailingTest { class _Group { final bool isSolo; final String name; - final test_package.TestLocation? location; final List<_Test> tests = <_Test>[]; - _Group(this.isSolo, this.name, this.location); + /// For static group-wide operations eg `setUpClass` and `tearDownClass`. + final ClassMirror _classMirror; + Future? _setUpCompletion; + + _Group(this.isSolo, this.name, this._classMirror); bool get hasSoloTest => tests.any((test) => test.isSolo); @@ -327,6 +344,19 @@ class _Group { tests.add(_Test(isSolo, fullName, function, timeout?._timeout, memberMirror.testLocation)); } + + /// Runs group-wide setup if it has not been started yet, + /// ensuring it only runs once for a group. Set up runs and + /// completes before any test of the group runs + Future ensureSetUpClass() => + _setUpCompletion ??= _invokeSymbolIfExists(_classMirror, #setUpClass); + + /// Runs group-wide tear down iff [ensureSetUpClass] was called at least once. + /// Must be called once and only called after all tests of the group have + /// completed + void tearDownClass() => _setUpCompletion != null + ? _invokeSymbolIfExists(_classMirror, #tearDownClass) + : null; } /// A marker annotation used to instruct dart2js to keep reflection information diff --git a/pkgs/test_reflective_loader/pubspec.yaml b/pkgs/test_reflective_loader/pubspec.yaml index 262a3498f..efdec89d1 100644 --- a/pkgs/test_reflective_loader/pubspec.yaml +++ b/pkgs/test_reflective_loader/pubspec.yaml @@ -1,5 +1,5 @@ name: test_reflective_loader -version: 0.3.0 +version: 0.4.0 description: Support for discovering tests and test suites using reflection. repository: https://github.com/dart-lang/tools/tree/main/pkgs/test_reflective_loader issue_tracker: https://github.com/dart-lang/tools/labels/package%3Atest_reflective_loader diff --git a/pkgs/test_reflective_loader/test/location_test.dart b/pkgs/test_reflective_loader/test/location_test.dart index 14984bb83..216857ccc 100644 --- a/pkgs/test_reflective_loader/test/location_test.dart +++ b/pkgs/test_reflective_loader/test/location_test.dart @@ -40,6 +40,11 @@ void main() { // the source code to ensure the locations match up. name = name.split('|').last.trim(); + // Skip "tearDownAll" or "setUpAll", it never has a location. + if (name case '(tearDownAll)' || '(setUpAll)') { + continue; + } + // Expect locations for all remaining fields. var url = test['url'] as String; var line = test['line'] as int; diff --git a/pkgs/test_reflective_loader/test/test_reflective_loader_test.dart b/pkgs/test_reflective_loader/test/test_reflective_loader_test.dart index fad98a5a1..ba0319305 100644 --- a/pkgs/test_reflective_loader/test/test_reflective_loader_test.dart +++ b/pkgs/test_reflective_loader/test/test_reflective_loader_test.dart @@ -11,14 +11,53 @@ import 'package:test_reflective_loader/test_reflective_loader.dart'; void main() { defineReflectiveSuite(() { + // ensure set-ups and tear-downs are not prematurely called ie before any + // tests actually execute + setUpAll(() { + expect(TestReflectiveLoaderTest.didSetUpClass, false); + expect(TestReflectiveLoaderTest.didTearDownClass, false); + expect(SecondTest.didSetUpClass, false); + expect(SecondTest.didTearDownClass, false); + }); defineReflectiveTests(TestReflectiveLoaderTest); + defineReflectiveTests(SecondTest); + tearDownAll(() { + expect(TestReflectiveLoaderTest.didSetUpClass, true); + expect(TestReflectiveLoaderTest.didTearDownClass, true); + expect(SecondTest.didSetUpClass, true); + expect(SecondTest.didTearDownClass, true); + }); }); } @reflectiveTest class TestReflectiveLoaderTest { - void test_passes() { - expect(true, true); + static bool didSetUpClass = false; + static bool didTearDownClass = false; + + // TODO(scheglov): Linter was updated to automatically ignore + // this but needs time before it is actually used. Remove this + // ignore and others like it in this file once the linter + // change is active in this project: + // ignore: unreachable_from_main + static void setUpClass() { + expect(didSetUpClass, false); + didSetUpClass = true; + expect(didTearDownClass, false); + } + + // TODO(scheglov): See comment directly above + // "TestReflectiveLoaderTest.setUpClass" for info about this ignore: + // ignore: unreachable_from_main + static void tearDownClass() { + expect(didSetUpClass, true); + expect(didTearDownClass, false); + didTearDownClass = true; + } + + void test_classwide_state() { + expect(didSetUpClass, true); + expect(didTearDownClass, false); } @failingTest @@ -26,8 +65,8 @@ class TestReflectiveLoaderTest { expect(false, true); } - @failingTest - void test_fails_throws_sync() { + @skippedTest + void test_fails_but_skipped() { throw StateError('foo'); } @@ -36,13 +75,46 @@ class TestReflectiveLoaderTest { return Future.error('foo'); } - @skippedTest - void test_fails_but_skipped() { + @failingTest + void test_fails_throws_sync() { throw StateError('foo'); } + void test_passes() { + expect(true, true); + } + @skippedTest void test_times_out_but_skipped() { while (true) {} } } + +@reflectiveTest +class SecondTest { + static bool didSetUpClass = false; + static bool didTearDownClass = false; + + // TODO(scheglov): See comment directly above + // "TestReflectiveLoaderTest.setUpClass" for info about this ignore: + // ignore: unreachable_from_main + static void setUpClass() { + expect(didSetUpClass, false); + didSetUpClass = true; + expect(didTearDownClass, false); + } + + // TODO(scheglov): See comment directly above + // "TestReflectiveLoaderTest.setUpClass" for info about this ignore: + // ignore: unreachable_from_main + static void tearDownClass() { + expect(didSetUpClass, true); + expect(didTearDownClass, false); + didTearDownClass = true; + } + + void test_classwide_state() { + expect(didSetUpClass, true); + expect(didTearDownClass, false); + } +} From 82825d3a054b4776b3972eb1e98066f371e18e0c Mon Sep 17 00:00:00 2001 From: Devon Carew Date: Wed, 24 Sep 2025 12:23:59 -0700 Subject: [PATCH 48/67] refactor the media types golden table (#2168) --- pkgs/mime/doc/media_types.md | 1554 +++++++++++++++---------------- pkgs/mime/tool/media_types.dart | 22 +- 2 files changed, 795 insertions(+), 781 deletions(-) diff --git a/pkgs/mime/doc/media_types.md b/pkgs/mime/doc/media_types.md index 5878c21bb..1b41cbd53 100644 --- a/pkgs/mime/doc/media_types.md +++ b/pkgs/mime/doc/media_types.md @@ -4,782 +4,782 @@ Supported media types and file extensions. -| MIME type | Default | Additional | -| --- | --- | --- | -| `application/andrew-inset` | `ez` | | -| `application/applixware` | `aw` | | -| `application/atom+xml` | `atom` | | -| `application/atomcat+xml` | `atomcat` | | -| `application/atomsvc+xml` | `atomsvc` | | -| `application/ccxml+xml` | `ccxml` | | -| `application/cdmi-capability` | `cdmia` | | -| `application/cdmi-container` | `cdmic` | | -| `application/cdmi-domain` | `cdmid` | | -| `application/cdmi-object` | `cdmio` | | -| `application/cdmi-queue` | `cdmiq` | | -| `application/cu-seeme` | `cu` | | -| `application/davmount+xml` | `davmount` | | -| `application/dicom` | `dcm` | | -| `application/docbook+xml` | `dbk` | | -| `application/dssc+der` | `dssc` | | -| `application/dssc+xml` | `xdssc` | | -| `application/ecmascript` | `ecma` | | -| `application/emma+xml` | `emma` | | -| `application/epub+zip` | `epub` | | -| `application/exi` | `exi` | | -| `application/font-tdpfr` | `pfr` | | -| `application/gml+xml` | `gml` | | -| `application/gpx+xml` | `gpx` | | -| `application/gxf` | `gxf` | | -| `application/hyperstudio` | `stk` | | -| `application/inkml+xml` | `inkml` | `ink` | -| `application/ipfix` | `ipfix` | | -| `application/java-archive` | `jar` | | -| `application/java-serialized-object` | `ser` | | -| `application/java-vm` | `class` | | -| `application/json` | `json` | | -| `application/jsonml+json` | `jsonml` | | -| `application/lost+xml` | `lostxml` | | -| `application/mac-binhex40` | `hqx` | | -| `application/mac-compactpro` | `cpt` | | -| `application/mads+xml` | `mads` | | -| `application/manifest+json` | `webmanifest` | | -| `application/marc` | `mrc` | | -| `application/marcxml+xml` | `mrcx` | | -| `application/mathematica` | `nb` | `ma`, `mb` | -| `application/mathml+xml` | `mathml` | | -| `application/mbox` | `mbox` | | -| `application/mediaservercontrol+xml` | `mscml` | | -| `application/metalink+xml` | `metalink` | | -| `application/metalink4+xml` | `meta4` | | -| `application/mets+xml` | `mets` | | -| `application/mods+xml` | `mods` | | -| `application/mp21` | `mp21` | `m21` | -| `application/mp4` | `mp4s` | | -| `application/msword` | `doc` | `dot` | -| `application/mxf` | `mxf` | | -| `application/octet-stream` | `so` | `bin`, `bpk`, `deploy`, `dist`, `distz`, `dms`, `dump`, `elc`, `lrf`, `mar`, `pkg` | -| `application/oda` | `oda` | | -| `application/oebps-package+xml` | `opf` | | -| `application/ogg` | `ogx` | | -| `application/omdoc+xml` | `omdoc` | | -| `application/onenote` | `onetoc2` | `onepkg`, `onetmp`, `onetoc` | -| `application/oxps` | `oxps` | | -| `application/patch-ops-error+xml` | `xer` | | -| `application/pdf` | `pdf` | | -| `application/pgp-encrypted` | `pgp` | | -| `application/pgp-signature` | `sig` | `asc` | -| `application/pics-rules` | `prf` | | -| `application/pkcs10` | `p10` | | -| `application/pkcs7-mime` | `p7m` | `p7c` | -| `application/pkcs7-signature` | `p7s` | | -| `application/pkcs8` | `p8` | | -| `application/pkix-attr-cert` | `ac` | | -| `application/pkix-cert` | `cer` | | -| `application/pkix-crl` | `crl` | | -| `application/pkix-pkipath` | `pkipath` | | -| `application/pkixcmp` | `pki` | | -| `application/pls+xml` | `pls` | | -| `application/postscript` | `ps` | `ai`, `eps` | -| `application/prs.cww` | `cww` | | -| `application/pskc+xml` | `pskcxml` | | -| `application/rdf+xml` | `rdf` | | -| `application/reginfo+xml` | `rif` | | -| `application/relax-ng-compact-syntax` | `rnc` | | -| `application/resource-lists+xml` | `rl` | | -| `application/resource-lists-diff+xml` | `rld` | | -| `application/rls-services+xml` | `rs` | | -| `application/rpki-ghostbusters` | `gbr` | | -| `application/rpki-manifest` | `mft` | | -| `application/rpki-roa` | `roa` | | -| `application/rsd+xml` | `rsd` | | -| `application/rss+xml` | `rss` | | -| `application/rtf` | `rtf` | | -| `application/sbml+xml` | `sbml` | | -| `application/scvp-cv-request` | `scq` | | -| `application/scvp-cv-response` | `scs` | | -| `application/scvp-vp-request` | `spq` | | -| `application/scvp-vp-response` | `spp` | | -| `application/sdp` | `sdp` | | -| `application/set-payment-initiation` | `setpay` | | -| `application/set-registration-initiation` | `setreg` | | -| `application/shf+xml` | `shf` | | -| `application/smil+xml` | `smil` | `smi` | -| `application/sparql-query` | `rq` | | -| `application/sparql-results+xml` | `srx` | | -| `application/srgs` | `gram` | | -| `application/srgs+xml` | `grxml` | | -| `application/sru+xml` | `sru` | | -| `application/ssdl+xml` | `ssdl` | | -| `application/ssml+xml` | `ssml` | | -| `application/tei+xml` | `teicorpus` | `tei` | -| `application/thraud+xml` | `tfi` | | -| `application/timestamped-data` | `tsd` | | -| `application/toml` | `toml` | | -| `application/vnd.3gpp.pic-bw-large` | `plb` | | -| `application/vnd.3gpp.pic-bw-small` | `psb` | | -| `application/vnd.3gpp.pic-bw-var` | `pvb` | | -| `application/vnd.3gpp2.tcap` | `tcap` | | -| `application/vnd.3m.post-it-notes` | `pwn` | | -| `application/vnd.accpac.simply.aso` | `aso` | | -| `application/vnd.accpac.simply.imp` | `imp` | | -| `application/vnd.acucobol` | `acu` | | -| `application/vnd.acucorp` | `atc` | `acutc` | -| `application/vnd.adobe.air-application-installer-package+zip` | `air` | | -| `application/vnd.adobe.formscentral.fcdt` | `fcdt` | | -| `application/vnd.adobe.fxp` | `fxpl` | `fxp` | -| `application/vnd.adobe.xdp+xml` | `xdp` | | -| `application/vnd.adobe.xfdf` | `xfdf` | | -| `application/vnd.ahead.space` | `ahead` | | -| `application/vnd.airzip.filesecure.azf` | `azf` | | -| `application/vnd.airzip.filesecure.azs` | `azs` | | -| `application/vnd.amazon.ebook` | `azw` | | -| `application/vnd.americandynamics.acc` | `acc` | | -| `application/vnd.amiga.ami` | `ami` | | -| `application/vnd.android.package-archive` | `apk` | | -| `application/vnd.anser-web-certificate-issue-initiation` | `cii` | | -| `application/vnd.anser-web-funds-transfer-initiation` | `fti` | | -| `application/vnd.antix.game-component` | `atx` | | -| `application/vnd.apple.installer+xml` | `mpkg` | | -| `application/vnd.apple.mpegurl` | `m3u8` | | -| `application/vnd.aristanetworks.swi` | `swi` | | -| `application/vnd.astraea-software.iota` | `iota` | | -| `application/vnd.audiograph` | `aep` | | -| `application/vnd.blueice.multipass` | `mpm` | | -| `application/vnd.bmi` | `bmi` | | -| `application/vnd.businessobjects` | `rep` | | -| `application/vnd.chemdraw+xml` | `cdxml` | | -| `application/vnd.chipnuts.karaoke-mmd` | `mmd` | | -| `application/vnd.cinderella` | `cdy` | | -| `application/vnd.claymore` | `cla` | | -| `application/vnd.cloanto.rp9` | `rp9` | | -| `application/vnd.clonk.c4group` | `c4u` | `c4d`, `c4f`, `c4g`, `c4p` | -| `application/vnd.cluetrust.cartomobile-config` | `c11amc` | | -| `application/vnd.cluetrust.cartomobile-config-pkg` | `c11amz` | | -| `application/vnd.commonspace` | `csp` | | -| `application/vnd.contact.cmsg` | `cdbcmsg` | | -| `application/vnd.cosmocaller` | `cmc` | | -| `application/vnd.crick.clicker` | `clkx` | | -| `application/vnd.crick.clicker.keyboard` | `clkk` | | -| `application/vnd.crick.clicker.palette` | `clkp` | | -| `application/vnd.crick.clicker.template` | `clkt` | | -| `application/vnd.crick.clicker.wordbank` | `clkw` | | -| `application/vnd.criticaltools.wbs+xml` | `wbs` | | -| `application/vnd.ctc-posml` | `pml` | | -| `application/vnd.cups-ppd` | `ppd` | | -| `application/vnd.curl.car` | `car` | | -| `application/vnd.curl.pcurl` | `pcurl` | | -| `application/vnd.data-vision.rdz` | `rdz` | | -| `application/vnd.dece.data` | `uvvf` | `uvd`, `uvf`, `uvvd` | -| `application/vnd.dece.ttml+xml` | `uvvt` | `uvt` | -| `application/vnd.dece.unspecified` | `uvx` | `uvvx` | -| `application/vnd.dece.zip` | `uvz` | `uvvz` | -| `application/vnd.denovo.fcselayout-link` | `fe_launch` | | -| `application/vnd.dna` | `dna` | | -| `application/vnd.dolby.mlp` | `mlp` | | -| `application/vnd.dpgraph` | `dpg` | | -| `application/vnd.dreamfactory` | `dfac` | | -| `application/vnd.ds-keypoint` | `kpxx` | | -| `application/vnd.dvb.ait` | `ait` | | -| `application/vnd.dvb.service` | `svc` | | -| `application/vnd.dynageo` | `geo` | | -| `application/vnd.ecowin.chart` | `mag` | | -| `application/vnd.enliven` | `nml` | | -| `application/vnd.epson.esf` | `esf` | | -| `application/vnd.epson.msf` | `msf` | | -| `application/vnd.epson.quickanime` | `qam` | | -| `application/vnd.epson.salt` | `slt` | | -| `application/vnd.epson.ssf` | `ssf` | | -| `application/vnd.eszigno3+xml` | `et3` | `es3` | -| `application/vnd.ezpix-album` | `ez2` | | -| `application/vnd.ezpix-package` | `ez3` | | -| `application/vnd.fdf` | `fdf` | | -| `application/vnd.fdsn.mseed` | `mseed` | | -| `application/vnd.fdsn.seed` | `seed` | `dataless` | -| `application/vnd.flographit` | `gph` | | -| `application/vnd.fluxtime.clip` | `ftc` | | -| `application/vnd.framemaker` | `maker` | `book`, `fm`, `frame` | -| `application/vnd.frogans.fnc` | `fnc` | | -| `application/vnd.frogans.ltf` | `ltf` | | -| `application/vnd.fsc.weblaunch` | `fsc` | | -| `application/vnd.fujitsu.oasys` | `oas` | | -| `application/vnd.fujitsu.oasys2` | `oa2` | | -| `application/vnd.fujitsu.oasys3` | `oa3` | | -| `application/vnd.fujitsu.oasysgp` | `fg5` | | -| `application/vnd.fujitsu.oasysprs` | `bh2` | | -| `application/vnd.fujixerox.ddd` | `ddd` | | -| `application/vnd.fujixerox.docuworks` | `xdw` | | -| `application/vnd.fujixerox.docuworks.binder` | `xbd` | | -| `application/vnd.fuzzysheet` | `fzs` | | -| `application/vnd.genomatix.tuxedo` | `txd` | | -| `application/vnd.geogebra.file` | `ggb` | | -| `application/vnd.geogebra.tool` | `ggt` | | -| `application/vnd.geometry-explorer` | `gre` | `gex` | -| `application/vnd.geonext` | `gxt` | | -| `application/vnd.geoplan` | `g2w` | | -| `application/vnd.geospace` | `g3w` | | -| `application/vnd.gmx` | `gmx` | | -| `application/vnd.google-earth.kml+xml` | `kml` | | -| `application/vnd.google-earth.kmz` | `kmz` | | -| `application/vnd.grafeq` | `gqs` | `gqf` | -| `application/vnd.groove-account` | `gac` | | -| `application/vnd.groove-help` | `ghf` | | -| `application/vnd.groove-identity-message` | `gim` | | -| `application/vnd.groove-injector` | `grv` | | -| `application/vnd.groove-tool-message` | `gtm` | | -| `application/vnd.groove-tool-template` | `tpl` | | -| `application/vnd.groove-vcard` | `vcg` | | -| `application/vnd.hal+xml` | `hal` | | -| `application/vnd.handheld-entertainment+xml` | `zmm` | | -| `application/vnd.hbci` | `hbci` | | -| `application/vnd.hhe.lesson-player` | `les` | | -| `application/vnd.hp-hpgl` | `hpgl` | | -| `application/vnd.hp-hpid` | `hpid` | | -| `application/vnd.hp-hps` | `hps` | | -| `application/vnd.hp-jlyt` | `jlt` | | -| `application/vnd.hp-pcl` | `pcl` | | -| `application/vnd.hp-pclxl` | `pclxl` | | -| `application/vnd.hydrostatix.sof-data` | `sfd-hdstx` | | -| `application/vnd.ibm.minipay` | `mpy` | | -| `application/vnd.ibm.modcap` | `listafp` | `afp`, `list3820` | -| `application/vnd.ibm.rights-management` | `irm` | | -| `application/vnd.ibm.secure-container` | `sc` | | -| `application/vnd.iccprofile` | `icm` | `icc` | -| `application/vnd.igloader` | `igl` | | -| `application/vnd.immervision-ivp` | `ivp` | | -| `application/vnd.immervision-ivu` | `ivu` | | -| `application/vnd.insors.igm` | `igm` | | -| `application/vnd.intercon.formnet` | `xpx` | `xpw` | -| `application/vnd.intergeo` | `i2g` | | -| `application/vnd.intu.qbo` | `qbo` | | -| `application/vnd.intu.qfx` | `qfx` | | -| `application/vnd.ipunplugged.rcprofile` | `rcprofile` | | -| `application/vnd.irepository.package+xml` | `irp` | | -| `application/vnd.is-xpr` | `xpr` | | -| `application/vnd.isac.fcs` | `fcs` | | -| `application/vnd.jam` | `jam` | | -| `application/vnd.jcp.javame.midlet-rms` | `rms` | | -| `application/vnd.jisp` | `jisp` | | -| `application/vnd.joost.joda-archive` | `joda` | | -| `application/vnd.kahootz` | `ktz` | `ktr` | -| `application/vnd.kde.karbon` | `karbon` | | -| `application/vnd.kde.kchart` | `chrt` | | -| `application/vnd.kde.kformula` | `kfo` | | -| `application/vnd.kde.kivio` | `flw` | | -| `application/vnd.kde.kontour` | `kon` | | -| `application/vnd.kde.kpresenter` | `kpt` | `kpr` | -| `application/vnd.kde.kspread` | `ksp` | | -| `application/vnd.kde.kword` | `kwt` | `kwd` | -| `application/vnd.kenameaapp` | `htke` | | -| `application/vnd.kidspiration` | `kia` | | -| `application/vnd.kinar` | `knp` | `kne` | -| `application/vnd.koan` | `skt` | `skd`, `skm`, `skp` | -| `application/vnd.kodak-descriptor` | `sse` | | -| `application/vnd.las.las+xml` | `lasxml` | | -| `application/vnd.llamagraphics.life-balance.desktop` | `lbd` | | -| `application/vnd.llamagraphics.life-balance.exchange+xml` | `lbe` | | -| `application/vnd.lotus-1-2-3` | `123` | | -| `application/vnd.lotus-approach` | `apr` | | -| `application/vnd.lotus-freelance` | `pre` | | -| `application/vnd.lotus-notes` | `nsf` | | -| `application/vnd.lotus-organizer` | `org` | | -| `application/vnd.lotus-screencam` | `scm` | | -| `application/vnd.lotus-wordpro` | `lwp` | | -| `application/vnd.macports.portpkg` | `portpkg` | | -| `application/vnd.mcd` | `mcd` | | -| `application/vnd.medcalcdata` | `mc1` | | -| `application/vnd.mediastation.cdkey` | `cdkey` | | -| `application/vnd.mfer` | `mwf` | | -| `application/vnd.mfmp` | `mfm` | | -| `application/vnd.micrografx.flo` | `flo` | | -| `application/vnd.micrografx.igx` | `igx` | | -| `application/vnd.mif` | `mif` | | -| `application/vnd.mobius.daf` | `daf` | | -| `application/vnd.mobius.dis` | `dis` | | -| `application/vnd.mobius.mbk` | `mbk` | | -| `application/vnd.mobius.mqy` | `mqy` | | -| `application/vnd.mobius.msl` | `msl` | | -| `application/vnd.mobius.plc` | `plc` | | -| `application/vnd.mobius.txf` | `txf` | | -| `application/vnd.mophun.application` | `mpn` | | -| `application/vnd.mophun.certificate` | `mpc` | | -| `application/vnd.mozilla.xul+xml` | `xul` | | -| `application/vnd.ms-artgalry` | `cil` | | -| `application/vnd.ms-cab-compressed` | `cab` | | -| `application/vnd.ms-excel` | `xls` | `xla`, `xlc`, `xlm`, `xlt`, `xlw` | -| `application/vnd.ms-excel.addin.macroenabled.12` | `xlam` | | -| `application/vnd.ms-excel.sheet.binary.macroenabled.12` | `xlsb` | | -| `application/vnd.ms-excel.sheet.macroenabled.12` | `xlsm` | | -| `application/vnd.ms-excel.template.macroenabled.12` | `xltm` | | -| `application/vnd.ms-fontobject` | `eot` | | -| `application/vnd.ms-htmlhelp` | `chm` | | -| `application/vnd.ms-ims` | `ims` | | -| `application/vnd.ms-lrm` | `lrm` | | -| `application/vnd.ms-officetheme` | `thmx` | | -| `application/vnd.ms-pki.seccat` | `cat` | | -| `application/vnd.ms-pki.stl` | `stl` | | -| `application/vnd.ms-powerpoint` | `ppt` | `pot`, `pps` | -| `application/vnd.ms-powerpoint.addin.macroenabled.12` | `ppam` | | -| `application/vnd.ms-powerpoint.presentation.macroenabled.12` | `pptm` | | -| `application/vnd.ms-powerpoint.slide.macroenabled.12` | `sldm` | | -| `application/vnd.ms-powerpoint.slideshow.macroenabled.12` | `ppsm` | | -| `application/vnd.ms-powerpoint.template.macroenabled.12` | `potm` | | -| `application/vnd.ms-project` | `mpt` | `mpp` | -| `application/vnd.ms-word.document.macroenabled.12` | `docm` | | -| `application/vnd.ms-word.template.macroenabled.12` | `dotm` | | -| `application/vnd.ms-works` | `wps` | `wcm`, `wdb`, `wks` | -| `application/vnd.ms-wpl` | `wpl` | | -| `application/vnd.ms-xpsdocument` | `xps` | | -| `application/vnd.mseq` | `mseq` | | -| `application/vnd.musician` | `mus` | | -| `application/vnd.muvee.style` | `msty` | | -| `application/vnd.mynfc` | `taglet` | | -| `application/vnd.neurolanguage.nlu` | `nlu` | | -| `application/vnd.nitf` | `ntf` | `nitf` | -| `application/vnd.noblenet-directory` | `nnd` | | -| `application/vnd.noblenet-sealer` | `nns` | | -| `application/vnd.noblenet-web` | `nnw` | | -| `application/vnd.nokia.n-gage.data` | `ngdat` | | -| `application/vnd.nokia.n-gage.symbian.install` | `n-gage` | | -| `application/vnd.nokia.radio-preset` | `rpst` | | -| `application/vnd.nokia.radio-presets` | `rpss` | | -| `application/vnd.novadigm.edm` | `edm` | | -| `application/vnd.novadigm.edx` | `edx` | | -| `application/vnd.novadigm.ext` | `ext` | | -| `application/vnd.oasis.opendocument.chart` | `odc` | | -| `application/vnd.oasis.opendocument.chart-template` | `otc` | | -| `application/vnd.oasis.opendocument.database` | `odb` | | -| `application/vnd.oasis.opendocument.formula` | `odf` | | -| `application/vnd.oasis.opendocument.formula-template` | `odft` | | -| `application/vnd.oasis.opendocument.graphics` | `odg` | | -| `application/vnd.oasis.opendocument.graphics-template` | `otg` | | -| `application/vnd.oasis.opendocument.image` | `odi` | | -| `application/vnd.oasis.opendocument.image-template` | `oti` | | -| `application/vnd.oasis.opendocument.presentation` | `odp` | | -| `application/vnd.oasis.opendocument.presentation-template` | `otp` | | -| `application/vnd.oasis.opendocument.spreadsheet` | `ods` | | -| `application/vnd.oasis.opendocument.spreadsheet-template` | `ots` | | -| `application/vnd.oasis.opendocument.text` | `odt` | | -| `application/vnd.oasis.opendocument.text-master` | `odm` | | -| `application/vnd.oasis.opendocument.text-template` | `ott` | | -| `application/vnd.oasis.opendocument.text-web` | `oth` | | -| `application/vnd.olpc-sugar` | `xo` | | -| `application/vnd.oma.dd2+xml` | `dd2` | | -| `application/vnd.openofficeorg.extension` | `oxt` | | -| `application/vnd.openxmlformats-officedocument.presentationml.presentation` | `pptx` | | -| `application/vnd.openxmlformats-officedocument.presentationml.slide` | `sldx` | | -| `application/vnd.openxmlformats-officedocument.presentationml.slideshow` | `ppsx` | | -| `application/vnd.openxmlformats-officedocument.presentationml.template` | `potx` | | -| `application/vnd.openxmlformats-officedocument.spreadsheetml.sheet` | `xlsx` | | -| `application/vnd.openxmlformats-officedocument.spreadsheetml.template` | `xltx` | | -| `application/vnd.openxmlformats-officedocument.wordprocessingml.document` | `docx` | | -| `application/vnd.openxmlformats-officedocument.wordprocessingml.template` | `dotx` | | -| `application/vnd.osgeo.mapguide.package` | `mgp` | | -| `application/vnd.osgi.dp` | `dp` | | -| `application/vnd.osgi.subsystem` | `esa` | | -| `application/vnd.palm` | `pqa` | `oprc`, `pdb` | -| `application/vnd.pawaafile` | `paw` | | -| `application/vnd.pg.format` | `str` | | -| `application/vnd.pg.osasli` | `ei6` | | -| `application/vnd.picsel` | `efif` | | -| `application/vnd.pmi.widget` | `wg` | | -| `application/vnd.pocketlearn` | `plf` | | -| `application/vnd.powerbuilder6` | `pbd` | | -| `application/vnd.previewsystems.box` | `box` | | -| `application/vnd.proteus.magazine` | `mgz` | | -| `application/vnd.publishare-delta-tree` | `qps` | | -| `application/vnd.pvi.ptid1` | `ptid` | | -| `application/vnd.quark.quarkxpress` | `qxt` | `qwd`, `qwt`, `qxb`, `qxd`, `qxl` | -| `application/vnd.realvnc.bed` | `bed` | | -| `application/vnd.recordare.musicxml` | `mxl` | | -| `application/vnd.recordare.musicxml+xml` | `musicxml` | | -| `application/vnd.rig.cryptonote` | `cryptonote` | | -| `application/vnd.rim.cod` | `cod` | | -| `application/vnd.rn-realmedia` | `rm` | | -| `application/vnd.rn-realmedia-vbr` | `rmvb` | | -| `application/vnd.route66.link66+xml` | `link66` | | -| `application/vnd.sailingtracker.track` | `st` | | -| `application/vnd.seemail` | `see` | | -| `application/vnd.sema` | `sema` | | -| `application/vnd.semd` | `semd` | | -| `application/vnd.semf` | `semf` | | -| `application/vnd.shana.informed.formdata` | `ifm` | | -| `application/vnd.shana.informed.formtemplate` | `itp` | | -| `application/vnd.shana.informed.interchange` | `iif` | | -| `application/vnd.shana.informed.package` | `ipk` | | -| `application/vnd.simtech-mindmapper` | `twds` | `twd` | -| `application/vnd.smaf` | `mmf` | | -| `application/vnd.smart.teacher` | `teacher` | | -| `application/vnd.solent.sdkm+xml` | `sdkm` | `sdkd` | -| `application/vnd.spotfire.dxp` | `dxp` | | -| `application/vnd.spotfire.sfs` | `sfs` | | -| `application/vnd.stardivision.calc` | `sdc` | | -| `application/vnd.stardivision.draw` | `sda` | | -| `application/vnd.stardivision.impress` | `sdd` | | -| `application/vnd.stardivision.math` | `smf` | | -| `application/vnd.stardivision.writer` | `vor` | `sdw` | -| `application/vnd.stardivision.writer-global` | `sgl` | | -| `application/vnd.stepmania.package` | `smzip` | | -| `application/vnd.stepmania.stepchart` | `sm` | | -| `application/vnd.sun.xml.calc` | `sxc` | | -| `application/vnd.sun.xml.calc.template` | `stc` | | -| `application/vnd.sun.xml.draw` | `sxd` | | -| `application/vnd.sun.xml.draw.template` | `std` | | -| `application/vnd.sun.xml.impress` | `sxi` | | -| `application/vnd.sun.xml.impress.template` | `sti` | | -| `application/vnd.sun.xml.math` | `sxm` | | -| `application/vnd.sun.xml.writer` | `sxw` | | -| `application/vnd.sun.xml.writer.global` | `sxg` | | -| `application/vnd.sun.xml.writer.template` | `stw` | | -| `application/vnd.sus-calendar` | `susp` | `sus` | -| `application/vnd.svd` | `svd` | | -| `application/vnd.symbian.install` | `sisx` | `sis` | -| `application/vnd.syncml+xml` | `xsm` | | -| `application/vnd.syncml.dm+wbxml` | `bdm` | | -| `application/vnd.syncml.dm+xml` | `xdm` | | -| `application/vnd.tao.intent-module-archive` | `tao` | | -| `application/vnd.tcpdump.pcap` | `pcap` | `cap`, `dmp` | -| `application/vnd.tmobile-livetv` | `tmo` | | -| `application/vnd.trid.tpt` | `tpt` | | -| `application/vnd.triscape.mxs` | `mxs` | | -| `application/vnd.trueapp` | `tra` | | -| `application/vnd.ufdl` | `ufdl` | `ufd` | -| `application/vnd.uiq.theme` | `utz` | | -| `application/vnd.umajin` | `umj` | | -| `application/vnd.unity` | `unityweb` | | -| `application/vnd.uoml+xml` | `uoml` | | -| `application/vnd.vcx` | `vcx` | | -| `application/vnd.visio` | `vsw` | `vsd`, `vss`, `vst` | -| `application/vnd.visionary` | `vis` | | -| `application/vnd.vsf` | `vsf` | | -| `application/vnd.wap.wbxml` | `wbxml` | | -| `application/vnd.wap.wmlc` | `wmlc` | | -| `application/vnd.wap.wmlscriptc` | `wmlsc` | | -| `application/vnd.webturbo` | `wtb` | | -| `application/vnd.wolfram.player` | `nbp` | | -| `application/vnd.wordperfect` | `wpd` | | -| `application/vnd.wqd` | `wqd` | | -| `application/vnd.wt.stf` | `stf` | | -| `application/vnd.xara` | `xar` | | -| `application/vnd.xfdl` | `xfdl` | | -| `application/vnd.yamaha.hv-dic` | `hvd` | | -| `application/vnd.yamaha.hv-script` | `hvs` | | -| `application/vnd.yamaha.hv-voice` | `hvp` | | -| `application/vnd.yamaha.openscoreformat` | `osf` | | -| `application/vnd.yamaha.openscoreformat.osfpvg+xml` | `osfpvg` | | -| `application/vnd.yamaha.smaf-audio` | `saf` | | -| `application/vnd.yamaha.smaf-phrase` | `spf` | | -| `application/vnd.yellowriver-custom-menu` | `cmp` | | -| `application/vnd.zul` | `zirz` | `zir` | -| `application/vnd.zzazz.deck+xml` | `zaz` | | -| `application/voicexml+xml` | `vxml` | | -| `application/wasm` | `wasm` | | -| `application/widget` | `wgt` | | -| `application/winhlp` | `hlp` | | -| `application/wsdl+xml` | `wsdl` | | -| `application/wspolicy+xml` | `wspolicy` | | -| `application/x-7z-compressed` | `7z` | | -| `application/x-abiword` | `abw` | | -| `application/x-ace-compressed` | `ace` | | -| `application/x-apple-diskimage` | `dmg` | | -| `application/x-authorware-bin` | `x32` | `aab`, `u32`, `vox` | -| `application/x-authorware-map` | `aam` | | -| `application/x-authorware-seg` | `aas` | | -| `application/x-bcpio` | `bcpio` | | -| `application/x-bittorrent` | `torrent` | | -| `application/x-blorb` | `blorb` | `blb` | -| `application/x-bzip` | `bz` | | -| `application/x-bzip2` | `bz2` | `boz` | -| `application/x-cbr` | `cbz` | `cb7`, `cba`, `cbr`, `cbt` | -| `application/x-cdlink` | `vcd` | | -| `application/x-cfs-compressed` | `cfs` | | -| `application/x-chat` | `chat` | | -| `application/x-chess-pgn` | `pgn` | | -| `application/x-conference` | `nsc` | | -| `application/x-cpio` | `cpio` | | -| `application/x-csh` | `csh` | | -| `application/x-debian-package` | `deb` | `udeb` | -| `application/x-dgc-compressed` | `dgc` | | -| `application/x-director` | `w3d` | `cct`, `cst`, `cxt`, `dcr`, `dir`, `dxr`, `fgd`, `swa` | -| `application/x-doom` | `wad` | | -| `application/x-dtbncx+xml` | `ncx` | | -| `application/x-dtbook+xml` | `dtb` | | -| `application/x-dtbresource+xml` | `res` | | -| `application/x-dvi` | `dvi` | | -| `application/x-envoy` | `evy` | | -| `application/x-eva` | `eva` | | -| `application/x-font-bdf` | `bdf` | | -| `application/x-font-ghostscript` | `gsf` | | -| `application/x-font-linux-psf` | `psf` | | -| `application/x-font-otf` | `otf` | | -| `application/x-font-pcf` | `pcf` | | -| `application/x-font-snf` | `snf` | | -| `application/x-font-ttf` | `ttf` | `ttc` | -| `application/x-font-type1` | `pfm` | `afm`, `pfa`, `pfb` | -| `application/x-font-woff` | `woff` | | -| `application/x-freearc` | `arc` | | -| `application/x-futuresplash` | `spl` | | -| `application/x-gca-compressed` | `gca` | | -| `application/x-glulx` | `ulx` | | -| `application/x-gnumeric` | `gnumeric` | | -| `application/x-gramps-xml` | `gramps` | | -| `application/x-gtar` | `gtar` | | -| `application/x-hdf` | `hdf` | | -| `application/x-install-instructions` | `install` | | -| `application/x-iso9660-image` | `iso` | | -| `application/x-java-jnlp-file` | `jnlp` | | -| `application/x-latex` | `latex` | | -| `application/x-lzh-compressed` | `lzh` | `lha` | -| `application/x-mie` | `mie` | | -| `application/x-mobipocket-ebook` | `prc` | `mobi` | -| `application/x-ms-application` | `application` | | -| `application/x-ms-shortcut` | `lnk` | | -| `application/x-ms-wmd` | `wmd` | | -| `application/x-ms-wmz` | `wmz` | | -| `application/x-ms-xbap` | `xbap` | | -| `application/x-msaccess` | `mdb` | | -| `application/x-msbinder` | `obd` | | -| `application/x-mscardfile` | `crd` | | -| `application/x-msclip` | `clp` | | -| `application/x-msdownload` | `msi` | `bat`, `com`, `dll`, `exe` | -| `application/x-msmediaview` | `mvb` | `m13`, `m14` | -| `application/x-msmetafile` | `wmf` | `emf`, `emz` | -| `application/x-msmoney` | `mny` | | -| `application/x-mspublisher` | `pub` | | -| `application/x-msschedule` | `scd` | | -| `application/x-msterminal` | `trm` | | -| `application/x-mswrite` | `wri` | | -| `application/x-netcdf` | `nc` | `cdf` | -| `application/x-nzb` | `nzb` | | -| `application/x-pkcs12` | `pfx` | `p12` | -| `application/x-pkcs7-certificates` | `spc` | `p7b` | -| `application/x-pkcs7-certreqresp` | `p7r` | | -| `application/x-rar-compressed` | `rar` | | -| `application/x-research-info-systems` | `ris` | | -| `application/x-sh` | `sh` | | -| `application/x-shar` | `shar` | | -| `application/x-shockwave-flash` | `swf` | | -| `application/x-silverlight-app` | `xap` | | -| `application/x-sql` | `sql` | | -| `application/x-stuffit` | `sit` | | -| `application/x-stuffitx` | `sitx` | | -| `application/x-subrip` | `srt` | | -| `application/x-sv4cpio` | `sv4cpio` | | -| `application/x-sv4crc` | `sv4crc` | | -| `application/x-t3vm-image` | `t3` | | -| `application/x-tads` | `gam` | | -| `application/x-tar` | `tar` | | -| `application/x-tcl` | `tcl` | | -| `application/x-tex` | `tex` | | -| `application/x-tex-tfm` | `tfm` | | -| `application/x-texinfo` | `texinfo` | `texi` | -| `application/x-tgif` | `obj` | | -| `application/x-ustar` | `ustar` | | -| `application/x-wais-source` | `src` | | -| `application/x-x509-ca-cert` | `der` | `crt` | -| `application/x-xfig` | `fig` | | -| `application/x-xliff+xml` | `xlf` | | -| `application/x-xpinstall` | `xpi` | | -| `application/x-xz` | `xz` | | -| `application/x-zmachine` | `z8` | `z1`, `z2`, `z3`, `z4`, `z5`, `z6`, `z7` | -| `application/xaml+xml` | `xaml` | | -| `application/xcap-diff+xml` | `xdf` | | -| `application/xenc+xml` | `xenc` | | -| `application/xhtml+xml` | `xhtml` | `xht` | -| `application/xml` | `xml` | `xsl` | -| `application/xml-dtd` | `dtd` | | -| `application/xop+xml` | `xop` | | -| `application/xproc+xml` | `xpl` | | -| `application/xslt+xml` | `xslt` | | -| `application/xspf+xml` | `xspf` | | -| `application/xv+xml` | `xvml` | `mxml`, `xhvml`, `xvm` | -| `application/yang` | `yang` | | -| `application/yin+xml` | `yin` | | -| `application/zip` | `zip` | | -| `audio/aac` | `aac` | | -| `audio/adpcm` | `adp` | | -| `audio/basic` | `snd` | `au` | -| `audio/midi` | `mid` | `kar`, `midi`, `rmi` | -| `audio/mp4` | `m4a` | `m4b`, `mp4a` | -| `audio/mpeg` | `mpga` | `m2a`, `m3a`, `mp2`, `mp2a`, `mp3` | -| `audio/ogg` | `ogg` | `oga`, `spx` | -| `audio/s3m` | `s3m` | | -| `audio/silk` | `sil` | | -| `audio/vnd.dece.audio` | `uvva` | `uva` | -| `audio/vnd.digital-winds` | `eol` | | -| `audio/vnd.dra` | `dra` | | -| `audio/vnd.dts` | `dts` | | -| `audio/vnd.dts.hd` | `dtshd` | | -| `audio/vnd.lucent.voice` | `lvp` | | -| `audio/vnd.ms-playready.media.pya` | `pya` | | -| `audio/vnd.nuera.ecelp4800` | `ecelp4800` | | -| `audio/vnd.nuera.ecelp7470` | `ecelp7470` | | -| `audio/vnd.nuera.ecelp9600` | `ecelp9600` | | -| `audio/vnd.rip` | `rip` | | -| `audio/webm` | `weba` | | -| `audio/x-aiff` | `aif` | `aifc`, `aiff` | -| `audio/x-caf` | `caf` | | -| `audio/x-flac` | `flac` | | -| `audio/x-matroska` | `mka` | | -| `audio/x-mpegurl` | `m3u` | | -| `audio/x-ms-wax` | `wax` | | -| `audio/x-ms-wma` | `wma` | | -| `audio/x-pn-realaudio` | `ram` | `ra` | -| `audio/x-pn-realaudio-plugin` | `rmp` | | -| `audio/x-wav` | `wav` | | -| `audio/xm` | `xm` | | -| `chemical/x-cdx` | `cdx` | | -| `chemical/x-cif` | `cif` | | -| `chemical/x-cmdf` | `cmdf` | | -| `chemical/x-cml` | `cml` | | -| `chemical/x-csml` | `csml` | | -| `chemical/x-xyz` | `xyz` | | -| `font/woff2` | `woff2` | | -| `image/avif` | `avif` | | -| `image/bmp` | `bmp` | | -| `image/cgm` | `cgm` | | -| `image/g3fax` | `g3` | | -| `image/gif` | `gif` | | -| `image/heic` | `heic` | | -| `image/heif` | `heif` | | -| `image/ief` | `ief` | | -| `image/jpeg` | `jpg` | `jpe`, `jpeg` | -| `image/ktx` | `ktx` | | -| `image/png` | `png` | | -| `image/prs.btif` | `btif` | | -| `image/sgi` | `sgi` | | -| `image/svg+xml` | `svg` | `svgz` | -| `image/tiff` | `tif` | `tiff` | -| `image/vnd.adobe.photoshop` | `psd` | | -| `image/vnd.dece.graphic` | `uvvi` | `uvg`, `uvi`, `uvvg` | -| `image/vnd.djvu` | `djvu` | `djv` | -| `image/vnd.dwg` | `dwg` | | -| `image/vnd.dxf` | `dxf` | | -| `image/vnd.fastbidsheet` | `fbs` | | -| `image/vnd.fpx` | `fpx` | | -| `image/vnd.fst` | `fst` | | -| `image/vnd.fujixerox.edmics-mmr` | `mmr` | | -| `image/vnd.fujixerox.edmics-rlc` | `rlc` | | -| `image/vnd.ms-modi` | `mdi` | | -| `image/vnd.ms-photo` | `wdp` | | -| `image/vnd.net-fpx` | `npx` | | -| `image/vnd.wap.wbmp` | `wbmp` | | -| `image/vnd.xiff` | `xif` | | -| `image/webp` | `webp` | | -| `image/x-3ds` | `3ds` | | -| `image/x-cmu-raster` | `ras` | | -| `image/x-cmx` | `cmx` | | -| `image/x-freehand` | `fhc` | `fh`, `fh4`, `fh5`, `fh7` | -| `image/x-icon` | `ico` | | -| `image/x-mrsid-image` | `sid` | | -| `image/x-pcx` | `pcx` | | -| `image/x-pict` | `pic` | `pct` | -| `image/x-portable-anymap` | `pnm` | | -| `image/x-portable-bitmap` | `pbm` | | -| `image/x-portable-graymap` | `pgm` | | -| `image/x-portable-pixmap` | `ppm` | | -| `image/x-rgb` | `rgb` | | -| `image/x-tga` | `tga` | | -| `image/x-xbitmap` | `xbm` | | -| `image/x-xpixmap` | `xpm` | | -| `image/x-xwindowdump` | `xwd` | | -| `message/rfc822` | `mime` | `eml` | -| `model/gltf+json` | `gltf` | | -| `model/gltf-binary` | `glb` | | -| `model/iges` | `igs` | `iges` | -| `model/mesh` | `silo` | `mesh`, `msh` | -| `model/vnd.collada+xml` | `dae` | | -| `model/vnd.dwf` | `dwf` | | -| `model/vnd.gdl` | `gdl` | | -| `model/vnd.gtw` | `gtw` | | -| `model/vnd.mts` | `mts` | | -| `model/vnd.vtu` | `vtu` | | -| `model/vrml` | `vrml` | `wrl` | -| `model/x3d+binary` | `x3dbz` | `x3db` | -| `model/x3d+vrml` | `x3dvz` | `x3dv` | -| `model/x3d+xml` | `x3dz` | `x3d` | -| `text/cache-manifest` | `appcache` | | -| `text/calendar` | `ics` | `ifb` | -| `text/css` | `css` | | -| `text/csv` | `csv` | | -| `text/html` | `html` | `htm` | -| `text/javascript` | `js` | `mjs` | -| `text/markdown` | `md` | `markdown` | -| `text/n3` | `n3` | | -| `text/plain` | `txt` | `conf`, `def`, `in`, `list`, `log`, `text` | -| `text/prs.lines.tag` | `dsc` | | -| `text/richtext` | `rtx` | | -| `text/sgml` | `sgml` | `sgm` | -| `text/tab-separated-values` | `tsv` | | -| `text/troff` | `tr` | `man`, `me`, `ms`, `roff`, `t` | -| `text/turtle` | `ttl` | | -| `text/uri-list` | `urls` | `uri`, `uris` | -| `text/vcard` | `vcard` | | -| `text/vnd.curl` | `curl` | | -| `text/vnd.curl.dcurl` | `dcurl` | | -| `text/vnd.curl.mcurl` | `mcurl` | | -| `text/vnd.curl.scurl` | `scurl` | | -| `text/vnd.dvb.subtitle` | `sub` | | -| `text/vnd.fly` | `fly` | | -| `text/vnd.fmi.flexstor` | `flx` | | -| `text/vnd.graphviz` | `gv` | | -| `text/vnd.in3d.3dml` | `3dml` | | -| `text/vnd.in3d.spot` | `spot` | | -| `text/vnd.sun.j2me.app-descriptor` | `jad` | | -| `text/vnd.wap.wml` | `wml` | | -| `text/vnd.wap.wmlscript` | `wmls` | | -| `text/x-asm` | `asm` | `s` | -| `text/x-c` | `c` | `cc`, `cpp`, `cxx`, `dic`, `h`, `hh` | -| `text/x-dart` | `dart` | | -| `text/x-fortran` | `for` | `f`, `f77`, `f90` | -| `text/x-java-source` | `java` | | -| `text/x-nfo` | `nfo` | | -| `text/x-opml` | `opml` | | -| `text/x-pascal` | `pas` | `p` | -| `text/x-setext` | `etx` | | -| `text/x-sfv` | `sfv` | | -| `text/x-uuencode` | `uu` | | -| `text/x-vcalendar` | `vcs` | | -| `text/x-vcard` | `vcf` | | -| `video/3gpp` | `3gp` | | -| `video/3gpp2` | `3g2` | | -| `video/h261` | `h261` | | -| `video/h263` | `h263` | | -| `video/h264` | `h264` | | -| `video/jpeg` | `jpgv` | | -| `video/jpm` | `jpm` | `jpgm` | -| `video/mj2` | `mjp2` | `mj2` | -| `video/mp4` | `mp4` | `mp4v`, `mpg4` | -| `video/mpeg` | `mpg` | `m1v`, `m2v`, `mpe`, `mpeg` | -| `video/ogg` | `ogv` | | -| `video/quicktime` | `mov` | `qt` | -| `video/vnd.dece.hd` | `uvvh` | `uvh` | -| `video/vnd.dece.mobile` | `uvvm` | `uvm` | -| `video/vnd.dece.pd` | `uvvp` | `uvp` | -| `video/vnd.dece.sd` | `uvvs` | `uvs` | -| `video/vnd.dece.video` | `uvvv` | `uvv` | -| `video/vnd.dvb.file` | `dvb` | | -| `video/vnd.fvt` | `fvt` | | -| `video/vnd.mpegurl` | `mxu` | `m4u` | -| `video/vnd.ms-playready.media.pyv` | `pyv` | | -| `video/vnd.uvvu.mp4` | `uvvu` | `uvu` | -| `video/vnd.vivo` | `viv` | | -| `video/webm` | `webm` | | -| `video/x-f4v` | `f4v` | | -| `video/x-fli` | `fli` | | -| `video/x-flv` | `flv` | | -| `video/x-m4v` | `m4v` | | -| `video/x-matroska` | `mkv` | `mk3d`, `mks` | -| `video/x-mng` | `mng` | | -| `video/x-ms-asf` | `asx` | `asf` | -| `video/x-ms-vob` | `vob` | | -| `video/x-ms-wm` | `wm` | | -| `video/x-ms-wmv` | `wmv` | | -| `video/x-ms-wmx` | `wmx` | | -| `video/x-ms-wvx` | `wvx` | | -| `video/x-msvideo` | `avi` | | -| `video/x-sgi-movie` | `movie` | | -| `video/x-smv` | `smv` | | -| `x-conference/x-cooltalk` | `ice` | | +| MIME type | Default | Additional | +| ---------------------------------------- | ----------- | ------------------- | +| application/andrew-inset | ez | | +| application/applixware | aw | | +| application/atom+xml | atom | | +| application/atomcat+xml | atomcat | | +| application/atomsvc+xml | atomsvc | | +| application/ccxml+xml | ccxml | | +| application/cdmi-capability | cdmia | | +| application/cdmi-container | cdmic | | +| application/cdmi-domain | cdmid | | +| application/cdmi-object | cdmio | | +| application/cdmi-queue | cdmiq | | +| application/cu-seeme | cu | | +| application/davmount+xml | davmount | | +| application/dicom | dcm | | +| application/docbook+xml | dbk | | +| application/dssc+der | dssc | | +| application/dssc+xml | xdssc | | +| application/ecmascript | ecma | | +| application/emma+xml | emma | | +| application/epub+zip | epub | | +| application/exi | exi | | +| application/font-tdpfr | pfr | | +| application/gml+xml | gml | | +| application/gpx+xml | gpx | | +| application/gxf | gxf | | +| application/hyperstudio | stk | | +| application/inkml+xml | inkml | ink | +| application/ipfix | ipfix | | +| application/java-archive | jar | | +| application/java-serialized-object | ser | | +| application/java-vm | class | | +| application/json | json | | +| application/jsonml+json | jsonml | | +| application/lost+xml | lostxml | | +| application/mac-binhex40 | hqx | | +| application/mac-compactpro | cpt | | +| application/mads+xml | mads | | +| application/manifest+json | webmanifest | | +| application/marc | mrc | | +| application/marcxml+xml | mrcx | | +| application/mathematica | nb | ma, mb | +| application/mathml+xml | mathml | | +| application/mbox | mbox | | +| application/mediaservercontrol+xml | mscml | | +| application/metalink+xml | metalink | | +| application/metalink4+xml | meta4 | | +| application/mets+xml | mets | | +| application/mods+xml | mods | | +| application/mp21 | mp21 | m21 | +| application/mp4 | mp4s | | +| application/msword | doc | dot | +| application/mxf | mxf | | +| application/octet-stream | so | bin, bpk, deploy, dist, distz, dms, dump, elc, lrf, mar, pkg | +| application/oda | oda | | +| application/oebps-package+xml | opf | | +| application/ogg | ogx | | +| application/omdoc+xml | omdoc | | +| application/onenote | onetoc2 | onepkg, onetmp, onetoc | +| application/oxps | oxps | | +| application/patch-ops-error+xml | xer | | +| application/pdf | pdf | | +| application/pgp-encrypted | pgp | | +| application/pgp-signature | sig | asc | +| application/pics-rules | prf | | +| application/pkcs10 | p10 | | +| application/pkcs7-mime | p7m | p7c | +| application/pkcs7-signature | p7s | | +| application/pkcs8 | p8 | | +| application/pkix-attr-cert | ac | | +| application/pkix-cert | cer | | +| application/pkix-crl | crl | | +| application/pkix-pkipath | pkipath | | +| application/pkixcmp | pki | | +| application/pls+xml | pls | | +| application/postscript | ps | ai, eps | +| application/prs.cww | cww | | +| application/pskc+xml | pskcxml | | +| application/rdf+xml | rdf | | +| application/reginfo+xml | rif | | +| application/relax-ng-compact-syntax | rnc | | +| application/resource-lists+xml | rl | | +| application/resource-lists-diff+xml | rld | | +| application/rls-services+xml | rs | | +| application/rpki-ghostbusters | gbr | | +| application/rpki-manifest | mft | | +| application/rpki-roa | roa | | +| application/rsd+xml | rsd | | +| application/rss+xml | rss | | +| application/rtf | rtf | | +| application/sbml+xml | sbml | | +| application/scvp-cv-request | scq | | +| application/scvp-cv-response | scs | | +| application/scvp-vp-request | spq | | +| application/scvp-vp-response | spp | | +| application/sdp | sdp | | +| application/set-payment-initiation | setpay | | +| application/set-registration-initiation | setreg | | +| application/shf+xml | shf | | +| application/smil+xml | smil | smi | +| application/sparql-query | rq | | +| application/sparql-results+xml | srx | | +| application/srgs | gram | | +| application/srgs+xml | grxml | | +| application/sru+xml | sru | | +| application/ssdl+xml | ssdl | | +| application/ssml+xml | ssml | | +| application/tei+xml | teicorpus | tei | +| application/thraud+xml | tfi | | +| application/timestamped-data | tsd | | +| application/toml | toml | | +| application/vnd.3gpp.pic-bw-large | plb | | +| application/vnd.3gpp.pic-bw-small | psb | | +| application/vnd.3gpp.pic-bw-var | pvb | | +| application/vnd.3gpp2.tcap | tcap | | +| application/vnd.3m.post-it-notes | pwn | | +| application/vnd.accpac.simply.aso | aso | | +| application/vnd.accpac.simply.imp | imp | | +| application/vnd.acucobol | acu | | +| application/vnd.acucorp | atc | acutc | +| application/vnd.adobe.air-application-installer-package+zip | air | | +| application/vnd.adobe.formscentral.fcdt | fcdt | | +| application/vnd.adobe.fxp | fxpl | fxp | +| application/vnd.adobe.xdp+xml | xdp | | +| application/vnd.adobe.xfdf | xfdf | | +| application/vnd.ahead.space | ahead | | +| application/vnd.airzip.filesecure.azf | azf | | +| application/vnd.airzip.filesecure.azs | azs | | +| application/vnd.amazon.ebook | azw | | +| application/vnd.americandynamics.acc | acc | | +| application/vnd.amiga.ami | ami | | +| application/vnd.android.package-archive | apk | | +| application/vnd.anser-web-certificate-issue-initiation | cii | | +| application/vnd.anser-web-funds-transfer-initiation | fti | | +| application/vnd.antix.game-component | atx | | +| application/vnd.apple.installer+xml | mpkg | | +| application/vnd.apple.mpegurl | m3u8 | | +| application/vnd.aristanetworks.swi | swi | | +| application/vnd.astraea-software.iota | iota | | +| application/vnd.audiograph | aep | | +| application/vnd.blueice.multipass | mpm | | +| application/vnd.bmi | bmi | | +| application/vnd.businessobjects | rep | | +| application/vnd.chemdraw+xml | cdxml | | +| application/vnd.chipnuts.karaoke-mmd | mmd | | +| application/vnd.cinderella | cdy | | +| application/vnd.claymore | cla | | +| application/vnd.cloanto.rp9 | rp9 | | +| application/vnd.clonk.c4group | c4u | c4d, c4f, c4g, c4p | +| application/vnd.cluetrust.cartomobile-config | c11amc | | +| application/vnd.cluetrust.cartomobile-config-pkg | c11amz | | +| application/vnd.commonspace | csp | | +| application/vnd.contact.cmsg | cdbcmsg | | +| application/vnd.cosmocaller | cmc | | +| application/vnd.crick.clicker | clkx | | +| application/vnd.crick.clicker.keyboard | clkk | | +| application/vnd.crick.clicker.palette | clkp | | +| application/vnd.crick.clicker.template | clkt | | +| application/vnd.crick.clicker.wordbank | clkw | | +| application/vnd.criticaltools.wbs+xml | wbs | | +| application/vnd.ctc-posml | pml | | +| application/vnd.cups-ppd | ppd | | +| application/vnd.curl.car | car | | +| application/vnd.curl.pcurl | pcurl | | +| application/vnd.data-vision.rdz | rdz | | +| application/vnd.dece.data | uvvf | uvd, uvf, uvvd | +| application/vnd.dece.ttml+xml | uvvt | uvt | +| application/vnd.dece.unspecified | uvx | uvvx | +| application/vnd.dece.zip | uvz | uvvz | +| application/vnd.denovo.fcselayout-link | fe\_launch | | +| application/vnd.dna | dna | | +| application/vnd.dolby.mlp | mlp | | +| application/vnd.dpgraph | dpg | | +| application/vnd.dreamfactory | dfac | | +| application/vnd.ds-keypoint | kpxx | | +| application/vnd.dvb.ait | ait | | +| application/vnd.dvb.service | svc | | +| application/vnd.dynageo | geo | | +| application/vnd.ecowin.chart | mag | | +| application/vnd.enliven | nml | | +| application/vnd.epson.esf | esf | | +| application/vnd.epson.msf | msf | | +| application/vnd.epson.quickanime | qam | | +| application/vnd.epson.salt | slt | | +| application/vnd.epson.ssf | ssf | | +| application/vnd.eszigno3+xml | et3 | es3 | +| application/vnd.ezpix-album | ez2 | | +| application/vnd.ezpix-package | ez3 | | +| application/vnd.fdf | fdf | | +| application/vnd.fdsn.mseed | mseed | | +| application/vnd.fdsn.seed | seed | dataless | +| application/vnd.flographit | gph | | +| application/vnd.fluxtime.clip | ftc | | +| application/vnd.framemaker | maker | book, fm, frame | +| application/vnd.frogans.fnc | fnc | | +| application/vnd.frogans.ltf | ltf | | +| application/vnd.fsc.weblaunch | fsc | | +| application/vnd.fujitsu.oasys | oas | | +| application/vnd.fujitsu.oasys2 | oa2 | | +| application/vnd.fujitsu.oasys3 | oa3 | | +| application/vnd.fujitsu.oasysgp | fg5 | | +| application/vnd.fujitsu.oasysprs | bh2 | | +| application/vnd.fujixerox.ddd | ddd | | +| application/vnd.fujixerox.docuworks | xdw | | +| application/vnd.fujixerox.docuworks.binder | xbd | | +| application/vnd.fuzzysheet | fzs | | +| application/vnd.genomatix.tuxedo | txd | | +| application/vnd.geogebra.file | ggb | | +| application/vnd.geogebra.tool | ggt | | +| application/vnd.geometry-explorer | gre | gex | +| application/vnd.geonext | gxt | | +| application/vnd.geoplan | g2w | | +| application/vnd.geospace | g3w | | +| application/vnd.gmx | gmx | | +| application/vnd.google-earth.kml+xml | kml | | +| application/vnd.google-earth.kmz | kmz | | +| application/vnd.grafeq | gqs | gqf | +| application/vnd.groove-account | gac | | +| application/vnd.groove-help | ghf | | +| application/vnd.groove-identity-message | gim | | +| application/vnd.groove-injector | grv | | +| application/vnd.groove-tool-message | gtm | | +| application/vnd.groove-tool-template | tpl | | +| application/vnd.groove-vcard | vcg | | +| application/vnd.hal+xml | hal | | +| application/vnd.handheld-entertainment+xml | zmm | | +| application/vnd.hbci | hbci | | +| application/vnd.hhe.lesson-player | les | | +| application/vnd.hp-hpgl | hpgl | | +| application/vnd.hp-hpid | hpid | | +| application/vnd.hp-hps | hps | | +| application/vnd.hp-jlyt | jlt | | +| application/vnd.hp-pcl | pcl | | +| application/vnd.hp-pclxl | pclxl | | +| application/vnd.hydrostatix.sof-data | sfd-hdstx | | +| application/vnd.ibm.minipay | mpy | | +| application/vnd.ibm.modcap | listafp | afp, list3820 | +| application/vnd.ibm.rights-management | irm | | +| application/vnd.ibm.secure-container | sc | | +| application/vnd.iccprofile | icm | icc | +| application/vnd.igloader | igl | | +| application/vnd.immervision-ivp | ivp | | +| application/vnd.immervision-ivu | ivu | | +| application/vnd.insors.igm | igm | | +| application/vnd.intercon.formnet | xpx | xpw | +| application/vnd.intergeo | i2g | | +| application/vnd.intu.qbo | qbo | | +| application/vnd.intu.qfx | qfx | | +| application/vnd.ipunplugged.rcprofile | rcprofile | | +| application/vnd.irepository.package+xml | irp | | +| application/vnd.is-xpr | xpr | | +| application/vnd.isac.fcs | fcs | | +| application/vnd.jam | jam | | +| application/vnd.jcp.javame.midlet-rms | rms | | +| application/vnd.jisp | jisp | | +| application/vnd.joost.joda-archive | joda | | +| application/vnd.kahootz | ktz | ktr | +| application/vnd.kde.karbon | karbon | | +| application/vnd.kde.kchart | chrt | | +| application/vnd.kde.kformula | kfo | | +| application/vnd.kde.kivio | flw | | +| application/vnd.kde.kontour | kon | | +| application/vnd.kde.kpresenter | kpt | kpr | +| application/vnd.kde.kspread | ksp | | +| application/vnd.kde.kword | kwt | kwd | +| application/vnd.kenameaapp | htke | | +| application/vnd.kidspiration | kia | | +| application/vnd.kinar | knp | kne | +| application/vnd.koan | skt | skd, skm, skp | +| application/vnd.kodak-descriptor | sse | | +| application/vnd.las.las+xml | lasxml | | +| application/vnd.llamagraphics.life-balance.desktop | lbd | | +| application/vnd.llamagraphics.life-balance.exchange+xml | lbe | | +| application/vnd.lotus-1-2-3 | 123 | | +| application/vnd.lotus-approach | apr | | +| application/vnd.lotus-freelance | pre | | +| application/vnd.lotus-notes | nsf | | +| application/vnd.lotus-organizer | org | | +| application/vnd.lotus-screencam | scm | | +| application/vnd.lotus-wordpro | lwp | | +| application/vnd.macports.portpkg | portpkg | | +| application/vnd.mcd | mcd | | +| application/vnd.medcalcdata | mc1 | | +| application/vnd.mediastation.cdkey | cdkey | | +| application/vnd.mfer | mwf | | +| application/vnd.mfmp | mfm | | +| application/vnd.micrografx.flo | flo | | +| application/vnd.micrografx.igx | igx | | +| application/vnd.mif | mif | | +| application/vnd.mobius.daf | daf | | +| application/vnd.mobius.dis | dis | | +| application/vnd.mobius.mbk | mbk | | +| application/vnd.mobius.mqy | mqy | | +| application/vnd.mobius.msl | msl | | +| application/vnd.mobius.plc | plc | | +| application/vnd.mobius.txf | txf | | +| application/vnd.mophun.application | mpn | | +| application/vnd.mophun.certificate | mpc | | +| application/vnd.mozilla.xul+xml | xul | | +| application/vnd.ms-artgalry | cil | | +| application/vnd.ms-cab-compressed | cab | | +| application/vnd.ms-excel | xls | xla, xlc, xlm, xlt, xlw | +| application/vnd.ms-excel.addin.macroenabled.12 | xlam | | +| application/vnd.ms-excel.sheet.binary.macroenabled.12 | xlsb | | +| application/vnd.ms-excel.sheet.macroenabled.12 | xlsm | | +| application/vnd.ms-excel.template.macroenabled.12 | xltm | | +| application/vnd.ms-fontobject | eot | | +| application/vnd.ms-htmlhelp | chm | | +| application/vnd.ms-ims | ims | | +| application/vnd.ms-lrm | lrm | | +| application/vnd.ms-officetheme | thmx | | +| application/vnd.ms-pki.seccat | cat | | +| application/vnd.ms-pki.stl | stl | | +| application/vnd.ms-powerpoint | ppt | pot, pps | +| application/vnd.ms-powerpoint.addin.macroenabled.12 | ppam | | +| application/vnd.ms-powerpoint.presentation.macroenabled.12 | pptm | | +| application/vnd.ms-powerpoint.slide.macroenabled.12 | sldm | | +| application/vnd.ms-powerpoint.slideshow.macroenabled.12 | ppsm | | +| application/vnd.ms-powerpoint.template.macroenabled.12 | potm | | +| application/vnd.ms-project | mpt | mpp | +| application/vnd.ms-word.document.macroenabled.12 | docm | | +| application/vnd.ms-word.template.macroenabled.12 | dotm | | +| application/vnd.ms-works | wps | wcm, wdb, wks | +| application/vnd.ms-wpl | wpl | | +| application/vnd.ms-xpsdocument | xps | | +| application/vnd.mseq | mseq | | +| application/vnd.musician | mus | | +| application/vnd.muvee.style | msty | | +| application/vnd.mynfc | taglet | | +| application/vnd.neurolanguage.nlu | nlu | | +| application/vnd.nitf | ntf | nitf | +| application/vnd.noblenet-directory | nnd | | +| application/vnd.noblenet-sealer | nns | | +| application/vnd.noblenet-web | nnw | | +| application/vnd.nokia.n-gage.data | ngdat | | +| application/vnd.nokia.n-gage.symbian.install | n-gage | | +| application/vnd.nokia.radio-preset | rpst | | +| application/vnd.nokia.radio-presets | rpss | | +| application/vnd.novadigm.edm | edm | | +| application/vnd.novadigm.edx | edx | | +| application/vnd.novadigm.ext | ext | | +| application/vnd.oasis.opendocument.chart | odc | | +| application/vnd.oasis.opendocument.chart-template | otc | | +| application/vnd.oasis.opendocument.database | odb | | +| application/vnd.oasis.opendocument.formula | odf | | +| application/vnd.oasis.opendocument.formula-template | odft | | +| application/vnd.oasis.opendocument.graphics | odg | | +| application/vnd.oasis.opendocument.graphics-template | otg | | +| application/vnd.oasis.opendocument.image | odi | | +| application/vnd.oasis.opendocument.image-template | oti | | +| application/vnd.oasis.opendocument.presentation | odp | | +| application/vnd.oasis.opendocument.presentation-template | otp | | +| application/vnd.oasis.opendocument.spreadsheet | ods | | +| application/vnd.oasis.opendocument.spreadsheet-template | ots | | +| application/vnd.oasis.opendocument.text | odt | | +| application/vnd.oasis.opendocument.text-master | odm | | +| application/vnd.oasis.opendocument.text-template | ott | | +| application/vnd.oasis.opendocument.text-web | oth | | +| application/vnd.olpc-sugar | xo | | +| application/vnd.oma.dd2+xml | dd2 | | +| application/vnd.openofficeorg.extension | oxt | | +| application/vnd.openxmlformats-officedocument.presentationml.presentation | pptx | | +| application/vnd.openxmlformats-officedocument.presentationml.slide | sldx | | +| application/vnd.openxmlformats-officedocument.presentationml.slideshow | ppsx | | +| application/vnd.openxmlformats-officedocument.presentationml.template | potx | | +| application/vnd.openxmlformats-officedocument.spreadsheetml.sheet | xlsx | | +| application/vnd.openxmlformats-officedocument.spreadsheetml.template | xltx | | +| application/vnd.openxmlformats-officedocument.wordprocessingml.document | docx | | +| application/vnd.openxmlformats-officedocument.wordprocessingml.template | dotx | | +| application/vnd.osgeo.mapguide.package | mgp | | +| application/vnd.osgi.dp | dp | | +| application/vnd.osgi.subsystem | esa | | +| application/vnd.palm | pqa | oprc, pdb | +| application/vnd.pawaafile | paw | | +| application/vnd.pg.format | str | | +| application/vnd.pg.osasli | ei6 | | +| application/vnd.picsel | efif | | +| application/vnd.pmi.widget | wg | | +| application/vnd.pocketlearn | plf | | +| application/vnd.powerbuilder6 | pbd | | +| application/vnd.previewsystems.box | box | | +| application/vnd.proteus.magazine | mgz | | +| application/vnd.publishare-delta-tree | qps | | +| application/vnd.pvi.ptid1 | ptid | | +| application/vnd.quark.quarkxpress | qxt | qwd, qwt, qxb, qxd, qxl | +| application/vnd.realvnc.bed | bed | | +| application/vnd.recordare.musicxml | mxl | | +| application/vnd.recordare.musicxml+xml | musicxml | | +| application/vnd.rig.cryptonote | cryptonote | | +| application/vnd.rim.cod | cod | | +| application/vnd.rn-realmedia | rm | | +| application/vnd.rn-realmedia-vbr | rmvb | | +| application/vnd.route66.link66+xml | link66 | | +| application/vnd.sailingtracker.track | st | | +| application/vnd.seemail | see | | +| application/vnd.sema | sema | | +| application/vnd.semd | semd | | +| application/vnd.semf | semf | | +| application/vnd.shana.informed.formdata | ifm | | +| application/vnd.shana.informed.formtemplate | itp | | +| application/vnd.shana.informed.interchange | iif | | +| application/vnd.shana.informed.package | ipk | | +| application/vnd.simtech-mindmapper | twds | twd | +| application/vnd.smaf | mmf | | +| application/vnd.smart.teacher | teacher | | +| application/vnd.solent.sdkm+xml | sdkm | sdkd | +| application/vnd.spotfire.dxp | dxp | | +| application/vnd.spotfire.sfs | sfs | | +| application/vnd.stardivision.calc | sdc | | +| application/vnd.stardivision.draw | sda | | +| application/vnd.stardivision.impress | sdd | | +| application/vnd.stardivision.math | smf | | +| application/vnd.stardivision.writer | vor | sdw | +| application/vnd.stardivision.writer-global | sgl | | +| application/vnd.stepmania.package | smzip | | +| application/vnd.stepmania.stepchart | sm | | +| application/vnd.sun.xml.calc | sxc | | +| application/vnd.sun.xml.calc.template | stc | | +| application/vnd.sun.xml.draw | sxd | | +| application/vnd.sun.xml.draw.template | std | | +| application/vnd.sun.xml.impress | sxi | | +| application/vnd.sun.xml.impress.template | sti | | +| application/vnd.sun.xml.math | sxm | | +| application/vnd.sun.xml.writer | sxw | | +| application/vnd.sun.xml.writer.global | sxg | | +| application/vnd.sun.xml.writer.template | stw | | +| application/vnd.sus-calendar | susp | sus | +| application/vnd.svd | svd | | +| application/vnd.symbian.install | sisx | sis | +| application/vnd.syncml+xml | xsm | | +| application/vnd.syncml.dm+wbxml | bdm | | +| application/vnd.syncml.dm+xml | xdm | | +| application/vnd.tao.intent-module-archive | tao | | +| application/vnd.tcpdump.pcap | pcap | cap, dmp | +| application/vnd.tmobile-livetv | tmo | | +| application/vnd.trid.tpt | tpt | | +| application/vnd.triscape.mxs | mxs | | +| application/vnd.trueapp | tra | | +| application/vnd.ufdl | ufdl | ufd | +| application/vnd.uiq.theme | utz | | +| application/vnd.umajin | umj | | +| application/vnd.unity | unityweb | | +| application/vnd.uoml+xml | uoml | | +| application/vnd.vcx | vcx | | +| application/vnd.visio | vsw | vsd, vss, vst | +| application/vnd.visionary | vis | | +| application/vnd.vsf | vsf | | +| application/vnd.wap.wbxml | wbxml | | +| application/vnd.wap.wmlc | wmlc | | +| application/vnd.wap.wmlscriptc | wmlsc | | +| application/vnd.webturbo | wtb | | +| application/vnd.wolfram.player | nbp | | +| application/vnd.wordperfect | wpd | | +| application/vnd.wqd | wqd | | +| application/vnd.wt.stf | stf | | +| application/vnd.xara | xar | | +| application/vnd.xfdl | xfdl | | +| application/vnd.yamaha.hv-dic | hvd | | +| application/vnd.yamaha.hv-script | hvs | | +| application/vnd.yamaha.hv-voice | hvp | | +| application/vnd.yamaha.openscoreformat | osf | | +| application/vnd.yamaha.openscoreformat.osfpvg+xml | osfpvg | | +| application/vnd.yamaha.smaf-audio | saf | | +| application/vnd.yamaha.smaf-phrase | spf | | +| application/vnd.yellowriver-custom-menu | cmp | | +| application/vnd.zul | zirz | zir | +| application/vnd.zzazz.deck+xml | zaz | | +| application/voicexml+xml | vxml | | +| application/wasm | wasm | | +| application/widget | wgt | | +| application/winhlp | hlp | | +| application/wsdl+xml | wsdl | | +| application/wspolicy+xml | wspolicy | | +| application/x-7z-compressed | 7z | | +| application/x-abiword | abw | | +| application/x-ace-compressed | ace | | +| application/x-apple-diskimage | dmg | | +| application/x-authorware-bin | x32 | aab, u32, vox | +| application/x-authorware-map | aam | | +| application/x-authorware-seg | aas | | +| application/x-bcpio | bcpio | | +| application/x-bittorrent | torrent | | +| application/x-blorb | blorb | blb | +| application/x-bzip | bz | | +| application/x-bzip2 | bz2 | boz | +| application/x-cbr | cbz | cb7, cba, cbr, cbt | +| application/x-cdlink | vcd | | +| application/x-cfs-compressed | cfs | | +| application/x-chat | chat | | +| application/x-chess-pgn | pgn | | +| application/x-conference | nsc | | +| application/x-cpio | cpio | | +| application/x-csh | csh | | +| application/x-debian-package | deb | udeb | +| application/x-dgc-compressed | dgc | | +| application/x-director | w3d | cct, cst, cxt, dcr, dir, dxr, fgd, swa | +| application/x-doom | wad | | +| application/x-dtbncx+xml | ncx | | +| application/x-dtbook+xml | dtb | | +| application/x-dtbresource+xml | res | | +| application/x-dvi | dvi | | +| application/x-envoy | evy | | +| application/x-eva | eva | | +| application/x-font-bdf | bdf | | +| application/x-font-ghostscript | gsf | | +| application/x-font-linux-psf | psf | | +| application/x-font-otf | otf | | +| application/x-font-pcf | pcf | | +| application/x-font-snf | snf | | +| application/x-font-ttf | ttf | ttc | +| application/x-font-type1 | pfm | afm, pfa, pfb | +| application/x-font-woff | woff | | +| application/x-freearc | arc | | +| application/x-futuresplash | spl | | +| application/x-gca-compressed | gca | | +| application/x-glulx | ulx | | +| application/x-gnumeric | gnumeric | | +| application/x-gramps-xml | gramps | | +| application/x-gtar | gtar | | +| application/x-hdf | hdf | | +| application/x-install-instructions | install | | +| application/x-iso9660-image | iso | | +| application/x-java-jnlp-file | jnlp | | +| application/x-latex | latex | | +| application/x-lzh-compressed | lzh | lha | +| application/x-mie | mie | | +| application/x-mobipocket-ebook | prc | mobi | +| application/x-ms-application | application | | +| application/x-ms-shortcut | lnk | | +| application/x-ms-wmd | wmd | | +| application/x-ms-wmz | wmz | | +| application/x-ms-xbap | xbap | | +| application/x-msaccess | mdb | | +| application/x-msbinder | obd | | +| application/x-mscardfile | crd | | +| application/x-msclip | clp | | +| application/x-msdownload | msi | bat, com, dll, exe | +| application/x-msmediaview | mvb | m13, m14 | +| application/x-msmetafile | wmf | emf, emz | +| application/x-msmoney | mny | | +| application/x-mspublisher | pub | | +| application/x-msschedule | scd | | +| application/x-msterminal | trm | | +| application/x-mswrite | wri | | +| application/x-netcdf | nc | cdf | +| application/x-nzb | nzb | | +| application/x-pkcs12 | pfx | p12 | +| application/x-pkcs7-certificates | spc | p7b | +| application/x-pkcs7-certreqresp | p7r | | +| application/x-rar-compressed | rar | | +| application/x-research-info-systems | ris | | +| application/x-sh | sh | | +| application/x-shar | shar | | +| application/x-shockwave-flash | swf | | +| application/x-silverlight-app | xap | | +| application/x-sql | sql | | +| application/x-stuffit | sit | | +| application/x-stuffitx | sitx | | +| application/x-subrip | srt | | +| application/x-sv4cpio | sv4cpio | | +| application/x-sv4crc | sv4crc | | +| application/x-t3vm-image | t3 | | +| application/x-tads | gam | | +| application/x-tar | tar | | +| application/x-tcl | tcl | | +| application/x-tex | tex | | +| application/x-tex-tfm | tfm | | +| application/x-texinfo | texinfo | texi | +| application/x-tgif | obj | | +| application/x-ustar | ustar | | +| application/x-wais-source | src | | +| application/x-x509-ca-cert | der | crt | +| application/x-xfig | fig | | +| application/x-xliff+xml | xlf | | +| application/x-xpinstall | xpi | | +| application/x-xz | xz | | +| application/x-zmachine | z8 | z1, z2, z3, z4, z5, z6, z7 | +| application/xaml+xml | xaml | | +| application/xcap-diff+xml | xdf | | +| application/xenc+xml | xenc | | +| application/xhtml+xml | xhtml | xht | +| application/xml | xml | xsl | +| application/xml-dtd | dtd | | +| application/xop+xml | xop | | +| application/xproc+xml | xpl | | +| application/xslt+xml | xslt | | +| application/xspf+xml | xspf | | +| application/xv+xml | xvml | mxml, xhvml, xvm | +| application/yang | yang | | +| application/yin+xml | yin | | +| application/zip | zip | | +| audio/aac | aac | | +| audio/adpcm | adp | | +| audio/basic | snd | au | +| audio/midi | mid | kar, midi, rmi | +| audio/mp4 | m4a | m4b, mp4a | +| audio/mpeg | mpga | m2a, m3a, mp2, mp2a, mp3 | +| audio/ogg | ogg | oga, spx | +| audio/s3m | s3m | | +| audio/silk | sil | | +| audio/vnd.dece.audio | uvva | uva | +| audio/vnd.digital-winds | eol | | +| audio/vnd.dra | dra | | +| audio/vnd.dts | dts | | +| audio/vnd.dts.hd | dtshd | | +| audio/vnd.lucent.voice | lvp | | +| audio/vnd.ms-playready.media.pya | pya | | +| audio/vnd.nuera.ecelp4800 | ecelp4800 | | +| audio/vnd.nuera.ecelp7470 | ecelp7470 | | +| audio/vnd.nuera.ecelp9600 | ecelp9600 | | +| audio/vnd.rip | rip | | +| audio/webm | weba | | +| audio/x-aiff | aif | aifc, aiff | +| audio/x-caf | caf | | +| audio/x-flac | flac | | +| audio/x-matroska | mka | | +| audio/x-mpegurl | m3u | | +| audio/x-ms-wax | wax | | +| audio/x-ms-wma | wma | | +| audio/x-pn-realaudio | ram | ra | +| audio/x-pn-realaudio-plugin | rmp | | +| audio/x-wav | wav | | +| audio/xm | xm | | +| chemical/x-cdx | cdx | | +| chemical/x-cif | cif | | +| chemical/x-cmdf | cmdf | | +| chemical/x-cml | cml | | +| chemical/x-csml | csml | | +| chemical/x-xyz | xyz | | +| font/woff2 | woff2 | | +| image/avif | avif | | +| image/bmp | bmp | | +| image/cgm | cgm | | +| image/g3fax | g3 | | +| image/gif | gif | | +| image/heic | heic | | +| image/heif | heif | | +| image/ief | ief | | +| image/jpeg | jpg | jpe, jpeg | +| image/ktx | ktx | | +| image/png | png | | +| image/prs.btif | btif | | +| image/sgi | sgi | | +| image/svg+xml | svg | svgz | +| image/tiff | tif | tiff | +| image/vnd.adobe.photoshop | psd | | +| image/vnd.dece.graphic | uvvi | uvg, uvi, uvvg | +| image/vnd.djvu | djvu | djv | +| image/vnd.dwg | dwg | | +| image/vnd.dxf | dxf | | +| image/vnd.fastbidsheet | fbs | | +| image/vnd.fpx | fpx | | +| image/vnd.fst | fst | | +| image/vnd.fujixerox.edmics-mmr | mmr | | +| image/vnd.fujixerox.edmics-rlc | rlc | | +| image/vnd.ms-modi | mdi | | +| image/vnd.ms-photo | wdp | | +| image/vnd.net-fpx | npx | | +| image/vnd.wap.wbmp | wbmp | | +| image/vnd.xiff | xif | | +| image/webp | webp | | +| image/x-3ds | 3ds | | +| image/x-cmu-raster | ras | | +| image/x-cmx | cmx | | +| image/x-freehand | fhc | fh, fh4, fh5, fh7 | +| image/x-icon | ico | | +| image/x-mrsid-image | sid | | +| image/x-pcx | pcx | | +| image/x-pict | pic | pct | +| image/x-portable-anymap | pnm | | +| image/x-portable-bitmap | pbm | | +| image/x-portable-graymap | pgm | | +| image/x-portable-pixmap | ppm | | +| image/x-rgb | rgb | | +| image/x-tga | tga | | +| image/x-xbitmap | xbm | | +| image/x-xpixmap | xpm | | +| image/x-xwindowdump | xwd | | +| message/rfc822 | mime | eml | +| model/gltf+json | gltf | | +| model/gltf-binary | glb | | +| model/iges | igs | iges | +| model/mesh | silo | mesh, msh | +| model/vnd.collada+xml | dae | | +| model/vnd.dwf | dwf | | +| model/vnd.gdl | gdl | | +| model/vnd.gtw | gtw | | +| model/vnd.mts | mts | | +| model/vnd.vtu | vtu | | +| model/vrml | vrml | wrl | +| model/x3d+binary | x3dbz | x3db | +| model/x3d+vrml | x3dvz | x3dv | +| model/x3d+xml | x3dz | x3d | +| text/cache-manifest | appcache | | +| text/calendar | ics | ifb | +| text/css | css | | +| text/csv | csv | | +| text/html | html | htm | +| text/javascript | js | mjs | +| text/markdown | md | markdown | +| text/n3 | n3 | | +| text/plain | txt | conf, def, in, list, log, text | +| text/prs.lines.tag | dsc | | +| text/richtext | rtx | | +| text/sgml | sgml | sgm | +| text/tab-separated-values | tsv | | +| text/troff | tr | man, me, ms, roff, t | +| text/turtle | ttl | | +| text/uri-list | urls | uri, uris | +| text/vcard | vcard | | +| text/vnd.curl | curl | | +| text/vnd.curl.dcurl | dcurl | | +| text/vnd.curl.mcurl | mcurl | | +| text/vnd.curl.scurl | scurl | | +| text/vnd.dvb.subtitle | sub | | +| text/vnd.fly | fly | | +| text/vnd.fmi.flexstor | flx | | +| text/vnd.graphviz | gv | | +| text/vnd.in3d.3dml | 3dml | | +| text/vnd.in3d.spot | spot | | +| text/vnd.sun.j2me.app-descriptor | jad | | +| text/vnd.wap.wml | wml | | +| text/vnd.wap.wmlscript | wmls | | +| text/x-asm | asm | s | +| text/x-c | c | cc, cpp, cxx, dic, h, hh | +| text/x-dart | dart | | +| text/x-fortran | for | f, f77, f90 | +| text/x-java-source | java | | +| text/x-nfo | nfo | | +| text/x-opml | opml | | +| text/x-pascal | pas | p | +| text/x-setext | etx | | +| text/x-sfv | sfv | | +| text/x-uuencode | uu | | +| text/x-vcalendar | vcs | | +| text/x-vcard | vcf | | +| video/3gpp | 3gp | | +| video/3gpp2 | 3g2 | | +| video/h261 | h261 | | +| video/h263 | h263 | | +| video/h264 | h264 | | +| video/jpeg | jpgv | | +| video/jpm | jpm | jpgm | +| video/mj2 | mjp2 | mj2 | +| video/mp4 | mp4 | mp4v, mpg4 | +| video/mpeg | mpg | m1v, m2v, mpe, mpeg | +| video/ogg | ogv | | +| video/quicktime | mov | qt | +| video/vnd.dece.hd | uvvh | uvh | +| video/vnd.dece.mobile | uvvm | uvm | +| video/vnd.dece.pd | uvvp | uvp | +| video/vnd.dece.sd | uvvs | uvs | +| video/vnd.dece.video | uvvv | uvv | +| video/vnd.dvb.file | dvb | | +| video/vnd.fvt | fvt | | +| video/vnd.mpegurl | mxu | m4u | +| video/vnd.ms-playready.media.pyv | pyv | | +| video/vnd.uvvu.mp4 | uvvu | uvu | +| video/vnd.vivo | viv | | +| video/webm | webm | | +| video/x-f4v | f4v | | +| video/x-fli | fli | | +| video/x-flv | flv | | +| video/x-m4v | m4v | | +| video/x-matroska | mkv | mk3d, mks | +| video/x-mng | mng | | +| video/x-ms-asf | asx | asf | +| video/x-ms-vob | vob | | +| video/x-ms-wm | wm | | +| video/x-ms-wmv | wmv | | +| video/x-ms-wmx | wmx | | +| video/x-ms-wvx | wvx | | +| video/x-msvideo | avi | | +| video/x-sgi-movie | movie | | +| video/x-smv | smv | | +| x-conference/x-cooltalk | ice | | diff --git a/pkgs/mime/tool/media_types.dart b/pkgs/mime/tool/media_types.dart index 8e1684006..2c5734ef4 100644 --- a/pkgs/mime/tool/media_types.dart +++ b/pkgs/mime/tool/media_types.dart @@ -37,8 +37,8 @@ void main() { final buf = StringBuffer(prefix); buf.writeln(); buf.write(''' -| MIME type | Default | Additional | -| --- | --- | --- | +| MIME type | Default | Additional | +| ---------------------------------------- | ----------- | ------------------- | '''); for (final mime in mimeTypes) { @@ -48,9 +48,11 @@ void main() { exts.remove(defaultExt); exts.sort(); - final additional = exts.isEmpty ? ' ' : ' `${exts.join('`, `')}` '; + final additional = exts.join(', '); - buf.writeln('| `$mime` | `$defaultExt` |$additional|'); + buf.writeln('| ${markdownEscape(mime).padRight(40)} | ' + '${markdownEscape(defaultExt).padRight(11)} | ' + '${markdownEscape(additional).padRight(19)} |'); } buf.writeln(); @@ -58,3 +60,15 @@ void main() { file.writeAsStringSync('${buf.toString().trim()}\n'); } + +String markdownEscape(String markdown) { + // See https://www.markdownguide.org/basic-syntax/#escaping-characters. + + // Escape the escape character. + markdown = markdown.replaceAll(r'\', r'\\'); + + // Escape other special characters. + markdown = markdown.replaceAll('_', r'\_'); + + return markdown; +} From 1ac4d8f39b465cab9195e3b0f94db29fca51748b Mon Sep 17 00:00:00 2001 From: Devon Carew Date: Mon, 29 Sep 2025 08:07:31 -0700 Subject: [PATCH 49/67] Update CODEOWNERS (#2177) --- .github/CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index a50b447bc..c1b9b727c 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -23,7 +23,7 @@ pkgs/graphs @natebosch pkgs/html @HosseinYousefi pkgs/io @dart-lang/dart-bat pkgs/json_rpc_2 @dart-lang/dart-ecosystem-team -pkgs/markdown @dart-lang/dart-model-team @dart-lang/dash-developer-services +pkgs/markdown @srawlins pkgs/mime @lrhn pkgs/oauth2 @dart-lang/dart-pub-team pkgs/package_config @dart-lang/dart-ecosystem-team From edadc0df1f82c869d7daff2cc477f764c2b13516 Mon Sep 17 00:00:00 2001 From: "Morgan :)" Date: Mon, 29 Sep 2025 08:15:57 -0700 Subject: [PATCH 50/67] Update CODEOWNERS for pkgs/watcher. (#2179) --- .github/CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index c1b9b727c..89d75bcd9 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -43,6 +43,6 @@ pkgs/term_glyph @dart-lang/dart-bat pkgs/test_reflective_loader @scheglov pkgs/timing @dart-lang/dart-bat pkgs/unified_analytics @bkonyi @dart-lang/dash-developer-services -pkgs/watcher @dart-lang/dart-ecosystem-team +pkgs/watcher @davidmorgan @dart-lang/dart-model-team pkgs/yaml @dart-lang/dart-pub-team pkgs/yaml_edit @dart-lang/dart-pub-team From f697db10f96fbf808953c4a01ecd7a409b81a3cc Mon Sep 17 00:00:00 2001 From: Slava Egorov Date: Tue, 30 Sep 2025 10:22:49 +0200 Subject: [PATCH 51/67] Ignore PathNotFoundException when watching subdirs (#2181) Watching subdirectories is inherently racy with file-system modifications thus watcher must be prepared for `Directory.watch` to fail with `PathNotFoundException`. This is revealed when running tests against a [CL][1] which changes timing of certain events slightly: `onDone` for `FileSystemEntity.watch()` stream is now delayed by few turns of the event loop while underlying watcher is being destroyed, previously it used to fire immediately which hid away some of the inherent races in the code. [1]: https://dart-review.googlesource.com/c/sdk/+/450921 --- pkgs/watcher/CHANGELOG.md | 8 ++++- .../lib/src/directory_watcher/linux.dart | 15 +++++++--- pkgs/watcher/pubspec.yaml | 2 +- .../test/directory_watcher/shared.dart | 29 +++++++++++++++++++ 4 files changed, 48 insertions(+), 6 deletions(-) diff --git a/pkgs/watcher/CHANGELOG.md b/pkgs/watcher/CHANGELOG.md index 30a19756e..fd65b4abd 100644 --- a/pkgs/watcher/CHANGELOG.md +++ b/pkgs/watcher/CHANGELOG.md @@ -1,3 +1,9 @@ +## 1.1.4-wip + +- Improve handling of subdirectories on Linux: ignore `PathNotFoundException` + due to subdirectory deletion during watch setup, instead of raising it on the + event stream. + ## 1.1.3 - Improve handling of @@ -6,7 +12,7 @@ events. But, the restart would sometimes silently fail. Now, it is more reliable. - Improving handling of directories that are created then immediately deleted on - Windows. Previously, that could cause a `PathNotfoundException` to be thrown. + Windows. Previously, that could cause a `PathNotFoundException` to be thrown. ## 1.1.2 diff --git a/pkgs/watcher/lib/src/directory_watcher/linux.dart b/pkgs/watcher/lib/src/directory_watcher/linux.dart index cb1d07781..f696a89b5 100644 --- a/pkgs/watcher/lib/src/directory_watcher/linux.dart +++ b/pkgs/watcher/lib/src/directory_watcher/linux.dart @@ -136,10 +136,17 @@ class _LinuxDirectoryWatcher // top-level clients such as barback as well, and could be implemented with // a wrapper similar to how listening/canceling works now. - // TODO(nweiz): Catch any errors here that indicate that the directory in - // question doesn't exist and silently stop watching it instead of - // propagating the errors. - var stream = Directory(path).watch(); + var stream = Directory(path).watch().transform( + StreamTransformer.fromHandlers( + handleError: (error, st, sink) { + // Directory might no longer exist at the point where we try to + // start the watcher. Simply ignore this error and let the stream + // close. + if (error is! PathNotFoundException) { + sink.addError(error, st); + } + }, + )); _subdirStreams[path] = stream; _nativeEvents.add(stream); } diff --git a/pkgs/watcher/pubspec.yaml b/pkgs/watcher/pubspec.yaml index 16af27bc2..80a8abbfd 100644 --- a/pkgs/watcher/pubspec.yaml +++ b/pkgs/watcher/pubspec.yaml @@ -1,5 +1,5 @@ name: watcher -version: 1.1.3 +version: 1.1.4-wip description: >- A file system watcher. It monitors changes to contents of directories and sends notifications when files have been added, removed, or modified. diff --git a/pkgs/watcher/test/directory_watcher/shared.dart b/pkgs/watcher/test/directory_watcher/shared.dart index 1ebc78d4b..a1d2239c6 100644 --- a/pkgs/watcher/test/directory_watcher/shared.dart +++ b/pkgs/watcher/test/directory_watcher/shared.dart @@ -1,8 +1,11 @@ // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. +import 'dart:io' as io; +import 'dart:isolate'; import 'package:test/test.dart'; +import 'package:test_descriptor/test_descriptor.dart' as d; import 'package:watcher/src/utils.dart'; import '../utils.dart'; @@ -340,5 +343,31 @@ void sharedTests() { events.add(isRemoveEvent('dir/sub')); await inAnyOrder(events); }); + + test('subdirectory watching is robust against races', () async { + // Make sandboxPath accessible to child isolates created by Isolate.run. + final sandboxPath = d.sandbox; + final dirNames = [for (var i = 0; i < 50; i++) 'dir$i']; + await startWatcher(); + + // Repeatedly create and delete subdirectories in attempt to trigger + // a race. + for (var i = 0; i < 10; i++) { + for (var dir in dirNames) { + createDir(dir); + } + await Isolate.run(() async { + await Future.wait([ + for (var dir in dirNames) + io.Directory('$sandboxPath/$dir').delete(), + ]); + }); + } + + writeFile('a/b/c/d/file.txt'); + await inAnyOrder([ + isAddEvent('a/b/c/d/file.txt'), + ]); + }); }); } From 5d2f633b677be07a04437bd1b58d3f306d640ccf Mon Sep 17 00:00:00 2001 From: Slava Egorov Date: Tue, 30 Sep 2025 05:05:02 -0700 Subject: [PATCH 52/67] Ignore Path{NotFound,Access}Exception in more places. (#2183) Directory.list() can fail with these errors due to concurrent file system modifications. Suppress these errors uniformly across the code base. --- pkgs/watcher/CHANGELOG.md | 6 ++-- .../lib/src/directory_watcher/linux.dart | 17 +++------- .../lib/src/directory_watcher/mac_os.dart | 6 ++-- .../lib/src/directory_watcher/polling.dart | 2 +- .../lib/src/directory_watcher/windows.dart | 31 ++++++++++++------- pkgs/watcher/lib/src/utils.dart | 27 ++++++++++++++++ .../test/directory_watcher/shared.dart | 2 +- 7 files changed, 60 insertions(+), 31 deletions(-) diff --git a/pkgs/watcher/CHANGELOG.md b/pkgs/watcher/CHANGELOG.md index fd65b4abd..01755b407 100644 --- a/pkgs/watcher/CHANGELOG.md +++ b/pkgs/watcher/CHANGELOG.md @@ -1,8 +1,8 @@ ## 1.1.4-wip -- Improve handling of subdirectories on Linux: ignore `PathNotFoundException` - due to subdirectory deletion during watch setup, instead of raising it on the - event stream. +- Improve handling of subdirectories: ignore `PathNotFoundException` due to + subdirectory deletion racing with watcher internals, instead of raising + it on the event stream. ## 1.1.3 diff --git a/pkgs/watcher/lib/src/directory_watcher/linux.dart b/pkgs/watcher/lib/src/directory_watcher/linux.dart index f696a89b5..99e2cf50e 100644 --- a/pkgs/watcher/lib/src/directory_watcher/linux.dart +++ b/pkgs/watcher/lib/src/directory_watcher/linux.dart @@ -92,7 +92,7 @@ class _LinuxDirectoryWatcher }); _listen( - Directory(path).list(recursive: true), + Directory(path).listRecursivelyIgnoringErrors(), (FileSystemEntity entity) { if (entity is Directory) { _watchSubdir(entity.path); @@ -136,17 +136,10 @@ class _LinuxDirectoryWatcher // top-level clients such as barback as well, and could be implemented with // a wrapper similar to how listening/canceling works now. - var stream = Directory(path).watch().transform( - StreamTransformer.fromHandlers( - handleError: (error, st, sink) { - // Directory might no longer exist at the point where we try to - // start the watcher. Simply ignore this error and let the stream - // close. - if (error is! PathNotFoundException) { - sink.addError(error, st); - } - }, - )); + // Directory might no longer exist at the point where we try to + // start the watcher. Simply ignore this error and let the stream + // close. + var stream = Directory(path).watch().ignoring(); _subdirStreams[path] = stream; _nativeEvents.add(stream); } diff --git a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart index b46138347..509cf6fe6 100644 --- a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart +++ b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart @@ -148,7 +148,9 @@ class _MacOSDirectoryWatcher if (_files.containsDir(path)) continue; - var stream = Directory(path).list(recursive: true); + var stream = Directory(path) + .list(recursive: true) + .ignoring(); var subscription = stream.listen((entity) { if (entity is Directory) return; if (_files.contains(path)) return; @@ -373,7 +375,7 @@ class _MacOSDirectoryWatcher _files.clear(); var completer = Completer(); - var stream = Directory(path).list(recursive: true); + var stream = Directory(path).listRecursivelyIgnoringErrors(); _initialListSubscription = stream.listen((entity) { if (entity is! Directory) _files.add(entity.path); }, onError: _emitError, onDone: completer.complete, cancelOnError: true); diff --git a/pkgs/watcher/lib/src/directory_watcher/polling.dart b/pkgs/watcher/lib/src/directory_watcher/polling.dart index 207679b1a..d3fa6eb60 100644 --- a/pkgs/watcher/lib/src/directory_watcher/polling.dart +++ b/pkgs/watcher/lib/src/directory_watcher/polling.dart @@ -112,7 +112,7 @@ class _PollingDirectoryWatcher _filesToProcess.add(null); } - var stream = Directory(path).list(recursive: true); + var stream = Directory(path).listRecursivelyIgnoringErrors(); _listSubscription = stream.listen((entity) { assert(!_events.isClosed); diff --git a/pkgs/watcher/lib/src/directory_watcher/windows.dart b/pkgs/watcher/lib/src/directory_watcher/windows.dart index 8f212684c..9b17f8db1 100644 --- a/pkgs/watcher/lib/src/directory_watcher/windows.dart +++ b/pkgs/watcher/lib/src/directory_watcher/windows.dart @@ -123,8 +123,15 @@ class _WindowsDirectoryWatcher void _startParentWatcher() { var absoluteDir = p.absolute(path); var parent = p.dirname(absoluteDir); - // Check if [path] is already the root directory. - if (FileSystemEntity.identicalSync(parent, path)) return; + try { + // Check if [path] is already the root directory. + if (FileSystemEntity.identicalSync(parent, path)) return; + } on FileSystemException catch (_) { + // Either parent or path or both might be gone due to concurrently + // occurring changes. Just ignore and continue. If we fail to + // watch path we will report an error from _startWatch. + return; + } var parentStream = Directory(parent).watch(recursive: false); _parentWatchSubscription = parentStream.listen( (event) { @@ -185,7 +192,14 @@ class _WindowsDirectoryWatcher if (_files.containsDir(path)) continue; - var stream = Directory(path).list(recursive: true); + // "Path not found" can be caused by creating then quickly removing + // a directory: continue without reporting an error. Nested files + // that get removed during the `list` are already ignored by `list` + // itself, so there are no other types of "path not found" that + // might need different handling here. + var stream = Directory(path) + .list(recursive: true) + .ignoring(); var subscription = stream.listen((entity) { if (entity is Directory) return; if (_files.contains(entity.path)) return; @@ -198,14 +212,7 @@ class _WindowsDirectoryWatcher }); subscription.onError((Object e, StackTrace stackTrace) { _listSubscriptions.remove(subscription); - // "Path not found" can be caused by creating then quickly removing - // a directory: continue without reporting an error. Nested files - // that get removed during the `list` are already ignored by `list` - // itself, so there are no other types of "path not found" that - // might need different handling here. - if (e is! PathNotFoundException) { - _emitError(e, stackTrace); - } + _emitError(e, stackTrace); }); _listSubscriptions.add(subscription); } else if (event is FileSystemModifyEvent) { @@ -435,7 +442,7 @@ class _WindowsDirectoryWatcher _files.clear(); var completer = Completer(); - var stream = Directory(path).list(recursive: true); + var stream = Directory(path).listRecursivelyIgnoringErrors(); void handleEntity(FileSystemEntity entity) { if (entity is! Directory) _files.add(entity.path); } diff --git a/pkgs/watcher/lib/src/utils.dart b/pkgs/watcher/lib/src/utils.dart index c2e71b3c1..e5ef54c66 100644 --- a/pkgs/watcher/lib/src/utils.dart +++ b/pkgs/watcher/lib/src/utils.dart @@ -50,3 +50,30 @@ extension BatchEvents on Stream { }).bind(this); } } + +extension IgnoringError on Stream { + /// Ignore all errors of type [E] emitted by the given stream. + /// + /// Everything else gets forwarded through as-is. + Stream ignoring() { + return transform(StreamTransformer.fromHandlers( + handleError: (error, st, sink) { + if (error is! E) { + sink.addError(error, st); + } + }, + )); + } +} + +extension DirectoryRobustRecursiveListing on Directory { + /// List the given directory recursively but ignore not-found or access + /// errors. + /// + /// Theses can arise from concurrent file-system modification. + Stream listRecursivelyIgnoringErrors() { + return list(recursive: true) + .ignoring() + .ignoring(); + } +} diff --git a/pkgs/watcher/test/directory_watcher/shared.dart b/pkgs/watcher/test/directory_watcher/shared.dart index a1d2239c6..816d2a2fa 100644 --- a/pkgs/watcher/test/directory_watcher/shared.dart +++ b/pkgs/watcher/test/directory_watcher/shared.dart @@ -347,7 +347,7 @@ void sharedTests() { test('subdirectory watching is robust against races', () async { // Make sandboxPath accessible to child isolates created by Isolate.run. final sandboxPath = d.sandbox; - final dirNames = [for (var i = 0; i < 50; i++) 'dir$i']; + final dirNames = [for (var i = 0; i < 500; i++) 'dir$i']; await startWatcher(); // Repeatedly create and delete subdirectories in attempt to trigger From fada90321ead1005840899f4a319febc0670dacd Mon Sep 17 00:00:00 2001 From: "Morgan :)" Date: Tue, 30 Sep 2025 05:09:56 -0700 Subject: [PATCH 53/67] Add test coverage around startup race and missing file on startup. (#2174) --- .../{shared.dart => file_tests.dart} | 34 ++++++++++++++-- .../test/file_watcher/native_test.dart | 10 ++--- .../test/file_watcher/polling_test.dart | 11 ++---- .../test/file_watcher/startup_race_tests.dart | 39 +++++++++++++++++++ pkgs/watcher/test/utils.dart | 27 +++++++++++-- 5 files changed, 101 insertions(+), 20 deletions(-) rename pkgs/watcher/test/file_watcher/{shared.dart => file_tests.dart} (69%) create mode 100644 pkgs/watcher/test/file_watcher/startup_race_tests.dart diff --git a/pkgs/watcher/test/file_watcher/shared.dart b/pkgs/watcher/test/file_watcher/file_tests.dart similarity index 69% rename from pkgs/watcher/test/file_watcher/shared.dart rename to pkgs/watcher/test/file_watcher/file_tests.dart index 081b92e11..74980f5f6 100644 --- a/pkgs/watcher/test/file_watcher/shared.dart +++ b/pkgs/watcher/test/file_watcher/file_tests.dart @@ -2,16 +2,24 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. +import 'dart:io'; + import 'package:test/test.dart'; import '../utils.dart'; -void sharedTests() { +void fileTests({required bool isNative}) { + setUp(() { + writeFile('file.txt'); + }); + test("doesn't notify if the file isn't modified", () async { + // TODO(davidmorgan): fix startup race on MacOS. + if (isNative && Platform.isMacOS) { + await Future.delayed(const Duration(milliseconds: 100)); + } await startWatcher(path: 'file.txt'); - await pumpEventQueue(); - deleteFile('file.txt'); - await expectRemoveEvent('file.txt'); + await expectNoEvents(); }); test('notifies when a file is modified', () async { @@ -70,4 +78,22 @@ void sharedTests() { // startWatcher awaits 'ready' await startWatcher(path: 'foo/bar/baz'); }); + + test('throws if file does not exist', () async { + await startWatcher(path: 'other_file.txt'); + + // TODO(davidmorgan): reconcile differences. + if (isNative && Platform.isLinux) { + expect(expectNoEvents, throwsA(isA())); + } else { + // The polling watcher and the MacOS watcher do not throw on missing file + // on watch. Instead, they report both creating and modification as + // modifications. + await expectNoEvents(); + writeFile('other_file.txt'); + await expectModifyEvent('other_file.txt'); + writeFile('other_file.txt'); + await expectModifyEvent('other_file.txt'); + } + }); } diff --git a/pkgs/watcher/test/file_watcher/native_test.dart b/pkgs/watcher/test/file_watcher/native_test.dart index 0d4ad6394..30f319432 100644 --- a/pkgs/watcher/test/file_watcher/native_test.dart +++ b/pkgs/watcher/test/file_watcher/native_test.dart @@ -9,14 +9,12 @@ import 'package:test/test.dart'; import 'package:watcher/src/file_watcher/native.dart'; import '../utils.dart'; -import 'shared.dart'; +import 'file_tests.dart'; +import 'startup_race_tests.dart'; void main() { watcherFactory = NativeFileWatcher.new; - setUp(() { - writeFile('file.txt'); - }); - - sharedTests(); + fileTests(isNative: true); + startupRaceTests(isNative: true); } diff --git a/pkgs/watcher/test/file_watcher/polling_test.dart b/pkgs/watcher/test/file_watcher/polling_test.dart index 861fcb222..b8e41570d 100644 --- a/pkgs/watcher/test/file_watcher/polling_test.dart +++ b/pkgs/watcher/test/file_watcher/polling_test.dart @@ -2,19 +2,16 @@ // for details. 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:test/test.dart'; import 'package:watcher/watcher.dart'; import '../utils.dart'; -import 'shared.dart'; +import 'file_tests.dart'; +import 'startup_race_tests.dart'; void main() { watcherFactory = (file) => PollingFileWatcher(file, pollingDelay: const Duration(milliseconds: 100)); - setUp(() { - writeFile('file.txt'); - }); - - sharedTests(); + fileTests(isNative: false); + startupRaceTests(isNative: false); } diff --git a/pkgs/watcher/test/file_watcher/startup_race_tests.dart b/pkgs/watcher/test/file_watcher/startup_race_tests.dart new file mode 100644 index 000000000..59e4f8e7b --- /dev/null +++ b/pkgs/watcher/test/file_watcher/startup_race_tests.dart @@ -0,0 +1,39 @@ +// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:io'; + +import 'package:test/test.dart'; + +import '../utils.dart'; + +/// Tests for a startup race that affects MacOS. +/// +/// As documented in `File.watch`, changes from shortly _before_ the `watch` +/// method is called might be reported on MacOS. They should be ignored. +void startupRaceTests({required bool isNative}) { + test('ignores events from before watch starts', () async { + // Write then immediately watch 100 times and count the events received. + var events = 0; + final futures = >[]; + for (var i = 0; i != 100; ++i) { + writeFile('file$i.txt'); + await startWatcher(path: 'file$i.txt'); + futures.add( + waitForEvent().then((event) { + if (event != null) ++events; + }), + ); + } + await Future.wait(futures); + + // TODO(davidmorgan): the MacOS watcher currently does get unwanted events, + // fix it. + if (isNative && Platform.isMacOS) { + expect(events, greaterThan(10)); + } else { + expect(events, 0); + } + }); +} diff --git a/pkgs/watcher/test/utils.dart b/pkgs/watcher/test/utils.dart index 7867b9fc2..12a277980 100644 --- a/pkgs/watcher/test/utils.dart +++ b/pkgs/watcher/test/utils.dart @@ -63,9 +63,13 @@ Future startWatcher({String? path}) async { final normalized = p.normalize(p.relative(path, from: d.sandbox)); // Make sure we got a path in the sandbox. - assert(p.isRelative(normalized) && !normalized.startsWith('..'), - 'Path is not in the sandbox: $path not in ${d.sandbox}'); - + if (!p.isRelative(normalized) || normalized.startsWith('..')) { + // The polling watcher can poll during test teardown, signal using an + // exception that it will ignore. + throw FileSystemException( + 'Path is not in the sandbox: $path not in ${d.sandbox}', + ); + } var mtime = _mockFileModificationTimes[normalized]; return mtime != null ? DateTime.fromMillisecondsSinceEpoch(mtime) : null; }); @@ -174,6 +178,23 @@ Matcher isModifyEvent(String path) => isWatchEvent(ChangeType.MODIFY, path); /// [path]. Matcher isRemoveEvent(String path) => isWatchEvent(ChangeType.REMOVE, path); +/// Takes the first event emitted during [duration], or returns `null` if there +/// is none. +Future waitForEvent({ + Duration duration = const Duration(seconds: 1), +}) async { + final result = await _watcherEvents.peek + .then((e) => e) + .timeout(duration, onTimeout: () => null); + if (result != null) _watcherEvents.take(1).ignore(); + return result; +} + +/// Expects that no events are omitted for [duration]. +Future expectNoEvents({Duration duration = const Duration(seconds: 1)}) async { + expect(await waitForEvent(duration: duration), isNull); +} + /// Expects that the next event emitted will be for an add event for [path]. Future expectAddEvent(String path) => _expectOrCollect(isWatchEvent(ChangeType.ADD, path)); From 3f5eb7f91f8917837a7fb1981d81b4e958a74808 Mon Sep 17 00:00:00 2001 From: Slava Egorov Date: Tue, 30 Sep 2025 05:28:10 -0700 Subject: [PATCH 54/67] Update watcher overflow detection (#2182) Current versions of Dart SDK contain a bug: when watcher overflows on Windows it throws a synchronous exception instead of emitting an error into the stream. Newer versions of Dart SDK will fix this bug - but this means watcher restart code needs to be updated to be prepared for this. --- pkgs/watcher/CHANGELOG.md | 3 ++ .../lib/src/directory_watcher/windows.dart | 43 +++++++++++-------- 2 files changed, 28 insertions(+), 18 deletions(-) diff --git a/pkgs/watcher/CHANGELOG.md b/pkgs/watcher/CHANGELOG.md index 01755b407..b4c823ea1 100644 --- a/pkgs/watcher/CHANGELOG.md +++ b/pkgs/watcher/CHANGELOG.md @@ -3,6 +3,9 @@ - Improve handling of subdirectories: ignore `PathNotFoundException` due to subdirectory deletion racing with watcher internals, instead of raising it on the event stream. +- Improve handling of watcher overflow on Windows: prepare for future versions + of SDK, which will properly forward `FileSystemException` into the stream + returned by the watcher. ## 1.1.3 diff --git a/pkgs/watcher/lib/src/directory_watcher/windows.dart b/pkgs/watcher/lib/src/directory_watcher/windows.dart index 9b17f8db1..87eca0f2f 100644 --- a/pkgs/watcher/lib/src/directory_watcher/windows.dart +++ b/pkgs/watcher/lib/src/directory_watcher/windows.dart @@ -405,35 +405,42 @@ class _WindowsDirectoryWatcher /// Start or restart the underlying [Directory.watch] stream. void _startWatch() { - // Note: "watcher closed" exceptions do not get sent over the stream - // returned by watch, and must be caught via a zone handler. + // Note: in older SDKs "watcher closed" exceptions might not get sent over + // the stream returned by watch, and must be caught via a zone handler. runZonedGuarded( () { var innerStream = Directory(path).watch(recursive: true); _watchSubscription = innerStream.listen( _onEvent, - onError: _eventsController.addError, + onError: _restartWatchOnOverflowOr(_eventsController.addError), onDone: _onDone, ); }, - (error, stackTrace) async { - if (error is FileSystemException && - error.message.startsWith('Directory watcher closed unexpectedly')) { - // Wait to work around https://github.com/dart-lang/sdk/issues/61378. - // Give the VM time to reset state after the error. See the issue for - // more discussion of the workaround. - await _watchSubscription?.cancel(); - await Future.delayed(const Duration(milliseconds: 1)); - _eventsController.addError(error, stackTrace); - _startWatch(); - } else { - // ignore: only_throw_errors - throw error; - } - }, + _restartWatchOnOverflowOr((error, stackTrace) { + // ignore: only_throw_errors + throw error; + }), ); } + void Function(Object, StackTrace) _restartWatchOnOverflowOr( + void Function(Object, StackTrace) otherwise) { + return (Object error, StackTrace stackTrace) async { + if (error is FileSystemException && + error.message.startsWith('Directory watcher closed unexpectedly')) { + // Wait to work around https://github.com/dart-lang/sdk/issues/61378. + // Give the VM time to reset state after the error. See the issue for + // more discussion of the workaround. + await _watchSubscription?.cancel(); + await Future.delayed(const Duration(milliseconds: 1)); + _eventsController.addError(error, stackTrace); + _startWatch(); + } else { + otherwise(error, stackTrace); + } + }; + } + /// Starts or restarts listing the watched directory to get an initial picture /// of its state. Future _listDir() { From f047d277dbc8673fb425752777ce123980b9d298 Mon Sep 17 00:00:00 2001 From: Slava Egorov Date: Tue, 30 Sep 2025 06:18:44 -0700 Subject: [PATCH 55/67] Prepare to release watcher 1.1.4 (#2184) --- pkgs/watcher/CHANGELOG.md | 2 +- pkgs/watcher/pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/watcher/CHANGELOG.md b/pkgs/watcher/CHANGELOG.md index b4c823ea1..562c2e587 100644 --- a/pkgs/watcher/CHANGELOG.md +++ b/pkgs/watcher/CHANGELOG.md @@ -1,4 +1,4 @@ -## 1.1.4-wip +## 1.1.4 - Improve handling of subdirectories: ignore `PathNotFoundException` due to subdirectory deletion racing with watcher internals, instead of raising diff --git a/pkgs/watcher/pubspec.yaml b/pkgs/watcher/pubspec.yaml index 80a8abbfd..eff642fe5 100644 --- a/pkgs/watcher/pubspec.yaml +++ b/pkgs/watcher/pubspec.yaml @@ -1,5 +1,5 @@ name: watcher -version: 1.1.4-wip +version: 1.1.4 description: >- A file system watcher. It monitors changes to contents of directories and sends notifications when files have been added, removed, or modified. From 4691d95643a4c611f78fce366b7387df41ffb725 Mon Sep 17 00:00:00 2001 From: Slava Egorov Date: Tue, 30 Sep 2025 07:26:13 -0700 Subject: [PATCH 56/67] Make subdirectory robustness test less strict (#2185) --- pkgs/watcher/test/directory_watcher/shared.dart | 5 ----- 1 file changed, 5 deletions(-) diff --git a/pkgs/watcher/test/directory_watcher/shared.dart b/pkgs/watcher/test/directory_watcher/shared.dart index 816d2a2fa..10541cf8d 100644 --- a/pkgs/watcher/test/directory_watcher/shared.dart +++ b/pkgs/watcher/test/directory_watcher/shared.dart @@ -363,11 +363,6 @@ void sharedTests() { ]); }); } - - writeFile('a/b/c/d/file.txt'); - await inAnyOrder([ - isAddEvent('a/b/c/d/file.txt'), - ]); }); }); } From 83481e16e021da6111b83c54b3502394075f4b9a Mon Sep 17 00:00:00 2001 From: Parker Lougheed Date: Tue, 30 Sep 2025 18:08:24 +0200 Subject: [PATCH 57/67] [markdown] Simplify deindentation logic for fenced code block lines (#2187) Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- pkgs/markdown/CHANGELOG.md | 1 + .../fenced_code_block_syntax.dart | 26 ++++++++++++++----- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/pkgs/markdown/CHANGELOG.md b/pkgs/markdown/CHANGELOG.md index 60a250887..cc4337702 100644 --- a/pkgs/markdown/CHANGELOG.md +++ b/pkgs/markdown/CHANGELOG.md @@ -4,6 +4,7 @@ (https://dart-lang.github.io/tools). * Update `package:web` API references in the example. * Fix performance and correctness of HTML comment parser. +* Optimize indentation processing of fenced code blocks. * Require Dart `^3.4.0`. ## 7.3.0 diff --git a/pkgs/markdown/lib/src/block_syntaxes/fenced_code_block_syntax.dart b/pkgs/markdown/lib/src/block_syntaxes/fenced_code_block_syntax.dart index cea110665..de695c7a9 100644 --- a/pkgs/markdown/lib/src/block_syntaxes/fenced_code_block_syntax.dart +++ b/pkgs/markdown/lib/src/block_syntaxes/fenced_code_block_syntax.dart @@ -4,6 +4,7 @@ import '../ast.dart'; import '../block_parser.dart'; +import '../charcode.dart' show $space; import '../line.dart'; import '../patterns.dart'; import '../util.dart'; @@ -50,11 +51,6 @@ class FencedCodeBlockSyntax extends BlockSyntax { return Element('pre', [code]); } - String _removeIndentation(String content, int length) { - final text = content.replaceFirst(RegExp('^\\s{0,$length}'), ''); - return content.substring(content.length - text.length); - } - @override List parseChildLines( BlockParser parser, [ @@ -76,7 +72,7 @@ class FencedCodeBlockSyntax extends BlockSyntax { !closingFence.marker.startsWith(openingMarker) || closingFence.hasInfo) { childLines.add( - Line(_removeIndentation(parser.current.content, indent)), + Line(_removeLeadingSpaces(parser.current.content, upTo: indent)), ); parser.advance(); } else { @@ -95,6 +91,24 @@ class FencedCodeBlockSyntax extends BlockSyntax { return childLines; } + + /// Removes the leading spaces (` `) from [content] up the given [upTo] count. + static String _removeLeadingSpaces(String content, {required int upTo}) { + var leadingSpacesCount = 0; + + // Find the index of the first non-space character + // or the first space after the maximum removed specified by 'upTo'. + while (leadingSpacesCount < upTo && leadingSpacesCount < content.length) { + // We can just check for space (` `) since fenced code blocks + // consider spaces before the opening code fence as the + // indentation that should be removed. + if (content.codeUnitAt(leadingSpacesCount) != $space) { + break; + } + leadingSpacesCount += 1; + } + return content.substring(leadingSpacesCount); + } } class _FenceMatch { From e806d545e72c70fcd9bd9c0ae453c00ddac2bdde Mon Sep 17 00:00:00 2001 From: Aleksey Garbarev Date: Wed, 1 Oct 2025 09:51:54 +0600 Subject: [PATCH 58/67] Fixing structure issue of nested lists indented by tabs (#2172) (#2173) --- pkgs/markdown/CHANGELOG.md | 1 + .../lib/src/block_syntaxes/list_syntax.dart | 4 +- .../test/original/unordered_lists.unit | 75 +++++++++++++++++++ 3 files changed, 79 insertions(+), 1 deletion(-) diff --git a/pkgs/markdown/CHANGELOG.md b/pkgs/markdown/CHANGELOG.md index cc4337702..289004db7 100644 --- a/pkgs/markdown/CHANGELOG.md +++ b/pkgs/markdown/CHANGELOG.md @@ -6,6 +6,7 @@ * Fix performance and correctness of HTML comment parser. * Optimize indentation processing of fenced code blocks. * Require Dart `^3.4.0`. +* Fix an issue with nested list structure when indented by tabs (#2172). ## 7.3.0 diff --git a/pkgs/markdown/lib/src/block_syntaxes/list_syntax.dart b/pkgs/markdown/lib/src/block_syntaxes/list_syntax.dart index 534ca3e73..5aa0865c2 100644 --- a/pkgs/markdown/lib/src/block_syntaxes/list_syntax.dart +++ b/pkgs/markdown/lib/src/block_syntaxes/list_syntax.dart @@ -211,7 +211,9 @@ abstract class ListSyntax extends BlockSyntax { // any indentation past the required whitespace character. indent = precedingWhitespaces; } else { - indent = precedingWhitespaces + contentWhitespances; + indent = precedingWhitespaces + + contentWhitespances + + (parser.current.tabRemaining ?? 0); } taskListItemState = null; diff --git a/pkgs/markdown/test/original/unordered_lists.unit b/pkgs/markdown/test/original/unordered_lists.unit index 601777511..1a802ab7e 100644 --- a/pkgs/markdown/test/original/unordered_lists.unit +++ b/pkgs/markdown/test/original/unordered_lists.unit @@ -118,6 +118,26 @@ two * two +<<< +
    +
  • +

    one

    +
      +
    • nested one
    • +
    • nested two
    • +
    +
  • +
  • +

    two

    +
  • +
+>>> can nest lists by tabs +* one + * nested one + * nested two + +* two + <<<
  • @@ -149,3 +169,58 @@ item
  • list

+>>> can nest multiple levels with tabs. +- Level 1, 1 +- Level 1, 2 + - Level 2, 1 + - Level 2, 2 + - Level 3, 1 + - Level 3, 2 + - Level 3, 3 + - Level 4, 1 + - Level 4, 2 + - Level 4, 3 + - Level 5, 1 + - Level 5, 2 + - Level 5, 3 + - Level 5, 4 + - Level 6, 1 + - Level 6, 2 + - Level 6, 3 +- Level 1, 4 +<<< +
    +
  • Level 1, 1
  • +
  • Level 1, 2 +
      +
    • Level 2, 1
    • +
    • Level 2, 2 +
        +
      • Level 3, 1
      • +
      • Level 3, 2
      • +
      • Level 3, 3 +
          +
        • Level 4, 1
        • +
        • Level 4, 2
        • +
        • Level 4, 3 +
            +
          • Level 5, 1
          • +
          • Level 5, 2
          • +
          • Level 5, 3
          • +
          • Level 5, 4 +
              +
            • Level 6, 1
            • +
            • Level 6, 2
            • +
            • Level 6, 3
            • +
            +
          • +
          +
        • +
        +
      • +
      +
    • +
    +
  • +
  • Level 1, 4
  • +
From 753467d1a521a39872476fd092053d9d3d197f48 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Oct 2025 05:36:24 +0000 Subject: [PATCH 59/67] Bump the github-actions group with 5 updates (#2188) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the github-actions group with 5 updates: | Package | From | To | | --- | --- | --- | | [actions/checkout](https://github.com/actions/checkout) | `4.2.2` | `5.0.0` | | [actions/setup-node](https://github.com/actions/setup-node) | `4` | `5` | | [actions/upload-pages-artifact](https://github.com/actions/upload-pages-artifact) | `3` | `4` | | [actions/stale](https://github.com/actions/stale) | `9.1.0` | `10.0.0` | | [actions/labeler](https://github.com/actions/labeler) | `5.0.0` | `6.0.1` | Updates `actions/checkout` from 4.2.2 to 5.0.0
Release notes

Sourced from actions/checkout's releases.

v5.0.0

What's Changed

⚠️ Minimum Compatible Runner Version

v2.327.1
Release Notes

Make sure your runner is updated to this version or newer to use this release.

Full Changelog: https://github.com/actions/checkout/compare/v4...v5.0.0

v4.3.0

What's Changed

New Contributors

Full Changelog: https://github.com/actions/checkout/compare/v4...v4.3.0

Changelog

Sourced from actions/checkout's changelog.

Changelog

V5.0.0

V4.3.0

v4.2.2

v4.2.1

v4.2.0

v4.1.7

v4.1.6

v4.1.5

v4.1.4

v4.1.3

... (truncated)

Commits

Updates `actions/setup-node` from 4 to 5
Release notes

Sourced from actions/setup-node's releases.

v5.0.0

What's Changed

Breaking Changes

This update, introduces automatic caching when a valid packageManager field is present in your package.json. This aims to improve workflow performance and make dependency management more seamless. To disable this automatic caching, set package-manager-cache: false

steps:
- uses: actions/checkout@v5
- uses: actions/setup-node@v5
  with:
    package-manager-cache: false

Make sure your runner is on version v2.327.1 or later to ensure compatibility with this release. See Release Notes

Dependency Upgrades

New Contributors

Full Changelog: https://github.com/actions/setup-node/compare/v4...v5.0.0

v4.4.0

What's Changed

Bug fixes:

Enhancement:

Dependency update:

New Contributors

Full Changeloghttps://github.com/actions/setup-node/compare/v4...v4.4.0

... (truncated)

Commits

Updates `actions/upload-pages-artifact` from 3 to 4
Release notes

Sourced from actions/upload-pages-artifact's releases.

v4.0.0

What's Changed

Full Changelog: https://github.com/actions/upload-pages-artifact/compare/v3.0.1...v4.0.0

v3.0.1

Changelog

See details of all code changes since previous release.

Commits
  • 7b1f4a7 Merge pull request #127 from heavymachinery/pin-sha
  • 4cc19c7 Pin actions/upload-artifact to SHA
  • 2d163be Merge pull request #107 from KittyChiu/main
  • c704843 fix: linted README
  • 9605915 Merge pull request #106 from KittyChiu/kittychiu/update-readme-1
  • e59cdfe Update README.md
  • a2d6704 doc: updated usage section in readme
  • 984864e Merge pull request #105 from actions/Jcambass-patch-1
  • 45dc788 Add workflow file for publishing releases to immutable action package
  • efaad07 Merge pull request #102 from actions/hidden-files
  • Additional commits viewable in compare view

Updates `actions/stale` from 9.1.0 to 10.0.0
Release notes

Sourced from actions/stale's releases.

v10.0.0

What's Changed

Breaking Changes

Enhancement

Dependency Upgrades

Documentation changes

New Contributors

Full Changelog: https://github.com/actions/stale/compare/v9...v10.0.0

Commits

Updates `actions/labeler` from 5.0.0 to 6.0.1
Release notes

Sourced from actions/labeler's releases.

v6.0.1

What's Changed

New Contributors

Full Changelog: https://github.com/actions/labeler/compare/v6.0.0...v6.0.1

v6.0.0

What's Changed

Breaking Changes

Dependency Upgrades

Documentation changes

... (truncated)

Commits

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
--- .github/workflows/bazel_worker.yaml | 2 +- .github/workflows/benchmark_harness.yaml | 6 +++--- .github/workflows/boolean_selector.yaml | 4 ++-- .github/workflows/browser_launcher.yaml | 2 +- .github/workflows/cli_config.yaml | 4 ++-- .github/workflows/cli_util.yaml | 4 ++-- .github/workflows/clock.yaml | 4 ++-- .github/workflows/code_builder.yaml | 4 ++-- .github/workflows/coverage.yaml | 6 +++--- .github/workflows/csslib.yaml | 4 ++-- .github/workflows/deploy_pages.yaml | 4 ++-- .github/workflows/extension_discovery.yaml | 2 +- .github/workflows/file.yaml | 4 ++-- .github/workflows/file_testing.yaml | 2 +- .github/workflows/glob.yaml | 2 +- .github/workflows/graphs.yaml | 2 +- .github/workflows/html.yaml | 4 ++-- .github/workflows/io.yaml | 4 ++-- .github/workflows/json_rpc_2.yaml | 4 ++-- .github/workflows/markdown.yaml | 8 ++++---- .github/workflows/markdown_crash_test.yaml | 2 +- .github/workflows/mime.yaml | 2 +- .github/workflows/no-response.yml | 2 +- .github/workflows/oauth2.yaml | 4 ++-- .github/workflows/package_config.yaml | 4 ++-- .github/workflows/pool.yaml | 4 ++-- .github/workflows/process.yaml | 2 +- .github/workflows/pub_semver.yaml | 4 ++-- .github/workflows/pubspec_parse.yaml | 4 ++-- .github/workflows/pull_request_label.yml | 2 +- .github/workflows/source_map_stack_trace.yaml | 4 ++-- .github/workflows/source_maps.yaml | 4 ++-- .github/workflows/source_span.yaml | 4 ++-- .github/workflows/sse.yaml | 4 ++-- .github/workflows/stack_trace.yaml | 4 ++-- .github/workflows/stream_channel.yaml | 4 ++-- .github/workflows/stream_transform.yaml | 4 ++-- .github/workflows/string_scanner.yaml | 4 ++-- .github/workflows/term_glyph.yaml | 4 ++-- .github/workflows/test_reflective_loader.yaml | 2 +- .github/workflows/timing.yaml | 4 ++-- .github/workflows/unified_analytics.yaml | 2 +- .github/workflows/watcher.yaml | 4 ++-- .github/workflows/yaml.yaml | 4 ++-- .github/workflows/yaml_edit.yaml | 4 ++-- 45 files changed, 81 insertions(+), 81 deletions(-) diff --git a/.github/workflows/bazel_worker.yaml b/.github/workflows/bazel_worker.yaml index 40b922757..e44fb17f2 100644 --- a/.github/workflows/bazel_worker.yaml +++ b/.github/workflows/bazel_worker.yaml @@ -29,7 +29,7 @@ jobs: matrix: sdk: [3.4, dev] steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c with: sdk: ${{ matrix.sdk }} diff --git a/.github/workflows/benchmark_harness.yaml b/.github/workflows/benchmark_harness.yaml index b3856751c..3c3e8946b 100644 --- a/.github/workflows/benchmark_harness.yaml +++ b/.github/workflows/benchmark_harness.yaml @@ -30,7 +30,7 @@ jobs: matrix: sdk: [dev] steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c with: sdk: ${{ matrix.sdk }} @@ -57,13 +57,13 @@ jobs: os: [ubuntu-latest] sdk: [3.2, dev] steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c with: sdk: ${{ matrix.sdk }} # Node 22 has wasmGC enabled, which allows the wasm tests to run! - name: Setup Node.js 22 - uses: actions/setup-node@v4 + uses: actions/setup-node@v5 with: node-version: 22 - id: install diff --git a/.github/workflows/boolean_selector.yaml b/.github/workflows/boolean_selector.yaml index 02cb9f1d1..10eb97c26 100644 --- a/.github/workflows/boolean_selector.yaml +++ b/.github/workflows/boolean_selector.yaml @@ -30,7 +30,7 @@ jobs: matrix: sdk: [dev] steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c with: sdk: ${{ matrix.sdk }} @@ -54,7 +54,7 @@ jobs: os: [ubuntu-latest] sdk: [3.1, dev] steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c with: sdk: ${{ matrix.sdk }} diff --git a/.github/workflows/browser_launcher.yaml b/.github/workflows/browser_launcher.yaml index e8fc2a221..011f8be39 100644 --- a/.github/workflows/browser_launcher.yaml +++ b/.github/workflows/browser_launcher.yaml @@ -30,7 +30,7 @@ jobs: matrix: sdk: [3.4, dev] steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c with: sdk: ${{ matrix.sdk }} diff --git a/.github/workflows/cli_config.yaml b/.github/workflows/cli_config.yaml index 1c23b684c..d2bf3ad16 100644 --- a/.github/workflows/cli_config.yaml +++ b/.github/workflows/cli_config.yaml @@ -28,7 +28,7 @@ jobs: - sdk: stable run-tests: true steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c with: sdk: ${{matrix.sdk}} @@ -66,7 +66,7 @@ jobs: - sdk: stable run-tests: true steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c with: sdk: ${{matrix.sdk}} diff --git a/.github/workflows/cli_util.yaml b/.github/workflows/cli_util.yaml index 302a3c379..2bbdfabb0 100644 --- a/.github/workflows/cli_util.yaml +++ b/.github/workflows/cli_util.yaml @@ -28,7 +28,7 @@ jobs: matrix: sdk: [dev] steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c with: sdk: ${{ matrix.sdk }} @@ -51,7 +51,7 @@ jobs: os: [ubuntu-latest] sdk: ['3.4', dev] steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c with: sdk: ${{ matrix.sdk }} diff --git a/.github/workflows/clock.yaml b/.github/workflows/clock.yaml index 4eefe5923..1bc4c1d22 100644 --- a/.github/workflows/clock.yaml +++ b/.github/workflows/clock.yaml @@ -30,7 +30,7 @@ jobs: matrix: sdk: [dev] steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c with: sdk: ${{ matrix.sdk }} @@ -57,7 +57,7 @@ jobs: os: [ubuntu-latest] sdk: [3.4, dev] steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c with: sdk: ${{ matrix.sdk }} diff --git a/.github/workflows/code_builder.yaml b/.github/workflows/code_builder.yaml index ad3280108..86f285328 100644 --- a/.github/workflows/code_builder.yaml +++ b/.github/workflows/code_builder.yaml @@ -30,7 +30,7 @@ jobs: matrix: sdk: [dev] steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c with: sdk: ${{ matrix.sdk }} @@ -53,7 +53,7 @@ jobs: os: [ubuntu-latest] sdk: [3.7.0, dev] steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c with: sdk: ${{ matrix.sdk }} diff --git a/.github/workflows/coverage.yaml b/.github/workflows/coverage.yaml index 997e33fda..81d6b07ac 100644 --- a/.github/workflows/coverage.yaml +++ b/.github/workflows/coverage.yaml @@ -29,7 +29,7 @@ jobs: matrix: sdk: [dev] steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c with: sdk: ${{ matrix.sdk }} @@ -58,7 +58,7 @@ jobs: os: [ubuntu-latest, macos-latest, windows-latest] sdk: [3.6, dev] steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c with: sdk: ${{ matrix.sdk }} @@ -76,7 +76,7 @@ jobs: run: working-directory: pkgs/coverage/ steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c with: sdk: dev diff --git a/.github/workflows/csslib.yaml b/.github/workflows/csslib.yaml index d7330eb74..c48c412df 100644 --- a/.github/workflows/csslib.yaml +++ b/.github/workflows/csslib.yaml @@ -30,7 +30,7 @@ jobs: matrix: sdk: [dev] steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c with: sdk: ${{ matrix.sdk }} @@ -57,7 +57,7 @@ jobs: os: [ubuntu-latest, windows-latest] sdk: [3.1, dev] steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c with: sdk: ${{ matrix.sdk }} diff --git a/.github/workflows/deploy_pages.yaml b/.github/workflows/deploy_pages.yaml index 3bf718bd4..a97ed9b0b 100644 --- a/.github/workflows/deploy_pages.yaml +++ b/.github/workflows/deploy_pages.yaml @@ -23,7 +23,7 @@ jobs: url: ${{steps.deployment.outputs.page_url}} steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c # Build the markdown playground. @@ -38,7 +38,7 @@ jobs: # Deploy to GitHub Pages. - uses: actions/configure-pages@v5 - - uses: actions/upload-pages-artifact@v3 + - uses: actions/upload-pages-artifact@v4 with: path: _site - uses: actions/deploy-pages@v4 diff --git a/.github/workflows/extension_discovery.yaml b/.github/workflows/extension_discovery.yaml index 4cb29a797..e7b1a5a80 100644 --- a/.github/workflows/extension_discovery.yaml +++ b/.github/workflows/extension_discovery.yaml @@ -29,7 +29,7 @@ jobs: - sdk: stable check-formatting: true steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c with: sdk: ${{matrix.sdk}} diff --git a/.github/workflows/file.yaml b/.github/workflows/file.yaml index 8e78edba3..3bb56c76b 100644 --- a/.github/workflows/file.yaml +++ b/.github/workflows/file.yaml @@ -27,7 +27,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c with: sdk: dev @@ -45,7 +45,7 @@ jobs: sdk: [stable, dev] steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c with: sdk: ${{ matrix.sdk }} diff --git a/.github/workflows/file_testing.yaml b/.github/workflows/file_testing.yaml index d543aafd3..e57cde804 100644 --- a/.github/workflows/file_testing.yaml +++ b/.github/workflows/file_testing.yaml @@ -26,7 +26,7 @@ jobs: correctness: runs-on: ubuntu-latest steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c with: sdk: dev diff --git a/.github/workflows/glob.yaml b/.github/workflows/glob.yaml index ccf5f3f36..9ff48a9fb 100644 --- a/.github/workflows/glob.yaml +++ b/.github/workflows/glob.yaml @@ -27,7 +27,7 @@ jobs: matrix: sdk: [stable, dev] steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c with: sdk: ${{ matrix.sdk }} diff --git a/.github/workflows/graphs.yaml b/.github/workflows/graphs.yaml index 31e55f412..a08f6f0ac 100644 --- a/.github/workflows/graphs.yaml +++ b/.github/workflows/graphs.yaml @@ -27,7 +27,7 @@ jobs: matrix: sdk: [stable, dev] steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c with: sdk: ${{ matrix.sdk }} diff --git a/.github/workflows/html.yaml b/.github/workflows/html.yaml index 63fa51341..afbaad4e5 100644 --- a/.github/workflows/html.yaml +++ b/.github/workflows/html.yaml @@ -27,7 +27,7 @@ jobs: matrix: sdk: [stable, dev] steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c with: sdk: ${{ matrix.sdk }} @@ -47,7 +47,7 @@ jobs: os: [ubuntu-latest] sdk: [3.2, stable, dev] steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c with: sdk: ${{ matrix.sdk }} diff --git a/.github/workflows/io.yaml b/.github/workflows/io.yaml index 06cb1b345..18bec35c7 100644 --- a/.github/workflows/io.yaml +++ b/.github/workflows/io.yaml @@ -30,7 +30,7 @@ jobs: matrix: sdk: [dev, 3.4] steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c with: sdk: ${{ matrix.sdk }} @@ -57,7 +57,7 @@ jobs: os: [ubuntu-latest] sdk: [dev, 3.4] steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c with: sdk: ${{ matrix.sdk }} diff --git a/.github/workflows/json_rpc_2.yaml b/.github/workflows/json_rpc_2.yaml index e47096f9d..a2778e3e1 100644 --- a/.github/workflows/json_rpc_2.yaml +++ b/.github/workflows/json_rpc_2.yaml @@ -30,7 +30,7 @@ jobs: matrix: sdk: [dev] steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c with: sdk: ${{ matrix.sdk }} @@ -57,7 +57,7 @@ jobs: os: [ubuntu-latest] sdk: [3.4, dev] steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c with: sdk: ${{ matrix.sdk }} diff --git a/.github/workflows/markdown.yaml b/.github/workflows/markdown.yaml index 4c8be1fee..eee44c33f 100644 --- a/.github/workflows/markdown.yaml +++ b/.github/workflows/markdown.yaml @@ -30,7 +30,7 @@ jobs: matrix: sdk: [dev] steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c with: sdk: ${{ matrix.sdk }} @@ -52,7 +52,7 @@ jobs: matrix: sdk: [stable] steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c with: sdk: ${{ matrix.sdk }} @@ -72,7 +72,7 @@ jobs: os: [ubuntu-latest] sdk: [3.4, dev] steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c with: sdk: ${{ matrix.sdk }} @@ -87,7 +87,7 @@ jobs: needs: test runs-on: ubuntu-latest steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c with: sdk: dev diff --git a/.github/workflows/markdown_crash_test.yaml b/.github/workflows/markdown_crash_test.yaml index 6d2cb4312..6fb7b44e6 100644 --- a/.github/workflows/markdown_crash_test.yaml +++ b/.github/workflows/markdown_crash_test.yaml @@ -27,7 +27,7 @@ jobs: crash-test: runs-on: ubuntu-latest steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c diff --git a/.github/workflows/mime.yaml b/.github/workflows/mime.yaml index 03144c4e4..9ac828129 100644 --- a/.github/workflows/mime.yaml +++ b/.github/workflows/mime.yaml @@ -30,7 +30,7 @@ jobs: steps: # These are the latest versions of the github actions; dependabot will # send PRs to keep these up-to-date. - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c - name: Install dependencies diff --git a/.github/workflows/no-response.yml b/.github/workflows/no-response.yml index b51dd23ad..6017dc67d 100644 --- a/.github/workflows/no-response.yml +++ b/.github/workflows/no-response.yml @@ -19,7 +19,7 @@ jobs: runs-on: ubuntu-latest if: ${{ github.repository_owner == 'dart-lang' }} steps: - - uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 + - uses: actions/stale@3a9db7e6a41a89f618792c92c0e97cc736e1b13f with: # Don't automatically mark inactive issues+PRs as stale. days-before-stale: -1 diff --git a/.github/workflows/oauth2.yaml b/.github/workflows/oauth2.yaml index 890584f40..95cb479ea 100644 --- a/.github/workflows/oauth2.yaml +++ b/.github/workflows/oauth2.yaml @@ -30,7 +30,7 @@ jobs: matrix: sdk: [dev] steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c with: sdk: ${{ matrix.sdk }} @@ -60,7 +60,7 @@ jobs: os: [ubuntu-latest] sdk: [3.4, dev] steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c with: sdk: ${{ matrix.sdk }} diff --git a/.github/workflows/package_config.yaml b/.github/workflows/package_config.yaml index 43786530b..7293f30f9 100644 --- a/.github/workflows/package_config.yaml +++ b/.github/workflows/package_config.yaml @@ -30,7 +30,7 @@ jobs: matrix: sdk: [dev] steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c with: sdk: ${{ matrix.sdk }} @@ -56,7 +56,7 @@ jobs: os: [ubuntu-latest, windows-latest] sdk: [3.7, dev] steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c with: sdk: ${{ matrix.sdk }} diff --git a/.github/workflows/pool.yaml b/.github/workflows/pool.yaml index 611252107..c9f3e0ddb 100644 --- a/.github/workflows/pool.yaml +++ b/.github/workflows/pool.yaml @@ -30,7 +30,7 @@ jobs: matrix: sdk: [dev] steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c with: sdk: ${{ matrix.sdk }} @@ -57,7 +57,7 @@ jobs: os: [ubuntu-latest] sdk: [3.4, dev] steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c with: sdk: ${{ matrix.sdk }} diff --git a/.github/workflows/process.yaml b/.github/workflows/process.yaml index 4d05828b4..976e88ab7 100644 --- a/.github/workflows/process.yaml +++ b/.github/workflows/process.yaml @@ -28,7 +28,7 @@ jobs: os: [ubuntu-latest, windows-latest] sdk: [stable, dev] steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c - run: dart pub get diff --git a/.github/workflows/pub_semver.yaml b/.github/workflows/pub_semver.yaml index 2a2968d38..dd59f849a 100644 --- a/.github/workflows/pub_semver.yaml +++ b/.github/workflows/pub_semver.yaml @@ -30,7 +30,7 @@ jobs: matrix: sdk: [dev] steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c with: sdk: ${{ matrix.sdk }} @@ -57,7 +57,7 @@ jobs: os: [ubuntu-latest] sdk: [3.4, dev] steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c with: sdk: ${{ matrix.sdk }} diff --git a/.github/workflows/pubspec_parse.yaml b/.github/workflows/pubspec_parse.yaml index eff392feb..9388669d9 100644 --- a/.github/workflows/pubspec_parse.yaml +++ b/.github/workflows/pubspec_parse.yaml @@ -30,7 +30,7 @@ jobs: matrix: sdk: [dev] steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c with: sdk: ${{ matrix.sdk }} @@ -56,7 +56,7 @@ jobs: os: [ubuntu-latest] sdk: [3.6, dev] steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c with: sdk: ${{ matrix.sdk }} diff --git a/.github/workflows/pull_request_label.yml b/.github/workflows/pull_request_label.yml index 54e3df537..3115ed454 100644 --- a/.github/workflows/pull_request_label.yml +++ b/.github/workflows/pull_request_label.yml @@ -16,7 +16,7 @@ jobs: pull-requests: write runs-on: ubuntu-latest steps: - - uses: actions/labeler@8558fd74291d67161a8a78ce36a881fa63b766a9 + - uses: actions/labeler@634933edcd8ababfe52f92936142cc22ac488b1b with: repo-token: "${{ secrets.GITHUB_TOKEN }}" sync-labels: true diff --git a/.github/workflows/source_map_stack_trace.yaml b/.github/workflows/source_map_stack_trace.yaml index 1175e0978..688c437ae 100644 --- a/.github/workflows/source_map_stack_trace.yaml +++ b/.github/workflows/source_map_stack_trace.yaml @@ -30,7 +30,7 @@ jobs: matrix: sdk: [dev] steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c with: sdk: ${{ matrix.sdk }} @@ -57,7 +57,7 @@ jobs: os: [ubuntu-latest] sdk: [3.3, dev] steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c with: sdk: ${{ matrix.sdk }} diff --git a/.github/workflows/source_maps.yaml b/.github/workflows/source_maps.yaml index 712ce7223..dcff43f08 100644 --- a/.github/workflows/source_maps.yaml +++ b/.github/workflows/source_maps.yaml @@ -30,7 +30,7 @@ jobs: matrix: sdk: [dev] steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c with: sdk: ${{ matrix.sdk }} @@ -57,7 +57,7 @@ jobs: os: [ubuntu-latest] sdk: [3.3.0, dev] steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c with: sdk: ${{ matrix.sdk }} diff --git a/.github/workflows/source_span.yaml b/.github/workflows/source_span.yaml index 61aef2d84..899d24565 100644 --- a/.github/workflows/source_span.yaml +++ b/.github/workflows/source_span.yaml @@ -30,7 +30,7 @@ jobs: matrix: sdk: [dev] steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c with: sdk: ${{ matrix.sdk }} @@ -57,7 +57,7 @@ jobs: os: [ubuntu-latest] sdk: [3.1.0, dev] steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c with: sdk: ${{ matrix.sdk }} diff --git a/.github/workflows/sse.yaml b/.github/workflows/sse.yaml index 4081dd4e4..a51b03cb0 100644 --- a/.github/workflows/sse.yaml +++ b/.github/workflows/sse.yaml @@ -30,7 +30,7 @@ jobs: matrix: sdk: [dev] steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c with: sdk: ${{ matrix.sdk }} @@ -57,7 +57,7 @@ jobs: os: [ubuntu-latest] sdk: [3.3, dev] steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c with: sdk: ${{ matrix.sdk }} diff --git a/.github/workflows/stack_trace.yaml b/.github/workflows/stack_trace.yaml index 95289ec8b..9ee826b95 100644 --- a/.github/workflows/stack_trace.yaml +++ b/.github/workflows/stack_trace.yaml @@ -30,7 +30,7 @@ jobs: matrix: sdk: [dev] steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c with: sdk: ${{ matrix.sdk }} @@ -57,7 +57,7 @@ jobs: os: [ubuntu-latest] sdk: [3.4, dev] steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c with: sdk: ${{ matrix.sdk }} diff --git a/.github/workflows/stream_channel.yaml b/.github/workflows/stream_channel.yaml index 709e063e1..b4b02b56e 100644 --- a/.github/workflows/stream_channel.yaml +++ b/.github/workflows/stream_channel.yaml @@ -30,7 +30,7 @@ jobs: matrix: sdk: [dev] steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c with: sdk: ${{ matrix.sdk }} @@ -57,7 +57,7 @@ jobs: os: [ubuntu-latest] sdk: [3.3, dev] steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c with: sdk: ${{ matrix.sdk }} diff --git a/.github/workflows/stream_transform.yaml b/.github/workflows/stream_transform.yaml index 812040cc7..4666be7c9 100644 --- a/.github/workflows/stream_transform.yaml +++ b/.github/workflows/stream_transform.yaml @@ -30,7 +30,7 @@ jobs: matrix: sdk: [dev] steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c with: sdk: ${{ matrix.sdk }} @@ -58,7 +58,7 @@ jobs: # Bump SDK for Legacy tests when changing min SDK. sdk: [3.4, dev] steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c with: sdk: ${{ matrix.sdk }} diff --git a/.github/workflows/string_scanner.yaml b/.github/workflows/string_scanner.yaml index 78bfb8a74..cd5982969 100644 --- a/.github/workflows/string_scanner.yaml +++ b/.github/workflows/string_scanner.yaml @@ -30,7 +30,7 @@ jobs: matrix: sdk: [dev] steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c with: sdk: ${{ matrix.sdk }} @@ -57,7 +57,7 @@ jobs: os: [ubuntu-latest] sdk: [3.1, dev] steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c with: sdk: ${{ matrix.sdk }} diff --git a/.github/workflows/term_glyph.yaml b/.github/workflows/term_glyph.yaml index ba6f7f997..8771578d1 100644 --- a/.github/workflows/term_glyph.yaml +++ b/.github/workflows/term_glyph.yaml @@ -30,7 +30,7 @@ jobs: matrix: sdk: [dev] steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c with: sdk: ${{ matrix.sdk }} @@ -57,7 +57,7 @@ jobs: os: [ubuntu-latest] sdk: [3.1, dev] steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c with: sdk: ${{ matrix.sdk }} diff --git a/.github/workflows/test_reflective_loader.yaml b/.github/workflows/test_reflective_loader.yaml index 8e70b85a3..ee0c2bd99 100644 --- a/.github/workflows/test_reflective_loader.yaml +++ b/.github/workflows/test_reflective_loader.yaml @@ -29,7 +29,7 @@ jobs: sdk: [dev, 3.5] steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c with: sdk: ${{ matrix.sdk }} diff --git a/.github/workflows/timing.yaml b/.github/workflows/timing.yaml index dfa51986d..7ba2924c8 100644 --- a/.github/workflows/timing.yaml +++ b/.github/workflows/timing.yaml @@ -30,7 +30,7 @@ jobs: matrix: sdk: [3.4, dev] steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c with: sdk: ${{ matrix.sdk }} @@ -54,7 +54,7 @@ jobs: os: [ubuntu-latest] sdk: [3.4, dev] steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c with: sdk: ${{ matrix.sdk }} diff --git a/.github/workflows/unified_analytics.yaml b/.github/workflows/unified_analytics.yaml index 42790012d..8fcadbeb0 100644 --- a/.github/workflows/unified_analytics.yaml +++ b/.github/workflows/unified_analytics.yaml @@ -28,7 +28,7 @@ jobs: - sdk: stable run-tests: true steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c with: sdk: ${{matrix.sdk}} diff --git a/.github/workflows/watcher.yaml b/.github/workflows/watcher.yaml index 4e98d7ab4..6a3b9ba70 100644 --- a/.github/workflows/watcher.yaml +++ b/.github/workflows/watcher.yaml @@ -30,7 +30,7 @@ jobs: matrix: sdk: [dev] steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c with: sdk: ${{ matrix.sdk }} @@ -56,7 +56,7 @@ jobs: os: [ubuntu-latest, macos-latest, windows-latest] sdk: [3.1, dev] steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c with: sdk: ${{ matrix.sdk }} diff --git a/.github/workflows/yaml.yaml b/.github/workflows/yaml.yaml index f52bc3dd9..1373fa62d 100644 --- a/.github/workflows/yaml.yaml +++ b/.github/workflows/yaml.yaml @@ -30,7 +30,7 @@ jobs: matrix: sdk: [dev] steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c with: sdk: ${{ matrix.sdk }} @@ -57,7 +57,7 @@ jobs: os: [ubuntu-latest] sdk: [3.4, dev] steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c with: sdk: ${{ matrix.sdk }} diff --git a/.github/workflows/yaml_edit.yaml b/.github/workflows/yaml_edit.yaml index 435da2da4..a7d08d716 100644 --- a/.github/workflows/yaml_edit.yaml +++ b/.github/workflows/yaml_edit.yaml @@ -30,7 +30,7 @@ jobs: matrix: sdk: [dev] steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c with: sdk: ${{ matrix.sdk }} @@ -58,7 +58,7 @@ jobs: sdk: ['3.1', stable, dev] platform: [vm, chrome] steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c with: sdk: ${{ matrix.sdk }} From 78a936d3835318f61bf29143ab1cca6cf8d11250 Mon Sep 17 00:00:00 2001 From: "Morgan :)" Date: Tue, 30 Sep 2025 23:47:03 -0700 Subject: [PATCH 60/67] Fix file watcher startup race on MacOS (#2176) --- pkgs/watcher/CHANGELOG.md | 7 ++++ pkgs/watcher/lib/src/file_watcher/native.dart | 36 +++++++++++++++---- pkgs/watcher/pubspec.yaml | 2 +- .../test/file_watcher/startup_race_tests.dart | 11 +----- 4 files changed, 39 insertions(+), 17 deletions(-) diff --git a/pkgs/watcher/CHANGELOG.md b/pkgs/watcher/CHANGELOG.md index 562c2e587..686126dc1 100644 --- a/pkgs/watcher/CHANGELOG.md +++ b/pkgs/watcher/CHANGELOG.md @@ -1,3 +1,10 @@ +## 1.1.5-wip + +- Bug fix: with `FileWatcher` on MacOS, a modify event was sometimes reported if + the file was created immediately before the watcher was created. Now, if the + file exists when the watcher is created then this modify event is not sent. + This matches the Linux native and polling (Windows) watchers. + ## 1.1.4 - Improve handling of subdirectories: ignore `PathNotFoundException` due to diff --git a/pkgs/watcher/lib/src/file_watcher/native.dart b/pkgs/watcher/lib/src/file_watcher/native.dart index 502aa1095..28cf8a180 100644 --- a/pkgs/watcher/lib/src/file_watcher/native.dart +++ b/pkgs/watcher/lib/src/file_watcher/native.dart @@ -35,6 +35,9 @@ class _NativeFileWatcher implements FileWatcher, ManuallyClosedWatcher { StreamSubscription>? _subscription; + /// On MacOS only, whether the file existed on startup. + bool? _existedAtStartup; + _NativeFileWatcher(this.path) { _listen(); @@ -43,12 +46,23 @@ class _NativeFileWatcher implements FileWatcher, ManuallyClosedWatcher { _readyCompleter.complete(); } - void _listen() { - // Batch the events together so that we can dedup them. - _subscription = File(path) - .watch() - .batchEvents() - .listen(_onBatch, onError: _eventsController.addError, onDone: _onDone); + void _listen() async { + var file = File(path); + + // Batch the events together so that we can dedupe them. + var stream = file.watch().batchEvents(); + + if (Platform.isMacOS) { + var existedAtStartupFuture = file.exists(); + // Delay processing watch events until the existence check finishes. + stream = stream.asyncMap((event) async { + _existedAtStartup ??= await existedAtStartupFuture; + return event; + }); + } + + _subscription = stream.listen(_onBatch, + onError: _eventsController.addError, onDone: _onDone); } void _onBatch(List batch) { @@ -58,6 +72,16 @@ class _NativeFileWatcher implements FileWatcher, ManuallyClosedWatcher { return; } + if (Platform.isMacOS) { + // On MacOS, a spurious `create` event can be received for a file that is + // created just before the `watch`. If the file existed at startup then it + // should be ignored. + if (_existedAtStartup! && + batch.every((event) => event.type == FileSystemEvent.create)) { + return; + } + } + _eventsController.add(WatchEvent(ChangeType.MODIFY, path)); } diff --git a/pkgs/watcher/pubspec.yaml b/pkgs/watcher/pubspec.yaml index eff642fe5..c7119052d 100644 --- a/pkgs/watcher/pubspec.yaml +++ b/pkgs/watcher/pubspec.yaml @@ -1,5 +1,5 @@ name: watcher -version: 1.1.4 +version: 1.1.5-wip description: >- A file system watcher. It monitors changes to contents of directories and sends notifications when files have been added, removed, or modified. diff --git a/pkgs/watcher/test/file_watcher/startup_race_tests.dart b/pkgs/watcher/test/file_watcher/startup_race_tests.dart index 59e4f8e7b..50f5328f6 100644 --- a/pkgs/watcher/test/file_watcher/startup_race_tests.dart +++ b/pkgs/watcher/test/file_watcher/startup_race_tests.dart @@ -2,8 +2,6 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. -import 'dart:io'; - import 'package:test/test.dart'; import '../utils.dart'; @@ -27,13 +25,6 @@ void startupRaceTests({required bool isNative}) { ); } await Future.wait(futures); - - // TODO(davidmorgan): the MacOS watcher currently does get unwanted events, - // fix it. - if (isNative && Platform.isMacOS) { - expect(events, greaterThan(10)); - } else { - expect(events, 0); - } + expect(events, 0); }); } From 0732e6a8c6abe792e86188ae54cadfce84f3e244 Mon Sep 17 00:00:00 2001 From: "Morgan :)" Date: Wed, 1 Oct 2025 04:26:49 -0700 Subject: [PATCH 61/67] Add test coverage for file watcher and symlinks. (#2178) --- .../watcher/test/file_watcher/link_tests.dart | 168 ++++++++++++++++++ pkgs/watcher/test/utils.dart | 28 +++ 2 files changed, 196 insertions(+) create mode 100644 pkgs/watcher/test/file_watcher/link_tests.dart diff --git a/pkgs/watcher/test/file_watcher/link_tests.dart b/pkgs/watcher/test/file_watcher/link_tests.dart new file mode 100644 index 000000000..a6eef1403 --- /dev/null +++ b/pkgs/watcher/test/file_watcher/link_tests.dart @@ -0,0 +1,168 @@ +// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file +// for details. 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:test/test.dart'; + +import '../utils.dart'; + +void linkTests({required bool isNative}) { + setUp(() async { + writeFile('target.txt'); + writeLink(link: 'link.txt', target: 'target.txt'); + }); + + test("doesn't notify if nothing is modified", () async { + await startWatcher(path: 'link.txt'); + await expectNoEvents(); + }); + + test('notifies when a link is overwritten with an identical file', () async { + await startWatcher(path: 'link.txt'); + writeFile('link.txt'); + await expectModifyEvent('link.txt'); + }); + + test('notifies when a link is overwritten with a different file', () async { + await startWatcher(path: 'link.txt'); + writeFile('link.txt', contents: 'modified'); + await expectModifyEvent('link.txt'); + }); + + test( + 'notifies when a link target is overwritten with an identical file', + () async { + await startWatcher(path: 'link.txt'); + writeFile('target.txt'); + + // TODO(davidmorgan): reconcile differences. + if (isNative) { + await expectModifyEvent('link.txt'); + } else { + await expectNoEvents(); + } + }, + ); + + test('notifies when a link target is modified', () async { + await startWatcher(path: 'link.txt'); + writeFile('target.txt', contents: 'modified'); + + // TODO(davidmorgan): reconcile differences. + if (isNative) { + await expectModifyEvent('link.txt'); + } else { + await expectNoEvents(); + } + }); + + test('notifies when a link is removed', () async { + await startWatcher(path: 'link.txt'); + deleteFile('link.txt'); + + // TODO(davidmorgan): reconcile differences. + if (isNative) { + await expectNoEvents(); + } else { + await expectRemoveEvent('link.txt'); + } + }); + + test('notifies when a link target is removed', () async { + await startWatcher(path: 'link.txt'); + deleteFile('target.txt'); + await expectRemoveEvent('link.txt'); + }); + + test('notifies when a link target is modified multiple times', () async { + await startWatcher(path: 'link.txt'); + + writeFile('target.txt', contents: 'modified'); + + // TODO(davidmorgan): reconcile differences. + if (isNative) { + await expectModifyEvent('link.txt'); + } else { + await expectNoEvents(); + } + + writeFile('target.txt', contents: 'modified again'); + + // TODO(davidmorgan): reconcile differences. + if (isNative) { + await expectModifyEvent('link.txt'); + } else { + await expectNoEvents(); + } + }); + + test('notifies when a link is moved away', () async { + await startWatcher(path: 'link.txt'); + renameFile('link.txt', 'new.txt'); + + // TODO(davidmorgan): reconcile differences. + if (isNative) { + await expectNoEvents(); + } else { + await expectRemoveEvent('link.txt'); + } + }); + + test('notifies when a link target is moved away', () async { + await startWatcher(path: 'link.txt'); + renameFile('target.txt', 'new.txt'); + await expectRemoveEvent('link.txt'); + }); + + test('notifies when an identical file is moved over the link', () async { + await startWatcher(path: 'link.txt'); + writeFile('old.txt'); + renameFile('old.txt', 'link.txt'); + + // TODO(davidmorgan): reconcile differences. + if (isNative) { + await expectNoEvents(); + } else { + await expectModifyEvent('link.txt'); + } + }); + + test('notifies when an different file is moved over the link', () async { + await startWatcher(path: 'link.txt'); + writeFile('old.txt', contents: 'modified'); + renameFile('old.txt', 'link.txt'); + + // TODO(davidmorgan): reconcile differences. + if (isNative) { + await expectNoEvents(); + } else { + await expectModifyEvent('link.txt'); + } + }); + + test('notifies when an identical file is moved over the target', () async { + await startWatcher(path: 'link.txt'); + writeFile('old.txt'); + renameFile('old.txt', 'target.txt'); + + // TODO(davidmorgan): reconcile differences. + if (isNative) { + await expectModifyEvent('link.txt'); + } else { + await expectNoEvents(); + } + }); + + test('notifies when a different file is moved over the target', () async { + await startWatcher(path: 'link.txt'); + writeFile('old.txt', contents: 'modified'); + renameFile('old.txt', 'target.txt'); + + // TODO(davidmorgan): reconcile differences. + if (isNative) { + await expectModifyEvent('link.txt'); + } else { + await expectNoEvents(); + } + }); +} diff --git a/pkgs/watcher/test/utils.dart b/pkgs/watcher/test/utils.dart index 12a277980..aa5cc2ea4 100644 --- a/pkgs/watcher/test/utils.dart +++ b/pkgs/watcher/test/utils.dart @@ -246,6 +246,34 @@ void writeFile(String path, {String? contents, bool? updateModified}) { } } +/// Schedules writing a file in the sandbox at [link] pointing to [target]. +/// +/// If [updateModified] is `false`, the mock file modification time is not +/// changed. +void writeLink({ + required String link, + required String target, + bool? updateModified, +}) { + updateModified ??= true; + + var fullPath = p.join(d.sandbox, link); + + // Create any needed subdirectories. + var dir = Directory(p.dirname(fullPath)); + if (!dir.existsSync()) { + dir.createSync(recursive: true); + } + + Link(fullPath).createSync(target); + + if (updateModified) { + link = p.normalize(link); + + _mockFileModificationTimes[link] = _nextTimestamp++; + } +} + /// Schedules deleting a file in the sandbox at [path]. void deleteFile(String path) { File(p.join(d.sandbox, path)).deleteSync(); From a52f39020a5370e44c983dbb9d425deb0041e929 Mon Sep 17 00:00:00 2001 From: "Morgan :)" Date: Wed, 1 Oct 2025 05:02:54 -0700 Subject: [PATCH 62/67] Run file watcher symlink tests. (#2189) --- pkgs/watcher/test/file_watcher/link_tests.dart | 2 +- pkgs/watcher/test/file_watcher/native_test.dart | 2 ++ pkgs/watcher/test/file_watcher/polling_test.dart | 2 ++ pkgs/watcher/test/utils.dart | 14 ++++++++++++++ 4 files changed, 19 insertions(+), 1 deletion(-) diff --git a/pkgs/watcher/test/file_watcher/link_tests.dart b/pkgs/watcher/test/file_watcher/link_tests.dart index a6eef1403..6b86cc236 100644 --- a/pkgs/watcher/test/file_watcher/link_tests.dart +++ b/pkgs/watcher/test/file_watcher/link_tests.dart @@ -98,7 +98,7 @@ void linkTests({required bool isNative}) { test('notifies when a link is moved away', () async { await startWatcher(path: 'link.txt'); - renameFile('link.txt', 'new.txt'); + renameLink('link.txt', 'new.txt'); // TODO(davidmorgan): reconcile differences. if (isNative) { diff --git a/pkgs/watcher/test/file_watcher/native_test.dart b/pkgs/watcher/test/file_watcher/native_test.dart index 30f319432..92224327d 100644 --- a/pkgs/watcher/test/file_watcher/native_test.dart +++ b/pkgs/watcher/test/file_watcher/native_test.dart @@ -10,11 +10,13 @@ import 'package:watcher/src/file_watcher/native.dart'; import '../utils.dart'; import 'file_tests.dart'; +import 'link_tests.dart'; import 'startup_race_tests.dart'; void main() { watcherFactory = NativeFileWatcher.new; fileTests(isNative: true); + linkTests(isNative: true); startupRaceTests(isNative: true); } diff --git a/pkgs/watcher/test/file_watcher/polling_test.dart b/pkgs/watcher/test/file_watcher/polling_test.dart index b8e41570d..c1590e783 100644 --- a/pkgs/watcher/test/file_watcher/polling_test.dart +++ b/pkgs/watcher/test/file_watcher/polling_test.dart @@ -6,6 +6,7 @@ import 'package:watcher/watcher.dart'; import '../utils.dart'; import 'file_tests.dart'; +import 'link_tests.dart'; import 'startup_race_tests.dart'; void main() { @@ -13,5 +14,6 @@ void main() { PollingFileWatcher(file, pollingDelay: const Duration(milliseconds: 100)); fileTests(isNative: false); + linkTests(isNative: false); startupRaceTests(isNative: false); } diff --git a/pkgs/watcher/test/utils.dart b/pkgs/watcher/test/utils.dart index aa5cc2ea4..6f61f9a8b 100644 --- a/pkgs/watcher/test/utils.dart +++ b/pkgs/watcher/test/utils.dart @@ -292,6 +292,20 @@ void renameFile(String from, String to) { ifAbsent: () => 1); } +/// Schedules renaming a link in the sandbox from [from] to [to]. +/// +/// On MacOS and Linux links can also be named with `renameFile`. On Windows, +/// however, a link must be renamed with this method. +void renameLink(String from, String to) { + Link(p.join(d.sandbox, from)).renameSync(p.join(d.sandbox, to)); + + // Make sure we always use the same separator on Windows. + to = p.normalize(to); + + _mockFileModificationTimes.update(to, (value) => value + 1, + ifAbsent: () => 1); +} + /// Schedules creating a directory in the sandbox at [path]. void createDir(String path) { Directory(p.join(d.sandbox, path)).createSync(); From bbd2227e61d51cdfac175c5e18dd5838b99cd065 Mon Sep 17 00:00:00 2001 From: Konstantin Scheglov Date: Sat, 4 Oct 2025 10:36:00 -0700 Subject: [PATCH 63/67] Add Event.analysisStatistics() constructor. (#2191) --- pkgs/unified_analytics/CHANGELOG.md | 3 +++ pkgs/unified_analytics/lib/src/constants.dart | 2 +- pkgs/unified_analytics/lib/src/enums.dart | 4 ++++ pkgs/unified_analytics/lib/src/event.dart | 14 ++++++++++++++ pkgs/unified_analytics/pubspec.yaml | 2 +- pkgs/unified_analytics/test/event_test.dart | 14 +++++++++++++- 6 files changed, 36 insertions(+), 3 deletions(-) diff --git a/pkgs/unified_analytics/CHANGELOG.md b/pkgs/unified_analytics/CHANGELOG.md index a3a04a9dc..fc882b254 100644 --- a/pkgs/unified_analytics/CHANGELOG.md +++ b/pkgs/unified_analytics/CHANGELOG.md @@ -1,3 +1,6 @@ +## 8.0.6 +- Added `Event.analysisStatistics` for events from Dart Analysis Server, about analysis. + ## 8.0.5 - Fix `Event.flutterWasmDryRun` fields. diff --git a/pkgs/unified_analytics/lib/src/constants.dart b/pkgs/unified_analytics/lib/src/constants.dart index f546bd200..73aec86a3 100644 --- a/pkgs/unified_analytics/lib/src/constants.dart +++ b/pkgs/unified_analytics/lib/src/constants.dart @@ -87,7 +87,7 @@ const int kMaxLogFileSize = 25 * (1 << 20); const String kLogFileName = 'dart-flutter-telemetry.log'; /// The current version of the package, should be in line with pubspec version. -const String kPackageVersion = '8.0.5'; +const String kPackageVersion = '8.0.6'; /// The minimum length for a session. const int kSessionDurationMinutes = 30; diff --git a/pkgs/unified_analytics/lib/src/enums.dart b/pkgs/unified_analytics/lib/src/enums.dart index 84eca3ec4..b3fb969b0 100644 --- a/pkgs/unified_analytics/lib/src/enums.dart +++ b/pkgs/unified_analytics/lib/src/enums.dart @@ -126,6 +126,10 @@ enum DashEvent { // Events for language_server below + analysisStatistics( + label: 'analysis_statistics', + description: 'Dart analyzer statistics', + ), clientNotification( label: 'client_notification', description: 'Notifications sent from the client', diff --git a/pkgs/unified_analytics/lib/src/event.dart b/pkgs/unified_analytics/lib/src/event.dart index e8b84f36f..e6fa46454 100644 --- a/pkgs/unified_analytics/lib/src/event.dart +++ b/pkgs/unified_analytics/lib/src/event.dart @@ -103,6 +103,20 @@ final class Event { }, ); + /// Event that is emitted periodically to report the performance of the + /// analyzer. + /// + /// [workingDuration] - json encoded percentile values indicating how long + /// the analysis status was "working". + Event.analysisStatistics({ + required String workingDuration, + }) : this._( + eventName: DashEvent.analysisStatistics, + eventData: { + 'workingDuration': workingDuration, + }, + ); + /// Event that is emitted periodically to report the performance of the /// analysis server's handling of a specific kind of request from the client. /// diff --git a/pkgs/unified_analytics/pubspec.yaml b/pkgs/unified_analytics/pubspec.yaml index 1840adac3..4430ef975 100644 --- a/pkgs/unified_analytics/pubspec.yaml +++ b/pkgs/unified_analytics/pubspec.yaml @@ -5,7 +5,7 @@ description: >- # LINT.IfChange # When updating this, keep the version consistent with the changelog and the # value in lib/src/constants.dart. -version: 8.0.5 +version: 8.0.6 # LINT.ThenChange(lib/src/constants.dart) repository: https://github.com/dart-lang/tools/tree/main/pkgs/unified_analytics issue_tracker: https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Aunified_analytics diff --git a/pkgs/unified_analytics/test/event_test.dart b/pkgs/unified_analytics/test/event_test.dart index 301f4c7f8..932cbef9b 100644 --- a/pkgs/unified_analytics/test/event_test.dart +++ b/pkgs/unified_analytics/test/event_test.dart @@ -10,6 +10,18 @@ import 'package:unified_analytics/src/event.dart'; import 'package:unified_analytics/unified_analytics.dart'; void main() { + test('Event.analysisStatistics constructed', () { + Event generateEvent() => + Event.analysisStatistics(workingDuration: 'workingDuration'); + + final constructedEvent = generateEvent(); + + expect(generateEvent, returnsNormally); + expect(constructedEvent.eventName, DashEvent.analysisStatistics); + expect(constructedEvent.eventData['workingDuration'], 'workingDuration'); + expect(constructedEvent.eventData.length, 1); + }); + test('Event.analyticsCollectionEnabled constructed', () { Event generateEvent() => Event.analyticsCollectionEnabled(status: false); @@ -729,7 +741,7 @@ void main() { // Change this integer below if your PR either adds or removes // an Event constructor - final eventsAccountedForInTests = 30; + final eventsAccountedForInTests = 31; expect(eventsAccountedForInTests, constructorCount, reason: 'If you added or removed an event constructor, ' 'ensure you have updated ' From f558715c534f644ddac94ce2a3be1e74cd042b5b Mon Sep 17 00:00:00 2001 From: "Morgan :)" Date: Mon, 6 Oct 2025 04:40:48 -0700 Subject: [PATCH 64/67] Fix flaky test on Windows. (#2192) --- pkgs/watcher/test/directory_watcher/windows_test.dart | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pkgs/watcher/test/directory_watcher/windows_test.dart b/pkgs/watcher/test/directory_watcher/windows_test.dart index 6489771c6..934b4feab 100644 --- a/pkgs/watcher/test/directory_watcher/windows_test.dart +++ b/pkgs/watcher/test/directory_watcher/windows_test.dart @@ -169,7 +169,12 @@ void main() { } // Events only happen when there is an async gap, wait for such a gap. - await Future.delayed(const Duration(milliseconds: 10)); + // The event usually arrives in under 10ms, try for 100ms. + var tries = 0; + while (errorsSeen == 0 && eventsSeen == 0 && tries < 10) { + await Future.delayed(const Duration(milliseconds: 10)); + ++tries; + } // If everything is going well, there should have been either one event // seen or one error seen. From 3ffdc871c05e39c1f2f8c5741b8ed95a7ba2e8b6 Mon Sep 17 00:00:00 2001 From: "Morgan :)" Date: Mon, 6 Oct 2025 06:19:51 -0700 Subject: [PATCH 65/67] Allow more time for tests on Windows. (#2193) --- pkgs/watcher/test/directory_watcher/polling_test.dart | 3 +++ pkgs/watcher/test/directory_watcher/windows_test.dart | 1 + 2 files changed, 4 insertions(+) diff --git a/pkgs/watcher/test/directory_watcher/polling_test.dart b/pkgs/watcher/test/directory_watcher/polling_test.dart index f4ec8f48a..af10c21ed 100644 --- a/pkgs/watcher/test/directory_watcher/polling_test.dart +++ b/pkgs/watcher/test/directory_watcher/polling_test.dart @@ -2,6 +2,9 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. +@Timeout.factor(2) +library; + import 'package:test/test.dart'; import 'package:watcher/watcher.dart'; diff --git a/pkgs/watcher/test/directory_watcher/windows_test.dart b/pkgs/watcher/test/directory_watcher/windows_test.dart index 934b4feab..709bcb59b 100644 --- a/pkgs/watcher/test/directory_watcher/windows_test.dart +++ b/pkgs/watcher/test/directory_watcher/windows_test.dart @@ -3,6 +3,7 @@ // BSD-style license that can be found in the LICENSE file. @TestOn('windows') +@Timeout.factor(2) library; import 'dart:async'; From 9b68074fbbcc7e2d36686b89da39509db32a3348 Mon Sep 17 00:00:00 2001 From: Moritz Date: Tue, 7 Oct 2025 09:40:54 +0200 Subject: [PATCH 66/67] [bazel_worker] Upgrade `protobuf` + cleanups (#2195) --- .github/workflows/bazel_worker.yaml | 18 +++++-- pkgs/bazel_worker/CHANGELOG.md | 5 ++ .../e2e_test/lib/async_worker.dart | 2 +- pkgs/bazel_worker/e2e_test/pubspec.yaml | 7 +-- pkgs/bazel_worker/example/client.dart | 13 ++--- pkgs/bazel_worker/example/worker.dart | 4 ++ .../lib/src/async_message_grouper.dart | 6 +-- pkgs/bazel_worker/lib/src/driver/driver.dart | 54 ++++++++++--------- .../lib/src/driver/driver_connection.dart | 10 ++-- .../lib/src/message_grouper_state.dart | 6 +-- .../lib/src/worker/async_worker_loop.dart | 2 +- .../lib/src/worker/sync_worker_loop.dart | 2 +- .../lib/src/worker/worker_connection.dart | 23 ++++---- pkgs/bazel_worker/lib/testing.dart | 6 +-- pkgs/bazel_worker/pubspec.yaml | 10 ++-- pkgs/bazel_worker/test/worker_loop_test.dart | 3 +- pkgs/bazel_worker/tool/travis.sh | 26 --------- 17 files changed, 99 insertions(+), 98 deletions(-) delete mode 100755 pkgs/bazel_worker/tool/travis.sh diff --git a/.github/workflows/bazel_worker.yaml b/.github/workflows/bazel_worker.yaml index e44fb17f2..19d9cd666 100644 --- a/.github/workflows/bazel_worker.yaml +++ b/.github/workflows/bazel_worker.yaml @@ -27,14 +27,24 @@ jobs: strategy: fail-fast: false matrix: - sdk: [3.4, dev] + sdk: [stable, dev] steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c with: sdk: ${{ matrix.sdk }} - run: dart pub get - - run: "dart format --output=none --set-exit-if-changed ." + + - run: dart format --output=none --set-exit-if-changed . if: ${{ matrix.sdk == 'dev' }} - - name: Test - run: ./tool/travis.sh + + - run: dart analyze --fatal-infos + + - run: dart run benchmark/benchmark.dart + + - run: dart test + + - name: dart test e2e_test + run: | + cd e2e_test + dart test diff --git a/pkgs/bazel_worker/CHANGELOG.md b/pkgs/bazel_worker/CHANGELOG.md index bdc46f5f8..6eb3eb30b 100644 --- a/pkgs/bazel_worker/CHANGELOG.md +++ b/pkgs/bazel_worker/CHANGELOG.md @@ -1,3 +1,8 @@ +## 1.1.4 + +* Require Dart SDK `^3.9.0`. +* Widen `package:protobuf` constraint to allow 5.0.0 and requiring minimum 4.0.0. + ## 1.1.3 * Require Dart SDK `^3.4.0`. diff --git a/pkgs/bazel_worker/e2e_test/lib/async_worker.dart b/pkgs/bazel_worker/e2e_test/lib/async_worker.dart index 55f517134..1c10bc0f8 100644 --- a/pkgs/bazel_worker/e2e_test/lib/async_worker.dart +++ b/pkgs/bazel_worker/e2e_test/lib/async_worker.dart @@ -12,7 +12,7 @@ import 'package:bazel_worker/bazel_worker.dart'; class ExampleAsyncWorker extends AsyncWorkerLoop { /// Set [sendPort] to run in an isolate. ExampleAsyncWorker([SendPort? sendPort]) - : super(connection: AsyncWorkerConnection(sendPort: sendPort)); + : super(connection: AsyncWorkerConnection(sendPort: sendPort)); @override Future performRequest(WorkRequest request) async { diff --git a/pkgs/bazel_worker/e2e_test/pubspec.yaml b/pkgs/bazel_worker/e2e_test/pubspec.yaml index 7eaa89a16..91928c56a 100644 --- a/pkgs/bazel_worker/e2e_test/pubspec.yaml +++ b/pkgs/bazel_worker/e2e_test/pubspec.yaml @@ -1,12 +1,13 @@ name: e2e_test publish_to: none +resolution: workspace + environment: - sdk: ^3.4.0 + sdk: ^3.9.0 dependencies: - bazel_worker: - path: ../ + bazel_worker: any dev_dependencies: cli_util: ^0.4.2 diff --git a/pkgs/bazel_worker/example/client.dart b/pkgs/bazel_worker/example/client.dart index 326bb180b..847e80a77 100644 --- a/pkgs/bazel_worker/example/client.dart +++ b/pkgs/bazel_worker/example/client.dart @@ -1,3 +1,7 @@ +// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + import 'dart:io'; import 'package:bazel_worker/driver.dart'; @@ -5,12 +9,9 @@ import 'package:bazel_worker/driver.dart'; void main() async { var scratchSpace = await Directory.systemTemp.createTemp(); var driver = BazelWorkerDriver( - () => Process.start( - Platform.resolvedExecutable, - [ - Platform.script.resolve('worker.dart').toFilePath(), - ], - workingDirectory: scratchSpace.path), + () => Process.start(Platform.resolvedExecutable, [ + Platform.script.resolve('worker.dart').toFilePath(), + ], workingDirectory: scratchSpace.path), maxWorkers: 4, ); var response = await driver.doWork(WorkRequest(arguments: ['foo'])); diff --git a/pkgs/bazel_worker/example/worker.dart b/pkgs/bazel_worker/example/worker.dart index ba3f48cc7..bba2c20c4 100644 --- a/pkgs/bazel_worker/example/worker.dart +++ b/pkgs/bazel_worker/example/worker.dart @@ -1,3 +1,7 @@ +// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + import 'dart:io'; import 'package:bazel_worker/bazel_worker.dart'; diff --git a/pkgs/bazel_worker/lib/src/async_message_grouper.dart b/pkgs/bazel_worker/lib/src/async_message_grouper.dart index 8fc47780a..547ee6af0 100644 --- a/pkgs/bazel_worker/lib/src/async_message_grouper.dart +++ b/pkgs/bazel_worker/lib/src/async_message_grouper.dart @@ -41,15 +41,15 @@ class AsyncMessageGrouper implements MessageGrouper { int _messagePos = 0; AsyncMessageGrouper(Stream> inputStream) - : _inputQueue = StreamQueue(inputStream); + : _inputQueue = StreamQueue(inputStream); /// Returns the next full message that is received, or null if none are left. @override Future?> get next async { try { // Loop while there is data in the input buffer or the input stream. - while ( - _inputBufferPos != _inputBuffer.length || await _inputQueue.hasNext) { + while (_inputBufferPos != _inputBuffer.length || + await _inputQueue.hasNext) { // If the input buffer is empty fill it from the input stream. if (_inputBufferPos == _inputBuffer.length) { _inputBuffer = await _inputQueue.next; diff --git a/pkgs/bazel_worker/lib/src/driver/driver.dart b/pkgs/bazel_worker/lib/src/driver/driver.dart index 06cf0feb1..954517e4e 100644 --- a/pkgs/bazel_worker/lib/src/driver/driver.dart +++ b/pkgs/bazel_worker/lib/src/driver/driver.dart @@ -49,9 +49,9 @@ class BazelWorkerDriver { int? maxIdleWorkers, int? maxWorkers, int? maxRetries, - }) : _maxIdleWorkers = maxIdleWorkers ?? 4, - _maxWorkers = maxWorkers ?? 4, - _maxRetries = maxRetries ?? 4; + }) : _maxIdleWorkers = maxIdleWorkers ?? 4, + _maxWorkers = maxWorkers ?? 4, + _maxRetries = maxRetries ?? 4; /// Waits for an available worker, and then sends [WorkRequest] to it. /// @@ -111,29 +111,31 @@ class BazelWorkerDriver { // work queue. var futureWorker = _spawnWorker(); _spawningWorkers.add(futureWorker); - futureWorker.then((worker) { - _spawningWorkers.remove(futureWorker); - _readyWorkers.add(worker); - var connection = StdDriverConnection.forWorker(worker); - _workerConnections[worker] = connection; - _runWorker(worker, attempt); - - // When the worker exits we should retry running the work queue in case - // there is more work to be done. This is primarily just a defensive - // thing but is cheap to do. - // - // We don't use `exitCode` because it is null for detached processes ( - // which is common for workers). - connection.done.then((_) { - _idleWorkers.remove(worker); - _readyWorkers.remove(worker); - _runWorkQueue(); - }); - }).onError((e, s) { - _spawningWorkers.remove(futureWorker); - if (attempt.responseCompleter.isCompleted) return; - attempt.responseCompleter.completeError(e, s); - }); + futureWorker + .then((worker) { + _spawningWorkers.remove(futureWorker); + _readyWorkers.add(worker); + var connection = StdDriverConnection.forWorker(worker); + _workerConnections[worker] = connection; + _runWorker(worker, attempt); + + // When the worker exits we should retry running the work queue in case + // there is more work to be done. This is primarily just a defensive + // thing but is cheap to do. + // + // We don't use `exitCode` because it is null for detached processes ( + // which is common for workers). + connection.done.then((_) { + _idleWorkers.remove(worker); + _readyWorkers.remove(worker); + _runWorkQueue(); + }); + }) + .onError((e, s) { + _spawningWorkers.remove(futureWorker); + if (attempt.responseCompleter.isCompleted) return; + attempt.responseCompleter.completeError(e, s); + }); } // Recursively calls itself until one of the bail out conditions are met. _runWorkQueue(); diff --git a/pkgs/bazel_worker/lib/src/driver/driver_connection.dart b/pkgs/bazel_worker/lib/src/driver/driver_connection.dart index 80d5c98cf..0d3e54dc3 100644 --- a/pkgs/bazel_worker/lib/src/driver/driver_connection.dart +++ b/pkgs/bazel_worker/lib/src/driver/driver_connection.dart @@ -37,13 +37,13 @@ class StdDriverConnection implements DriverConnection { StdDriverConnection({ Stream>? inputStream, StreamSink>? outputStream, - }) : _messageGrouper = AsyncMessageGrouper(inputStream ?? stdin), - _outputStream = outputStream ?? stdout; + }) : _messageGrouper = AsyncMessageGrouper(inputStream ?? stdin), + _outputStream = outputStream ?? stdout; factory StdDriverConnection.forWorker(Process worker) => StdDriverConnection( - inputStream: worker.stdout, - outputStream: worker.stdin, - ); + inputStream: worker.stdout, + outputStream: worker.stdin, + ); /// Note: This will attempts to recover from invalid proto messages by parsing /// them as strings. This is a common error case for workers (they print a diff --git a/pkgs/bazel_worker/lib/src/message_grouper_state.dart b/pkgs/bazel_worker/lib/src/message_grouper_state.dart index 26568354b..3b90096ad 100644 --- a/pkgs/bazel_worker/lib/src/message_grouper_state.dart +++ b/pkgs/bazel_worker/lib/src/message_grouper_state.dart @@ -109,9 +109,9 @@ class _MessageReader { int _numMessageBytesReceived = 0; _MessageReader(int length) - : _message = Uint8List(length), - _length = length, - _done = length == 0; + : _message = Uint8List(length), + _length = length, + _done = length == 0; /// Reads [byte] into [_message]. void readByte(int byte) { diff --git a/pkgs/bazel_worker/lib/src/worker/async_worker_loop.dart b/pkgs/bazel_worker/lib/src/worker/async_worker_loop.dart index a95d09a1f..2d36810bd 100644 --- a/pkgs/bazel_worker/lib/src/worker/async_worker_loop.dart +++ b/pkgs/bazel_worker/lib/src/worker/async_worker_loop.dart @@ -16,7 +16,7 @@ abstract class AsyncWorkerLoop implements WorkerLoop { final AsyncWorkerConnection connection; AsyncWorkerLoop({AsyncWorkerConnection? connection}) - : connection = connection ?? StdAsyncWorkerConnection(); + : connection = connection ?? StdAsyncWorkerConnection(); /// Perform a single [WorkRequest], and return a [WorkResponse]. @override diff --git a/pkgs/bazel_worker/lib/src/worker/sync_worker_loop.dart b/pkgs/bazel_worker/lib/src/worker/sync_worker_loop.dart index 51da684ab..116588e90 100644 --- a/pkgs/bazel_worker/lib/src/worker/sync_worker_loop.dart +++ b/pkgs/bazel_worker/lib/src/worker/sync_worker_loop.dart @@ -15,7 +15,7 @@ abstract class SyncWorkerLoop implements WorkerLoop { final SyncWorkerConnection connection; SyncWorkerLoop({SyncWorkerConnection? connection}) - : connection = connection ?? StdSyncWorkerConnection(); + : connection = connection ?? StdSyncWorkerConnection(); /// Perform a single [WorkRequest], and return a [WorkResponse]. @override diff --git a/pkgs/bazel_worker/lib/src/worker/worker_connection.dart b/pkgs/bazel_worker/lib/src/worker/worker_connection.dart index fd5508e4a..203c384f3 100644 --- a/pkgs/bazel_worker/lib/src/worker/worker_connection.dart +++ b/pkgs/bazel_worker/lib/src/worker/worker_connection.dart @@ -33,13 +33,12 @@ abstract class AsyncWorkerConnection implements WorkerConnection { Stream>? inputStream, StreamSink>? outputStream, SendPort? sendPort, - }) => - sendPort == null - ? StdAsyncWorkerConnection( - inputStream: inputStream, - outputStream: outputStream, - ) - : SendPortAsyncWorkerConnection(sendPort); + }) => sendPort == null + ? StdAsyncWorkerConnection( + inputStream: inputStream, + outputStream: outputStream, + ) + : SendPortAsyncWorkerConnection(sendPort); @override Future readRequest(); @@ -59,8 +58,8 @@ class StdAsyncWorkerConnection implements AsyncWorkerConnection { StdAsyncWorkerConnection({ Stream>? inputStream, StreamSink>? outputStream, - }) : _messageGrouper = AsyncMessageGrouper(inputStream ?? stdin), - _outputStream = outputStream ?? stdout; + }) : _messageGrouper = AsyncMessageGrouper(inputStream ?? stdin), + _outputStream = outputStream ?? stdout; @override Future readRequest() async { @@ -89,7 +88,7 @@ class SendPortAsyncWorkerConnection implements AsyncWorkerConnection { } SendPortAsyncWorkerConnection._(this.receivePort, this.sendPort) - : receivePortIterator = StreamIterator(receivePort.cast()); + : receivePortIterator = StreamIterator(receivePort.cast()); @override Future readRequest() async { @@ -110,8 +109,8 @@ class StdSyncWorkerConnection implements SyncWorkerConnection { final Stdout _stdoutStream; StdSyncWorkerConnection({Stdin? stdinStream, Stdout? stdoutStream}) - : _messageGrouper = SyncMessageGrouper(stdinStream ?? stdin), - _stdoutStream = stdoutStream ?? stdout; + : _messageGrouper = SyncMessageGrouper(stdinStream ?? stdin), + _stdoutStream = stdoutStream ?? stdout; @override WorkRequest? readRequest() { diff --git a/pkgs/bazel_worker/lib/testing.dart b/pkgs/bazel_worker/lib/testing.dart index 7aefabbd8..7593872fb 100644 --- a/pkgs/bazel_worker/lib/testing.dart +++ b/pkgs/bazel_worker/lib/testing.dart @@ -130,7 +130,7 @@ class TestSyncWorkerConnection extends StdSyncWorkerConnection final List responses = []; TestSyncWorkerConnection(Stdin stdinStream, Stdout stdoutStream) - : super(stdinStream: stdinStream, stdoutStream: stdoutStream); + : super(stdinStream: stdinStream, stdoutStream: stdoutStream); @override void writeResponse(WorkResponse response) { @@ -148,7 +148,7 @@ class TestSyncWorkerLoop extends SyncWorkerLoop implements TestWorkerLoop { final String? printMessage; TestSyncWorkerLoop(SyncWorkerConnection connection, {this.printMessage}) - : super(connection: connection); + : super(connection: connection); @override WorkResponse performRequest(WorkRequest request) { @@ -193,7 +193,7 @@ class TestAsyncWorkerLoop extends AsyncWorkerLoop implements TestWorkerLoop { final String? printMessage; TestAsyncWorkerLoop(AsyncWorkerConnection connection, {this.printMessage}) - : super(connection: connection); + : super(connection: connection); @override Future performRequest(WorkRequest request) async { diff --git a/pkgs/bazel_worker/pubspec.yaml b/pkgs/bazel_worker/pubspec.yaml index 4bd06443f..dd10193fc 100644 --- a/pkgs/bazel_worker/pubspec.yaml +++ b/pkgs/bazel_worker/pubspec.yaml @@ -1,16 +1,20 @@ name: bazel_worker -version: 1.1.3 +version: 1.1.4 description: >- Protocol and utilities to implement or invoke persistent bazel workers. repository: https://github.com/dart-lang/tools/tree/main/pkgs/bazel_worker issue_tracker: https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Abazel_worker +# Using a workspace so the integration tests resolve to the local version of this package. +workspace: + - e2e_test + environment: - sdk: ^3.4.0 + sdk: ^3.9.0 dependencies: async: ^2.5.0 - protobuf: ">=3.0.0 <5.0.0" + protobuf: ">=4.0.0 <6.0.0" dev_dependencies: dart_flutter_team_lints: ^3.0.0 diff --git a/pkgs/bazel_worker/test/worker_loop_test.dart b/pkgs/bazel_worker/test/worker_loop_test.dart index 24068b1d8..4461c7324 100644 --- a/pkgs/bazel_worker/test/worker_loop_test.dart +++ b/pkgs/bazel_worker/test/worker_loop_test.dart @@ -83,7 +83,8 @@ void runTests( expect( printMessages, isEmpty, - reason: 'The worker loop should hide all print calls from the parent ' + reason: + 'The worker loop should hide all print calls from the parent ' 'zone.', ); diff --git a/pkgs/bazel_worker/tool/travis.sh b/pkgs/bazel_worker/tool/travis.sh deleted file mode 100755 index 05adb02d5..000000000 --- a/pkgs/bazel_worker/tool/travis.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/bin/bash - -# Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file -# for details. All rights reserved. Use of this source code is governed by a -# BSD-style license that can be found in the LICENSE file. - -# Fast fail the script on failures. -set -e - -dart pub get - -# Verify that the libraries are error free. -dart analyze --fatal-infos \ - lib/bazel_worker.dart \ - lib/driver.dart \ - lib/testing.dart \ - test/test_all.dart - -# Run the tests. -dart test - -pushd e2e_test -dart pub get -dart analyze --fatal-infos test/e2e_test.dart -dart test -popd From cad7da30b60677b2ed14090cfc30c21a0d58d267 Mon Sep 17 00:00:00 2001 From: Nate Bosch Date: Thu, 16 Oct 2025 23:15:16 +0000 Subject: [PATCH 67/67] Force merge into better state --- .github/workflows/html.yaml | 2 +- .github/workflows/pubspec_parse.yaml | 2 +- .github/workflows/watcher.yaml | 2 +- pkgs/graphs/pubspec.yaml | 2 +- pkgs/html/CHANGELOG.md | 4 + pkgs/html/pubspec.yaml | 6 +- pkgs/html/tool/generate_trie.dart | 4 +- pkgs/markdown/CHANGELOG.md | 2 + .../fenced_code_block_syntax.dart | 65 +++-- .../test/common_mark/fenced_code_blocks.unit | 4 +- .../markdown/test/gfm/fenced_code_blocks.unit | 4 +- .../test/original/fenced_code_block.unit | 24 ++ pkgs/oauth2/CHANGELOG.md | 5 +- pkgs/oauth2/README.md | 47 ++- pkgs/oauth2/pubspec.yaml | 2 +- pkgs/process/pubspec.yaml | 2 +- pkgs/pubspec_parse/CHANGELOG.md | 1 + pkgs/pubspec_parse/lib/src/dependency.dart | 49 ++-- pkgs/pubspec_parse/lib/src/dependency.g.dart | 111 ++++---- pkgs/pubspec_parse/lib/src/pubspec.dart | 44 ++- pkgs/pubspec_parse/lib/src/pubspec.g.dart | 185 ++++++------ pkgs/pubspec_parse/pubspec.yaml | 12 +- pkgs/pubspec_parse/test/dependency_test.dart | 120 ++++---- pkgs/pubspec_parse/test/parse_test.dart | 203 +++++-------- pkgs/pubspec_parse/test/pub_utils.dart | 5 +- pkgs/pubspec_parse/test/test_utils.dart | 46 +-- pkgs/pubspec_parse/test/tojson_test.dart | 173 +++++------ pkgs/source_maps/CHANGELOG.md | 4 + pkgs/source_maps/lib/parser.dart | 46 +-- .../test/continued_region_test.dart | 110 +++++++ pkgs/source_maps/test/refactor_test.dart | 13 +- pkgs/watcher/CHANGELOG.md | 7 + .../lib/src/directory_watcher/linux.dart | 63 ++-- .../lib/src/directory_watcher/mac_os.dart | 227 +++++++-------- .../lib/src/directory_watcher/polling.dart | 7 +- .../lib/src/directory_watcher/windows.dart | 265 ++++++++--------- pkgs/watcher/lib/src/event.dart | 122 ++++++++ pkgs/watcher/lib/src/file_watcher/native.dart | 11 +- pkgs/watcher/lib/src/stat.dart | 2 +- pkgs/watcher/lib/src/utils.dart | 17 +- pkgs/watcher/pubspec.yaml | 2 +- .../{shared.dart => file_tests.dart} | 7 +- .../test/directory_watcher/link_tests.dart | 268 ++++++++++++++++++ .../test/directory_watcher/linux_test.dart | 6 +- .../test/directory_watcher/mac_os_test.dart | 6 +- .../test/directory_watcher/polling_test.dart | 66 ++++- .../test/directory_watcher/windows_test.dart | 6 +- .../watcher/test/file_watcher/link_tests.dart | 60 ++-- .../test/file_watcher/polling_test.dart | 21 +- pkgs/watcher/test/utils.dart | 247 ++++++++++++---- 50 files changed, 1691 insertions(+), 1018 deletions(-) create mode 100644 pkgs/source_maps/test/continued_region_test.dart create mode 100644 pkgs/watcher/lib/src/event.dart rename pkgs/watcher/test/directory_watcher/{shared.dart => file_tests.dart} (98%) create mode 100644 pkgs/watcher/test/directory_watcher/link_tests.dart diff --git a/.github/workflows/html.yaml b/.github/workflows/html.yaml index afbaad4e5..e2702b1d1 100644 --- a/.github/workflows/html.yaml +++ b/.github/workflows/html.yaml @@ -45,7 +45,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest] - sdk: [3.2, stable, dev] + sdk: [3.6, stable, dev] steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c diff --git a/.github/workflows/pubspec_parse.yaml b/.github/workflows/pubspec_parse.yaml index 9388669d9..a5dea386c 100644 --- a/.github/workflows/pubspec_parse.yaml +++ b/.github/workflows/pubspec_parse.yaml @@ -54,7 +54,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest] - sdk: [3.6, dev] + sdk: [3.8, dev] steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c diff --git a/.github/workflows/watcher.yaml b/.github/workflows/watcher.yaml index 6a3b9ba70..0afb7079a 100644 --- a/.github/workflows/watcher.yaml +++ b/.github/workflows/watcher.yaml @@ -54,7 +54,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest, macos-latest, windows-latest] - sdk: [3.1, dev] + sdk: [3.3, dev] steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c diff --git a/pkgs/graphs/pubspec.yaml b/pkgs/graphs/pubspec.yaml index e25ee85de..d3779b5a8 100644 --- a/pkgs/graphs/pubspec.yaml +++ b/pkgs/graphs/pubspec.yaml @@ -15,6 +15,6 @@ dev_dependencies: test: ^1.21.6 # For examples - analyzer: '>=5.2.0 <8.0.0' + analyzer: '>=5.2.0 <9.0.0' path: ^1.8.0 pool: ^1.5.0 diff --git a/pkgs/html/CHANGELOG.md b/pkgs/html/CHANGELOG.md index c0e48769e..17cbb8466 100644 --- a/pkgs/html/CHANGELOG.md +++ b/pkgs/html/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.15.7-wip + +- Require Dart `3.6` + ## 0.15.6 - Performance improvements. diff --git a/pkgs/html/pubspec.yaml b/pkgs/html/pubspec.yaml index 75327df30..c2ce3c370 100644 --- a/pkgs/html/pubspec.yaml +++ b/pkgs/html/pubspec.yaml @@ -1,5 +1,5 @@ name: html -version: 0.15.6 +version: 0.15.7-wip description: APIs for parsing and manipulating HTML content outside the browser. repository: https://github.com/dart-lang/tools/tree/main/pkgs/html issue_tracker: https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Ahtml @@ -9,7 +9,7 @@ topics: - web environment: - sdk: ^3.2.0 + sdk: ^3.6.0 dependencies: csslib: ^1.0.0 @@ -17,6 +17,6 @@ dependencies: dev_dependencies: dart_flutter_team_lints: ^3.0.0 - dart_style: ^2.3.6 + dart_style: ^3.0.0 path: ^1.8.0 test: ^1.16.6 diff --git a/pkgs/html/tool/generate_trie.dart b/pkgs/html/tool/generate_trie.dart index e7fc99619..9042830e0 100644 --- a/pkgs/html/tool/generate_trie.dart +++ b/pkgs/html/tool/generate_trie.dart @@ -17,7 +17,9 @@ void main() { '''// AUTO GENERATED by 'tool/generate_trie.dart'. DO NOT EDIT!\n''' 'const entitiesTrieRoot = $root;' .replaceAll('{}', '{}'); - final formatted = DartFormatter().format(source); + final formatted = DartFormatter( + languageVersion: DartFormatter.latestShortStyleLanguageVersion) + .format(source); final htmlDir = File(Platform.script.path).parent.parent; File(join(htmlDir.path, 'lib', 'src', 'trie.dart')) .writeAsStringSync(formatted); diff --git a/pkgs/markdown/CHANGELOG.md b/pkgs/markdown/CHANGELOG.md index 289004db7..8e947759a 100644 --- a/pkgs/markdown/CHANGELOG.md +++ b/pkgs/markdown/CHANGELOG.md @@ -1,5 +1,7 @@ ## 7.3.1-wip +* Preserve metadata passed to fenced code blocks as + `data-metadata` on the created `pre` element. * Update the README link to the markdown playground (https://dart-lang.github.io/tools). * Update `package:web` API references in the example. diff --git a/pkgs/markdown/lib/src/block_syntaxes/fenced_code_block_syntax.dart b/pkgs/markdown/lib/src/block_syntaxes/fenced_code_block_syntax.dart index de695c7a9..c6a4f52e1 100644 --- a/pkgs/markdown/lib/src/block_syntaxes/fenced_code_block_syntax.dart +++ b/pkgs/markdown/lib/src/block_syntaxes/fenced_code_block_syntax.dart @@ -22,9 +22,9 @@ class FencedCodeBlockSyntax extends BlockSyntax { @override Node parse(BlockParser parser) { - final openingFence = _FenceMatch.fromMatch(pattern.firstMatch( - escapePunctuation(parser.current.content), - )!); + final openingFence = _FenceMatch.fromMatch( + pattern.firstMatch(escapePunctuation(parser.current.content))!, + ); var text = parseChildLines( parser, @@ -39,16 +39,31 @@ class FencedCodeBlockSyntax extends BlockSyntax { text = '$text\n'; } + final (languageString, metadataString) = openingFence.languageAndMetadata; + final code = Element.text('code', text); - if (openingFence.hasLanguage) { - var language = decodeHtmlCharacters(openingFence.language); - if (parser.document.encodeHtml) { - language = escapeHtmlAttribute(language); - } - code.attributes['class'] = 'language-$language'; + if (languageString != null) { + final processedLanguage = _processAttribute(languageString, + encodeHtml: parser.document.encodeHtml); + code.attributes['class'] = 'language-$processedLanguage'; + } + + final pre = Element('pre', [code]); + if (metadataString != null) { + final processedMetadata = _processAttribute(metadataString, + encodeHtml: parser.document.encodeHtml); + pre.attributes['data-metadata'] = processedMetadata; } - return Element('pre', [code]); + return pre; + } + + static String _processAttribute(String value, {bool encodeHtml = false}) { + final decodedValue = decodeHtmlCharacters(value); + if (encodeHtml) { + return escapeHtmlAttribute(decodedValue); + } + return decodedValue; } @override @@ -144,12 +159,30 @@ class _FenceMatch { // https://spec.commonmark.org/0.30/#info-string. final String info; - // The first word of the info string is typically used to specify the language - // of the code sample, - // https://spec.commonmark.org/0.30/#example-143. - String get language => info.split(' ').first; + /// Returns the language and remaining metadata from the [info] string. + /// + /// The language is the first word of the info string, + /// to match the (unspecified, but typical) behavior of CommonMark parsers, + /// as suggested in https://spec.commonmark.org/0.30/#example-143. + /// + /// The metadata is any remaining part of the info string after the language. + (String? language, String? metadata) get languageAndMetadata { + if (info.isEmpty) { + return (null, null); + } - bool get hasInfo => info.isNotEmpty; + // We assume the info string is trimmed already. + final firstSpaceIndex = info.indexOf(' '); + if (firstSpaceIndex == -1) { + // If there is no space, the whole string is the language. + return (info, null); + } - bool get hasLanguage => language.isNotEmpty; + return ( + info.substring(0, firstSpaceIndex), + info.substring(firstSpaceIndex + 1), + ); + } + + bool get hasInfo => info.isNotEmpty; } diff --git a/pkgs/markdown/test/common_mark/fenced_code_blocks.unit b/pkgs/markdown/test/common_mark/fenced_code_blocks.unit index 06dec5865..5854bc370 100644 --- a/pkgs/markdown/test/common_mark/fenced_code_blocks.unit +++ b/pkgs/markdown/test/common_mark/fenced_code_blocks.unit @@ -214,7 +214,7 @@ def foo(x) end ~~~~~~~ <<< -
def foo(x)
+
def foo(x)
   return 3
 end
 
@@ -234,7 +234,7 @@ foo

foo ~~~ <<< -
foo
+
foo
 
>>> Fenced code blocks - 147 ``` diff --git a/pkgs/markdown/test/gfm/fenced_code_blocks.unit b/pkgs/markdown/test/gfm/fenced_code_blocks.unit index f6fa8432a..52f217ae1 100644 --- a/pkgs/markdown/test/gfm/fenced_code_blocks.unit +++ b/pkgs/markdown/test/gfm/fenced_code_blocks.unit @@ -214,7 +214,7 @@ def foo(x) end ~~~~~~~ <<< -
def foo(x)
+
def foo(x)
   return 3
 end
 
@@ -234,7 +234,7 @@ foo

foo ~~~ <<< -
foo
+
foo
 
>>> Fenced code blocks - 117 ``` diff --git a/pkgs/markdown/test/original/fenced_code_block.unit b/pkgs/markdown/test/original/fenced_code_block.unit index 779e747c3..3492dfca6 100644 --- a/pkgs/markdown/test/original/fenced_code_block.unit +++ b/pkgs/markdown/test/original/fenced_code_block.unit @@ -5,3 +5,27 @@ <<<
'foo'
 
+>>> with basic metadata string +```dart meta +code +``` + +<<< +
code
+
+>>> with characters to escape +```dart title="main.dart" +code +``` + +<<< +
code
+
+>>> with HTML character reference +```dart | +code +``` + +<<< +
code
+
\ No newline at end of file diff --git a/pkgs/oauth2/CHANGELOG.md b/pkgs/oauth2/CHANGELOG.md index ff39de3fb..82f693878 100644 --- a/pkgs/oauth2/CHANGELOG.md +++ b/pkgs/oauth2/CHANGELOG.md @@ -1,4 +1,7 @@ -## 2.0.4-wip +## 2.0.4 + +* Updated README with example for Flutter Web. + * Require Dart 3.4 diff --git a/pkgs/oauth2/README.md b/pkgs/oauth2/README.md index 07a5976b6..8ffcba9d5 100644 --- a/pkgs/oauth2/README.md +++ b/pkgs/oauth2/README.md @@ -128,7 +128,7 @@ because different options exist for each platform. For Flutter apps, there's two popular approaches: 1. Launch a browser using [url_launcher][] and listen for a redirect using - [uni_links][]. + [app_links][]. ```dart if (await canLaunch(authorizationUrl.toString())) { @@ -136,7 +136,8 @@ For Flutter apps, there's two popular approaches: // ------- 8< ------- - final linksStream = getLinksStream().listen((Uri uri) async { + final appLinks = AppLinks(); + final linksStream = appLinks.uriLinkStream.listen((Uri uri) async { if (uri.toString().startsWith(redirectUrl)) { responseUrl = uri; } @@ -161,6 +162,46 @@ For Flutter apps, there's two popular approaches: ); ``` + +1. To handle redirect on Flutter Web you would need to add an html file to the web folder with some +additional JS code to handle the redirect back to the app (in this example the code will be saved +and passed through localStorage). + + ```html + + + + + OAuth Callback + + + + + + ``` + + After redirect to the application the code can be extracted and processed using the dart.html + package + + ```dart + import 'dart:html' as html; + ... + if(html.window.localStorage.containsKey('oauth_code') + code = html.window.localStorage.remove('oauth_code') + ... + ``` + For Dart apps, the best approach depends on the available options for accessing a browser. In general, you'll need to launch the authorization URL through the client's browser and listen for the redirect URL. @@ -255,6 +296,6 @@ File('~/.myapp/credentials.json').writeAsString(client.credentials.toJson()); [resourceOwnerPasswordGrantDocs]: https://oauth.net/2/grant-types/password/ [resourceOwnerPasswordGrantMethod]: https://pub.dev/documentation/oauth2/latest/oauth2/resourceOwnerPasswordGrant.html [resourceOwnerPasswordGrantSection]: #resource-owner-password-grant -[uni_links]: https://pub.dev/packages/uni_links +[app_links]: https://pub.dev/packages/app_links [url_launcher]: https://pub.dev/packages/url_launcher [webview_flutter]: https://pub.dev/packages/webview_flutter diff --git a/pkgs/oauth2/pubspec.yaml b/pkgs/oauth2/pubspec.yaml index ab705ba40..ebaa7107c 100644 --- a/pkgs/oauth2/pubspec.yaml +++ b/pkgs/oauth2/pubspec.yaml @@ -1,5 +1,5 @@ name: oauth2 -version: 2.0.4-wip +version: 2.0.4 description: >- A client library for authenticating with a remote service via OAuth2 on behalf of a user, and making authorized HTTP requests with the user's diff --git a/pkgs/process/pubspec.yaml b/pkgs/process/pubspec.yaml index 32a977708..064e9411b 100644 --- a/pkgs/process/pubspec.yaml +++ b/pkgs/process/pubspec.yaml @@ -16,5 +16,5 @@ dependencies: platform: '^3.0.0' dev_dependencies: - lints: ^5.0.0 + lints: ^6.0.0 test: ^1.16.8 diff --git a/pkgs/pubspec_parse/CHANGELOG.md b/pkgs/pubspec_parse/CHANGELOG.md index cc5c06b16..26bf1035c 100644 --- a/pkgs/pubspec_parse/CHANGELOG.md +++ b/pkgs/pubspec_parse/CHANGELOG.md @@ -1,6 +1,7 @@ ## 1.6.0-wip - Added `toJson` method to `Pubspec` to serialize the object back to a `Map`. +- Require Dart 3.8 ## 1.5.0 diff --git a/pkgs/pubspec_parse/lib/src/dependency.dart b/pkgs/pubspec_parse/lib/src/dependency.dart index 8e0d0c0ed..4658ac38e 100644 --- a/pkgs/pubspec_parse/lib/src/dependency.dart +++ b/pkgs/pubspec_parse/lib/src/dependency.dart @@ -46,14 +46,17 @@ Dependency? _fromJson(Object? data, String name) { } if (data is Map) { - final matchedKeys = - data.keys.cast().where((key) => key != 'version').toList(); + final matchedKeys = data.keys + .cast() + .where((key) => key != 'version') + .toList(); if (data.isEmpty || (matchedKeys.isEmpty && data.containsKey('version'))) { return _$HostedDependencyFromJson(data); } else { - final firstUnrecognizedKey = - matchedKeys.firstWhereOrNull((k) => !_sourceKeys.contains(k)); + final firstUnrecognizedKey = matchedKeys.firstWhereOrNull( + (k) => !_sourceKeys.contains(k), + ); return $checkedNew('Dependency', data, () { if (firstUnrecognizedKey != null) { @@ -78,8 +81,9 @@ Dependency? _fromJson(Object? data, String name) { 'git' => GitDependency.fromData(data[key]), 'path' => PathDependency.fromData(data[key]), 'sdk' => _$SdkDependencyFromJson(data), - 'hosted' => _$HostedDependencyFromJson(data) - ..hosted?._nameOfPackage = name, + 'hosted' => _$HostedDependencyFromJson( + data, + )..hosted?._nameOfPackage = name, _ => throw StateError('There is a bug in pubspec_parse.'), }; }); @@ -101,7 +105,7 @@ class SdkDependency extends Dependency { final VersionConstraint version; SdkDependency(this.sdk, {VersionConstraint? version}) - : version = version ?? VersionConstraint.any; + : version = version ?? VersionConstraint.any; @override bool operator ==(Object other) => @@ -114,10 +118,7 @@ class SdkDependency extends Dependency { String toString() => 'SdkDependency: $sdk'; @override - Map toJson() => { - 'sdk': sdk, - 'version': version.toString(), - }; + Map toJson() => {'sdk': sdk, 'version': version.toString()}; } @JsonSerializable() @@ -156,12 +157,12 @@ class GitDependency extends Dependency { @override Map toJson() => { - 'git': { - 'url': url.toString(), - if (ref != null) 'ref': ref, - if (path != null) 'path': path, - }, - }; + 'git': { + 'url': url.toString(), + if (ref != null) 'ref': ref, + if (path != null) 'path': path, + }, + }; } Uri? parseGitUriOrNull(String? value) => @@ -235,7 +236,7 @@ class HostedDependency extends Dependency { final HostedDetails? hosted; HostedDependency({VersionConstraint? version, this.hosted}) - : version = version ?? VersionConstraint.any; + : version = version ?? VersionConstraint.any; @override bool operator ==(Object other) => @@ -251,9 +252,9 @@ class HostedDependency extends Dependency { @override Map toJson() => { - 'version': version.toString(), - if (hosted != null) 'hosted': hosted!.toJson(), - }; + 'version': version.toString(), + if (hosted != null) 'hosted': hosted!.toJson(), + }; } @JsonSerializable(disallowUnrecognizedKeys: true) @@ -299,9 +300,9 @@ class HostedDetails { int get hashCode => Object.hash(name, url); Map toJson() => { - if (declaredName != null) 'name': declaredName, - 'url': url.toString(), - }; + if (declaredName != null) 'name': declaredName, + 'url': url.toString(), + }; } VersionConstraint _constraintFromString(String? input) => diff --git a/pkgs/pubspec_parse/lib/src/dependency.g.dart b/pkgs/pubspec_parse/lib/src/dependency.g.dart index 1a504f1fd..8300ebee9 100644 --- a/pkgs/pubspec_parse/lib/src/dependency.g.dart +++ b/pkgs/pubspec_parse/lib/src/dependency.g.dart @@ -8,65 +8,58 @@ part of 'dependency.dart'; // JsonSerializableGenerator // ************************************************************************** -SdkDependency _$SdkDependencyFromJson(Map json) => $checkedCreate( - 'SdkDependency', - json, - ($checkedConvert) { - final val = SdkDependency( - $checkedConvert('sdk', (v) => v as String), - version: $checkedConvert( - 'version', (v) => _constraintFromString(v as String?)), - ); - return val; - }, - ); +SdkDependency _$SdkDependencyFromJson(Map json) => + $checkedCreate('SdkDependency', json, ($checkedConvert) { + final val = SdkDependency( + $checkedConvert('sdk', (v) => v as String), + version: $checkedConvert( + 'version', + (v) => _constraintFromString(v as String?), + ), + ); + return val; + }); -GitDependency _$GitDependencyFromJson(Map json) => $checkedCreate( - 'GitDependency', - json, - ($checkedConvert) { - final val = GitDependency( - $checkedConvert('url', (v) => parseGitUri(v as String)), - ref: $checkedConvert('ref', (v) => v as String?), - path: $checkedConvert('path', (v) => v as String?), - ); - return val; - }, - ); +GitDependency _$GitDependencyFromJson(Map json) => + $checkedCreate('GitDependency', json, ($checkedConvert) { + final val = GitDependency( + $checkedConvert('url', (v) => parseGitUri(v as String)), + ref: $checkedConvert('ref', (v) => v as String?), + path: $checkedConvert('path', (v) => v as String?), + ); + return val; + }); -HostedDependency _$HostedDependencyFromJson(Map json) => $checkedCreate( - 'HostedDependency', - json, - ($checkedConvert) { - $checkKeys( - json, - allowedKeys: const ['version', 'hosted'], - disallowNullValues: const ['hosted'], - ); - final val = HostedDependency( - version: $checkedConvert( - 'version', (v) => _constraintFromString(v as String?)), - hosted: $checkedConvert('hosted', - (v) => v == null ? null : HostedDetails.fromJson(v as Object)), - ); - return val; - }, - ); +HostedDependency _$HostedDependencyFromJson(Map json) => + $checkedCreate('HostedDependency', json, ($checkedConvert) { + $checkKeys( + json, + allowedKeys: const ['version', 'hosted'], + disallowNullValues: const ['hosted'], + ); + final val = HostedDependency( + version: $checkedConvert( + 'version', + (v) => _constraintFromString(v as String?), + ), + hosted: $checkedConvert( + 'hosted', + (v) => v == null ? null : HostedDetails.fromJson(v as Object), + ), + ); + return val; + }); -HostedDetails _$HostedDetailsFromJson(Map json) => $checkedCreate( - 'HostedDetails', - json, - ($checkedConvert) { - $checkKeys( - json, - allowedKeys: const ['name', 'url'], - disallowNullValues: const ['url'], - ); - final val = HostedDetails( - $checkedConvert('name', (v) => v as String?), - $checkedConvert('url', (v) => parseGitUriOrNull(v as String?)), - ); - return val; - }, - fieldKeyMap: const {'declaredName': 'name'}, - ); +HostedDetails _$HostedDetailsFromJson(Map json) => + $checkedCreate('HostedDetails', json, ($checkedConvert) { + $checkKeys( + json, + allowedKeys: const ['name', 'url'], + disallowNullValues: const ['url'], + ); + final val = HostedDetails( + $checkedConvert('name', (v) => v as String?), + $checkedConvert('url', (v) => parseGitUriOrNull(v as String?)), + ); + return val; + }, fieldKeyMap: const {'declaredName': 'name'}); diff --git a/pkgs/pubspec_parse/lib/src/pubspec.dart b/pkgs/pubspec_parse/lib/src/pubspec.dart index 01a1a0d39..b8c2aea71 100644 --- a/pkgs/pubspec_parse/lib/src/pubspec.dart +++ b/pkgs/pubspec_parse/lib/src/pubspec.dart @@ -57,9 +57,7 @@ class Pubspec { /// If there is exactly 1 value in [authors], returns it. /// /// If there are 0 or more than 1, returns `null`. - @Deprecated( - 'See https://dart.dev/tools/pub/pubspec#authorauthors', - ) + @Deprecated('See https://dart.dev/tools/pub/pubspec#authorauthors') String? get author { if (authors.length == 1) { return authors.single; @@ -67,9 +65,7 @@ class Pubspec { return null; } - @Deprecated( - 'See https://dart.dev/tools/pub/pubspec#authorauthors', - ) + @Deprecated('See https://dart.dev/tools/pub/pubspec#authorauthors') final List authors; final String? documentation; @@ -109,13 +105,9 @@ class Pubspec { this.name, { this.version, this.publishTo, - @Deprecated( - 'See https://dart.dev/tools/pub/pubspec#authorauthors', - ) + @Deprecated('See https://dart.dev/tools/pub/pubspec#authorauthors') String? author, - @Deprecated( - 'See https://dart.dev/tools/pub/pubspec#authorauthors', - ) + @Deprecated('See https://dart.dev/tools/pub/pubspec#authorauthors') List? authors, Map? environment, this.homepage, @@ -134,14 +126,16 @@ class Pubspec { Map? dependencyOverrides, this.flutter, Map? executables, - }) : - // ignore: deprecated_member_use_from_same_package - authors = _normalizeAuthors(author, authors), - environment = environment ?? const {}, - dependencies = dependencies ?? const {}, - devDependencies = devDependencies ?? const {}, - executables = executables ?? const {}, - dependencyOverrides = dependencyOverrides ?? const {} { + }) : authors // ignore: deprecated_member_use_from_same_package + = _normalizeAuthors( + author, + authors, + ), + environment = environment ?? const {}, + dependencies = dependencies ?? const {}, + devDependencies = devDependencies ?? const {}, + executables = executables ?? const {}, + dependencyOverrides = dependencyOverrides ?? const {} { if (name.isEmpty) { throw ArgumentError.value(name, 'name', '"name" cannot be empty.'); } @@ -192,10 +186,7 @@ class Pubspec { ); static List _normalizeAuthors(String? author, List? authors) { - final value = { - if (author != null) author, - ...?authors, - }; + final value = {if (author != null) author, ...?authors}; return value.toList(); } } @@ -262,10 +253,7 @@ Map _executablesMap(Map? source) => Map _serializeEnvironment( Map map, -) => - map.map( - (key, value) => MapEntry(key, value?.toString()), - ); +) => map.map((key, value) => MapEntry(key, value?.toString())); String? _versionToString(Version? version) => version?.toString(); diff --git a/pkgs/pubspec_parse/lib/src/pubspec.g.dart b/pkgs/pubspec_parse/lib/src/pubspec.g.dart index 96b1af203..326f5f60a 100644 --- a/pkgs/pubspec_parse/lib/src/pubspec.g.dart +++ b/pkgs/pubspec_parse/lib/src/pubspec.g.dart @@ -9,86 +9,111 @@ part of 'pubspec.dart'; // ************************************************************************** Pubspec _$PubspecFromJson(Map json) => $checkedCreate( - 'Pubspec', - json, - ($checkedConvert) { - final val = Pubspec( - $checkedConvert('name', (v) => v as String), - version: $checkedConvert( - 'version', (v) => _versionFromString(v as String?)), - publishTo: $checkedConvert('publish_to', (v) => v as String?), - author: $checkedConvert('author', (v) => v as String?), - authors: $checkedConvert('authors', - (v) => (v as List?)?.map((e) => e as String).toList()), - environment: - $checkedConvert('environment', (v) => _environmentMap(v as Map?)), - homepage: $checkedConvert('homepage', (v) => v as String?), - repository: $checkedConvert( - 'repository', (v) => v == null ? null : Uri.parse(v as String)), - issueTracker: $checkedConvert('issue_tracker', - (v) => v == null ? null : Uri.parse(v as String)), - funding: $checkedConvert( - 'funding', - (v) => (v as List?) - ?.map((e) => Uri.parse(e as String)) - .toList()), - topics: $checkedConvert('topics', - (v) => (v as List?)?.map((e) => e as String).toList()), - ignoredAdvisories: $checkedConvert('ignored_advisories', - (v) => (v as List?)?.map((e) => e as String).toList()), - screenshots: $checkedConvert( - 'screenshots', (v) => parseScreenshots(v as List?)), - documentation: $checkedConvert('documentation', (v) => v as String?), - description: $checkedConvert('description', (v) => v as String?), - workspace: $checkedConvert('workspace', - (v) => (v as List?)?.map((e) => e as String).toList()), - resolution: $checkedConvert('resolution', (v) => v as String?), - dependencies: - $checkedConvert('dependencies', (v) => parseDeps(v as Map?)), - devDependencies: - $checkedConvert('dev_dependencies', (v) => parseDeps(v as Map?)), - dependencyOverrides: $checkedConvert( - 'dependency_overrides', (v) => parseDeps(v as Map?)), - flutter: $checkedConvert( - 'flutter', - (v) => (v as Map?)?.map( - (k, e) => MapEntry(k as String, e), - )), - executables: - $checkedConvert('executables', (v) => _executablesMap(v as Map?)), - ); - return val; - }, - fieldKeyMap: const { - 'publishTo': 'publish_to', - 'issueTracker': 'issue_tracker', - 'ignoredAdvisories': 'ignored_advisories', - 'devDependencies': 'dev_dependencies', - 'dependencyOverrides': 'dependency_overrides' - }, + 'Pubspec', + json, + ($checkedConvert) { + final val = Pubspec( + $checkedConvert('name', (v) => v as String), + version: $checkedConvert( + 'version', + (v) => _versionFromString(v as String?), + ), + publishTo: $checkedConvert('publish_to', (v) => v as String?), + author: $checkedConvert('author', (v) => v as String?), + authors: $checkedConvert( + 'authors', + (v) => (v as List?)?.map((e) => e as String).toList(), + ), + environment: $checkedConvert( + 'environment', + (v) => _environmentMap(v as Map?), + ), + homepage: $checkedConvert('homepage', (v) => v as String?), + repository: $checkedConvert( + 'repository', + (v) => v == null ? null : Uri.parse(v as String), + ), + issueTracker: $checkedConvert( + 'issue_tracker', + (v) => v == null ? null : Uri.parse(v as String), + ), + funding: $checkedConvert( + 'funding', + (v) => + (v as List?)?.map((e) => Uri.parse(e as String)).toList(), + ), + topics: $checkedConvert( + 'topics', + (v) => (v as List?)?.map((e) => e as String).toList(), + ), + ignoredAdvisories: $checkedConvert( + 'ignored_advisories', + (v) => (v as List?)?.map((e) => e as String).toList(), + ), + screenshots: $checkedConvert( + 'screenshots', + (v) => parseScreenshots(v as List?), + ), + documentation: $checkedConvert('documentation', (v) => v as String?), + description: $checkedConvert('description', (v) => v as String?), + workspace: $checkedConvert( + 'workspace', + (v) => (v as List?)?.map((e) => e as String).toList(), + ), + resolution: $checkedConvert('resolution', (v) => v as String?), + dependencies: $checkedConvert( + 'dependencies', + (v) => parseDeps(v as Map?), + ), + devDependencies: $checkedConvert( + 'dev_dependencies', + (v) => parseDeps(v as Map?), + ), + dependencyOverrides: $checkedConvert( + 'dependency_overrides', + (v) => parseDeps(v as Map?), + ), + flutter: $checkedConvert( + 'flutter', + (v) => (v as Map?)?.map((k, e) => MapEntry(k as String, e)), + ), + executables: $checkedConvert( + 'executables', + (v) => _executablesMap(v as Map?), + ), ); + return val; + }, + fieldKeyMap: const { + 'publishTo': 'publish_to', + 'issueTracker': 'issue_tracker', + 'ignoredAdvisories': 'ignored_advisories', + 'devDependencies': 'dev_dependencies', + 'dependencyOverrides': 'dependency_overrides', + }, +); Map _$PubspecToJson(Pubspec instance) => { - 'name': instance.name, - 'version': _versionToString(instance.version), - 'description': instance.description, - 'homepage': instance.homepage, - 'publish_to': instance.publishTo, - 'repository': instance.repository?.toString(), - 'issue_tracker': instance.issueTracker?.toString(), - 'funding': instance.funding?.map((e) => e.toString()).toList(), - 'topics': instance.topics, - 'ignored_advisories': instance.ignoredAdvisories, - 'screenshots': serializeScreenshots(instance.screenshots), - 'author': instance.author, - 'authors': instance.authors, - 'documentation': instance.documentation, - 'environment': _serializeEnvironment(instance.environment), - 'dependencies': serializeDeps(instance.dependencies), - 'dev_dependencies': serializeDeps(instance.devDependencies), - 'dependency_overrides': serializeDeps(instance.dependencyOverrides), - 'flutter': instance.flutter, - 'executables': _serializeExecutables(instance.executables), - 'workspace': instance.workspace, - 'resolution': instance.resolution, - }; + 'name': instance.name, + 'version': _versionToString(instance.version), + 'description': instance.description, + 'homepage': instance.homepage, + 'publish_to': instance.publishTo, + 'repository': instance.repository?.toString(), + 'issue_tracker': instance.issueTracker?.toString(), + 'funding': instance.funding?.map((e) => e.toString()).toList(), + 'topics': instance.topics, + 'ignored_advisories': instance.ignoredAdvisories, + 'screenshots': serializeScreenshots(instance.screenshots), + 'author': instance.author, + 'authors': instance.authors, + 'documentation': instance.documentation, + 'environment': _serializeEnvironment(instance.environment), + 'dependencies': serializeDeps(instance.dependencies), + 'dev_dependencies': serializeDeps(instance.devDependencies), + 'dependency_overrides': serializeDeps(instance.dependencyOverrides), + 'flutter': instance.flutter, + 'executables': _serializeExecutables(instance.executables), + 'workspace': instance.workspace, + 'resolution': instance.resolution, +}; diff --git a/pkgs/pubspec_parse/pubspec.yaml b/pkgs/pubspec_parse/pubspec.yaml index 4a5f4568f..699a01398 100644 --- a/pkgs/pubspec_parse/pubspec.yaml +++ b/pkgs/pubspec_parse/pubspec.yaml @@ -10,24 +10,24 @@ topics: - dart-pub environment: - sdk: ^3.6.0 + sdk: ^3.8.0 dependencies: checked_yaml: ^2.0.1 collection: ^1.19.0 json_annotation: ^4.9.0 pub_semver: ^2.1.4 - yaml: ^3.0.0 + yaml: ^3.1.2 dev_dependencies: - build_runner: ^2.4.6 + build_runner: ^2.6.0 build_verify: ^3.0.0 dart_flutter_team_lints: ^3.0.0 - json_serializable: ^6.9.1 + json_serializable: ^6.11.1 path: ^1.9.0 # Needed because we are configuring `combining_builder` - source_gen: ^2.0.0 + source_gen: ^4.0.0 stack_trace: ^1.10.0 - test: ^1.24.4 + test: ^1.25.9 test_descriptor: ^2.0.0 test_process: ^2.0.0 diff --git a/pkgs/pubspec_parse/test/dependency_test.dart b/pkgs/pubspec_parse/test/dependency_test.dart index f1e4f5776..8d4353fe8 100644 --- a/pkgs/pubspec_parse/test/dependency_test.dart +++ b/pkgs/pubspec_parse/test/dependency_test.dart @@ -18,29 +18,23 @@ void main() { group('errors', () { test('List', () { - _expectThrows( - [], - r''' + _expectThrows([], r''' line 4, column 10: Unsupported value for "dep". Not a valid dependency value. ╷ 4 │ "dep": [] │ ^^ - ╵''', - ); + ╵'''); }); test('int', () { - _expectThrows( - 42, - r''' + _expectThrows(42, r''' line 4, column 10: Unsupported value for "dep". Not a valid dependency value. ╷ 4 │ "dep": 42 │ ┌──────────^ 5 │ │ } │ └─^ - ╵''', - ); + ╵'''); }); test('map with too many keys', () { @@ -91,15 +85,12 @@ void _hostedDependency() { }); test('bad string version', () { - _expectThrows( - 'not a version', - r''' + _expectThrows('not a version', r''' line 4, column 10: Unsupported value for "dep". Could not parse version "not a version". Unknown text at "not a version". ╷ 4 │ "dep": "not a version" │ ^^^^^^^^^^^^^^^ - ╵''', - ); + ╵'''); }); test('map w/ just version', () async { @@ -216,26 +207,25 @@ void _sdkDependency() { }); test('with version', () async { - final dep = await _dependency( - {'sdk': 'flutter', 'version': '>=1.2.3 <2.0.0'}, - ); + final dep = await _dependency({ + 'sdk': 'flutter', + 'version': '>=1.2.3 <2.0.0', + }); expect(dep.sdk, 'flutter'); expect(dep.version.toString(), '>=1.2.3 <2.0.0'); expect(dep.toString(), 'SdkDependency: flutter'); }); test('null content', () { - _expectThrowsContaining( - {'sdk': null}, - r"type 'Null' is not a subtype of type 'String'", - ); + _expectThrowsContaining({ + 'sdk': null, + }, r"type 'Null' is not a subtype of type 'String'"); }); test('number content', () { - _expectThrowsContaining( - {'sdk': 42}, - r"type 'int' is not a subtype of type 'String'", - ); + _expectThrowsContaining({ + 'sdk': 42, + }, r"type 'int' is not a subtype of type 'String'"); }); } @@ -250,8 +240,10 @@ void _gitDependency() { test('string with version key is ignored', () async { // Regression test for https://github.com/dart-lang/pubspec_parse/issues/13 - final dep = - await _dependency({'git': 'url', 'version': '^1.2.3'}); + final dep = await _dependency({ + 'git': 'url', + 'version': '^1.2.3', + }); expect(dep.url.toString(), 'url'); expect(dep.path, isNull); expect(dep.ref, isNull); @@ -263,10 +255,9 @@ void _gitDependency() { if (skipTryParse) { print('FYI: not validating git@ URI on travis due to failure'); } - final dep = await _dependency( - {'git': 'git@localhost:dep.git'}, - skipTryPub: skipTryParse, - ); + final dep = await _dependency({ + 'git': 'git@localhost:dep.git', + }, skipTryPub: skipTryParse); expect(dep.url.toString(), 'ssh://git@localhost/dep.git'); expect(dep.path, isNull); expect(dep.ref, isNull); @@ -324,28 +315,21 @@ line 5, column 11: Unsupported value for "git". Must be a String or a Map. }); test('git - empty map', () { - _expectThrowsContaining( - {'git': {}}, - r"type 'Null' is not a subtype of type 'String'", - ); + _expectThrowsContaining({ + 'git': {}, + }, r"type 'Null' is not a subtype of type 'String'"); }); test('git - null url', () { - _expectThrowsContaining( - { - 'git': {'url': null}, - }, - r"type 'Null' is not a subtype of type 'String'", - ); + _expectThrowsContaining({ + 'git': {'url': null}, + }, r"type 'Null' is not a subtype of type 'String'"); }); test('git - int url', () { - _expectThrowsContaining( - { - 'git': {'url': 42}, - }, - r"type 'int' is not a subtype of type 'String'", - ); + _expectThrowsContaining({ + 'git': {'url': 42}, + }, r"type 'int' is not a subtype of type 'String'"); }); } @@ -357,9 +341,10 @@ void _pathDependency() { }); test('valid with version key is ignored', () async { - final dep = await _dependency( - {'path': '../path', 'version': '^1.2.3'}, - ); + final dep = await _dependency({ + 'path': '../path', + 'version': '^1.2.3', + }); expect(dep.path, '../path'); expect(dep.toString(), 'PathDependency: path@../path'); }); @@ -406,36 +391,27 @@ line 5, column 12: Unsupported value for "path". Must be a String. } void _expectThrows(Object content, String expectedError) { - expectParseThrows( - { - 'name': 'sample', - 'dependencies': {'dep': content}, - }, - expectedError, - ); + expectParseThrows({ + 'name': 'sample', + 'dependencies': {'dep': content}, + }, expectedError); } void _expectThrowsContaining(Object content, String errorText) { - expectParseThrowsContaining( - { - 'name': 'sample', - 'dependencies': {'dep': content}, - }, - errorText, - ); + expectParseThrowsContaining({ + 'name': 'sample', + 'dependencies': {'dep': content}, + }, errorText); } Future _dependency( Object? content, { bool skipTryPub = false, }) async { - final value = await parse( - { - ...defaultPubspec, - 'dependencies': {'dep': content}, - }, - skipTryPub: skipTryPub, - ); + final value = await parse({ + ...defaultPubspec, + 'dependencies': {'dep': content}, + }, skipTryPub: skipTryPub); expect(value.name, 'sample'); expect(value.dependencies, hasLength(1)); diff --git a/pkgs/pubspec_parse/test/parse_test.dart b/pkgs/pubspec_parse/test/parse_test.dart index e0698af16..f700b4efa 100644 --- a/pkgs/pubspec_parse/test/parse_test.dart +++ b/pkgs/pubspec_parse/test/parse_test.dart @@ -20,10 +20,9 @@ void main() { expect(value.homepage, isNull); expect(value.author, isNull); expect(value.authors, isEmpty); - expect( - value.environment, - {'sdk': VersionConstraint.parse('>=2.12.0 <3.0.0')}, - ); + expect(value.environment, { + 'sdk': VersionConstraint.parse('>=2.12.0 <3.0.0'), + }); expect(value.documentation, isNull); expect(value.dependencies, isEmpty); expect(value.devDependencies, isEmpty); @@ -40,38 +39,30 @@ void main() { test('all fields set', () async { final version = Version.parse('1.2.3'); final sdkConstraint = VersionConstraint.parse('>=3.6.0 <4.0.0'); - final value = await parse( - { - 'name': 'sample', - 'version': version.toString(), - 'publish_to': 'none', - 'author': 'name@example.com', - 'environment': {'sdk': sdkConstraint.toString()}, - 'description': 'description', - 'homepage': 'homepage', - 'documentation': 'documentation', - 'repository': 'https://github.com/example/repo', - 'issue_tracker': 'https://github.com/example/repo/issues', - 'funding': [ - 'https://patreon.com/example', - ], - 'topics': ['widget', 'button'], - 'ignored_advisories': ['111', '222'], - 'screenshots': [ - {'description': 'my screenshot', 'path': 'path/to/screenshot'}, - ], - 'workspace': [ - 'pkg1', - 'pkg2', - ], - 'resolution': 'workspace', - 'executables': { - 'my_script': 'bin/my_script.dart', - 'my_script2': 'bin/my_script2.dart', - }, + final value = await parse({ + 'name': 'sample', + 'version': version.toString(), + 'publish_to': 'none', + 'author': 'name@example.com', + 'environment': {'sdk': sdkConstraint.toString()}, + 'description': 'description', + 'homepage': 'homepage', + 'documentation': 'documentation', + 'repository': 'https://github.com/example/repo', + 'issue_tracker': 'https://github.com/example/repo/issues', + 'funding': ['https://patreon.com/example'], + 'topics': ['widget', 'button'], + 'ignored_advisories': ['111', '222'], + 'screenshots': [ + {'description': 'my screenshot', 'path': 'path/to/screenshot'}, + ], + 'workspace': ['pkg1', 'pkg2'], + 'resolution': 'workspace', + 'executables': { + 'my_script': 'bin/my_script.dart', + 'my_script2': 'bin/my_script2.dart', }, - skipTryPub: true, - ); + }, skipTryPub: true); expect(value.name, 'sample'); expect(value.version, version); expect(value.publishTo, 'none'); @@ -113,16 +104,10 @@ void main() { }); test('environment values can be null', () async { - final value = await parse( - { - 'name': 'sample', - 'environment': { - 'sdk': '>=2.12.0 <3.0.0', - 'bob': null, - }, - }, - skipTryPub: true, - ); + final value = await parse({ + 'name': 'sample', + 'environment': {'sdk': '>=2.12.0 <3.0.0', 'bob': null}, + }, skipTryPub: true); expect(value.name, 'sample'); expect(value.environment, hasLength(2)); expect(value.environment, containsPair('bob', isNull)); @@ -262,9 +247,7 @@ line 3, column 16: Unsupported value for "publish_to". Must be an http or https expectParseThrowsContaining( { ...defaultPubspec, - 'executables': { - 'script': 32, - }, + 'executables': {'script': 32}, }, 'Unsupported value for "script". `32` is not a String.', skipTryPub: true, @@ -272,13 +255,10 @@ line 3, column 16: Unsupported value for "publish_to". Must be an http or https }); test('invalid executable - lenient', () async { - final value = await parse( - { - ...defaultPubspec, - 'executables': 'Invalid value', - }, - lenient: true, - ); + final value = await parse({ + ...defaultPubspec, + 'executables': 'Invalid value', + }, lenient: true); expect(value.name, 'sample'); expect(value.executables, isEmpty); }); @@ -286,37 +266,28 @@ line 3, column 16: Unsupported value for "publish_to". Must be an http or https group('invalid', () { test('null', () { - expectParseThrows( - null, - r''' + expectParseThrows(null, r''' line 1, column 1: Not a map ╷ 1 │ null │ ^^^^ - ╵''', - ); + ╵'''); }); test('empty string', () { - expectParseThrows( - '', - r''' + expectParseThrows('', r''' line 1, column 1: Not a map ╷ 1 │ "" │ ^^ - ╵''', - ); + ╵'''); }); test('array', () { - expectParseThrows( - [], - r''' + expectParseThrows([], r''' line 1, column 1: Not a map ╷ 1 │ [] │ ^^ - ╵''', - ); + ╵'''); }); test('missing name', () { @@ -430,10 +401,7 @@ line 4, column 10: Unsupported value for "sdk". Could not parse version "silly". group('funding', () { test('not a list', () { expectParseThrowsContaining( - { - ...defaultPubspec, - 'funding': 1, - }, + {...defaultPubspec, 'funding': 1}, "Unsupported value for \"funding\". type 'int' is not a subtype of type 'List?'", skipTryPub: true, ); @@ -471,10 +439,7 @@ line 6, column 13: Unsupported value for "funding". Illegal scheme character at group('topics', () { test('not a list', () { expectParseThrowsContaining( - { - ...defaultPubspec, - 'topics': 1, - }, + {...defaultPubspec, 'topics': 1}, "Unsupported value for \"topics\". type 'int' is not a subtype of type 'List?'", skipTryPub: true, ); @@ -508,10 +473,7 @@ line 6, column 13: Unsupported value for "funding". Illegal scheme character at group('ignored_advisories', () { test('not a list', () { expectParseThrowsContaining( - { - ...defaultPubspec, - 'ignored_advisories': 1, - }, + {...defaultPubspec, 'ignored_advisories': 1}, "Unsupported value for \"ignored_advisories\". type 'int' is not a subtype of type 'List?'", skipTryPub: true, ); @@ -594,10 +556,7 @@ line 6, column 13: Unsupported value for "funding". Illegal scheme character at test('invalid entries', () async { final value = await parse({ ...defaultPubspec, - 'screenshots': [ - 42, - 'not a screenshot', - ], + 'screenshots': [42, 'not a screenshot'], }); expect(value.screenshots, isEmpty); }); @@ -665,10 +624,7 @@ line 8, column 19: Unsupported value for "description". `42` is not a String { ...defaultPubspec, 'screenshots': [ - { - 'description': '', - 'path': 42, - }, + {'description': '', 'path': 42}, ], }, r''' @@ -684,13 +640,10 @@ line 9, column 12: Unsupported value for "path". `42` is not a String }); test('invalid screenshot - lenient', () async { - final value = await parse( - { - ...defaultPubspec, - 'screenshots': 'Invalid value', - }, - lenient: true, - ); + final value = await parse({ + ...defaultPubspec, + 'screenshots': 'Invalid value', + }, lenient: true); expect(value.name, 'sample'); expect(value.screenshots, isEmpty); }); @@ -698,29 +651,21 @@ line 9, column 12: Unsupported value for "path". `42` is not a String group('lenient', () { test('null', () { - expectParseThrows( - null, - r''' + expectParseThrows(null, r''' line 1, column 1: Not a map ╷ 1 │ null │ ^^^^ - ╵''', - lenient: true, - ); + ╵''', lenient: true); }); test('empty string', () { - expectParseThrows( - '', - r''' + expectParseThrows('', r''' line 1, column 1: Not a map ╷ 1 │ "" │ ^^ - ╵''', - lenient: true, - ); + ╵''', lenient: true); }); test('name cannot be empty', () { @@ -732,38 +677,29 @@ line 1, column 1: Not a map }); test('bad repository url', () async { - final value = await parse( - { - ...defaultPubspec, - 'repository': {'x': 'y'}, - }, - lenient: true, - ); + final value = await parse({ + ...defaultPubspec, + 'repository': {'x': 'y'}, + }, lenient: true); expect(value.name, 'sample'); expect(value.repository, isNull); }); test('bad issue_tracker url', () async { - final value = await parse( - { - ...defaultPubspec, - 'issue_tracker': {'x': 'y'}, - }, - lenient: true, - ); + final value = await parse({ + ...defaultPubspec, + 'issue_tracker': {'x': 'y'}, + }, lenient: true); expect(value.name, 'sample'); expect(value.issueTracker, isNull); }); test('multiple bad values', () async { - final value = await parse( - { - ...defaultPubspec, - 'repository': {'x': 'y'}, - 'issue_tracker': {'x': 'y'}, - }, - lenient: true, - ); + final value = await parse({ + ...defaultPubspec, + 'repository': {'x': 'y'}, + 'issue_tracker': {'x': 'y'}, + }, lenient: true); expect(value.name, 'sample'); expect(value.repository, isNull); expect(value.issueTracker, isNull); @@ -792,10 +728,7 @@ line 1, column 1: Not a map group('workspaces', () { test('workspace key must be a list', () { expectParseThrowsContaining( - { - ...defaultPubspec, - 'workspace': 42, - }, + {...defaultPubspec, 'workspace': 42}, 'Unsupported value for "workspace". type \'int\' is not a subtype of type \'List?\' in type cast', skipTryPub: true, ); diff --git a/pkgs/pubspec_parse/test/pub_utils.dart b/pkgs/pubspec_parse/test/pub_utils.dart index a60aa2a99..3660f5ec5 100644 --- a/pkgs/pubspec_parse/test/pub_utils.dart +++ b/pkgs/pubspec_parse/test/pub_utils.dart @@ -32,8 +32,9 @@ Future tryPub(String content) async { ); if (result.exitCode == 0) { - final lockContent = - File(p.join(d.sandbox, 'pubspec.lock')).readAsStringSync(); + final lockContent = File( + p.join(d.sandbox, 'pubspec.lock'), + ).readAsStringSync(); printOnFailure( [ diff --git a/pkgs/pubspec_parse/test/test_utils.dart b/pkgs/pubspec_parse/test/test_utils.dart index cc46522b7..e04c41088 100644 --- a/pkgs/pubspec_parse/test/test_utils.dart +++ b/pkgs/pubspec_parse/test/test_utils.dart @@ -21,17 +21,17 @@ String _encodeJson(Object? input) => const JsonEncoder.withIndent(' ').convert(input); Matcher _throwsParsedYamlException(String prettyValue) => throwsA( - const TypeMatcher().having( - (e) { - final message = e.formattedMessage; - printOnFailure("Actual error format:\nr'''\n$message'''"); - _printDebugParsedYamlException(e); - return message; - }, - 'formattedMessage', - prettyValue, - ), - ); + const TypeMatcher().having( + (e) { + final message = e.formattedMessage; + printOnFailure("Actual error format:\nr'''\n$message'''"); + _printDebugParsedYamlException(e); + return message; + }, + 'formattedMessage', + prettyValue, + ), +); void _printDebugParsedYamlException(ParsedYamlException e) { var innerError = e.innerError; @@ -52,8 +52,9 @@ void _printDebugParsedYamlException(ParsedYamlException e) { items.add(Trace.format(innerStack)); } - final content = - LineSplitter.split(items.join('\n')).map((e) => ' $e').join('\n'); + final content = LineSplitter.split( + items.join('\n'), + ).map((e) => ' $e').join('\n'); printOnFailure('Inner error details:\n$content'); } @@ -112,16 +113,15 @@ void expectParseThrows( String expectedError, { bool skipTryPub = false, bool lenient = false, -}) => - expect( - () => parse( - content, - lenient: lenient, - quietOnError: true, - skipTryPub: skipTryPub, - ), - _throwsParsedYamlException(expectedError), - ); +}) => expect( + () => parse( + content, + lenient: lenient, + quietOnError: true, + skipTryPub: skipTryPub, + ), + _throwsParsedYamlException(expectedError), +); void expectParseThrowsContaining( Object? content, diff --git a/pkgs/pubspec_parse/test/tojson_test.dart b/pkgs/pubspec_parse/test/tojson_test.dart index eb9161f15..1c23662e2 100644 --- a/pkgs/pubspec_parse/test/tojson_test.dart +++ b/pkgs/pubspec_parse/test/tojson_test.dart @@ -41,36 +41,33 @@ void main() { final version = Version.parse('1.2.3'); final sdkConstraint = VersionConstraint.parse('>=3.6.0 <4.0.0'); - final value = await parse( - { - 'name': 'sample', - 'version': version.toString(), - 'publish_to': 'none', - 'author': 'name@example.com', - 'environment': {'sdk': sdkConstraint.toString()}, - 'description': 'description', - 'homepage': 'homepage', - 'documentation': 'documentation', - 'repository': 'https://github.com/example/repo', - 'issue_tracker': 'https://github.com/example/repo/issues', - 'funding': ['https://patreon.com/example'], - 'topics': ['widget', 'button'], - 'ignored_advisories': ['111', '222'], - 'screenshots': [ - {'description': 'my screenshot', 'path': 'path/to/screenshot'}, - ], - 'workspace': ['pkg1', 'pkg2'], - 'resolution': 'workspace', - 'executables': { - 'my_script': 'bin/my_script.dart', - 'my_script2': 'bin/my_script2.dart', - }, - 'dependencies': {'foo': '1.0.0'}, - 'dev_dependencies': {'bar': '2.0.0'}, - 'dependency_overrides': {'baz': '3.0.0'}, + final value = await parse({ + 'name': 'sample', + 'version': version.toString(), + 'publish_to': 'none', + 'author': 'name@example.com', + 'environment': {'sdk': sdkConstraint.toString()}, + 'description': 'description', + 'homepage': 'homepage', + 'documentation': 'documentation', + 'repository': 'https://github.com/example/repo', + 'issue_tracker': 'https://github.com/example/repo/issues', + 'funding': ['https://patreon.com/example'], + 'topics': ['widget', 'button'], + 'ignored_advisories': ['111', '222'], + 'screenshots': [ + {'description': 'my screenshot', 'path': 'path/to/screenshot'}, + ], + 'workspace': ['pkg1', 'pkg2'], + 'resolution': 'workspace', + 'executables': { + 'my_script': 'bin/my_script.dart', + 'my_script2': 'bin/my_script2.dart', }, - skipTryPub: true, - ); + 'dependencies': {'foo': '1.0.0'}, + 'dev_dependencies': {'bar': '2.0.0'}, + 'dependency_overrides': {'baz': '3.0.0'}, + }, skipTryPub: true); final jsonValue = value.toJson(); @@ -94,16 +91,12 @@ void main() { expect(jsonValue['dev_dependencies'], hasLength(1)); expect( jsonValue['dev_dependencies'], - containsPair('bar', { - 'version': '2.0.0', - }), + containsPair('bar', {'version': '2.0.0'}), ); expect(jsonValue['dependency_overrides'], hasLength(1)); expect( jsonValue['dependency_overrides'], - containsPair('baz', { - 'version': '3.0.0', - }), + containsPair('baz', {'version': '3.0.0'}), ); expect(jsonValue['repository'], 'https://github.com/example/repo'); expect( @@ -155,36 +148,33 @@ void main() { test('all fields set', () async { final version = Version.parse('1.2.3'); final sdkConstraint = VersionConstraint.parse('>=3.6.0 <4.0.0'); - final value = await parse( - { - 'name': 'sample', - 'version': version.toString(), - 'publish_to': 'none', - 'author': 'name@example.com', - 'environment': {'sdk': sdkConstraint.toString()}, - 'description': 'description', - 'homepage': 'homepage', - 'documentation': 'documentation', - 'repository': 'https://github.com/example/repo', - 'issue_tracker': 'https://github.com/example/repo/issues', - 'funding': ['https://patreon.com/example'], - 'topics': ['widget', 'button'], - 'ignored_advisories': ['111', '222'], - 'screenshots': [ - {'description': 'my screenshot', 'path': 'path/to/screenshot'}, - ], - 'workspace': ['pkg1', 'pkg2'], - 'resolution': 'workspace', - 'executables': { - 'my_script': 'bin/my_script.dart', - 'my_script2': 'bin/my_script2.dart', - }, - 'dependencies': {'foo': '1.0.0'}, - 'dev_dependencies': {'bar': '2.0.0'}, - 'dependency_overrides': {'baz': '3.0.0'}, + final value = await parse({ + 'name': 'sample', + 'version': version.toString(), + 'publish_to': 'none', + 'author': 'name@example.com', + 'environment': {'sdk': sdkConstraint.toString()}, + 'description': 'description', + 'homepage': 'homepage', + 'documentation': 'documentation', + 'repository': 'https://github.com/example/repo', + 'issue_tracker': 'https://github.com/example/repo/issues', + 'funding': ['https://patreon.com/example'], + 'topics': ['widget', 'button'], + 'ignored_advisories': ['111', '222'], + 'screenshots': [ + {'description': 'my screenshot', 'path': 'path/to/screenshot'}, + ], + 'workspace': ['pkg1', 'pkg2'], + 'resolution': 'workspace', + 'executables': { + 'my_script': 'bin/my_script.dart', + 'my_script2': 'bin/my_script2.dart', }, - skipTryPub: true, - ); + 'dependencies': {'foo': '1.0.0'}, + 'dev_dependencies': {'bar': '2.0.0'}, + 'dependency_overrides': {'baz': '3.0.0'}, + }, skipTryPub: true); final jsonValue = value.toJson(); @@ -217,45 +207,28 @@ void main() { }); test('dependencies', () async { - final value = await parse( - { - ...defaultPubspec, - 'dependencies': { - 'flutter': { - 'sdk': 'flutter', - }, - 'http': '^1.1.0', - 'provider': { - 'version': '^6.0.5', - }, - 'firebase_core': { - 'hosted': { - 'name': 'firebase_core', - 'url': 'https://pub.dev', - }, - 'version': '^2.13.0', - }, - 'google_fonts': { - 'sdk': 'flutter', - 'version': '^4.0.3', - }, - 'flutter_bloc': { - 'git': 'https://github.com/felangel/bloc.git', - }, - 'shared_preferences': { - 'git': { - 'url': 'https://github.com/flutter/plugins.git', - 'ref': 'main', - 'path': 'packages/shared_preferences/shared_preferences', - }, - }, - 'local_utils': { - 'path': '../local_utils', + final value = await parse({ + ...defaultPubspec, + 'dependencies': { + 'flutter': {'sdk': 'flutter'}, + 'http': '^1.1.0', + 'provider': {'version': '^6.0.5'}, + 'firebase_core': { + 'hosted': {'name': 'firebase_core', 'url': 'https://pub.dev'}, + 'version': '^2.13.0', + }, + 'google_fonts': {'sdk': 'flutter', 'version': '^4.0.3'}, + 'flutter_bloc': {'git': 'https://github.com/felangel/bloc.git'}, + 'shared_preferences': { + 'git': { + 'url': 'https://github.com/flutter/plugins.git', + 'ref': 'main', + 'path': 'packages/shared_preferences/shared_preferences', }, }, + 'local_utils': {'path': '../local_utils'}, }, - skipTryPub: true, - ); + }, skipTryPub: true); final jsonValue = value.toJson(); diff --git a/pkgs/source_maps/CHANGELOG.md b/pkgs/source_maps/CHANGELOG.md index b06ac72ea..ad79d389f 100644 --- a/pkgs/source_maps/CHANGELOG.md +++ b/pkgs/source_maps/CHANGELOG.md @@ -1,5 +1,9 @@ ## 0.10.14-wip +* Fix `SingleMapping.spanFor` to use the entry from a previous line as specified + by the sourcemap specification + (https://tc39.es/ecma426/#sec-GetOriginalPositions), + ## 0.10.13 * Require Dart 3.3 diff --git a/pkgs/source_maps/lib/parser.dart b/pkgs/source_maps/lib/parser.dart index 590dfc682..2ce4d6d38 100644 --- a/pkgs/source_maps/lib/parser.dart +++ b/pkgs/source_maps/lib/parser.dart @@ -500,31 +500,37 @@ class SingleMapping extends Mapping { StateError('Invalid entry in sourcemap, expected 1, 4, or 5' ' values, but got $seen.\ntargeturl: $targetUrl, line: $line'); - /// Returns [TargetLineEntry] which includes the location in the target [line] - /// number. In particular, the resulting entry is the last entry whose line - /// number is lower or equal to [line]. - TargetLineEntry? _findLine(int line) { - var index = binarySearch(lines, (e) => e.line > line); - return (index <= 0) ? null : lines[index - 1]; - } - - /// Returns [TargetEntry] which includes the location denoted by - /// [line], [column]. If [lineEntry] corresponds to [line], then this will be - /// the last entry whose column is lower or equal than [column]. If - /// [lineEntry] corresponds to a line prior to [line], then the result will be - /// the very last entry on that line. - TargetEntry? _findColumn(int line, int column, TargetLineEntry? lineEntry) { - if (lineEntry == null || lineEntry.entries.isEmpty) return null; - if (lineEntry.line != line) return lineEntry.entries.last; - var entries = lineEntry.entries; - var index = binarySearch(entries, (e) => e.column > column); - return (index <= 0) ? null : entries[index - 1]; + /// Returns the last [TargetEntry] which includes the location denoted by + /// [line], [column]. + /// + /// This corresponds to the computation of _last_ in [GetOriginalPositions][1] + /// in the sourcemap specification. + /// + /// [1]: https://tc39.es/ecma426/#sec-GetOriginalPositions + TargetEntry? _findEntry(int line, int column) { + // To find the *last* TargetEntry, we scan backwards, starting from the + // first line after our target line, or the end of [lines]. + var lineIndex = binarySearch(lines, (e) => e.line > line); + while (--lineIndex >= 0) { + final lineEntry = lines[lineIndex]; + final entries = lineEntry.entries; + if (entries.isEmpty) continue; + // If we scan to a line before the target line, the last entry extends to + // cover our search location. + if (lineEntry.line != line) return entries.last; + final index = binarySearch(entries, (e) => e.column > column); + if (index > 0) return entries[index - 1]; + // We get here when the line has entries, but they are all after the + // column. When this happens, the line and column correspond to the + // previous entry, usually the last entry at the previous `lineIndex`. + } + return null; } @override SourceMapSpan? spanFor(int line, int column, {Map? files, String? uri}) { - var entry = _findColumn(line, column, _findLine(line)); + final entry = _findEntry(line, column); if (entry == null) return null; var sourceUrlId = entry.sourceUrlId; diff --git a/pkgs/source_maps/test/continued_region_test.dart b/pkgs/source_maps/test/continued_region_test.dart new file mode 100644 index 000000000..eb12e3012 --- /dev/null +++ b/pkgs/source_maps/test/continued_region_test.dart @@ -0,0 +1,110 @@ +// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file +// for details. 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:source_maps/source_maps.dart'; +import 'package:source_span/source_span.dart'; +import 'package:test/test.dart'; + +void main() { + /// This is a test for spans of the generated file that continue over several + /// lines. + /// + /// In a sourcemap, a span continues from the start encoded position until the + /// next position, regardless of whether the second position in on the same + /// line in the generated file or a subsequent line. + void testSpans(int lineA, int columnA, int lineB, int columnB) { + // Create a sourcemap describing a 'rectangular' generated file with three + // spans, each potentially over several lines: (1) an initial span that is + // unmapped, (2) a span that maps to file 'A', the span continuing until (3) + // a span that maps to file 'B'. + // + // We can describe the mapping by an 'image' of the generated file, where + // the positions marked as 'A' in the 'image' correspond to locations in the + // generated file that map to locations in source file 'A'. Lines and + // columns are zero-based. + // + // 0123456789 + // 0: ---------- + // 1: ----AAAAAA lineA: 1, columnA: 4, i.e. locationA + // 2: AABBBBBBBB lineB: 2, columnB: 2, i.e. locationB + // 3: BBBBBBBBBB + // + // Once we have the mapping, we probe every position in a 8x10 rectangle to + // validate that it maps to the intended original source file. + + expect(isBefore(lineB, columnB, lineA, columnA), isFalse, + reason: 'Test valid only for ordered positions'); + + SourceLocation location(Uri? uri, int line, int column) { + final offset = line * 10 + column; + return SourceLocation(offset, sourceUrl: uri, line: line, column: column); + } + + // Locations in the generated file. + final uriMap = Uri.parse('output.js.map'); + final locationA = location(uriMap, lineA, columnA); + final locationB = location(uriMap, lineB, columnB); + + // Original source locations. + final sourceA = location(Uri.parse('A'), 0, 0); + final sourceB = location(Uri.parse('B'), 0, 0); + + final json = (SourceMapBuilder() + ..addLocation(sourceA, locationA, null) + ..addLocation(sourceB, locationB, null)) + .build(uriMap.toString()); + + final mapping = parseJson(json); + + // Validate by comparing 'images' of the generate file. + final expectedImage = StringBuffer(); + final actualImage = StringBuffer(); + + for (var line = 0; line < 8; line++) { + for (var column = 0; column < 10; column++) { + final span = mapping.spanFor(line, column); + final expected = isBefore(line, column, lineA, columnA) + ? '-' + : isBefore(line, column, lineB, columnB) + ? 'A' + : 'B'; + final actual = span?.start.sourceUrl?.path ?? '-'; // Unmapped -> '-'. + + expectedImage.write(expected); + actualImage.write(actual); + } + expectedImage.writeln(); + actualImage.writeln(); + } + expect(actualImage.toString(), expectedImage.toString()); + } + + test('continued span, same position', () { + testSpans(2, 4, 2, 4); + }); + + test('continued span, same line', () { + testSpans(2, 4, 2, 7); + }); + + test('continued span, next line, earlier column', () { + testSpans(2, 4, 3, 2); + }); + + test('continued span, next line, later column', () { + testSpans(2, 4, 3, 6); + }); + + test('continued span, later line, earlier column', () { + testSpans(2, 4, 5, 2); + }); + + test('continued span, later line, later column', () { + testSpans(2, 4, 5, 6); + }); +} + +bool isBefore(int line1, int column1, int line2, int column2) { + return line1 < line2 || line1 == line2 && column1 < column2; +} diff --git a/pkgs/source_maps/test/refactor_test.dart b/pkgs/source_maps/test/refactor_test.dart index 5bc3818e5..5ac239c54 100644 --- a/pkgs/source_maps/test/refactor_test.dart +++ b/pkgs/source_maps/test/refactor_test.dart @@ -126,9 +126,16 @@ void main() { ' | ^\n' " '"); - // Lines added have no mapping (they should inherit the last mapping), - // but the end of the edit region continues were we left off: - expect(_span(4, 1, map, file), isNull); + // Newly added lines had no additional mapping, so they inherit the last + // position on the previously mapped line. The end of the region continues + // where the previous mapping left off. + expect( + _span(4, 1, map, file), + 'line 3, column 6: \n' + ' ,\n' + '3 | 01*3456789\n' + ' | ^\n' + ' \''); expect( _span(4, 5, map, file), 'line 3, column 8: \n' diff --git a/pkgs/watcher/CHANGELOG.md b/pkgs/watcher/CHANGELOG.md index 686126dc1..6e54b1f5c 100644 --- a/pkgs/watcher/CHANGELOG.md +++ b/pkgs/watcher/CHANGELOG.md @@ -4,6 +4,13 @@ the file was created immediately before the watcher was created. Now, if the file exists when the watcher is created then this modify event is not sent. This matches the Linux native and polling (Windows) watchers. +- Bug fix: with `DirectoryWatcher` on Windows, a move over an existing file was + reported incorrectly. For example, if `a` and `b` already exist, then `a` is + moved onto `b`, it would be reported as three events: delete `a`, delete `b`, + create `b`. Now it's reported as two events: delete `a`, modify `b`. This + matches the behavior of the Linux and MacOS watchers. +- Bug fix: with `PollingDirectoryWatcher`, fix spurious modify event emitted + because of a file delete during polling. ## 1.1.4 diff --git a/pkgs/watcher/lib/src/directory_watcher/linux.dart b/pkgs/watcher/lib/src/directory_watcher/linux.dart index 99e2cf50e..5fdda77a8 100644 --- a/pkgs/watcher/lib/src/directory_watcher/linux.dart +++ b/pkgs/watcher/lib/src/directory_watcher/linux.dart @@ -8,6 +8,7 @@ import 'dart:io'; import 'package:async/async.dart'; import '../directory_watcher.dart'; +import '../event.dart'; import '../path_set.dart'; import '../resubscribable.dart'; import '../utils.dart'; @@ -81,7 +82,7 @@ class _LinuxDirectoryWatcher }))); // Batch the inotify changes together so that we can dedup events. - var innerStream = _nativeEvents.stream.batchEvents(); + var innerStream = _nativeEvents.stream.batchAndConvertEvents(); _listen(innerStream, _onBatch, onError: (Object error, StackTrace stackTrace) { // Guarantee that ready always completes. @@ -145,7 +146,7 @@ class _LinuxDirectoryWatcher } /// The callback that's run when a batch of changes comes in. - void _onBatch(List batch) { + void _onBatch(List batch) { var files = {}; var dirs = {}; var changed = {}; @@ -162,30 +163,40 @@ class _LinuxDirectoryWatcher changed.add(event.path); - if (event is FileSystemMoveEvent) { - files.remove(event.path); - dirs.remove(event.path); - - var destination = event.destination; - if (destination == null) continue; - - changed.add(destination); - if (event.isDirectory) { - files.remove(destination); - dirs.add(destination); - } else { - files.add(destination); - dirs.remove(destination); - } - } else if (event is FileSystemDeleteEvent) { - files.remove(event.path); - dirs.remove(event.path); - } else if (event.isDirectory) { - files.remove(event.path); - dirs.add(event.path); - } else { - files.add(event.path); - dirs.remove(event.path); + switch (event.type) { + case EventType.moveFile: + files.remove(event.path); + dirs.remove(event.path); + var destination = event.destination; + if (destination != null) { + changed.add(destination); + files.add(destination); + dirs.remove(destination); + } + + case EventType.moveDirectory: + files.remove(event.path); + dirs.remove(event.path); + var destination = event.destination; + if (destination != null) { + changed.add(destination); + files.remove(destination); + dirs.add(destination); + } + + case EventType.delete: + files.remove(event.path); + dirs.remove(event.path); + + case EventType.createDirectory: + case EventType.modifyDirectory: + files.remove(event.path); + dirs.add(event.path); + + case EventType.createFile: + case EventType.modifyFile: + files.add(event.path); + dirs.remove(event.path); } } diff --git a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart index 509cf6fe6..8cb44e310 100644 --- a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart +++ b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart @@ -8,6 +8,7 @@ import 'dart:io'; import 'package:path/path.dart' as p; import '../directory_watcher.dart'; +import '../event.dart'; import '../path_set.dart'; import '../resubscribable.dart'; import '../utils.dart'; @@ -63,7 +64,7 @@ class _MacOSDirectoryWatcher /// /// This is separate from [_listSubscriptions] because this stream /// occasionally needs to be resubscribed in order to work around issue 14849. - StreamSubscription>? _watchSubscription; + StreamSubscription>? _watchSubscription; /// The subscription to the [Directory.list] call for the initial listing of /// the directory to determine its initial state. @@ -109,7 +110,7 @@ class _MacOSDirectoryWatcher } /// The callback that's run when [Directory.watch] emits a batch of events. - void _onBatch(List batch) { + void _onBatch(List batch) { // If we get a batch of events before we're ready to begin emitting events, // it's probable that it's a batch of pre-watcher events (see issue 14373). // Ignore those events and re-list the directory. @@ -132,8 +133,8 @@ class _MacOSDirectoryWatcher : [canonicalEvent]; for (var event in events) { - if (event is FileSystemCreateEvent) { - if (!event.isDirectory) { + switch (event.type) { + case EventType.createFile: // If we already know about the file, treat it like a modification. // This can happen if a file is copied on top of an existing one. // We'll see an ADD event for the latter file when from the user's @@ -143,34 +144,39 @@ class _MacOSDirectoryWatcher _emitEvent(type, path); _files.add(path); - continue; - } - - if (_files.containsDir(path)) continue; - - var stream = Directory(path) - .list(recursive: true) - .ignoring(); - var subscription = stream.listen((entity) { - if (entity is Directory) return; - if (_files.contains(path)) return; - - _emitEvent(ChangeType.ADD, entity.path); - _files.add(entity.path); - }, cancelOnError: true); - subscription.onDone(() { - _listSubscriptions.remove(subscription); - }); - subscription.onError(_emitError); - _listSubscriptions.add(subscription); - } else if (event is FileSystemModifyEvent) { - assert(!event.isDirectory); - _emitEvent(ChangeType.MODIFY, path); - } else { - assert(event is FileSystemDeleteEvent); - for (var removedPath in _files.remove(path)) { - _emitEvent(ChangeType.REMOVE, removedPath); - } + + case EventType.createDirectory: + if (_files.containsDir(path)) continue; + + var stream = Directory(path) + .list(recursive: true) + .ignoring(); + var subscription = stream.listen((entity) { + if (entity is Directory) return; + if (_files.contains(path)) return; + + _emitEvent(ChangeType.ADD, entity.path); + _files.add(entity.path); + }, cancelOnError: true); + subscription.onDone(() { + _listSubscriptions.remove(subscription); + }); + subscription.onError(_emitError); + _listSubscriptions.add(subscription); + + case EventType.modifyFile: + _emitEvent(ChangeType.MODIFY, path); + + case EventType.delete: + for (var removedPath in _files.remove(path)) { + _emitEvent(ChangeType.REMOVE, removedPath); + } + + // Dropped by [Event.checkAndConvert]. + case EventType.moveFile: + case EventType.moveDirectory: + case EventType.modifyDirectory: + assert(event.type.isNeverReceivedOnMacOS); } } }); @@ -178,122 +184,78 @@ class _MacOSDirectoryWatcher /// Sort all the events in a batch into sets based on their path. /// - /// A single input event may result in multiple events in the returned map; - /// for example, a MOVE event becomes a DELETE event for the source and a - /// CREATE event for the destination. + /// Events for [path] are discarded. /// - /// The returned events won't contain any [FileSystemMoveEvent]s, nor will it - /// contain any events relating to [path]. - Map> _sortEvents(List batch) { - var eventsForPaths = >{}; + /// Events under directories that are created are discarded. + Map> _sortEvents(List batch) { + var eventsForPaths = >{}; // FSEvents can report past events, including events on the root directory // such as it being created. We want to ignore these. If the directory is // really deleted, that's handled by [_onDone]. batch = batch.where((event) => event.path != path).toList(); - // Events within directories that already have events are superfluous; the - // directory's full contents will be examined anyway, so we ignore such - // events. Emitting them could cause useless or out-of-order events. - var directories = unionAll(batch.map((event) { - if (!event.isDirectory) return {}; - if (event is FileSystemMoveEvent) { - var destination = event.destination; - if (destination != null) { - return {event.path, destination}; - } - } - return {event.path}; + // Events within directories that already have create events are not needed + // as the directory's full content will be listed. + var createdDirectories = unionAll(batch.map((event) { + return event.type == EventType.createDirectory + ? {event.path} + : const {}; })); - bool isInModifiedDirectory(String path) => - directories.any((dir) => path != dir && p.isWithin(dir, path)); + bool isInCreatedDirectory(String path) => + createdDirectories.any((dir) => path != dir && p.isWithin(dir, path)); - void addEvent(String path, FileSystemEvent event) { - if (isInModifiedDirectory(path)) return; - eventsForPaths.putIfAbsent(path, () => {}).add(event); + void addEvent(String path, Event event) { + if (isInCreatedDirectory(path)) return; + eventsForPaths.putIfAbsent(path, () => {}).add(event); } for (var event in batch) { - // The Mac OS watcher doesn't emit move events. See issue 14806. - assert(event is! FileSystemMoveEvent); addEvent(event.path, event); } return eventsForPaths; } - /// Returns the canonical event from a batch of events on the same path, if - /// one exists. - /// - /// If [batch] doesn't contain any contradictory events (e.g. DELETE and - /// CREATE, or events with different values for `isDirectory`), this returns a - /// single event that describes what happened to the path in question. - /// - /// If [batch] does contain contradictory events, this returns `null` to - /// indicate that the state of the path on the filesystem should be checked to - /// determine what occurred. - FileSystemEvent? _canonicalEvent(Set batch) { - // An empty batch indicates that we've learned earlier that the batch is - // contradictory (e.g. because of a move). + /// Returns the canonical event from a batch of events on the same path, or + /// `null` to indicate that the filesystem should be checked. + Event? _canonicalEvent(Set batch) { + // If the batch is empty, return `null`. if (batch.isEmpty) return null; - var type = batch.first.type; - var isDir = batch.first.isDirectory; - var hadModifyEvent = false; - - for (var event in batch.skip(1)) { - // If one event reports that the file is a directory and another event - // doesn't, that's a contradiction. - if (isDir != event.isDirectory) return null; - - // Modify events don't contradict either CREATE or REMOVE events. We can - // safely assume the file was modified after a CREATE or before the - // REMOVE; otherwise there will also be a REMOVE or CREATE event - // (respectively) that will be contradictory. - if (event is FileSystemModifyEvent) { - hadModifyEvent = true; - continue; - } - assert(event is FileSystemCreateEvent || event is FileSystemDeleteEvent); - - // If we previously thought this was a MODIFY, we now consider it to be a - // CREATE or REMOVE event. This is safe for the same reason as above. - if (type == FileSystemEvent.modify) { - type = event.type; - continue; + // Resolve the event type for the batch. + var types = batch.map((e) => e.type).toSet(); + EventType type; + if (types.length == 1) { + // There's only one event. + type = types.single; + } else if (types.length == 2 && + types.contains(EventType.modifyFile) && + types.contains(EventType.createFile)) { + // Combine events of type [EventType.modifyFile] and + // [EventType.createFile] to one event. + if (_files.contains(batch.first.path)) { + // The file already existed: this can happen due to a create from + // before the watcher started being reported. + type = EventType.modifyFile; + } else { + type = EventType.createFile; } - - // A CREATE event contradicts a REMOVE event and vice versa. - assert(type == FileSystemEvent.create || type == FileSystemEvent.delete); - if (type != event.type) return null; + } else { + // There are incompatible event types, check the filesystem. + return null; } - // If we got a CREATE event for a file we already knew about, that comes - // from FSEvents reporting an add that happened prior to the watch - // beginning. If we also received a MODIFY event, we want to report that, - // but not the CREATE. - if (type == FileSystemEvent.create && - hadModifyEvent && - _files.contains(batch.first.path)) { - type = FileSystemEvent.modify; + // Issue 16003 means that a CREATE event for a directory can indicate + // that the directory was moved and then re-created. + // [_eventsBasedOnFileSystem] will handle this correctly by producing a + // DELETE event followed by a CREATE event if the directory exists. + if (type == EventType.createDirectory) { + return null; } - switch (type) { - case FileSystemEvent.create: - // Issue 16003 means that a CREATE event for a directory can indicate - // that the directory was moved and then re-created. - // [_eventsBasedOnFileSystem] will handle this correctly by producing a - // DELETE event followed by a CREATE event if the directory exists. - if (isDir) return null; - return FileSystemCreateEvent(batch.first.path, false); - case FileSystemEvent.delete: - return FileSystemDeleteEvent(batch.first.path, isDir); - case FileSystemEvent.modify: - return FileSystemModifyEvent(batch.first.path, isDir, false); - default: - throw StateError('unreachable'); - } + return batch.firstWhere((e) => e.type == type); } /// Returns one or more events that describe the change between the last known @@ -303,35 +265,35 @@ class _MacOSDirectoryWatcher /// to the user, unlike the batched events from [Directory.watch]. The /// returned list may be empty, indicating that no changes occurred to [path] /// (probably indicating that it was created and then immediately deleted). - List _eventsBasedOnFileSystem(String path) { + List _eventsBasedOnFileSystem(String path) { var fileExisted = _files.contains(path); var dirExisted = _files.containsDir(path); var fileExists = File(path).existsSync(); var dirExists = Directory(path).existsSync(); - var events = []; + var events = []; if (fileExisted) { if (fileExists) { - events.add(FileSystemModifyEvent(path, false, false)); + events.add(Event.modifyFile(path)); } else { - events.add(FileSystemDeleteEvent(path, false)); + events.add(Event.delete(path)); } } else if (dirExisted) { if (dirExists) { // If we got contradictory events for a directory that used to exist and // still exists, we need to rescan the whole thing in case it was // replaced with a different directory. - events.add(FileSystemDeleteEvent(path, true)); - events.add(FileSystemCreateEvent(path, true)); + events.add(Event.delete(path)); + events.add(Event.createDirectory(path)); } else { - events.add(FileSystemDeleteEvent(path, true)); + events.add(Event.delete(path)); } } if (!fileExisted && fileExists) { - events.add(FileSystemCreateEvent(path, false)); + events.add(Event.createFile(path)); } else if (!dirExisted && dirExists) { - events.add(FileSystemCreateEvent(path, true)); + events.add(Event.createDirectory(path)); } return events; @@ -362,7 +324,8 @@ class _MacOSDirectoryWatcher /// Start or restart the underlying [Directory.watch] stream. void _startWatch() { // Batch the FSEvent changes together so that we can dedup events. - var innerStream = Directory(path).watch(recursive: true).batchEvents(); + var innerStream = + Directory(path).watch(recursive: true).batchAndConvertEvents(); _watchSubscription = innerStream.listen(_onBatch, onError: _eventsController.addError, onDone: _onDone); } diff --git a/pkgs/watcher/lib/src/directory_watcher/polling.dart b/pkgs/watcher/lib/src/directory_watcher/polling.dart index d3fa6eb60..172fabeea 100644 --- a/pkgs/watcher/lib/src/directory_watcher/polling.dart +++ b/pkgs/watcher/lib/src/directory_watcher/polling.dart @@ -160,8 +160,13 @@ class _PollingDirectoryWatcher if (_events.isClosed) return; - _lastModifieds[file] = modified; _polledFiles.add(file); + if (modified == null) { + // The file was in the directory listing but has been removed since then. + // Don't add to _lastModifieds, it will be reported as a REMOVE. + return; + } + _lastModifieds[file] = modified; // Only notify if we're ready to emit events. if (!isReady) return; diff --git a/pkgs/watcher/lib/src/directory_watcher/windows.dart b/pkgs/watcher/lib/src/directory_watcher/windows.dart index 87eca0f2f..bbed14e40 100644 --- a/pkgs/watcher/lib/src/directory_watcher/windows.dart +++ b/pkgs/watcher/lib/src/directory_watcher/windows.dart @@ -10,6 +10,7 @@ import 'dart:io'; import 'package:path/path.dart' as p; import '../directory_watcher.dart'; +import '../event.dart'; import '../path_set.dart'; import '../resubscribable.dart'; import '../utils.dart'; @@ -26,10 +27,10 @@ class WindowsDirectoryWatcher extends ResubscribableWatcher class _EventBatcher { static const Duration _batchDelay = Duration(milliseconds: 100); - final List events = []; + final List events = []; Timer? timer; - void addEvent(FileSystemEvent event, void Function() callback) { + void addEvent(Event event, void Function() callback) { events.add(event); timer?.cancel(); timer = Timer(_batchDelay, callback); @@ -163,8 +164,28 @@ class _WindowsDirectoryWatcher ); } - void _onEvent(FileSystemEvent event) { + void _onEvent(FileSystemEvent fileSystemEvent) { assert(isReady); + final event = Event.checkAndConvert(fileSystemEvent); + if (event == null) return; + if (event.type == EventType.moveFile) { + _batchEvent(Event.delete(event.path)); + final destination = event.destination; + if (destination != null) { + _batchEvent(Event.createFile(destination)); + } + } else if (event.type == EventType.moveDirectory) { + _batchEvent(Event.delete(event.path)); + final destination = event.destination; + if (destination != null) { + _batchEvent(Event.createDirectory(destination)); + } + } else { + _batchEvent(event); + } + } + + void _batchEvent(Event event) { final batcher = _eventBatchers.putIfAbsent(event.path, _EventBatcher.new); batcher.addEvent(event, () { _eventBatchers.remove(event.path); @@ -173,7 +194,7 @@ class _WindowsDirectoryWatcher } /// The callback that's run when [Directory.watch] emits a batch of events. - void _onBatch(List batch) { + void _onBatch(List batch) { _sortEvents(batch).forEach((path, eventSet) { var canonicalEvent = _canonicalEvent(eventSet); var events = canonicalEvent == null @@ -181,164 +202,112 @@ class _WindowsDirectoryWatcher : [canonicalEvent]; for (var event in events) { - if (event is FileSystemCreateEvent) { - if (!event.isDirectory) { + switch (event.type) { + case EventType.createFile: if (_files.contains(path)) continue; - _emitEvent(ChangeType.ADD, path); _files.add(path); - continue; - } - if (_files.containsDir(path)) continue; - - // "Path not found" can be caused by creating then quickly removing - // a directory: continue without reporting an error. Nested files - // that get removed during the `list` are already ignored by `list` - // itself, so there are no other types of "path not found" that - // might need different handling here. - var stream = Directory(path) - .list(recursive: true) - .ignoring(); - var subscription = stream.listen((entity) { - if (entity is Directory) return; - if (_files.contains(entity.path)) return; - - _emitEvent(ChangeType.ADD, entity.path); - _files.add(entity.path); - }, cancelOnError: true); - subscription.onDone(() { - _listSubscriptions.remove(subscription); - }); - subscription.onError((Object e, StackTrace stackTrace) { - _listSubscriptions.remove(subscription); - _emitError(e, stackTrace); - }); - _listSubscriptions.add(subscription); - } else if (event is FileSystemModifyEvent) { - if (!event.isDirectory) { + case EventType.createDirectory: + if (_files.containsDir(path)) continue; + + // "Path not found" can be caused by creating then quickly removing + // a directory: continue without reporting an error. Nested files + // that get removed during the `list` are already ignored by `list` + // itself, so there are no other types of "path not found" that + // might need different handling here. + var stream = Directory(path) + .list(recursive: true) + .ignoring(); + var subscription = stream.listen((entity) { + if (entity is Directory) return; + if (_files.contains(entity.path)) return; + + _emitEvent(ChangeType.ADD, entity.path); + _files.add(entity.path); + }, cancelOnError: true); + subscription.onDone(() { + _listSubscriptions.remove(subscription); + }); + subscription.onError((Object e, StackTrace stackTrace) { + _listSubscriptions.remove(subscription); + _emitError(e, stackTrace); + }); + _listSubscriptions.add(subscription); + + case EventType.modifyFile: _emitEvent(ChangeType.MODIFY, path); - } - } else { - assert(event is FileSystemDeleteEvent); - for (var removedPath in _files.remove(path)) { - _emitEvent(ChangeType.REMOVE, removedPath); - } + + case EventType.delete: + for (var removedPath in _files.remove(path)) { + _emitEvent(ChangeType.REMOVE, removedPath); + } + + // Move events are removed by `_onEvent` and never returned by + // `_eventsBasedOnFileSystem`. + case EventType.moveFile: + case EventType.moveDirectory: + throw StateError(event.type.name); + + // Dropped by [Event.checkAndConvert]. + case EventType.modifyDirectory: + assert(event.type.isIgnoredOnWindows); } } }); } /// Sort all the events in a batch into sets based on their path. - /// - /// A single input event may result in multiple events in the returned map; - /// for example, a MOVE event becomes a DELETE event for the source and a - /// CREATE event for the destination. - /// - /// The returned events won't contain any [FileSystemMoveEvent]s, nor will it - /// contain any events relating to [path]. - Map> _sortEvents(List batch) { - var eventsForPaths = >{}; - - // Events within directories that already have events are superfluous; the - // directory's full contents will be examined anyway, so we ignore such - // events. Emitting them could cause useless or out-of-order events. - var directories = unionAll( - batch.map((event) { - if (!event.isDirectory) return {}; - if (event is FileSystemMoveEvent) { - var destination = event.destination; - if (destination != null) { - return {event.path, destination}; - } - } - return {event.path}; - }), - ); - - bool isInModifiedDirectory(String path) => - directories.any((dir) => path != dir && p.isWithin(dir, path)); - - void addEvent(String path, FileSystemEvent event) { - if (isInModifiedDirectory(path)) return; - eventsForPaths.putIfAbsent(path, () => {}).add(event); + Map> _sortEvents(List batch) { + var eventsForPaths = >{}; + + // Events within directories that already have create events are not needed + // as the directory's full content will be listed. + var createdDirectories = unionAll(batch.map((event) { + return event.type == EventType.createDirectory + ? {event.path} + : const {}; + })); + + bool isInCreatedDirectory(String path) => + createdDirectories.any((dir) => path != dir && p.isWithin(dir, path)); + + void addEvent(String path, Event event) { + if (isInCreatedDirectory(path)) return; + eventsForPaths.putIfAbsent(path, () => {}).add(event); } for (var event in batch) { - if (event is FileSystemMoveEvent) { - var destination = event.destination; - if (destination != null) { - addEvent(destination, event); - } - } addEvent(event.path, event); } return eventsForPaths; } - /// Returns the canonical event from a batch of events on the same path, if - /// one exists. - /// - /// If [batch] doesn't contain any contradictory events (e.g. DELETE and - /// CREATE, or events with different values for `isDirectory`), this returns a - /// single event that describes what happened to the path in question. - /// - /// If [batch] does contain contradictory events, this returns `null` to - /// indicate that the state of the path on the filesystem should be checked to - /// determine what occurred. - FileSystemEvent? _canonicalEvent(Set batch) { - // An empty batch indicates that we've learned earlier that the batch is - // contradictory (e.g. because of a move). + /// Returns the canonical event from a batch of events on the same path, or + /// `null` to indicate that the filesystem should be checked. + Event? _canonicalEvent(Set batch) { + // If the batch is empty, return `null`. if (batch.isEmpty) return null; - var type = batch.first.type; - var isDir = batch.first.isDirectory; - - for (var event in batch.skip(1)) { - // If one event reports that the file is a directory and another event - // doesn't, that's a contradiction. - if (isDir != event.isDirectory) return null; - - // Modify events don't contradict either CREATE or REMOVE events. We can - // safely assume the file was modified after a CREATE or before the - // REMOVE; otherwise there will also be a REMOVE or CREATE event - // (respectively) that will be contradictory. - if (event is FileSystemModifyEvent) continue; - assert( - event is FileSystemCreateEvent || - event is FileSystemDeleteEvent || - event is FileSystemMoveEvent, - ); - - // If we previously thought this was a MODIFY, we now consider it to be a - // CREATE or REMOVE event. This is safe for the same reason as above. - if (type == FileSystemEvent.modify) { - type = event.type; - continue; - } - - // A CREATE event contradicts a REMOVE event and vice versa. - assert( - type == FileSystemEvent.create || - type == FileSystemEvent.delete || - type == FileSystemEvent.move, - ); - if (type != event.type) return null; + // Resolve the event type for the batch. + var types = batch.map((e) => e.type).toSet(); + EventType type; + if (types.length == 1) { + // There's only one event. + type = types.single; + } else if (types.length == 2 && + types.contains(EventType.modifyFile) && + types.contains(EventType.createFile)) { + // Combine events of type [EventType.modifyFile] and + // [EventType.createFile] to one event. + type = EventType.createFile; + } else { + // There are incompatible event types, check the filesystem. + return null; } - switch (type) { - case FileSystemEvent.create: - return FileSystemCreateEvent(batch.first.path, isDir); - case FileSystemEvent.delete: - return FileSystemDeleteEvent(batch.first.path, isDir); - case FileSystemEvent.modify: - return FileSystemModifyEvent(batch.first.path, isDir, false); - case FileSystemEvent.move: - return null; - default: - throw StateError('unreachable'); - } + return batch.firstWhere((e) => e.type == type); } /// Returns zero or more events that describe the change between the last @@ -348,7 +317,7 @@ class _WindowsDirectoryWatcher /// to the user, unlike the batched events from [Directory.watch]. The /// returned list may be empty, indicating that no changes occurred to [path] /// (probably indicating that it was created and then immediately deleted). - List _eventsBasedOnFileSystem(String path) { + List _eventsBasedOnFileSystem(String path) { var fileExisted = _files.contains(path); var dirExisted = _files.containsDir(path); @@ -358,32 +327,32 @@ class _WindowsDirectoryWatcher fileExists = File(path).existsSync(); dirExists = Directory(path).existsSync(); } on FileSystemException { - return const []; + return const []; } - var events = []; + var events = []; if (fileExisted) { if (fileExists) { - events.add(FileSystemModifyEvent(path, false, false)); + events.add(Event.modifyFile(path)); } else { - events.add(FileSystemDeleteEvent(path, false)); + events.add(Event.delete(path)); } } else if (dirExisted) { if (dirExists) { // If we got contradictory events for a directory that used to exist and // still exists, we need to rescan the whole thing in case it was // replaced with a different directory. - events.add(FileSystemDeleteEvent(path, true)); - events.add(FileSystemCreateEvent(path, true)); + events.add(Event.delete(path)); + events.add(Event.createDirectory(path)); } else { - events.add(FileSystemDeleteEvent(path, true)); + events.add(Event.delete(path)); } } if (!fileExisted && fileExists) { - events.add(FileSystemCreateEvent(path, false)); + events.add(Event.createFile(path)); } else if (!dirExisted && dirExists) { - events.add(FileSystemCreateEvent(path, true)); + events.add(Event.createDirectory(path)); } return events; diff --git a/pkgs/watcher/lib/src/event.dart b/pkgs/watcher/lib/src/event.dart new file mode 100644 index 000000000..f2f9ac860 --- /dev/null +++ b/pkgs/watcher/lib/src/event.dart @@ -0,0 +1,122 @@ +// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:io'; + +/// Extension type replacing [FileSystemEvent] for `package:watcher` internal +/// use. +/// +/// The [FileSystemDeleteEvent] subclass of [FileSystemEvent] does something +/// surprising for `isDirectory`: it always returns `false`. The constructor +/// accepts a boolean called `isDirectory` but discards it. +/// +/// So, this extension type hides `isDirectory` and instead provides an +/// [EventType] enum with the seven types of event actually used. +extension type Event._(FileSystemEvent _event) { + /// Converts [event] to an [Event]. + /// + /// Returns `null` and asserts `false` if [event] is unexpected on this + /// platform. So, it will cause tests to fail but real code can continue + /// ignoring the event. + /// + /// Returns `null` if [event] should be ignored on this platform. + static Event? checkAndConvert(FileSystemEvent event) { + var result = Event._(event); + if (Platform.isMacOS) { + if (result.type.isNeverReceivedOnMacOS) { + assert(false); + return null; + } + } else if (Platform.isWindows) { + if (result.type.isIgnoredOnWindows) { + return null; + } + } + return result; + } + + /// A create event for a file at [path]. + static Event createFile(String path) => + Event._(FileSystemCreateEvent(path, false)); + + /// A create event for a directory at [path]. + static Event createDirectory(String path) => + Event._(FileSystemCreateEvent(path, true)); + + /// A delete event for [path]. + /// + /// Delete events do not specify whether they are for files or directories. + static Event delete(String path) => Event._(FileSystemDeleteEvent( + path, + // `FileSystemDeleteEvent` just discards `isDirectory`. + false /* isDirectory */)); + + /// A modify event for the file at [path]. + static Event modifyFile(String path) => Event._(FileSystemModifyEvent( + path, + false /* isDirectory */, + // Don't set `contentChanged`, even pass through from the OS, as + // `package:watcher` never reads it. + false /* contentChanged */)); + + /// See [FileSystemEvent.path]. + String get path => _event.path; + + EventType get type { + switch (_event.type) { + case FileSystemEvent.create: + return _event.isDirectory + ? EventType.createDirectory + : EventType.createFile; + case FileSystemEvent.delete: + return EventType.delete; + case FileSystemEvent.modify: + return _event.isDirectory + ? EventType.modifyDirectory + : EventType.modifyFile; + case FileSystemEvent.move: + return _event.isDirectory + ? EventType.moveDirectory + : EventType.moveFile; + default: + throw StateError('Invalid event type ${_event.type}.'); + } + } + + /// See [FileSystemMoveEvent.destination]. + /// + /// For other types of event, always `null`. + String? get destination => _event.type == FileSystemEvent.move + ? (_event as FileSystemMoveEvent).destination + : null; +} + +/// See [FileSystemEvent.type]. +/// +/// This additionally encodes [FileSystemEvent.isDirectory], which is specified +/// for all event types except deletes. +enum EventType { + delete, + createFile, + createDirectory, + modifyFile, + modifyDirectory, + moveFile, + moveDirectory; + + bool get isNeverReceivedOnMacOS { + // See https://github.com/dart-lang/sdk/issues/14806. + if (this == moveFile || this == moveDirectory) { + return true; + } + if (this == modifyDirectory) return true; + return false; + } + + bool get isIgnoredOnWindows { + // Ignore [modifyDirectory] because it's always accompanied by either + // [createDirectory] or [deleteDirectory]. + return this == modifyDirectory; + } +} diff --git a/pkgs/watcher/lib/src/file_watcher/native.dart b/pkgs/watcher/lib/src/file_watcher/native.dart index 28cf8a180..ae961fe18 100644 --- a/pkgs/watcher/lib/src/file_watcher/native.dart +++ b/pkgs/watcher/lib/src/file_watcher/native.dart @@ -5,6 +5,7 @@ import 'dart:async'; import 'dart:io'; +import '../event.dart'; import '../file_watcher.dart'; import '../resubscribable.dart'; import '../utils.dart'; @@ -33,7 +34,7 @@ class _NativeFileWatcher implements FileWatcher, ManuallyClosedWatcher { Future get ready => _readyCompleter.future; final _readyCompleter = Completer(); - StreamSubscription>? _subscription; + StreamSubscription>? _subscription; /// On MacOS only, whether the file existed on startup. bool? _existedAtStartup; @@ -50,7 +51,7 @@ class _NativeFileWatcher implements FileWatcher, ManuallyClosedWatcher { var file = File(path); // Batch the events together so that we can dedupe them. - var stream = file.watch().batchEvents(); + var stream = file.watch().batchAndConvertEvents(); if (Platform.isMacOS) { var existedAtStartupFuture = file.exists(); @@ -65,8 +66,8 @@ class _NativeFileWatcher implements FileWatcher, ManuallyClosedWatcher { onError: _eventsController.addError, onDone: _onDone); } - void _onBatch(List batch) { - if (batch.any((event) => event.type == FileSystemEvent.delete)) { + void _onBatch(List batch) { + if (batch.any((event) => event.type == EventType.delete)) { // If the file is deleted, the underlying stream will close. We handle // emitting our own REMOVE event in [_onDone]. return; @@ -77,7 +78,7 @@ class _NativeFileWatcher implements FileWatcher, ManuallyClosedWatcher { // created just before the `watch`. If the file existed at startup then it // should be ignored. if (_existedAtStartup! && - batch.every((event) => event.type == FileSystemEvent.create)) { + batch.every((event) => event.type == EventType.createFile)) { return; } } diff --git a/pkgs/watcher/lib/src/stat.dart b/pkgs/watcher/lib/src/stat.dart index fe0f15578..25019ef3b 100644 --- a/pkgs/watcher/lib/src/stat.dart +++ b/pkgs/watcher/lib/src/stat.dart @@ -16,7 +16,7 @@ MockTimeCallback? _mockTimeCallback; /// The OS file modification time has pretty rough granularity (like a few /// seconds) which can make for slow tests that rely on modtime. This lets you /// replace it with something you control. -void mockGetModificationTime(MockTimeCallback callback) { +void mockGetModificationTime(MockTimeCallback? callback) { _mockTimeCallback = callback; } diff --git a/pkgs/watcher/lib/src/utils.dart b/pkgs/watcher/lib/src/utils.dart index e5ef54c66..b63a93bb6 100644 --- a/pkgs/watcher/lib/src/utils.dart +++ b/pkgs/watcher/lib/src/utils.dart @@ -6,6 +6,8 @@ import 'dart:async'; import 'dart:collection'; import 'dart:io'; +import 'event.dart'; + /// Returns `true` if [error] is a [FileSystemException] for a missing /// directory. bool isDirectoryNotFoundException(Object error) { @@ -20,7 +22,7 @@ bool isDirectoryNotFoundException(Object error) { Set unionAll(Iterable> sets) => sets.fold({}, (union, set) => union.union(set)); -extension BatchEvents on Stream { +extension BatchEvents on Stream { /// Batches all events that are sent at the same time. /// /// When multiple events are synchronously added to a stream controller, the @@ -28,11 +30,16 @@ extension BatchEvents on Stream { /// asynchronous firing of each event. In order to recreate the synchronous /// batches, this collates all the events that are received in "nearby" /// microtasks. - Stream> batchEvents() { - var batch = Queue(); - return StreamTransformer>.fromHandlers( + /// + /// Converts to [Event] using [Event.checkAndConvert], discarding events for + /// which it returns `null`. + Stream> batchAndConvertEvents() { + var batch = Queue(); + return StreamTransformer>.fromHandlers( handleData: (event, sink) { - batch.add(event); + var convertedEvent = Event.checkAndConvert(event); + if (convertedEvent == null) return; + batch.add(convertedEvent); // [Timer.run] schedules an event that runs after any microtasks that have // been scheduled. diff --git a/pkgs/watcher/pubspec.yaml b/pkgs/watcher/pubspec.yaml index c7119052d..fde8fe3dd 100644 --- a/pkgs/watcher/pubspec.yaml +++ b/pkgs/watcher/pubspec.yaml @@ -7,7 +7,7 @@ repository: https://github.com/dart-lang/tools/tree/main/pkgs/watcher issue_tracker: https://github.com/dart-lang/tools/labels/package%3Awatcher environment: - sdk: ^3.1.0 + sdk: ^3.3.0 dependencies: async: ^2.5.0 diff --git a/pkgs/watcher/test/directory_watcher/shared.dart b/pkgs/watcher/test/directory_watcher/file_tests.dart similarity index 98% rename from pkgs/watcher/test/directory_watcher/shared.dart rename to pkgs/watcher/test/directory_watcher/file_tests.dart index 10541cf8d..9b3fcd6c2 100644 --- a/pkgs/watcher/test/directory_watcher/shared.dart +++ b/pkgs/watcher/test/directory_watcher/file_tests.dart @@ -1,6 +1,7 @@ // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. + import 'dart:io' as io; import 'dart:isolate'; @@ -10,7 +11,7 @@ import 'package:watcher/src/utils.dart'; import '../utils.dart'; -void sharedTests() { +void fileTests() { test('does not notify for files that already exist when started', () async { // Make some pre-existing files. writeFile('a.txt'); @@ -137,8 +138,6 @@ void sharedTests() { renameFile('from.txt', 'to.txt'); await inAnyOrder([isRemoveEvent('from.txt'), isModifyEvent('to.txt')]); - }, onPlatform: { - 'windows': const Skip('https://github.com/dart-lang/watcher/issues/125') }); }); @@ -280,8 +279,6 @@ void sharedTests() { isRemoveEvent('old'), isAddEvent('new') ]); - }, onPlatform: { - 'windows': const Skip('https://github.com/dart-lang/watcher/issues/21') }); test('emits events for many nested files added at once', () async { diff --git a/pkgs/watcher/test/directory_watcher/link_tests.dart b/pkgs/watcher/test/directory_watcher/link_tests.dart new file mode 100644 index 000000000..f4919d1fb --- /dev/null +++ b/pkgs/watcher/test/directory_watcher/link_tests.dart @@ -0,0 +1,268 @@ +// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:io'; + +import 'package:test/test.dart'; + +import '../utils.dart'; + +void linkTests({required bool isNative}) { + test('notifies when a link is added', () async { + createDir('targets'); + createDir('links'); + writeFile('targets/a.target'); + await startWatcher(path: 'links'); + + writeLink(link: 'links/a.link', target: 'targets/a.target'); + + await expectAddEvent('links/a.link'); + }); + + test( + 'notifies when a link is replaced with a link to a different target ' + 'with the same contents', () async { + createDir('targets'); + createDir('links'); + writeFile('targets/a.target'); + writeFile('targets/b.target'); + writeLink(link: 'links/a.link', target: 'targets/a.target'); + await startWatcher(path: 'links'); + + deleteLink('links/a.link'); + writeLink(link: 'links/a.link', target: 'targets/b.target'); + + await expectModifyEvent('links/a.link'); + }); + + test( + 'notifies when a link is replaced with a link to a different target ' + 'with different contents', () async { + writeFile('targets/a.target', contents: 'a'); + writeFile('targets/b.target', contents: 'b'); + writeLink(link: 'links/a.link', target: 'targets/a.target'); + await startWatcher(path: 'links'); + + deleteLink('links/a.link'); + writeLink(link: 'links/a.link', target: 'targets/b.target'); + + await expectModifyEvent('links/a.link'); + }); + + test('does not notify when a link target is modified', () async { + createDir('targets'); + createDir('links'); + writeFile('targets/a.target'); + writeLink(link: 'links/a.link', target: 'targets/a.target'); + await startWatcher(path: 'links'); + writeFile('targets/a.target', contents: 'modified'); + + // TODO(davidmorgan): reconcile differences. + if (isNative) { + await expectNoEvents(); + } else { + await expectModifyEvent('links/a.link'); + } + }); + + test('does not notify when a link target is removed', () async { + createDir('targets'); + createDir('links'); + writeFile('targets/a.target'); + writeLink(link: 'links/a.link', target: 'targets/a.target'); + await startWatcher(path: 'links'); + + deleteFile('targets/a.target'); + + // TODO(davidmorgan): reconcile differences. + if (isNative) { + await expectNoEvents(); + } else { + await expectRemoveEvent('links/a.link'); + } + }); + + test('notifies when a link is moved within the watched directory', () async { + createDir('targets'); + createDir('links'); + writeFile('targets/a.target'); + writeLink(link: 'links/a.link', target: 'targets/a.target'); + await startWatcher(path: 'links'); + + renameLink('links/a.link', 'links/b.link'); + + await inAnyOrder( + [isAddEvent('links/b.link'), isRemoveEvent('links/a.link')]); + }); + + test('notifies when a link to an empty directory is added', () async { + createDir('targets'); + createDir('links'); + createDir('targets/a.targetdir'); + await startWatcher(path: 'links'); + + writeLink(link: 'links/a.link', target: 'targets/a.targetdir'); + + // TODO(davidmorgan): reconcile differences. + if (isNative) { + await expectAddEvent('links/a.link'); + } else { + await expectNoEvents(); + } + }); + + test( + 'does not notify about directory contents ' + 'when a link to a directory is added', () async { + createDir('targets'); + createDir('links'); + createDir('targets/a.targetdir'); + writeFile('targets/a.targetdir/a.target'); + await startWatcher(path: 'links'); + + writeLink(link: 'links/a.link', target: 'targets/a.targetdir'); + + // TODO(davidmorgan): reconcile differences. + if (isNative) { + await expectAddEvent('links/a.link'); + } else { + await expectAddEvent('links/a.link/a.target'); + } + }); + + test('notifies when a file is added to a linked directory', () async { + createDir('targets'); + createDir('links'); + createDir('targets/a.targetdir'); + writeLink(link: 'links/a.link', target: 'targets/a.targetdir'); + await startWatcher(path: 'links'); + + writeFile('targets/a.targetdir/a.txt'); + + // TODO(davidmorgan): reconcile differences. + if (!isNative || Platform.isLinux) { + await expectAddEvent('links/a.link/a.txt'); + } else { + await expectNoEvents(); + } + }); + + test( + 'notifies about linked directory contents when a directory with a linked ' + 'subdirectory is moved in', () async { + createDir('targets'); + createDir('links'); + createDir('targets/a.targetdir'); + createDir('watched'); + writeFile('targets/a.targetdir/a.txt'); + writeLink(link: 'links/a.link', target: 'targets/a.targetdir'); + await startWatcher(path: 'watched'); + + renameDir('links', 'watched/links'); + + await expectAddEvent('watched/links/a.link/a.txt'); + }); + + test( + 'notifies about linked directory contents when a directory with a linked ' + 'subdirectory containing a link loop is moved in', () async { + createDir('targets'); + createDir('links'); + createDir('targets/a.targetdir'); + createDir('watched'); + writeFile('targets/a.targetdir/a.txt'); + writeLink(link: 'links/a.link', target: 'targets/a.targetdir'); + writeLink( + link: 'targets/a.targetdir/cycle.link', target: 'targets/a.targetdir'); + await startWatcher(path: 'watched'); + + renameDir('links', 'watched/links'); + + // TODO(davidmorgan): reconcile differences. + if (isNative && (Platform.isLinux || Platform.isMacOS)) { + await inAnyOrder([ + isAddEvent('watched/links/a.link/a.txt'), + isAddEvent('watched/links/a.link/cycle.link/a.txt'), + isAddEvent('watched/links/a.link/cycle.link/cycle.link'), + ]); + await expectNoEvents(); + } else if (isNative && Platform.isWindows) { + await inAnyOrder([ + isAddEvent('watched/links/a.link/a.txt'), + isAddEvent('watched/links/a.link/cycle.link'), + ]); + await expectNoEvents(); + } else if (!isNative && Platform.isWindows) { + await inAnyOrder([ + isAddEvent('watched/links/a.link/a.txt'), + ]); + await expectNoEvents(); + } else { + assert(!isNative); + await inAnyOrder([ + isAddEvent('watched/links/a.link/a.txt'), + isAddEvent('watched/links/a.link/cycle.link/a.txt'), + ]); + await expectNoEvents(); + } + }); + + test( + 'notifies about linked directory contents when a directory with a linked ' + 'subdirectory containing two link loops is moved in', () async { + createDir('targets'); + createDir('links'); + createDir('targets/a.targetdir'); + createDir('watched'); + writeFile('targets/a.targetdir/a.txt'); + writeLink(link: 'links/a.link', target: 'targets/a.targetdir'); + writeLink( + link: 'targets/a.targetdir/cycle1.link', target: 'targets/a.targetdir'); + writeLink( + link: 'targets/a.targetdir/cycle2.link', target: 'targets/a.targetdir'); + await startWatcher(path: 'watched'); + + renameDir('links', 'watched/links'); + + // TODO(davidmorgan): reconcile differences. + if (isNative && (Platform.isLinux || Platform.isMacOS)) { + await inAnyOrder([ + isAddEvent('watched/links/a.link/a.txt'), + isAddEvent('watched/links/a.link/cycle1.link/a.txt'), + isAddEvent('watched/links/a.link/cycle1.link/cycle1.link'), + isAddEvent('watched/links/a.link/cycle1.link/cycle2.link/a.txt'), + isAddEvent('watched/links/a.link/cycle1.link/cycle2.link/cycle1.link'), + isAddEvent('watched/links/a.link/cycle1.link/cycle2.link/cycle2.link'), + isAddEvent('watched/links/a.link/cycle2.link/a.txt'), + isAddEvent('watched/links/a.link/cycle2.link/cycle1.link/a.txt'), + isAddEvent('watched/links/a.link/cycle2.link/cycle1.link/cycle1.link'), + isAddEvent('watched/links/a.link/cycle2.link/cycle1.link/cycle2.link'), + isAddEvent('watched/links/a.link/cycle2.link/cycle2.link'), + ]); + await expectNoEvents(); + } else if (isNative && Platform.isWindows) { + await inAnyOrder([ + isAddEvent('watched/links/a.link/a.txt'), + isAddEvent('watched/links/a.link/cycle1.link'), + isAddEvent('watched/links/a.link/cycle2.link'), + ]); + await expectNoEvents(); + } else if (!isNative && Platform.isWindows) { + await inAnyOrder([ + isAddEvent('watched/links/a.link/a.txt'), + ]); + await expectNoEvents(); + } else { + assert(!isNative); + await inAnyOrder([ + isAddEvent('watched/links/a.link/a.txt'), + isAddEvent('watched/links/a.link/cycle1.link/a.txt'), + isAddEvent('watched/links/a.link/cycle1.link/cycle2.link/a.txt'), + isAddEvent('watched/links/a.link/cycle2.link/a.txt'), + isAddEvent('watched/links/a.link/cycle2.link/cycle1.link/a.txt'), + ]); + await expectNoEvents(); + } + }); +} diff --git a/pkgs/watcher/test/directory_watcher/linux_test.dart b/pkgs/watcher/test/directory_watcher/linux_test.dart index a10a72c33..a47779466 100644 --- a/pkgs/watcher/test/directory_watcher/linux_test.dart +++ b/pkgs/watcher/test/directory_watcher/linux_test.dart @@ -10,12 +10,14 @@ import 'package:watcher/src/directory_watcher/linux.dart'; import 'package:watcher/watcher.dart'; import '../utils.dart'; -import 'shared.dart'; +import 'file_tests.dart'; +import 'link_tests.dart'; void main() { watcherFactory = LinuxDirectoryWatcher.new; - sharedTests(); + fileTests(); + linkTests(isNative: true); test('DirectoryWatcher creates a LinuxDirectoryWatcher on Linux', () { expect(DirectoryWatcher('.'), const TypeMatcher()); diff --git a/pkgs/watcher/test/directory_watcher/mac_os_test.dart b/pkgs/watcher/test/directory_watcher/mac_os_test.dart index 337662646..ba52d1958 100644 --- a/pkgs/watcher/test/directory_watcher/mac_os_test.dart +++ b/pkgs/watcher/test/directory_watcher/mac_os_test.dart @@ -10,12 +10,14 @@ import 'package:watcher/src/directory_watcher/mac_os.dart'; import 'package:watcher/watcher.dart'; import '../utils.dart'; -import 'shared.dart'; +import 'file_tests.dart'; +import 'link_tests.dart'; void main() { watcherFactory = MacOSDirectoryWatcher.new; - sharedTests(); + fileTests(); + linkTests(isNative: true); test('DirectoryWatcher creates a MacOSDirectoryWatcher on Mac OS', () { expect(DirectoryWatcher('.'), const TypeMatcher()); diff --git a/pkgs/watcher/test/directory_watcher/polling_test.dart b/pkgs/watcher/test/directory_watcher/polling_test.dart index af10c21ed..829ded36e 100644 --- a/pkgs/watcher/test/directory_watcher/polling_test.dart +++ b/pkgs/watcher/test/directory_watcher/polling_test.dart @@ -9,21 +9,69 @@ import 'package:test/test.dart'; import 'package:watcher/watcher.dart'; import '../utils.dart'; -import 'shared.dart'; +import 'file_tests.dart'; +import 'link_tests.dart'; void main() { // Use a short delay to make the tests run quickly. watcherFactory = (dir) => PollingDirectoryWatcher(dir, pollingDelay: const Duration(milliseconds: 100)); - sharedTests(); + // Filesystem modification times can be low resolution, mock them. + group('with mock mtime', () { + setUp(enableMockModificationTimes); - test('does not notify if the modification time did not change', () async { - writeFile('a.txt', contents: 'before'); - writeFile('b.txt', contents: 'before'); - await startWatcher(); - writeFile('a.txt', contents: 'after', updateModified: false); - writeFile('b.txt', contents: 'after'); - await expectModifyEvent('b.txt'); + fileTests(); + linkTests(isNative: false); + + test('does not notify if the modification time did not change', () async { + writeFile('a.txt', contents: 'before'); + writeFile('b.txt', contents: 'before'); + await startWatcher(); + writeFile('a.txt', contents: 'after', updateModified: false); + writeFile('b.txt', contents: 'after'); + await expectModifyEvent('b.txt'); + }); + + // A poll does an async directory list then checks mtime on each file. Check + // handling of a file that is deleted between the two. + test('deletes during poll', () async { + await startWatcher(); + + for (var i = 0; i != 300; ++i) { + writeFile('$i'); + } + // A series of deletes with delays in between for 300ms, which will + // intersect with the 100ms polling multiple times. + for (var i = 0; i != 300; ++i) { + deleteFile('$i'); + await Future.delayed(const Duration(milliseconds: 1)); + } + + final events = + await takeEvents(duration: const Duration(milliseconds: 500)); + + // Events should be adds and removes that pair up, with no modify events. + final adds = {}; + final removes = {}; + for (var event in events) { + if (event.type == ChangeType.ADD) { + adds.add(event.path); + } else if (event.type == ChangeType.REMOVE) { + removes.add(event.path); + } else { + fail('Unexpected event: $event'); + } + } + expect(adds, removes); + }); + }); + + // Also test with delayed writes and real mtimes. + group('with real mtime', () { + setUp(enableWaitingForDifferentModificationTimes); + + fileTests(); + linkTests(isNative: false); }); } diff --git a/pkgs/watcher/test/directory_watcher/windows_test.dart b/pkgs/watcher/test/directory_watcher/windows_test.dart index 709bcb59b..406a0755e 100644 --- a/pkgs/watcher/test/directory_watcher/windows_test.dart +++ b/pkgs/watcher/test/directory_watcher/windows_test.dart @@ -15,12 +15,14 @@ import 'package:watcher/src/directory_watcher/windows.dart'; import 'package:watcher/watcher.dart'; import '../utils.dart'; -import 'shared.dart'; +import 'file_tests.dart'; +import 'link_tests.dart'; void main() { watcherFactory = WindowsDirectoryWatcher.new; - group('Shared Tests:', sharedTests); + fileTests(); + linkTests(isNative: true); test('DirectoryWatcher creates a WindowsDirectoryWatcher on Windows', () { expect(DirectoryWatcher('.'), const TypeMatcher()); diff --git a/pkgs/watcher/test/file_watcher/link_tests.dart b/pkgs/watcher/test/file_watcher/link_tests.dart index 6b86cc236..8cb5e102d 100644 --- a/pkgs/watcher/test/file_watcher/link_tests.dart +++ b/pkgs/watcher/test/file_watcher/link_tests.dart @@ -20,13 +20,25 @@ void linkTests({required bool isNative}) { test('notifies when a link is overwritten with an identical file', () async { await startWatcher(path: 'link.txt'); writeFile('link.txt'); - await expectModifyEvent('link.txt'); + + // TODO(davidmorgan): reconcile differences. + if (isNative) { + await expectNoEvents(); + } else { + await expectModifyEvent('link.txt'); + } }); test('notifies when a link is overwritten with a different file', () async { await startWatcher(path: 'link.txt'); writeFile('link.txt', contents: 'modified'); - await expectModifyEvent('link.txt'); + + // TODO(davidmorgan): reconcile differences. + if (isNative) { + await expectNoEvents(); + } else { + await expectModifyEvent('link.txt'); + } }); test( @@ -35,12 +47,7 @@ void linkTests({required bool isNative}) { await startWatcher(path: 'link.txt'); writeFile('target.txt'); - // TODO(davidmorgan): reconcile differences. - if (isNative) { - await expectModifyEvent('link.txt'); - } else { - await expectNoEvents(); - } + await expectModifyEvent('link.txt'); }, ); @@ -48,17 +55,12 @@ void linkTests({required bool isNative}) { await startWatcher(path: 'link.txt'); writeFile('target.txt', contents: 'modified'); - // TODO(davidmorgan): reconcile differences. - if (isNative) { - await expectModifyEvent('link.txt'); - } else { - await expectNoEvents(); - } + await expectModifyEvent('link.txt'); }); test('notifies when a link is removed', () async { await startWatcher(path: 'link.txt'); - deleteFile('link.txt'); + deleteLink('link.txt'); // TODO(davidmorgan): reconcile differences. if (isNative) { @@ -79,21 +81,11 @@ void linkTests({required bool isNative}) { writeFile('target.txt', contents: 'modified'); - // TODO(davidmorgan): reconcile differences. - if (isNative) { - await expectModifyEvent('link.txt'); - } else { - await expectNoEvents(); - } + await expectModifyEvent('link.txt'); writeFile('target.txt', contents: 'modified again'); - // TODO(davidmorgan): reconcile differences. - if (isNative) { - await expectModifyEvent('link.txt'); - } else { - await expectNoEvents(); - } + await expectModifyEvent('link.txt'); }); test('notifies when a link is moved away', () async { @@ -145,12 +137,7 @@ void linkTests({required bool isNative}) { writeFile('old.txt'); renameFile('old.txt', 'target.txt'); - // TODO(davidmorgan): reconcile differences. - if (isNative) { - await expectModifyEvent('link.txt'); - } else { - await expectNoEvents(); - } + await expectModifyEvent('link.txt'); }); test('notifies when a different file is moved over the target', () async { @@ -158,11 +145,6 @@ void linkTests({required bool isNative}) { writeFile('old.txt', contents: 'modified'); renameFile('old.txt', 'target.txt'); - // TODO(davidmorgan): reconcile differences. - if (isNative) { - await expectModifyEvent('link.txt'); - } else { - await expectNoEvents(); - } + await expectModifyEvent('link.txt'); }); } diff --git a/pkgs/watcher/test/file_watcher/polling_test.dart b/pkgs/watcher/test/file_watcher/polling_test.dart index c1590e783..3acb8a8ff 100644 --- a/pkgs/watcher/test/file_watcher/polling_test.dart +++ b/pkgs/watcher/test/file_watcher/polling_test.dart @@ -2,6 +2,7 @@ // for details. 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:test/test.dart'; import 'package:watcher/watcher.dart'; import '../utils.dart'; @@ -13,7 +14,21 @@ void main() { watcherFactory = (file) => PollingFileWatcher(file, pollingDelay: const Duration(milliseconds: 100)); - fileTests(isNative: false); - linkTests(isNative: false); - startupRaceTests(isNative: false); + // Filesystem modification times can be low resolution, mock them. + group('with mock mtime', () { + setUp(enableMockModificationTimes); + + fileTests(isNative: false); + linkTests(isNative: false); + startupRaceTests(isNative: false); + }); + +// Also test with delayed writes and real mtimes. + group('with real mtime', () { + setUp(enableWaitingForDifferentModificationTimes); + fileTests(isNative: false); + linkTests(isNative: false); + // Don't run `startupRaceTests`, polling can't have a race and the test is + // too slow on Windows when waiting for modification times. + }); } diff --git a/pkgs/watcher/test/utils.dart b/pkgs/watcher/test/utils.dart index 6f61f9a8b..354387c56 100644 --- a/pkgs/watcher/test/utils.dart +++ b/pkgs/watcher/test/utils.dart @@ -28,7 +28,11 @@ set watcherFactory(WatcherFactory factory) { /// /// Instead, we'll just mock that out. Each time a file is written, we manually /// increment the mod time for that file instantly. -final _mockFileModificationTimes = {}; +Map? _mockFileModificationTimes; + +/// If real modification times are used, a directory where a test file will be +/// updated to wait for a new modification time. +Directory? _waitForModificationTimesDirectory; late WatcherFactory _watcherFactory; @@ -54,13 +58,62 @@ late StreamQueue _watcherEvents; /// be done automatically via [addTearDown] in [startWatcher]. var _hasClosedStream = true; -/// Creates a new [Watcher] that watches a temporary file or directory and -/// starts monitoring it for events. +/// Enables waiting before writes to ensure a different modification time. /// -/// If [path] is provided, watches a path in the sandbox with that name. -Future startWatcher({String? path}) async { +/// This will allow polling watchers to notice all writes. +/// +/// Resets at the end of the test. +void enableWaitingForDifferentModificationTimes() { + if (_waitForModificationTimesDirectory != null) return; + _waitForModificationTimesDirectory = + Directory.systemTemp.createTempSync('dart_test_'); + addTearDown(() { + _waitForModificationTimesDirectory!.deleteSync(recursive: true); + _waitForModificationTimesDirectory = null; + }); +} + +/// If [enableWaitingForDifferentModificationTimes] was called, sleeps until a +/// modified file has a new modified timestamp. +void _maybeWaitForDifferentModificationTime() { + if (_waitForModificationTimesDirectory == null) return; + var file = File(p.join(_waitForModificationTimesDirectory!.path, 'file')); + if (file.existsSync()) file.deleteSync(); + file.createSync(); + final time = file.statSync().modified; + while (true) { + file.deleteSync(); + file.createSync(); + final updatedTime = file.statSync().modified; + if (time != updatedTime) { + return; + } + sleep(const Duration(milliseconds: 1)); + } +} + +/// Enables mock modification times so that all writes set a different +/// modification time. +/// +/// This will allow polling watchers to notice all writes. +/// +/// Resets at the end of the test. +void enableMockModificationTimes() { + _mockFileModificationTimes = {}; mockGetModificationTime((path) { - final normalized = p.normalize(p.relative(path, from: d.sandbox)); + // Resolve symbolic links before looking up mtime to match documented + // behavior of `FileSystemEntity.stat`. + final link = Link(path); + if (link.existsSync()) { + path = link.resolveSymbolicLinksSync(); + } + // Also resolve symbolic links in the enclosing directory. + final file = File(path); + if (file.existsSync()) { + path = file.resolveSymbolicLinksSync(); + } + + var normalized = p.normalize(p.relative(path, from: d.sandbox)); // Make sure we got a path in the sandbox. if (!p.isRelative(normalized) || normalized.startsWith('..')) { @@ -70,10 +123,21 @@ Future startWatcher({String? path}) async { 'Path is not in the sandbox: $path not in ${d.sandbox}', ); } - var mtime = _mockFileModificationTimes[normalized]; + final mockFileModificationTimes = _mockFileModificationTimes!; + var mtime = mockFileModificationTimes[normalized]; return mtime != null ? DateTime.fromMillisecondsSinceEpoch(mtime) : null; }); + addTearDown(() { + _mockFileModificationTimes = null; + mockGetModificationTime(null); + }); +} +/// Creates a new [Watcher] that watches a temporary file or directory and +/// starts monitoring it for events. +/// +/// If [path] is provided, watches a path in the sandbox with that name. +Future startWatcher({String? path}) async { // We want to wait until we're ready *after* we subscribe to the watcher's // events. var watcher = createWatcher(path: path); @@ -190,11 +254,24 @@ Future waitForEvent({ return result; } -/// Expects that no events are omitted for [duration]. +/// Expects that no events are emitted for [duration]. Future expectNoEvents({Duration duration = const Duration(seconds: 1)}) async { expect(await waitForEvent(duration: duration), isNull); } +/// Takes all events emitted for [duration]. +Future> takeEvents({required Duration duration}) async { + final result = []; + final stopwatch = Stopwatch()..start(); + while (stopwatch.elapsed < duration) { + final event = await waitForEvent(duration: duration - stopwatch.elapsed); + if (event != null) { + result.add(event); + } + } + return result; +} + /// Expects that the next event emitted will be for an add event for [path]. Future expectAddEvent(String path) => _expectOrCollect(isWatchEvent(ChangeType.ADD, path)); @@ -221,11 +298,18 @@ Future allowModifyEvent(String path) => /// set back to a previously used value. int _nextTimestamp = 1; -/// Schedules writing a file in the sandbox at [path] with [contents]. +/// Writes a file in the sandbox at [path] with [contents]. +/// +/// If [path] is currently a link it is deleted and a file is written in its +/// place. /// -/// If [contents] is omitted, creates an empty file. If [updateModified] is -/// `false`, the mock file modification time is not changed. +/// If [contents] is omitted, creates an empty file. +/// +/// If [updateModified] is `false` and mock modification times are in use, the +/// mock file modification time is not changed. void writeFile(String path, {String? contents, bool? updateModified}) { + _maybeWaitForDifferentModificationTime(); + contents ??= ''; updateModified ??= true; @@ -237,24 +321,38 @@ void writeFile(String path, {String? contents, bool? updateModified}) { dir.createSync(recursive: true); } - File(fullPath).writeAsStringSync(contents); + var file = File(fullPath); + // `File.writeAsStringSync` would write through the link, so if there is a + // link then start by deleting it. + if (FileSystemEntity.typeSync(fullPath, followLinks: false) == + FileSystemEntityType.link) { + file.deleteSync(); + } + file.writeAsStringSync(contents); + // Check that `fullPath` now refers to a file, not a link. + expect(FileSystemEntity.typeSync(fullPath), FileSystemEntityType.file); - if (updateModified) { + final mockFileModificationTimes = _mockFileModificationTimes; + if (mockFileModificationTimes != null && updateModified) { path = p.normalize(path); - _mockFileModificationTimes[path] = _nextTimestamp++; + mockFileModificationTimes[path] = _nextTimestamp++; } } -/// Schedules writing a file in the sandbox at [link] pointing to [target]. +/// Writes a file in the sandbox at [link] pointing to [target]. +/// +/// [target] is relative to the sandbox, not to [link]. /// -/// If [updateModified] is `false`, the mock file modification time is not -/// changed. +/// If [updateModified] is `false` and mock modification times are in use, the +/// mock file modification time is not changed. void writeLink({ required String link, required String target, bool? updateModified, }) { + _maybeWaitForDifferentModificationTime(); + updateModified ??= true; var fullPath = p.join(d.sandbox, link); @@ -265,70 +363,117 @@ void writeLink({ dir.createSync(recursive: true); } - Link(fullPath).createSync(target); + Link(fullPath).createSync(p.join(d.sandbox, target)); if (updateModified) { link = p.normalize(link); - _mockFileModificationTimes[link] = _nextTimestamp++; + final mockFileModificationTimes = _mockFileModificationTimes; + if (mockFileModificationTimes != null) { + mockFileModificationTimes[link] = _nextTimestamp++; + } } } -/// Schedules deleting a file in the sandbox at [path]. +/// Deletes a file in the sandbox at [path]. void deleteFile(String path) { - File(p.join(d.sandbox, path)).deleteSync(); - - _mockFileModificationTimes.remove(path); + final fullPath = p.join(d.sandbox, path); + expect(FileSystemEntity.typeSync(fullPath, followLinks: false), + FileSystemEntityType.file); + File(fullPath).deleteSync(); + + final mockFileModificationTimes = _mockFileModificationTimes; + if (mockFileModificationTimes != null) { + mockFileModificationTimes.remove(path); + } } -/// Schedules renaming a file in the sandbox from [from] to [to]. -void renameFile(String from, String to) { - File(p.join(d.sandbox, from)).renameSync(p.join(d.sandbox, to)); +/// Deletes a link in the sandbox at [path]. +void deleteLink(String path) { + final fullPath = p.join(d.sandbox, path); + expect(FileSystemEntity.typeSync(fullPath, followLinks: false), + FileSystemEntityType.link); + Link(fullPath).deleteSync(); - // Make sure we always use the same separator on Windows. - to = p.normalize(to); + final mockFileModificationTimes = _mockFileModificationTimes; + if (mockFileModificationTimes != null) { + mockFileModificationTimes.remove(path); + } +} - _mockFileModificationTimes.update(to, (value) => value + 1, - ifAbsent: () => 1); +/// Renames a file in the sandbox from [from] to [to]. +void renameFile(String from, String to) { + _maybeWaitForDifferentModificationTime(); + + var absoluteTo = p.join(d.sandbox, to); + File(p.join(d.sandbox, from)).renameSync(absoluteTo); + expect(FileSystemEntity.typeSync(absoluteTo, followLinks: false), + FileSystemEntityType.file); + + final mockFileModificationTimes = _mockFileModificationTimes; + if (mockFileModificationTimes != null) { + // Make sure we always use the same separator on Windows. + to = p.normalize(to); + mockFileModificationTimes.update(to, (value) => value + 1, + ifAbsent: () => 1); + } } -/// Schedules renaming a link in the sandbox from [from] to [to]. +/// Renames a link in the sandbox from [from] to [to]. /// /// On MacOS and Linux links can also be named with `renameFile`. On Windows, /// however, a link must be renamed with this method. void renameLink(String from, String to) { - Link(p.join(d.sandbox, from)).renameSync(p.join(d.sandbox, to)); - - // Make sure we always use the same separator on Windows. - to = p.normalize(to); - - _mockFileModificationTimes.update(to, (value) => value + 1, - ifAbsent: () => 1); + _maybeWaitForDifferentModificationTime(); + + var absoluteTo = p.join(d.sandbox, to); + Link(p.join(d.sandbox, from)).renameSync(absoluteTo); + expect(FileSystemEntity.typeSync(absoluteTo, followLinks: false), + FileSystemEntityType.link); + + final mockFileModificationTimes = _mockFileModificationTimes; + if (mockFileModificationTimes != null) { + // Make sure we always use the same separator on Windows. + to = p.normalize(to); + mockFileModificationTimes.update(to, (value) => value + 1, + ifAbsent: () => 1); + } } -/// Schedules creating a directory in the sandbox at [path]. +/// Creates a directory in the sandbox at [path]. void createDir(String path) { Directory(p.join(d.sandbox, path)).createSync(); } -/// Schedules renaming a directory in the sandbox from [from] to [to]. +/// Renames a directory in the sandbox from [from] to [to]. void renameDir(String from, String to) { - Directory(p.join(d.sandbox, from)).renameSync(p.join(d.sandbox, to)); - - // Migrate timestamps for any files in this folder. - final knownFilePaths = _mockFileModificationTimes.keys.toList(); - for (final filePath in knownFilePaths) { - if (p.isWithin(from, filePath)) { - _mockFileModificationTimes[filePath.replaceAll(from, to)] = - _mockFileModificationTimes[filePath]!; - _mockFileModificationTimes.remove(filePath); + var absoluteTo = p.join(d.sandbox, to); + Directory(p.join(d.sandbox, from)).renameSync(absoluteTo); + expect(FileSystemEntity.typeSync(absoluteTo, followLinks: false), + FileSystemEntityType.directory); + + final mockFileModificationTimes = _mockFileModificationTimes; + if (mockFileModificationTimes != null) { + // Migrate timestamps for any files in this folder. + final knownFilePaths = mockFileModificationTimes.keys.toList(); + for (final filePath in knownFilePaths) { + if (p.isWithin(from, filePath)) { + final movedPath = + p.normalize(p.join(to, filePath.substring(from.length + 1))); + mockFileModificationTimes[movedPath] = + mockFileModificationTimes[filePath]!; + mockFileModificationTimes.remove(filePath); + } } } } -/// Schedules deleting a directory in the sandbox at [path]. +/// Deletes a directory in the sandbox at [path]. void deleteDir(String path) { - Directory(p.join(d.sandbox, path)).deleteSync(recursive: true); + final fullPath = p.join(d.sandbox, path); + expect(FileSystemEntity.typeSync(fullPath, followLinks: false), + FileSystemEntityType.directory); + Directory(fullPath).deleteSync(recursive: true); } /// Runs [callback] with every permutation of non-negative numbers for each