| // Copyright (c) 2019, 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 'package:dev_compiler/dev_compiler.dart' show Compiler; | |
| import 'package:frontend_server/src/javascript_bundle.dart'; | |
| import 'package:kernel/ast.dart'; | |
| import 'package:kernel/class_hierarchy.dart'; | |
| import 'package:kernel/core_types.dart'; | |
| import 'package:package_config/package_config.dart'; | |
| import 'package:test/test.dart'; | |
| /// Additional indexed types required by the dev_compiler's NativeTypeSet. | |
| final Map<String, Map<String, List<String>>> additionalRequiredClasses = { | |
| 'dart:core': {'Comparable': []}, | |
| 'dart:async': { | |
| 'StreamIterator': ['get:current', 'moveNext', 'cancel', ''], | |
| '_SyncStarIterator': ['_current', '_datum', '_yieldStar'], | |
| '_IterationMarker': ['yieldSingle', 'yieldStar'], | |
| }, | |
| 'dart:_interceptors': { | |
| 'JSBool': [], | |
| 'JSNumber': [], | |
| 'JSArray': [], | |
| 'JSString': [], | |
| 'LegacyJavaScriptObject': [], | |
| }, | |
| 'dart:_native_typed_data': {}, | |
| 'dart:collection': { | |
| 'ListBase': [], | |
| 'MapBase': [], | |
| 'LinkedHashSet': [], | |
| '_HashSet': [], | |
| '_IdentityHashSet': [], | |
| }, | |
| 'dart:math': {'Rectangle': []}, | |
| 'dart:html': {}, | |
| 'dart:_rti': {'Rti': [], '_Universe': []}, | |
| 'dart:indexed_db': {}, | |
| 'dart:svg': {}, | |
| 'dart:web_audio': {}, | |
| 'dart:web_gl': {}, | |
| 'dart:_js_helper': { | |
| 'PrivateSymbol': [], | |
| 'LinkedMap': [], | |
| 'IdentityMap': [], | |
| 'LinkedSet': [], | |
| 'IdentitySet': [], | |
| 'SyncIterable': [], | |
| }, | |
| }; | |
| /// Additional indexed top level methods required by the dev_compiler. | |
| final Map<String, List<String>> requiredTopLevels = { | |
| 'dart:_runtime': ['assertInterop'], | |
| 'dart:async': [ | |
| '_asyncAwait', | |
| '_asyncReturn', | |
| '_asyncRethrow', | |
| '_asyncStarHelper', | |
| '_asyncStartSync', | |
| '_makeAsyncAwaitCompleter', | |
| '_makeSyncStarIterable', | |
| '_makeAsyncStarStreamController', | |
| '_makeSyncStarIterable', | |
| '_streamOfController', | |
| '_wrapJsFunctionForAsync', | |
| ], | |
| 'dart:_rti': [ | |
| '_asBool', | |
| '_asDouble', | |
| '_asInt', | |
| '_asNum', | |
| '_asObject', | |
| '_asString', | |
| '_asBoolQ', | |
| '_asDoubleQ', | |
| '_asIntQ', | |
| '_asNumQ', | |
| '_asStringQ', | |
| ], | |
| }; | |
| void main() { | |
| Map<String, List<String>> allRequiredTypes = _combineMaps( | |
| CoreTypes.requiredClasses, | |
| additionalRequiredClasses | |
| .map((k, v) => new MapEntry(k, v.keys.toList()))); | |
| List<String> allRequiredLibraries = [ | |
| ...allRequiredTypes.keys, | |
| ...requiredTopLevels.keys, | |
| ]; | |
| List<Library> testCoreLibraries = []; | |
| for (String requiredLibrary in allRequiredLibraries) { | |
| Library library = new Library(Uri.parse(requiredLibrary), | |
| fileUri: Uri.parse(requiredLibrary)); | |
| for (String requiredClass in allRequiredTypes[requiredLibrary] ?? []) { | |
| library.addClass(new Class( | |
| name: requiredClass, | |
| fileUri: Uri.parse(requiredLibrary), | |
| procedures: [ | |
| for (String requiredMember | |
| in (additionalRequiredClasses[requiredLibrary] | |
| ?[requiredClass] ?? | |
| [])) | |
| new Procedure(new Name(requiredMember, library), | |
| ProcedureKind.Method, new FunctionNode(new EmptyStatement()), | |
| fileUri: Uri.parse(requiredLibrary)) | |
| ])); | |
| } | |
| for (String requiredMember in requiredTopLevels[requiredLibrary] ?? []) { | |
| library.addProcedure(new Procedure(new Name(requiredMember, library), | |
| ProcedureKind.Method, new FunctionNode(new EmptyStatement()), | |
| fileUri: Uri.parse(requiredLibrary))); | |
| } | |
| testCoreLibraries.add(library); | |
| } | |
| final PackageConfig packageConfig = PackageConfig.parseJson({ | |
| 'configVersion': 2, | |
| 'packages': [ | |
| { | |
| 'name': 'a', | |
| 'rootUri': 'file:///pkg/a', | |
| 'packageUri': 'lib/', | |
| } | |
| ], | |
| }, Uri.base); | |
| final String multiRootScheme = 'org-dartlang-app'; | |
| for (final bool debuggerNames in [true, false]) { | |
| group('Debugger module names: $debuggerNames |', () { | |
| test('Creates component uris for file paths', () async { | |
| final Uri fileUri = new Uri.file('/c.dart'); | |
| final IncrementalJavaScriptBundler javaScriptBundler = | |
| new IncrementalJavaScriptBundler( | |
| null, | |
| {}, | |
| multiRootScheme, | |
| useDebuggerModuleNames: debuggerNames, | |
| ); | |
| final String componentUrl = | |
| javaScriptBundler.urlForComponentUri(fileUri, packageConfig); | |
| final String libraryBundleName = | |
| javaScriptBundler.makeLibraryBundleName(componentUrl); | |
| expect(componentUrl, '/c.dart'); | |
| expect(libraryBundleName, 'c.dart'); | |
| }); | |
| test('Creates component uris for package paths', () async { | |
| final Uri packageUri = Uri.parse('package:a/a.dart'); | |
| final IncrementalJavaScriptBundler javaScriptBundler = | |
| new IncrementalJavaScriptBundler( | |
| null, | |
| {}, | |
| multiRootScheme, | |
| useDebuggerModuleNames: debuggerNames, | |
| ); | |
| final String componentUrl = | |
| javaScriptBundler.urlForComponentUri(packageUri, packageConfig); | |
| final String libraryBundleName = | |
| javaScriptBundler.makeLibraryBundleName(componentUrl); | |
| expect(componentUrl, | |
| debuggerNames ? 'packages/a/lib/a.dart' : '/packages/a/a.dart'); | |
| expect(libraryBundleName, | |
| debuggerNames ? 'packages/a/lib/a.dart' : 'packages/a/a.dart'); | |
| }); | |
| test('compiles JavaScript code', () async { | |
| final Uri uri = new Uri.file('/c.dart'); | |
| final Library library = new Library( | |
| uri, | |
| fileUri: uri, | |
| procedures: [ | |
| new Procedure(new Name('ArbitrarilyChosen'), ProcedureKind.Method, | |
| new FunctionNode(new Block([])), | |
| fileUri: uri) | |
| ], | |
| ); | |
| final Component testComponent = | |
| new Component(libraries: [library, ...testCoreLibraries]); | |
| final IncrementalJavaScriptBundler javaScriptBundler = | |
| new IncrementalJavaScriptBundler( | |
| null, | |
| {}, | |
| multiRootScheme, | |
| useDebuggerModuleNames: debuggerNames, | |
| ); | |
| await javaScriptBundler.initialize(testComponent, uri, packageConfig); | |
| final _MemorySink manifestSink = new _MemorySink(); | |
| final _MemorySink codeSink = new _MemorySink(); | |
| final _MemorySink sourcemapSink = new _MemorySink(); | |
| final _MemorySink metadataSink = new _MemorySink(); | |
| final _MemorySink symbolsSink = new _MemorySink(); | |
| final CoreTypes coreTypes = new CoreTypes(testComponent); | |
| final Map<String, Compiler> compilers = await javaScriptBundler.compile( | |
| new ClassHierarchy(testComponent, coreTypes), | |
| coreTypes, | |
| packageConfig, | |
| codeSink, | |
| manifestSink, | |
| sourcemapSink, | |
| metadataSink, | |
| symbolsSink, | |
| ); | |
| final Map<String, dynamic> manifest = | |
| json.decode(utf8.decode(manifestSink.buffer)); | |
| final String code = utf8.decode(codeSink.buffer); | |
| expect(manifest, { | |
| '/c.dart.lib.js': { | |
| 'code': [0, codeSink.buffer.length], | |
| 'sourcemap': [0, sourcemapSink.buffer.length], | |
| }, | |
| }); | |
| expect(code, contains('ArbitrarilyChosen')); | |
| // Verify source map url is correct. | |
| expect(code, contains('sourceMappingURL=c.dart.lib.js.map')); | |
| // Verify program compilers are created. | |
| expect(compilers.keys, equals([library.importUri.toString()])); | |
| }); | |
| test('converts package: uris into /packages/ uris', () async { | |
| Uri importUri = Uri.parse('package:a/a.dart'); | |
| Uri fileUri = packageConfig.resolve(importUri)!; | |
| final Library library = new Library( | |
| importUri, | |
| fileUri: fileUri, | |
| procedures: [ | |
| new Procedure(new Name('ArbitrarilyChosen'), ProcedureKind.Method, | |
| new FunctionNode(new Block([])), | |
| fileUri: fileUri) | |
| ], | |
| ); | |
| final Component testComponent = | |
| new Component(libraries: [library, ...testCoreLibraries]); | |
| final IncrementalJavaScriptBundler javaScriptBundler = | |
| new IncrementalJavaScriptBundler( | |
| null, | |
| {}, | |
| multiRootScheme, | |
| useDebuggerModuleNames: debuggerNames, | |
| ); | |
| await javaScriptBundler.initialize( | |
| testComponent, importUri, packageConfig); | |
| final _MemorySink manifestSink = new _MemorySink(); | |
| final _MemorySink codeSink = new _MemorySink(); | |
| final _MemorySink sourcemapSink = new _MemorySink(); | |
| final _MemorySink metadataSink = new _MemorySink(); | |
| final _MemorySink symbolsSink = new _MemorySink(); | |
| final CoreTypes coreTypes = new CoreTypes(testComponent); | |
| await javaScriptBundler.compile( | |
| new ClassHierarchy(testComponent, coreTypes), | |
| coreTypes, | |
| packageConfig, | |
| codeSink, | |
| manifestSink, | |
| sourcemapSink, | |
| metadataSink, | |
| symbolsSink, | |
| ); | |
| final Map<String, dynamic> manifest = | |
| json.decode(utf8.decode(manifestSink.buffer)); | |
| final String code = utf8.decode(codeSink.buffer); | |
| final String componentUrl = javaScriptBundler.urlForComponentUri( | |
| library.importUri, packageConfig); | |
| expect(manifest, { | |
| '$componentUrl.lib.js': { | |
| 'code': [0, codeSink.buffer.length], | |
| 'sourcemap': [0, sourcemapSink.buffer.length], | |
| }, | |
| }); | |
| expect(code, contains('ArbitrarilyChosen')); | |
| // Verify source map url is correct. | |
| expect(code, contains('sourceMappingURL=a.dart.lib.js.map')); | |
| }); | |
| test('multi-root uris create library bundles relative to the root', | |
| () async { | |
| Uri importUri = Uri.parse('$multiRootScheme:/web/main.dart'); | |
| Uri fileUri = importUri; | |
| final Library library = new Library( | |
| importUri, | |
| fileUri: fileUri, | |
| procedures: [ | |
| new Procedure(new Name('ArbitrarilyChosen'), ProcedureKind.Method, | |
| new FunctionNode(new Block([])), | |
| fileUri: fileUri) | |
| ], | |
| ); | |
| final Component testComponent = | |
| new Component(libraries: [library, ...testCoreLibraries]); | |
| final IncrementalJavaScriptBundler javaScriptBundler = | |
| new IncrementalJavaScriptBundler( | |
| null, | |
| {}, | |
| multiRootScheme, | |
| useDebuggerModuleNames: debuggerNames, | |
| ); | |
| await javaScriptBundler.initialize( | |
| testComponent, importUri, packageConfig); | |
| final _MemorySink manifestSink = new _MemorySink(); | |
| final _MemorySink codeSink = new _MemorySink(); | |
| final _MemorySink sourcemapSink = new _MemorySink(); | |
| final _MemorySink metadataSink = new _MemorySink(); | |
| final _MemorySink symbolsSink = new _MemorySink(); | |
| final CoreTypes coreTypes = new CoreTypes(testComponent); | |
| await javaScriptBundler.compile( | |
| new ClassHierarchy(testComponent, coreTypes), | |
| coreTypes, | |
| packageConfig, | |
| codeSink, | |
| manifestSink, | |
| sourcemapSink, | |
| metadataSink, | |
| symbolsSink, | |
| ); | |
| final Map manifest = json.decode(utf8.decode(manifestSink.buffer)); | |
| final String code = utf8.decode(codeSink.buffer); | |
| expect(manifest, { | |
| '${importUri.path}.lib.js': { | |
| 'code': [0, codeSink.buffer.length], | |
| 'sourcemap': [0, sourcemapSink.buffer.length], | |
| }, | |
| }); | |
| expect(code, contains('ArbitrarilyChosen')); | |
| // Verify source map url is correct. | |
| expect(code, contains('sourceMappingURL=main.dart.lib.js.map')); | |
| }); | |
| }); | |
| } | |
| test('can combine strongly connected components', () async { | |
| // Create three libraries A, B, C where A is the entrypoint and B & C | |
| // circularly import each other. | |
| final Library libraryC = | |
| new Library(new Uri.file('/c.dart'), fileUri: new Uri.file('/c.dart')); | |
| final Library libraryB = | |
| new Library(new Uri.file('/b.dart'), fileUri: new Uri.file('/b.dart')); | |
| libraryC.dependencies.add(new LibraryDependency.import(libraryB)); | |
| libraryB.dependencies.add(new LibraryDependency.import(libraryC)); | |
| final Uri uriA = new Uri.file('/a.dart'); | |
| final Library libraryA = new Library( | |
| uriA, | |
| fileUri: uriA, | |
| dependencies: [ | |
| new LibraryDependency.import(libraryB), | |
| ], | |
| procedures: [ | |
| new Procedure(new Name('ArbitrarilyChosen'), ProcedureKind.Method, | |
| new FunctionNode(new Block([])), | |
| fileUri: uriA) | |
| ], | |
| ); | |
| final Component testComponent = new Component( | |
| libraries: [libraryA, libraryB, libraryC, ...testCoreLibraries]); | |
| final IncrementalJavaScriptBundler javaScriptBundler = | |
| new IncrementalJavaScriptBundler( | |
| null, | |
| {}, | |
| multiRootScheme, | |
| ); | |
| await javaScriptBundler.initialize(testComponent, uriA, packageConfig); | |
| final _MemorySink manifestSink = new _MemorySink(); | |
| final _MemorySink codeSink = new _MemorySink(); | |
| final _MemorySink sourcemapSink = new _MemorySink(); | |
| final _MemorySink metadataSink = new _MemorySink(); | |
| final _MemorySink symbolsSink = new _MemorySink(); | |
| final CoreTypes coreTypes = new CoreTypes(testComponent); | |
| await javaScriptBundler.compile( | |
| new ClassHierarchy(testComponent, coreTypes), | |
| coreTypes, | |
| packageConfig, | |
| codeSink, | |
| manifestSink, | |
| sourcemapSink, | |
| metadataSink, | |
| symbolsSink, | |
| ); | |
| final String code = utf8.decode(codeSink.buffer); | |
| final Map<String, dynamic> manifest = | |
| json.decode(utf8.decode(manifestSink.buffer)); | |
| // There should only be two modules since C and B should be combined. | |
| final String moduleHeader = r"define(['dart_sdk'], (function load__"; | |
| expect(moduleHeader.allMatches(code), hasLength(2)); | |
| // Expected module headers. | |
| final String aModuleHeader = | |
| r"define(['dart_sdk'], (function load__a_dart(dart_sdk) {"; | |
| expect(code, contains(aModuleHeader)); | |
| final String cModuleHeader = | |
| r"define(['dart_sdk'], (function load__c_dart(dart_sdk) {"; | |
| expect(code, contains(cModuleHeader)); | |
| // Verify source map url is correct. | |
| expect(code, contains('sourceMappingURL=a.dart.lib.js.map')); | |
| final List<dynamic> offsets = manifest['/a.dart.lib.js']['sourcemap']; | |
| final Map<String, dynamic> sourcemapModuleA = json.decode( | |
| utf8.decode(sourcemapSink.buffer.sublist(offsets.first, offsets.last))); | |
| // Verify source maps are pointing at correct source files. | |
| expect(sourcemapModuleA['file'], 'a.dart.lib.js'); | |
| }); | |
| test('can invalidate changes to strongly connected components', () async { | |
| final Uri uriA = new Uri.file('/a.dart'); | |
| final Uri uriB = new Uri.file('/b.dart'); | |
| final Uri uriC = new Uri.file('/c.dart'); | |
| // Create three libraries A, B, C where A is the entrypoint and B & C | |
| // circularly import each other. | |
| final Library libraryC = new Library(uriC, fileUri: uriC); | |
| final Library libraryB = new Library(uriB, fileUri: uriB); | |
| libraryC.dependencies.add(new LibraryDependency.import(libraryB)); | |
| libraryB.dependencies.add(new LibraryDependency.import(libraryC)); | |
| final Library libraryA = new Library( | |
| uriA, | |
| fileUri: uriA, | |
| dependencies: [ | |
| new LibraryDependency.import(libraryB), | |
| ], | |
| procedures: [ | |
| new Procedure(new Name('ArbitrarilyChosen'), ProcedureKind.Method, | |
| new FunctionNode(new Block([])), | |
| fileUri: uriA) | |
| ], | |
| ); | |
| final Component testComponent = new Component( | |
| libraries: [libraryA, libraryB, libraryC, ...testCoreLibraries]); | |
| final IncrementalJavaScriptBundler javaScriptBundler = | |
| new IncrementalJavaScriptBundler( | |
| null, | |
| {}, | |
| multiRootScheme, | |
| ); | |
| await javaScriptBundler.initialize(testComponent, uriA, packageConfig); | |
| // Now change A and B so that they no longer import each other. | |
| final Library libraryC2 = new Library(uriC, fileUri: uriC); | |
| final Library libraryB2 = new Library(uriB, fileUri: uriB); | |
| libraryB2.dependencies.add(new LibraryDependency.import(libraryC2)); | |
| final Library libraryA2 = new Library( | |
| uriA, | |
| fileUri: uriA, | |
| dependencies: [ | |
| new LibraryDependency.import(libraryB2), | |
| ], | |
| procedures: [ | |
| new Procedure(new Name('ArbitrarilyChosen'), ProcedureKind.Method, | |
| new FunctionNode(new Block([])), | |
| fileUri: uriA) | |
| ], | |
| ); | |
| final Component partialComponent = | |
| new Component(libraries: [libraryA2, libraryB2, libraryC2]); | |
| await javaScriptBundler.invalidate( | |
| partialComponent, testComponent, uriA, packageConfig, | |
| recompileRestart: false); | |
| final _MemorySink manifestSink = new _MemorySink(); | |
| final _MemorySink codeSink = new _MemorySink(); | |
| final _MemorySink sourcemapSink = new _MemorySink(); | |
| final _MemorySink metadataSink = new _MemorySink(); | |
| final _MemorySink symbolsSink = new _MemorySink(); | |
| final CoreTypes coreTypes = new CoreTypes(testComponent); | |
| await javaScriptBundler.compile( | |
| new ClassHierarchy(testComponent, coreTypes), | |
| coreTypes, | |
| packageConfig, | |
| codeSink, | |
| manifestSink, | |
| sourcemapSink, | |
| metadataSink, | |
| symbolsSink, | |
| ); | |
| final String code = utf8.decode(codeSink.buffer); | |
| final Map<String, dynamic> manifest = | |
| json.decode(utf8.decode(manifestSink.buffer)); | |
| // There should be 3 modules since A, B, C are now compiled separately | |
| final String moduleHeader = r"define(['dart_sdk'], (function load__"; | |
| expect(moduleHeader.allMatches(code), hasLength(3)); | |
| // Expected module headers. | |
| final String aModuleHeader = | |
| r"define(['dart_sdk'], (function load__a_dart(dart_sdk) {"; | |
| expect(code, contains(aModuleHeader)); | |
| final String bModuleHeader = | |
| r"define(['dart_sdk'], (function load__b_dart(dart_sdk) {"; | |
| expect(code, contains(bModuleHeader)); | |
| final String cModuleHeader = | |
| r"define(['dart_sdk'], (function load__c_dart(dart_sdk) {"; | |
| expect(code, contains(cModuleHeader)); | |
| // Verify source map url is correct. | |
| expect(code, contains('sourceMappingURL=a.dart.lib.js.map')); | |
| final List<dynamic> offsets = manifest['/a.dart.lib.js']['sourcemap']; | |
| final Map<String, dynamic> sourcemapModuleA = json.decode( | |
| utf8.decode(sourcemapSink.buffer.sublist(offsets.first, offsets.last))); | |
| // Verify source maps are pointing at correct source files. | |
| expect(sourcemapModuleA['file'], 'a.dart.lib.js'); | |
| }); | |
| test('can compile using the advanced invalidation', () async { | |
| final Uri uriC = new Uri.file('/c.dart'); | |
| // Given 3 libraries A -> B -> C | |
| final Library libraryC = new Library( | |
| uriC, | |
| fileUri: uriC, | |
| procedures: [ | |
| new Procedure(new Name('CheckForContents'), ProcedureKind.Method, | |
| new FunctionNode(new Block([])), | |
| fileUri: uriC) | |
| ], | |
| ); | |
| final Uri uriB = new Uri.file('/b.dart'); | |
| final Library libraryB = new Library(uriB, fileUri: uriB); | |
| libraryB.dependencies.add(new LibraryDependency.import(libraryC)); | |
| final Uri uriA = new Uri.file('/a.dart'); | |
| final Library libraryA = new Library( | |
| uriA, | |
| fileUri: uriA, | |
| dependencies: [ | |
| new LibraryDependency.import(libraryB), | |
| ], | |
| procedures: [ | |
| new Procedure(new Name('ArbitrarilyChosen'), ProcedureKind.Method, | |
| new FunctionNode(new Block([])), | |
| fileUri: uriA) | |
| ], | |
| ); | |
| final Component testComponent = new Component( | |
| libraries: [libraryA, libraryB, libraryC, ...testCoreLibraries]); | |
| final IncrementalJavaScriptBundler javaScriptBundler = | |
| new IncrementalJavaScriptBundler( | |
| null, | |
| {}, | |
| multiRootScheme, | |
| ); | |
| await javaScriptBundler.initialize(testComponent, uriA, packageConfig); | |
| // Create a new component that only contains C. | |
| final Library libraryC2 = new Library( | |
| uriC, | |
| fileUri: uriC, | |
| procedures: [ | |
| new Procedure(new Name('AlternativeContents'), ProcedureKind.Method, | |
| new FunctionNode(new Block([])), | |
| fileUri: uriC) | |
| ], | |
| ); | |
| final Component partialComponent = new Component(libraries: [libraryC2]); | |
| await javaScriptBundler.invalidate( | |
| partialComponent, testComponent, uriA, packageConfig, | |
| recompileRestart: false); | |
| final _MemorySink manifestSink = new _MemorySink(); | |
| final _MemorySink codeSink = new _MemorySink(); | |
| final _MemorySink sourcemapSink = new _MemorySink(); | |
| final _MemorySink metadataSink = new _MemorySink(); | |
| final _MemorySink symbolsSink = new _MemorySink(); | |
| final CoreTypes coreTypes = new CoreTypes(testComponent); | |
| await javaScriptBundler.compile( | |
| new ClassHierarchy(testComponent, coreTypes), | |
| coreTypes, | |
| packageConfig, | |
| codeSink, | |
| manifestSink, | |
| sourcemapSink, | |
| metadataSink, | |
| symbolsSink, | |
| ); | |
| final String code = utf8.decode(codeSink.buffer); | |
| // There should be only a single module. | |
| final String moduleHeader = r"define(['dart_sdk'], (function load__"; | |
| expect(moduleHeader.allMatches(code), hasLength(1)); | |
| // C source code should be updated. | |
| expect(code, contains('AlternativeContents')); | |
| }); | |
| } | |
| class _MemorySink implements IOSink { | |
| final List<int> buffer = <int>[]; | |
| @override | |
| void add(List<int> data) { | |
| buffer.addAll(data); | |
| } | |
| @override | |
| Future<void> close() => new Future.value(); | |
| @override | |
| void noSuchMethod(Invocation invocation) { | |
| throw new UnsupportedError(invocation.memberName.toString()); | |
| } | |
| } | |
| Map<String, List<String>> _combineMaps( | |
| Map<String, List<String>> left, | |
| Map<String, List<String>> right, | |
| ) { | |
| final Map<String, List<String>> result = | |
| new Map<String, List<String>>.of(left); | |
| for (String key in right.keys) { | |
| (result[key] ??= []).addAll(right[key]!); | |
| } | |
| return result; | |
| } |